| # 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. | |
| """The implementation of cc_static_library.""" | |
| load("@bazel_skylib//lib:paths.bzl", "paths") | |
| load("//cc:action_names.bzl", "ACTION_NAMES") | |
| load("//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain") | |
| load("//cc/common:cc_common.bzl", "cc_common") | |
| load("//cc/common:cc_helper.bzl", "artifact_category") | |
| load("//cc/common:cc_info.bzl", "CcInfo") | |
| def _declare_static_library(*, name, actions, cc_toolchain): | |
| basename = paths.basename(name) | |
| new_basename = cc_common.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) | |
| env = cc_common.get_environment_variables( | |
| feature_configuration = feature_configuration, | |
| action_name = ACTION_NAMES.validate_static_library, | |
| variables = cc_common.empty_variables(), | |
| ) | |
| 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], | |
| env = env, | |
| 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 | |
| _MAIN_REPOSITORY_LABEL_PREFIXES = ("@@//", "@//") # buildifier: disable=canonical-repository | |
| def _pretty_label(label): | |
| s = str(label) | |
| # Emit main repo labels (both with and without --enable_bzlmod) without a | |
| # repo prefix. | |
| if s.startswith(_MAIN_REPOSITORY_LABEL_PREFIXES): | |
| 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 = find_cc_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 = use_cc_toolchain(), | |
| fragments = ["cpp"], | |
| ) |