|
1 | | -# Copyright 2025 The Bazel Authors. All rights reserved. |
| 1 | +# Copyright 2024 The Bazel Authors. All rights reserved. |
2 | 2 | # |
3 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
4 | 4 | # you may not use this file except in compliance with the License. |
|
11 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | | -"""cc_static_library rule without a macro""" |
| 14 | +"""This is an experimental implementation of cc_static_library. |
15 | 15 |
|
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