| # Copyright 2024 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """This is an experimental implementation of cc_static_library. |
| |
| We may change the implementation at any moment or even delete this file. Do not |
| rely on this. |
| """ |
| |
| load(":common/cc/action_names.bzl", "ACTION_NAMES") |
| load(":common/cc/cc_common.bzl", "cc_common") |
| load(":common/cc/cc_helper.bzl", "artifact_category", "cc_helper") |
| load(":common/cc/cc_info.bzl", "CcInfo") |
| load(":common/paths.bzl", "paths") |
| |
| cc_internal = _builtins.internal.cc_internal |
| |
| def _declare_static_library(*, name, actions, cc_toolchain): |
| basename = paths.basename(name) |
| new_basename = cc_internal.get_artifact_name_for_category( |
| cc_toolchain = cc_toolchain, |
| category = artifact_category.STATIC_LIBRARY, |
| output_name = basename, |
| ) |
| return actions.declare_file(name.removesuffix(basename) + new_basename) |
| |
| def _collect_linker_inputs(deps): |
| transitive_linker_inputs = [dep[CcInfo].linking_context.linker_inputs for dep in deps] |
| return depset(transitive = transitive_linker_inputs, order = "topological") |
| |
| def _flatten_and_get_objects(linker_inputs): |
| # Flattening a depset to get the action inputs. |
| transitive_objects = [] |
| for linker_input in linker_inputs.to_list(): |
| for lib in linker_input.libraries: |
| if lib._contains_objects: |
| transitive_objects.append(depset(lib.pic_objects)) |
| transitive_objects.append(depset(lib.objects)) |
| |
| return depset(transitive = transitive_objects, order = "topological") |
| |
| def _archive_objects(*, name, actions, cc_toolchain, feature_configuration, objects): |
| static_library = _declare_static_library( |
| name = name, |
| actions = actions, |
| cc_toolchain = cc_toolchain, |
| ) |
| |
| archiver_path = cc_common.get_tool_for_action( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.cpp_link_static_library, |
| ) |
| archiver_variables = cc_common.create_link_variables( |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| output_file = static_library.path, |
| is_using_linker = False, |
| ) |
| command_line = cc_common.get_memory_inefficient_command_line( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.cpp_link_static_library, |
| variables = archiver_variables, |
| ) |
| args = actions.args() |
| args.add_all(command_line) |
| args.add_all(objects) |
| |
| if cc_common.is_enabled( |
| feature_configuration = feature_configuration, |
| feature_name = "archive_param_file", |
| ): |
| # TODO: The flag file arg should come from the toolchain instead. |
| args.use_param_file("@%s", use_always = True) |
| |
| env = cc_common.get_environment_variables( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.cpp_link_static_library, |
| variables = archiver_variables, |
| ) |
| execution_requirements_keys = cc_common.get_execution_requirements( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.cpp_link_static_library, |
| ) |
| |
| actions.run( |
| executable = archiver_path, |
| arguments = [args], |
| env = env, |
| execution_requirements = {k: "" for k in execution_requirements_keys}, |
| inputs = depset(transitive = [cc_toolchain.all_files, objects]), |
| outputs = [static_library], |
| use_default_shell_env = True, |
| mnemonic = "CppTransitiveArchive", |
| progress_message = "Creating static library %{output}", |
| ) |
| |
| return static_library |
| |
| def _validate_static_library(*, name, actions, cc_toolchain, feature_configuration, static_library): |
| if not cc_common.action_is_enabled( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.validate_static_library, |
| ): |
| return None |
| |
| validation_output = actions.declare_file(name + "_validation_output.txt") |
| |
| validator_path = cc_common.get_tool_for_action( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.validate_static_library, |
| ) |
| args = actions.args() |
| args.add(static_library) |
| args.add(validation_output) |
| |
| execution_requirements_keys = cc_common.get_execution_requirements( |
| feature_configuration = feature_configuration, |
| action_name = ACTION_NAMES.validate_static_library, |
| ) |
| |
| actions.run( |
| executable = validator_path, |
| arguments = [args], |
| execution_requirements = {k: "" for k in execution_requirements_keys}, |
| inputs = depset( |
| direct = [static_library], |
| transitive = [cc_toolchain.all_files], |
| ), |
| outputs = [validation_output], |
| use_default_shell_env = True, |
| mnemonic = "ValidateStaticLibrary", |
| progress_message = "Validating static library %{label}", |
| ) |
| |
| return validation_output |
| |
| def _pretty_label(label): |
| s = str(label) |
| |
| # Emit main repo labels (both with and without --enable_bzlmod) without a |
| # repo prefix. |
| if s.startswith("@@//") or s.startswith("@//"): |
| return s.lstrip("@") |
| return s |
| |
| def _linkdeps_map_each(linker_input): |
| has_library = False |
| for lib in linker_input.libraries: |
| if lib._contains_objects: |
| # Has been added to the archive. |
| return None |
| if lib.pic_static_library != None or lib.static_library != None or lib.dynamic_library != None or lib.interface_library != None: |
| has_library = True |
| if not has_library: |
| # Does not provide any linkable artifact. May still contribute to linkopts. |
| return None |
| |
| return _pretty_label(linker_input.owner) |
| |
| def _linkopts_map_each(linker_input): |
| return linker_input.user_link_flags |
| |
| def _format_linker_inputs(*, actions, name, linker_inputs, map_each): |
| file = actions.declare_file(name) |
| args = actions.args().add_all(linker_inputs, map_each = map_each) |
| actions.write(output = file, content = args) |
| return file |
| |
| def _cc_static_library_impl(ctx): |
| cc_toolchain = cc_helper.find_cpp_toolchain(ctx) |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features + ["symbol_check"], |
| unsupported_features = ctx.disabled_features, |
| ) |
| |
| linker_inputs = _collect_linker_inputs(ctx.attr.deps) |
| |
| static_library = _archive_objects( |
| name = ctx.label.name, |
| actions = ctx.actions, |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| objects = _flatten_and_get_objects(linker_inputs), |
| ) |
| |
| linkdeps_file = _format_linker_inputs( |
| actions = ctx.actions, |
| name = ctx.label.name + "_linkdeps.txt", |
| linker_inputs = linker_inputs, |
| map_each = _linkdeps_map_each, |
| ) |
| |
| linkopts_file = _format_linker_inputs( |
| actions = ctx.actions, |
| name = ctx.label.name + "_linkopts.txt", |
| linker_inputs = linker_inputs, |
| map_each = _linkopts_map_each, |
| ) |
| |
| validation_output = _validate_static_library( |
| name = ctx.label.name, |
| actions = ctx.actions, |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| static_library = static_library, |
| ) |
| |
| output_groups = { |
| "linkdeps": depset([linkdeps_file]), |
| "linkopts": depset([linkopts_file]), |
| } |
| if validation_output: |
| output_groups["_validation"] = depset([validation_output]) |
| |
| runfiles = ctx.runfiles().merge_all([ |
| dep[DefaultInfo].default_runfiles |
| for dep in ctx.attr.deps |
| ]) |
| |
| return [ |
| DefaultInfo( |
| files = depset([static_library]), |
| runfiles = runfiles, |
| ), |
| OutputGroupInfo(**output_groups), |
| ] |
| |
| cc_static_library = rule( |
| implementation = _cc_static_library_impl, |
| doc = """ |
| Produces a static library from a list of targets and their transitive dependencies. |
| |
| <p>The resulting static library contains the object files of the targets listed in |
| <code>deps</code> as well as their transitive dependencies, with preference given to |
| <code>PIC</code> objects.</p> |
| |
| <h4 id="cc_static_library_output_groups">Output groups</h4> |
| |
| <h5><code>linkdeps</code></h5> |
| <p>A text file containing the labels of those transitive dependencies of targets listed in |
| <code>deps</code> that did not contribute any object files to the static library, but do |
| provide at least one static, dynamic or interface library. The resulting static library |
| may require these libraries to be available at link time.</p> |
| |
| <h5><code>linkopts</code></h5> |
| <p>A text file containing the user-provided <code>linkopts</code> of all transitive |
| dependencies of targets listed in <code>deps</code>. |
| |
| <h4 id="cc_static_library_symbol_check">Duplicate symbols</h4> |
| <p>By default, the <code>cc_static_library</code> rule checks that the resulting static |
| library does not contain any duplicate symbols. If it does, the build fails with an error |
| message that lists the duplicate symbols and the object files containing them.</p> |
| |
| <p>This check can be disabled per target or per package by setting |
| <code>features = ["-symbol_check"]</code> or globally via |
| <code>--features=-symbol_check</code>.</p> |
| |
| <h5 id="cc_static_library_symbol_check_toolchain">Toolchain support for <code>symbol_check</code></h5> |
| <p>The auto-configured C++ toolchains shipped with Bazel support the |
| <code>symbol_check</code> feature on all platforms. Custom toolchains can add support for |
| it in one of two ways:</p> |
| <ul> |
| <li>Implementing the <code>ACTION_NAMES.validate_static_library</code> action and |
| enabling it with the <code>symbol_check</code> feature. The tool set in the action is |
| invoked with two arguments, the static library to check for duplicate symbols and the |
| path of a file that must be created if the check passes.</li> |
| <li>Having the <code>symbol_check</code> feature add archiver flags that cause the |
| action creating the static library to fail on duplicate symbols.</li> |
| </ul> |
| """, |
| attrs = { |
| "deps": attr.label_list( |
| providers = [CcInfo], |
| doc = """ |
| The list of targets to combine into a static library, including all their transitive |
| dependencies. |
| |
| <p>Dependencies that do not provide any object files are not included in the static |
| library, but their labels are collected in the file provided by the |
| <code>linkdeps</code> output group.</p> |
| """, |
| ), |
| }, |
| toolchains = cc_helper.use_cpp_toolchain(), |
| fragments = ["cpp"], |
| ) |