Skip to content

Commit 2ba751f

Browse files
hvadehracopybara-github
authored andcommitted
Extract the cc_static_library rule from @_builtins to @rules_cc
PiperOrigin-RevId: 786663475 Change-Id: Idaa9ed2db6acfd0c41c27eb117c9ee7afd53c02b
1 parent 8e0a527 commit 2ba751f

File tree

3 files changed

+314
-3
lines changed

3 files changed

+314
-3
lines changed

cc/common/cc_helper.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ load(
2020
"get_relative_path",
2121
"is_versioned_shared_library_extension_valid",
2222
"path_contains_up_level_references",
23+
_artifact_category = "artifact_category",
2324
_package_source_root = "package_source_root",
2425
_repository_exec_path = "repository_exec_path",
2526
)
@@ -37,6 +38,8 @@ linker_mode = struct(
3738

3839
# LINT.IfChange(forked_exports)
3940

41+
artifact_category = _artifact_category
42+
4043
def _get_compilation_contexts_from_deps(deps):
4144
compilation_contexts = []
4245
for dep in deps:

cc/common/cc_helper_internal.bzl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,33 @@ load("@bazel_skylib//lib:paths.bzl", "paths")
2222

2323
# LINT.IfChange(forked_exports)
2424

25+
artifact_category = struct(
26+
STATIC_LIBRARY = "STATIC_LIBRARY",
27+
ALWAYSLINK_STATIC_LIBRARY = "ALWAYSLINK_STATIC_LIBRARY",
28+
DYNAMIC_LIBRARY = "DYNAMIC_LIBRARY",
29+
EXECUTABLE = "EXECUTABLE",
30+
INTERFACE_LIBRARY = "INTERFACE_LIBRARY",
31+
PIC_FILE = "PIC_FILE",
32+
INCLUDED_FILE_LIST = "INCLUDED_FILE_LIST",
33+
SERIALIZED_DIAGNOSTICS_FILE = "SERIALIZED_DIAGNOSTICS_FILE",
34+
OBJECT_FILE = "OBJECT_FILE",
35+
PIC_OBJECT_FILE = "PIC_OBJECT_FILE",
36+
CPP_MODULE = "CPP_MODULE",
37+
CPP_MODULE_GCM = "CPP_MODULE_GCM",
38+
CPP_MODULE_IFC = "CPP_MODULE_IFC",
39+
CPP_MODULES_INFO = "CPP_MODULES_INFO",
40+
CPP_MODULES_DDI = "CPP_MODULES_DDI",
41+
CPP_MODULES_MODMAP = "CPP_MODULES_MODMAP",
42+
CPP_MODULES_MODMAP_INPUT = "CPP_MODULES_MODMAP_INPUT",
43+
GENERATED_ASSEMBLY = "GENERATED_ASSEMBLY",
44+
PROCESSED_HEADER = "PROCESSED_HEADER",
45+
GENERATED_HEADER = "GENERATED_HEADER",
46+
PREPROCESSED_C_SOURCE = "PREPROCESSED_C_SOURCE",
47+
PREPROCESSED_CPP_SOURCE = "PREPROCESSED_CPP_SOURCE",
48+
COVERAGE_DATA_FILE = "COVERAGE_DATA_FILE",
49+
CLIF_OUTPUT_PROTO = "CLIF_OUTPUT_PROTO",
50+
)
51+
2552
def is_versioned_shared_library_extension_valid(shared_library_name):
2653
"""Validates the name against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
2754

cc/private/rules_impl/cc_static_library.bzl

Lines changed: 284 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2025 The Bazel Authors. All rights reserved.
1+
# Copyright 2024 The Bazel Authors. All rights reserved.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -11,6 +11,287 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
"""cc_static_library rule without a macro"""
14+
"""This is an experimental implementation of cc_static_library.
1515
16-
cc_static_library = native.cc_static_library
16+
We may change the implementation at any moment or even delete this file. Do not
17+
rely on this.
18+
"""
19+
20+
load("@bazel_skylib//lib:paths.bzl", "paths")
21+
load("//cc:action_names.bzl", "ACTION_NAMES")
22+
load("//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain")
23+
load("//cc/common:cc_common.bzl", "cc_common")
24+
load("//cc/common:cc_helper.bzl", "artifact_category")
25+
load("//cc/common:cc_info.bzl", "CcInfo")
26+
27+
def _declare_static_library(*, name, actions, cc_toolchain):
28+
basename = paths.basename(name)
29+
new_basename = cc_common.get_artifact_name_for_category(
30+
cc_toolchain = cc_toolchain,
31+
category = artifact_category.STATIC_LIBRARY,
32+
output_name = basename,
33+
)
34+
return actions.declare_file(name.removesuffix(basename) + new_basename)
35+
36+
def _collect_linker_inputs(deps):
37+
transitive_linker_inputs = [dep[CcInfo].linking_context.linker_inputs for dep in deps]
38+
return depset(transitive = transitive_linker_inputs, order = "topological")
39+
40+
def _flatten_and_get_objects(linker_inputs):
41+
# Flattening a depset to get the action inputs.
42+
transitive_objects = []
43+
for linker_input in linker_inputs.to_list():
44+
for lib in linker_input.libraries:
45+
if lib._contains_objects:
46+
transitive_objects.append(depset(lib.pic_objects))
47+
transitive_objects.append(depset(lib.objects))
48+
49+
return depset(transitive = transitive_objects, order = "topological")
50+
51+
def _archive_objects(*, name, actions, cc_toolchain, feature_configuration, objects):
52+
static_library = _declare_static_library(
53+
name = name,
54+
actions = actions,
55+
cc_toolchain = cc_toolchain,
56+
)
57+
58+
archiver_path = cc_common.get_tool_for_action(
59+
feature_configuration = feature_configuration,
60+
action_name = ACTION_NAMES.cpp_link_static_library,
61+
)
62+
archiver_variables = cc_common.create_link_variables(
63+
cc_toolchain = cc_toolchain,
64+
feature_configuration = feature_configuration,
65+
output_file = static_library.path,
66+
is_using_linker = False,
67+
)
68+
command_line = cc_common.get_memory_inefficient_command_line(
69+
feature_configuration = feature_configuration,
70+
action_name = ACTION_NAMES.cpp_link_static_library,
71+
variables = archiver_variables,
72+
)
73+
args = actions.args()
74+
args.add_all(command_line)
75+
args.add_all(objects)
76+
77+
if cc_common.is_enabled(
78+
feature_configuration = feature_configuration,
79+
feature_name = "archive_param_file",
80+
):
81+
# TODO: The flag file arg should come from the toolchain instead.
82+
args.use_param_file("@%s", use_always = True)
83+
84+
env = cc_common.get_environment_variables(
85+
feature_configuration = feature_configuration,
86+
action_name = ACTION_NAMES.cpp_link_static_library,
87+
variables = archiver_variables,
88+
)
89+
execution_requirements_keys = cc_common.get_execution_requirements(
90+
feature_configuration = feature_configuration,
91+
action_name = ACTION_NAMES.cpp_link_static_library,
92+
)
93+
94+
actions.run(
95+
executable = archiver_path,
96+
arguments = [args],
97+
env = env,
98+
execution_requirements = {k: "" for k in execution_requirements_keys},
99+
inputs = depset(transitive = [cc_toolchain.all_files, objects]),
100+
outputs = [static_library],
101+
use_default_shell_env = True,
102+
mnemonic = "CppTransitiveArchive",
103+
progress_message = "Creating static library %{output}",
104+
)
105+
106+
return static_library
107+
108+
def _validate_static_library(*, name, actions, cc_toolchain, feature_configuration, static_library):
109+
if not cc_common.action_is_enabled(
110+
feature_configuration = feature_configuration,
111+
action_name = ACTION_NAMES.validate_static_library,
112+
):
113+
return None
114+
115+
validation_output = actions.declare_file(name + "_validation_output.txt")
116+
117+
validator_path = cc_common.get_tool_for_action(
118+
feature_configuration = feature_configuration,
119+
action_name = ACTION_NAMES.validate_static_library,
120+
)
121+
args = actions.args()
122+
args.add(static_library)
123+
args.add(validation_output)
124+
125+
execution_requirements_keys = cc_common.get_execution_requirements(
126+
feature_configuration = feature_configuration,
127+
action_name = ACTION_NAMES.validate_static_library,
128+
)
129+
130+
actions.run(
131+
executable = validator_path,
132+
arguments = [args],
133+
execution_requirements = {k: "" for k in execution_requirements_keys},
134+
inputs = depset(
135+
direct = [static_library],
136+
transitive = [cc_toolchain.all_files],
137+
),
138+
outputs = [validation_output],
139+
use_default_shell_env = True,
140+
mnemonic = "ValidateStaticLibrary",
141+
progress_message = "Validating static library %{label}",
142+
)
143+
144+
return validation_output
145+
146+
def _pretty_label(label):
147+
s = str(label)
148+
149+
# Emit main repo labels (both with and without --enable_bzlmod) without a
150+
# repo prefix.
151+
if s.startswith("@@//") or s.startswith("@//"):
152+
return s.lstrip("@")
153+
return s
154+
155+
def _linkdeps_map_each(linker_input):
156+
has_library = False
157+
for lib in linker_input.libraries:
158+
if lib._contains_objects:
159+
# Has been added to the archive.
160+
return None
161+
if lib.pic_static_library != None or lib.static_library != None or lib.dynamic_library != None or lib.interface_library != None:
162+
has_library = True
163+
if not has_library:
164+
# Does not provide any linkable artifact. May still contribute to linkopts.
165+
return None
166+
167+
return _pretty_label(linker_input.owner)
168+
169+
def _linkopts_map_each(linker_input):
170+
return linker_input.user_link_flags
171+
172+
def _format_linker_inputs(*, actions, name, linker_inputs, map_each):
173+
file = actions.declare_file(name)
174+
args = actions.args().add_all(linker_inputs, map_each = map_each)
175+
actions.write(output = file, content = args)
176+
return file
177+
178+
def _cc_static_library_impl(ctx):
179+
cc_toolchain = find_cc_toolchain(ctx)
180+
feature_configuration = cc_common.configure_features(
181+
ctx = ctx,
182+
cc_toolchain = cc_toolchain,
183+
requested_features = ctx.features + ["symbol_check"],
184+
unsupported_features = ctx.disabled_features,
185+
)
186+
187+
linker_inputs = _collect_linker_inputs(ctx.attr.deps)
188+
189+
static_library = _archive_objects(
190+
name = ctx.label.name,
191+
actions = ctx.actions,
192+
cc_toolchain = cc_toolchain,
193+
feature_configuration = feature_configuration,
194+
objects = _flatten_and_get_objects(linker_inputs),
195+
)
196+
197+
linkdeps_file = _format_linker_inputs(
198+
actions = ctx.actions,
199+
name = ctx.label.name + "_linkdeps.txt",
200+
linker_inputs = linker_inputs,
201+
map_each = _linkdeps_map_each,
202+
)
203+
204+
linkopts_file = _format_linker_inputs(
205+
actions = ctx.actions,
206+
name = ctx.label.name + "_linkopts.txt",
207+
linker_inputs = linker_inputs,
208+
map_each = _linkopts_map_each,
209+
)
210+
211+
validation_output = _validate_static_library(
212+
name = ctx.label.name,
213+
actions = ctx.actions,
214+
cc_toolchain = cc_toolchain,
215+
feature_configuration = feature_configuration,
216+
static_library = static_library,
217+
)
218+
219+
output_groups = {
220+
"linkdeps": depset([linkdeps_file]),
221+
"linkopts": depset([linkopts_file]),
222+
}
223+
if validation_output:
224+
output_groups["_validation"] = depset([validation_output])
225+
226+
runfiles = ctx.runfiles().merge_all([
227+
dep[DefaultInfo].default_runfiles
228+
for dep in ctx.attr.deps
229+
])
230+
231+
return [
232+
DefaultInfo(
233+
files = depset([static_library]),
234+
runfiles = runfiles,
235+
),
236+
OutputGroupInfo(**output_groups),
237+
]
238+
239+
cc_static_library = rule(
240+
implementation = _cc_static_library_impl,
241+
doc = """
242+
Produces a static library from a list of targets and their transitive dependencies.
243+
244+
<p>The resulting static library contains the object files of the targets listed in
245+
<code>deps</code> as well as their transitive dependencies, with preference given to
246+
<code>PIC</code> objects.</p>
247+
248+
<h4 id="cc_static_library_output_groups">Output groups</h4>
249+
250+
<h5><code>linkdeps</code></h5>
251+
<p>A text file containing the labels of those transitive dependencies of targets listed in
252+
<code>deps</code> that did not contribute any object files to the static library, but do
253+
provide at least one static, dynamic or interface library. The resulting static library
254+
may require these libraries to be available at link time.</p>
255+
256+
<h5><code>linkopts</code></h5>
257+
<p>A text file containing the user-provided <code>linkopts</code> of all transitive
258+
dependencies of targets listed in <code>deps</code>.
259+
260+
<h4 id="cc_static_library_symbol_check">Duplicate symbols</h4>
261+
<p>By default, the <code>cc_static_library</code> rule checks that the resulting static
262+
library does not contain any duplicate symbols. If it does, the build fails with an error
263+
message that lists the duplicate symbols and the object files containing them.</p>
264+
265+
<p>This check can be disabled per target or per package by setting
266+
<code>features = ["-symbol_check"]</code> or globally via
267+
<code>--features=-symbol_check</code>.</p>
268+
269+
<h5 id="cc_static_library_symbol_check_toolchain">Toolchain support for <code>symbol_check</code></h5>
270+
<p>The auto-configured C++ toolchains shipped with Bazel support the
271+
<code>symbol_check</code> feature on all platforms. Custom toolchains can add support for
272+
it in one of two ways:</p>
273+
<ul>
274+
<li>Implementing the <code>ACTION_NAMES.validate_static_library</code> action and
275+
enabling it with the <code>symbol_check</code> feature. The tool set in the action is
276+
invoked with two arguments, the static library to check for duplicate symbols and the
277+
path of a file that must be created if the check passes.</li>
278+
<li>Having the <code>symbol_check</code> feature add archiver flags that cause the
279+
action creating the static library to fail on duplicate symbols.</li>
280+
</ul>
281+
""",
282+
attrs = {
283+
"deps": attr.label_list(
284+
providers = [CcInfo],
285+
doc = """
286+
The list of targets to combine into a static library, including all their transitive
287+
dependencies.
288+
289+
<p>Dependencies that do not provide any object files are not included in the static
290+
library, but their labels are collected in the file provided by the
291+
<code>linkdeps</code> output group.</p>
292+
""",
293+
),
294+
},
295+
toolchains = use_cc_toolchain(),
296+
fragments = ["cpp"],
297+
)

0 commit comments

Comments
 (0)