From f2ae01d52da6f0ae32b59ba1cff231782184332c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 9 Aug 2024 13:27:19 -0700 Subject: [PATCH] Add experimental `cc_static_library` rule RELNOTES: The new `cc_static_library` rule produces a static library that bundles given targets and all their transitive dependencies. It has to be enabled via `--experimental_cc_static_library`. Implements https://docs.google.com/document/d/1jN0LUmp6_-rV9_f-Chx-Cs6t_5iOm3fziOklzBGjGIg/edit Fixes #1920 Closes #16954. PiperOrigin-RevId: 661382285 Change-Id: I972afd1a38d50ab4e48d9b3c189f1662b0096bbf --- .../build/lib/bazel/rules/CcRules.java | 2 + .../semantics/BuildLanguageOptions.java | 15 + .../build/lib/rules/cpp/CcModule.java | 6 + .../lib/rules/cpp/CcStaticLibraryRule.java | 103 ++++++ .../lib/starlarkbuildapi/cpp/CcModuleApi.java | 7 + .../builtins_bzl/common/cc/action_names.bzl | 3 + .../builtins_bzl/common/cc/cc_common.bzl | 5 + .../cc/experimental_cc_static_library.bzl | 301 ++++++++++++++++++ .../starlark/builtins_bzl/common/exports.bzl | 2 + src/main/starlark/docgen/cpp.bzl | 0 src/main/starlark/tests/builtins_bzl/BUILD | 2 +- .../test_cc_static_library/BUILD.builtin_test | 62 ++++ .../test_cc_static_library/bar.cc | 20 ++ .../test_cc_static_library/bar.h | 20 ++ .../cc_static_library_integration_test.sh | 41 +++ .../test_cc_static_library/foo.cc | 16 + .../test_cc_static_library/foo.h | 19 ++ .../test_cc_static_library/lib_only.cc | 15 + .../test_cc_static_library/mock_toolchain.bzl | 156 +++++++++ .../test_cc_static_library/starlark_tests.bzl | 232 ++++++++++++++ .../test_cc_static_library/test.cc | 21 ++ .../tests/builtins_bzl/cc_builtin_tests.sh | 144 +++++++++ tools/build_defs/cc/action_names.bzl | 4 + tools/cpp/BUILD.tpl | 5 + tools/cpp/unix_cc_configure.bzl | 19 +- tools/cpp/unix_cc_toolchain_config.bzl | 22 ++ tools/cpp/validate_static_library.sh.tpl | 44 +++ tools/cpp/windows_cc_toolchain_config.bzl | 12 + 28 files changed, 1296 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/cpp/CcStaticLibraryRule.java create mode 100644 src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl create mode 100644 src/main/starlark/docgen/cpp.bzl create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/BUILD.builtin_test create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h create mode 100755 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/cc_static_library_integration_test.sh create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.cc create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/lib_only.cc create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/starlark_tests.bzl create mode 100644 src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/test.cc create mode 100755 tools/cpp/validate_static_library.sh.tpl diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java index bd8bf2c4e4da42..68692657c42948 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/CcRules.java @@ -31,6 +31,7 @@ import com.google.devtools.build.lib.rules.cpp.CcLibcTopAlias; import com.google.devtools.build.lib.rules.cpp.CcNativeLibraryInfo; import com.google.devtools.build.lib.rules.cpp.CcSharedLibraryRule; +import com.google.devtools.build.lib.rules.cpp.CcStaticLibraryRule; import com.google.devtools.build.lib.rules.cpp.CcToolchainAliasRule; import com.google.devtools.build.lib.rules.cpp.CcToolchainConfigInfo; import com.google.devtools.build.lib.rules.cpp.CcToolchainRule; @@ -90,6 +91,7 @@ public void init(ConfiguredRuleClassProvider.Builder builder) { builder.addRuleDefinition(new BazelCppRuleClasses.CcBinaryBaseRule()); builder.addRuleDefinition(new BazelCcBinaryRule()); builder.addRuleDefinition(new CcSharedLibraryRule()); + builder.addRuleDefinition(new CcStaticLibraryRule()); builder.addRuleDefinition(new BazelCcTestRule()); builder.addRuleDefinition(new BazelCppRuleClasses.CcLibraryBaseRule()); builder.addRuleDefinition(new BazelCcLibraryRule()); diff --git a/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java b/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java index 3df4f24621cd5c..ecb191a65c1bb9 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java +++ b/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java @@ -283,6 +283,19 @@ public final class BuildLanguageOptions extends OptionsBase { + "cc_shared_library will be available") public boolean experimentalCcSharedLibrary; + @Option( + name = "experimental_cc_static_library", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS, + effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS, OptionEffectTag.LOADING_AND_ANALYSIS}, + metadataTags = { + OptionMetadataTag.EXPERIMENTAL, + }, + help = + "If set to true, rule attributes and Starlark API methods needed for the rule " + + "cc_static_library will be available") + public boolean experimentalCcStaticLibrary; + @Option( name = "incompatible_require_linker_input_cc_api", defaultValue = "true", @@ -779,6 +792,7 @@ public StarlarkSemantics toStarlarkSemantics() { .setBool(EXPERIMENTAL_GOOGLE_LEGACY_API, experimentalGoogleLegacyApi) .setBool(EXPERIMENTAL_PLATFORMS_API, experimentalPlatformsApi) .setBool(EXPERIMENTAL_CC_SHARED_LIBRARY, experimentalCcSharedLibrary) + .setBool(EXPERIMENTAL_CC_STATIC_LIBRARY, experimentalCcStaticLibrary) .setBool(EXPERIMENTAL_REPO_REMOTE_EXEC, experimentalRepoRemoteExec) .setBool(EXPERIMENTAL_DISABLE_EXTERNAL_PACKAGE, experimentalDisableExternalPackage) .setBool(EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT, experimentalSiblingRepositoryLayout) @@ -874,6 +888,7 @@ public StarlarkSemantics toStarlarkSemantics() { public static final String EXPERIMENTAL_BZL_VISIBILITY = "+experimental_bzl_visibility"; public static final String CHECK_BZL_VISIBILITY = "+check_bzl_visibility"; public static final String EXPERIMENTAL_CC_SHARED_LIBRARY = "-experimental_cc_shared_library"; + public static final String EXPERIMENTAL_CC_STATIC_LIBRARY = "-experimental_cc_static_library"; public static final String EXPERIMENTAL_DISABLE_EXTERNAL_PACKAGE = "-experimental_disable_external_package"; public static final String EXPERIMENTAL_ENABLE_ANDROID_MIGRATION_APIS = diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java index a8361a29a04a91..5ba57c20655334 100755 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java @@ -1113,6 +1113,12 @@ public boolean checkExperimentalCcSharedLibrary(StarlarkThread thread) throws Ev return thread.getSemantics().getBool(BuildLanguageOptions.EXPERIMENTAL_CC_SHARED_LIBRARY); } + @Override + public boolean checkExperimentalCcStaticLibrary(StarlarkThread thread) throws EvalException { + isCalledFromStarlarkCcCommon(thread); + return thread.getSemantics().getBool(BuildLanguageOptions.EXPERIMENTAL_CC_STATIC_LIBRARY); + } + @Override public boolean getIncompatibleDisableObjcLibraryTransition(StarlarkThread thread) throws EvalException { diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStaticLibraryRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStaticLibraryRule.java new file mode 100644 index 00000000000000..0075850194decc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStaticLibraryRule.java @@ -0,0 +1,103 @@ +// Copyright 2014 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. +package com.google.devtools.build.lib.rules.cpp; + +import static com.google.devtools.build.lib.packages.Attribute.attr; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; +import static com.google.devtools.build.lib.packages.Type.STRING_LIST; + +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier; +import com.google.devtools.build.lib.util.FileTypeSet; + +/** A dummy rule for cc_static_library rule. */ +public final class CcStaticLibraryRule implements RuleDefinition { + + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return builder + /* + The list of targets to combine into a static library, including all their transitive + dependencies. + +

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 + linkdeps output group.

+ */ + .add( + attr("deps", LABEL_LIST) + .skipAnalysisTimeFileTypeCheck() + .allowedFileTypes(FileTypeSet.NO_FILE) + .mandatoryProviders(StarlarkProviderIdentifier.forKey(CcInfo.PROVIDER.getKey()))) + .add( + attr("tags", STRING_LIST) + .orderIndependent() + .taggable() + .nonconfigurable("low-level attribute, used in TargetUtils without configurations")) + .build(); + } + + @Override + public Metadata getMetadata() { + return Metadata.builder() + .name("cc_static_library") + .factoryClass(BaseRuleClasses.EmptyRuleConfiguredTargetFactory.class) + .build(); + } +} +/* +Produces a static library from a list of targets and their transitive dependencies. + +

The resulting static library contains the object files of the targets listed in +deps as well as their transitive dependencies, with preference given to +PIC objects.

+ +

Output groups

+ +
linkdeps
+

A text file containing the labels of those transitive dependencies of targets listed in +deps 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.

+ +
linkopts
+

A text file containing the user-provided linkopts of all transitive +dependencies of targets listed in deps. + +

Duplicate symbols

+

By default, the cc_static_library 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.

+ +

This check can be disabled per target or per package by setting +features = ["-symbol_check"] or globally via +--features=-symbol_check.

+ +
Toolchain support for symbol_check
+

The auto-configured C++ toolchains shipped with Bazel support the +symbol_check feature on all platforms. Custom toolchains can add support for +it in one of two ways:

+ + +*/ diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java index 6174591c125bf1..d8bd7aa8847775 100755 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java @@ -1417,6 +1417,13 @@ LinkerInputT createLinkerInput( documented = false) boolean checkExperimentalCcSharedLibrary(StarlarkThread thread) throws EvalException; + @StarlarkMethod( + name = "check_experimental_cc_static_library", + doc = "DO NOT USE. This is to guard use of cc_static_library.", + useStarlarkThread = true, + documented = false) + boolean checkExperimentalCcStaticLibrary(StarlarkThread thread) throws EvalException; + @StarlarkMethod( name = "incompatible_disable_objc_library_transition", useStarlarkThread = true, diff --git a/src/main/starlark/builtins_bzl/common/cc/action_names.bzl b/src/main/starlark/builtins_bzl/common/cc/action_names.bzl index 620ac7ffd7becb..479413fd4766c6 100644 --- a/src/main/starlark/builtins_bzl/common/cc/action_names.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/action_names.bzl @@ -95,6 +95,8 @@ CLIF_MATCH_ACTION_NAME = "clif-match" # A string constant for the obj copy actions. OBJ_COPY_ACTION_NAME = "objcopy_embed_data" +VALIDATE_STATIC_LIBRARY = "validate-static-library" + ACTION_NAMES = struct( c_compile = C_COMPILE_ACTION_NAME, cpp_compile = CPP_COMPILE_ACTION_NAME, @@ -122,4 +124,5 @@ ACTION_NAMES = struct( objcpp_compile = OBJCPP_COMPILE_ACTION_NAME, clif_match = CLIF_MATCH_ACTION_NAME, objcopy_embed_data = OBJ_COPY_ACTION_NAME, + validate_static_library = VALIDATE_STATIC_LIBRARY, ) diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl index 529d5cdef58999..38ec784e1308f6 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl @@ -623,6 +623,10 @@ def _check_experimental_cc_shared_library(): cc_common_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) return cc_common_internal.check_experimental_cc_shared_library() +def _check_experimental_cc_static_library(): + cc_common_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) + return cc_common_internal.check_experimental_cc_static_library() + def _incompatible_disable_objc_library_transition(): cc_common_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) return cc_common_internal.incompatible_disable_objc_library_transition() @@ -902,6 +906,7 @@ cc_common = struct( merge_compilation_contexts = _merge_compilation_contexts, merge_linking_contexts = _merge_linking_contexts, check_experimental_cc_shared_library = _check_experimental_cc_shared_library, + check_experimental_cc_static_library = _check_experimental_cc_static_library, create_module_map = _create_module_map, create_debug_context = _create_debug_context, merge_debug_context = _merge_debug_context, diff --git a/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl new file mode 100644 index 00000000000000..0a55c042cf382d --- /dev/null +++ b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl @@ -0,0 +1,301 @@ +# 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_toolchain.get_artifact_name_for_category( + 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.pic_objects: + transitive_objects.append(depset(lib.pic_objects)) + elif lib.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.pic_objects or lib.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): + if not cc_common.check_experimental_cc_static_library(): + fail("cc_static_library is an experimental rule and must be enabled with --experimental_cc_static_library") + + 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. + +

The resulting static library contains the object files of the targets listed in +deps as well as their transitive dependencies, with preference given to +PIC objects.

+ +

Output groups

+ +
linkdeps
+

A text file containing the labels of those transitive dependencies of targets listed in +deps 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.

+ +
linkopts
+

A text file containing the user-provided linkopts of all transitive +dependencies of targets listed in deps. + +

Duplicate symbols

+

By default, the cc_static_library 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.

+ +

This check can be disabled per target or per package by setting +features = ["-symbol_check"] or globally via +--features=-symbol_check.

+ +
Toolchain support for symbol_check
+

The auto-configured C++ toolchains shipped with Bazel support the +symbol_check feature on all platforms. Custom toolchains can add support for +it in one of two ways:

+ +""", + attrs = { + "deps": attr.label_list( + providers = [CcInfo], + doc = """ +The list of targets to combine into a static library, including all their transitive +dependencies. + +

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 +linkdeps output group.

+""", + ), + }, + toolchains = cc_helper.use_cpp_toolchain(), + fragments = ["cpp"], +) diff --git a/src/main/starlark/builtins_bzl/common/exports.bzl b/src/main/starlark/builtins_bzl/common/exports.bzl index 0bc0ce41b4d828..0fb02ca7a2c894 100755 --- a/src/main/starlark/builtins_bzl/common/exports.bzl +++ b/src/main/starlark/builtins_bzl/common/exports.bzl @@ -26,6 +26,7 @@ load("@_builtins//:common/cc/cc_test_wrapper.bzl", "cc_test") load("@_builtins//:common/cc/cc_toolchain_alias.bzl", "cc_toolchain_alias") load("@_builtins//:common/cc/cc_toolchain_provider_helper.bzl", "get_cc_toolchain_provider") load("@_builtins//:common/cc/cc_toolchain_wrapper.bzl", "apple_cc_toolchain", "cc_toolchain") +load("@_builtins//:common/cc/experimental_cc_static_library.bzl", "cc_static_library") load("@_builtins//:common/java/proto/java_lite_proto_library.bzl", "java_lite_proto_library") load("@_builtins//:common/objc/compilation_support.bzl", "compilation_support") load("@_builtins//:common/objc/j2objc_library.bzl", "j2objc_library") @@ -75,6 +76,7 @@ exported_rules = { "objc_library": objc_library, "j2objc_library": j2objc_library, "cc_shared_library": cc_shared_library, + "cc_static_library": cc_static_library, "cc_binary": cc_binary, "cc_test": cc_test, "cc_library": cc_library, diff --git a/src/main/starlark/docgen/cpp.bzl b/src/main/starlark/docgen/cpp.bzl new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/main/starlark/tests/builtins_bzl/BUILD b/src/main/starlark/tests/builtins_bzl/BUILD index c84c5126a7b93a..ec06d4d82f017f 100644 --- a/src/main/starlark/tests/builtins_bzl/BUILD +++ b/src/main/starlark/tests/builtins_bzl/BUILD @@ -15,7 +15,7 @@ filegroup( sh_test( name = "cc_builtin_tests", - size = "medium", + size = "large", srcs = ["cc_builtin_tests.sh"], data = [ ":builtin_test_setup", diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/BUILD.builtin_test b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/BUILD.builtin_test new file mode 100644 index 00000000000000..38127882a02aae --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/BUILD.builtin_test @@ -0,0 +1,62 @@ +load(":starlark_tests.bzl", "analysis_test_suite") + +cc_static_library( + name = "static", + deps = [ + ":bar", + ":lib_only_static_lib", + ], +) + +cc_library( + name = "bar", + srcs = ["bar.cc"], + hdrs = ["bar.h"], + deps = [":foo"], +) + +cc_library( + name = "foo", + srcs = ["foo.cc"], + hdrs = ["foo.h"], +) + +cc_library( + name = "lib_only", + srcs = ["lib_only.cc"], + linkstatic = True, +) + +cc_import( + name = "lib_only_static_lib", + static_library = ":lib_only", +) + +cc_import( + name = "static_import", + hdrs = ["bar.h"], + static_library = ":static", +) + +cc_test( + name = "test", + srcs = ["test.cc"], + deps = [":static_import"], +) + +sh_test( + name = "cc_static_library_integration_test", + srcs = [ + "cc_static_library_integration_test.sh", + ], + data = [ + ":static", + ], + target_compatible_with = select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], + }), +) + +analysis_test_suite(name = "analysis_test_suite") diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc new file mode 100644 index 00000000000000..79e9e0086926b7 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc @@ -0,0 +1,20 @@ +// Copyright 2022 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. +#include "src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h" + +#include "src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h" + +int bar() { return 2 * foo(); } + +int unused() { return 0; } diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h new file mode 100644 index 00000000000000..5477b94d051fbc --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h @@ -0,0 +1,20 @@ +// Copyright 2022 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. +#ifndef EXAMPLES_TEST_CC_STATIC_LIBRARY_BAR_H_ +#define EXAMPLES_TEST_CC_STATIC_LIBRARY_BAR_H_ + +int bar(); +int unused(); + +#endif // EXAMPLES_TEST_CC_STATIC_LIBRARY_BAR_H_ diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/cc_static_library_integration_test.sh b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/cc_static_library_integration_test.sh new file mode 100755 index 00000000000000..0bf334d3f39aad --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/cc_static_library_integration_test.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2019 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. + +set -euo pipefail + +function check_symbol_present() { + message="Should have seen '$2' but didn't." + echo "$1" | (grep -q "$2" || (echo "$message" && exit 1)) +} + +function check_symbol_absent() { + message="Shouldn't have seen '$2' but did." + if [ "$(echo $1 | grep -c $2)" -gt 0 ]; then + echo "$message" + exit 1 + fi +} + +function test_static_library_symbols() { + libstatic_a=$(find . -name libstatic.a) + symbols=$(nm -C $libstatic_a) + check_symbol_present "$symbols" "T foo" + check_symbol_present "$symbols" "T bar" + check_symbol_present "$symbols" "T unused" + check_symbol_absent "$symbols" "lib_only" +} + +test_static_library_symbols diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.cc b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.cc new file mode 100644 index 00000000000000..bfefab8399dae4 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.cc @@ -0,0 +1,16 @@ +// Copyright 2022 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. +#include "src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h" + +int foo() { return 42; } diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h new file mode 100644 index 00000000000000..0b7d929efcbe4b --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/foo.h @@ -0,0 +1,19 @@ +// Copyright 2022 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. +#ifndef EXAMPLES_TEST_CC_STATIC_LIBRARY_FOO_H_ +#define EXAMPLES_TEST_CC_STATIC_LIBRARY_FOO_H_ + +int foo(); + +#endif // EXAMPLES_TEST_CC_STATIC_LIBRARY_FOO_H_ diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/lib_only.cc b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/lib_only.cc new file mode 100644 index 00000000000000..cf0d237490ea89 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/lib_only.cc @@ -0,0 +1,15 @@ +// Copyright 2022 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. + +int lib_only() { return -1; } diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl new file mode 100644 index 00000000000000..47fe96dc832572 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl @@ -0,0 +1,156 @@ +# 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. + +"""Mock toolchains for starlark tests for cc_static_library""" + +load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") +load("@rules_testing//lib:util.bzl", "util") +load( + "//tools/cpp:cc_toolchain_config_lib.bzl", + "action_config", + "artifact_name_pattern", + "env_entry", + "env_set", + "feature", + "flag_group", + "flag_set", + "tool", +) + +def _mock_cc_toolchain_config_impl(ctx): + return cc_common.create_cc_toolchain_config_info( + ctx = ctx, + action_configs = [ + action_config( + action_name = ACTION_NAMES.cpp_link_static_library, + enabled = True, + tools = [tool(path = "/usr/bin/my-ar")], + ), + ] + ( + [ + action_config( + action_name = ACTION_NAMES.validate_static_library, + tools = [tool(path = "validate_static_library.sh")], + ), + ] if ctx.attr.provide_validate_static_library else [] + ), + features = [ + feature( + name = "archiver_flags", + enabled = True, + env_sets = [ + env_set( + actions = [ACTION_NAMES.cpp_link_static_library], + env_entries = [ + env_entry( + key = "MY_KEY", + value = "my_value", + ), + ], + ), + ], + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group(flags = ["abc"]), + flag_group( + flags = ["/MY_OUT:%{output_execpath}"], + expand_if_available = "output_execpath", + ), + ], + ), + ], + ), + feature( + name = "symbol_check", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group(flags = ["--check-symbols"]), + ], + ), + ], + implies = [ACTION_NAMES.validate_static_library] if ctx.attr.provide_validate_static_library else [], + ), + ], + artifact_name_patterns = [ + artifact_name_pattern( + category_name = "static_library", + prefix = "prefix", + extension = ".lib", + ), + ], + toolchain_identifier = "mock_toolchain", + host_system_name = "local", + target_system_name = "local", + target_cpu = "local", + target_libc = "local", + compiler = "compiler", + ) + +_mock_cc_toolchain_config = rule( + implementation = _mock_cc_toolchain_config_impl, + attrs = { + "provide_validate_static_library": attr.bool(mandatory = True), + }, + provides = [CcToolchainConfigInfo], + doc = "Mock toolchain for cc_static_library tests", +) + +def mock_cc_toolchain(name, provide_validate_static_library = True): + """Creates a mock cc_toolchain for testing cc_static_library. + + Args: + name: The name of the cc_toolchain. + provide_validate_static_library: Whether to provide the + validate_static_library action_config. + """ + archiver = util.empty_file( + name = name + "_my_ar", + ) + + _mock_cc_toolchain_config( + name = name + "_config", + provide_validate_static_library = provide_validate_static_library, + ) + + empty = name + "_empty" + native.filegroup( + name = empty, + ) + + all_files = name + "_all_files" + native.filegroup( + name = all_files, + srcs = [archiver], + ) + + native.cc_toolchain( + name = name + "_cc_toolchain", + toolchain_config = name + "_config", + all_files = all_files, + dwp_files = empty, + compiler_files = empty, + linker_files = empty, + objcopy_files = empty, + strip_files = empty, + ) + + native.toolchain( + name = name, + toolchain = name + "_cc_toolchain", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", + ) diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/starlark_tests.bzl b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/starlark_tests.bzl new file mode 100644 index 00000000000000..848511f6cd63f1 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/starlark_tests.bzl @@ -0,0 +1,232 @@ +# Copyright 2021 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. + +"""Starlark tests for cc_static_library""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:util.bzl", "util") +load(":mock_toolchain.bzl", "mock_cc_toolchain") + +def _set_up_subject(name): + util.helper_target( + native.cc_import, + name = name + "_dynamic_import", + shared_library = "mylib.dll", + ) + util.helper_target( + native.cc_import, + name = name + "_interface_import", + interface_library = "mylib.lib", + shared_library = "mylib.dll", + ) + util.helper_target( + native.cc_import, + name = name + "_static_import", + static_library = "mylib.lib", + ) + util.helper_target( + native.cc_import, + name = name + "_system_import", + interface_library = "mylib.lib", + system_provided = True, + ) + util.helper_target( + native.cc_library, + name = name + "_imports", + deps = [ + name + "_dynamic_import", + name + "_interface_import", + name + "_static_import", + name + "_system_import", + ], + ) + util.helper_target( + native.cc_library, + name = name + "_dep_1", + srcs = ["file.cc"], + linkopts = [ + "dep_1_arg_1", + "dep_1_arg_2", + "dep_1_arg_1", + ], + ) + util.helper_target( + native.cc_library, + name = name + "_linkopts_only", + linkopts = [ + "linkopts_only_arg_1", + "linkopts_only_arg_2", + "linkopts_only_arg_1", + ], + deps = [ + name + "_duplicate_linkopts", + ], + ) + util.helper_target( + native.cc_library, + name = name + "_duplicate_linkopts", + linkopts = [ + "linkopts_only_arg_1", + "linkopts_only_arg_2", + "linkopts_only_arg_1", + ], + ) + util.helper_target( + native.cc_static_library, + name = name + "_subject", + deps = [ + name + "_dep_1", + name + "_linkopts_only", + name + "_imports", + ], + ) + +def _test_default_outputs(name): + _set_up_subject(name) + mock_cc_toolchain(name + "_toolchain") + analysis_test( + name = name, + impl = _test_default_outputs_impl, + target = name + "_subject", + config_settings = { + "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + "//command_line_option:action_env": ["PATH=/usr/bin/special"], + }, + ) + +def _test_default_outputs_impl(env, target): + static_lib_path = target.label.package + "/" + "prefix" + target.label.name + ".lib" + + env.expect.that_target(target).default_outputs().contains_exactly([static_lib_path]) + + action = env.expect.that_target(target).action_generating(static_lib_path) + action.mnemonic().equals("CppTransitiveArchive") + + action_env = action.env() + action_env.contains_at_least({ + "MY_KEY": "my_value", + "PATH": "/usr/bin/special", + }) + + argv = action.argv() + argv.contains_at_least([ + "/usr/bin/my-ar", + "abc", + ]).in_order() + argv.contains_at_least([ + file.path + for file in action.actual.inputs.to_list() + if file.basename.endswith(".o") + ]).in_order() + argv.contains("--check-symbols") + +def _test_output_groups(name): + _set_up_subject(name) + analysis_test( + name = name, + impl = _test_output_groups_impl, + target = name + "_subject", + ) + +def _test_output_groups_impl(env, target): + path_prefix = target.label.package + "/" + target.label.name + base_label = "//" + target.label.package + ":" + target.label.name.removesuffix("_subject") + subject = env.expect.that_target(target) + + subject.output_group("linkdeps").contains_exactly([path_prefix + "_linkdeps.txt"]) + subject.action_generating(path_prefix + "_linkdeps.txt").content().split("\n").contains_exactly([ + base_label + "_dynamic_import", + base_label + "_interface_import", + base_label + "_static_import", + base_label + "_system_import", + "", + ]).in_order() + + subject.output_group("linkopts").contains_exactly([path_prefix + "_linkopts.txt"]) + subject.action_generating(path_prefix + "_linkopts.txt").content().split("\n").contains_exactly([ + "dep_1_arg_1", + "dep_1_arg_2", + "dep_1_arg_1", + "linkopts_only_arg_1", + "linkopts_only_arg_2", + "linkopts_only_arg_1", + "linkopts_only_arg_1", + "linkopts_only_arg_2", + "linkopts_only_arg_1", + "", + ]).in_order() + +def _test_validation_enabled(name): + _set_up_subject(name) + mock_cc_toolchain(name + "_toolchain", provide_validate_static_library = True) + analysis_test( + name = name, + impl = _test_validation_enabled_impl, + target = name + "_subject", + config_settings = { + "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + "//command_line_option:action_env": ["PATH=/usr/bin/special"], + }, + ) + +def _test_validation_enabled_impl(env, target): + action = env.expect.that_target(target).action_named("ValidateStaticLibrary") + + outputs = action.actual.outputs.to_list() + env.expect.that_collection(outputs).has_size(1) + validation_output = outputs[0] + + action.env().contains_at_least({"PATH": "/usr/bin/special"}) + + static_lib = target[DefaultInfo].files.to_list()[0] + action.argv().contains_exactly([ + target.label.package + "/validate_static_library.sh", + static_lib.path, + validation_output.path, + ]).in_order() + + env.expect.that_target(target).output_group("_validation").contains_exactly( + [validation_output.short_path], + ) + +def _test_validation_disabled(name): + _set_up_subject(name) + mock_cc_toolchain(name + "_toolchain", provide_validate_static_library = False) + analysis_test( + name = name, + impl = _test_validation_disabled_impl, + target = name + "_subject", + config_settings = { + "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + }, + ) + +def _test_validation_disabled_impl(env, target): + env.expect.that_collection(target.actions).transform( + "mnemonics", + map_each = lambda a: a.mnemonic, + ).contains_none_of(["ValidateStaticLibrary"]) + + env.expect.that_collection(dir(target[OutputGroupInfo])).contains_none_of(["_validation"]) + +def analysis_test_suite(name): + test_suite( + name = name, + tests = [ + _test_default_outputs, + _test_output_groups, + _test_validation_enabled, + _test_validation_disabled, + ], + ) diff --git a/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/test.cc b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/test.cc new file mode 100644 index 00000000000000..6cbbd17648b307 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/test.cc @@ -0,0 +1,21 @@ +// Copyright 2022 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. +#include "src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.h" + +int main() { + if (bar() != 84) { + return 1; + } + return 0; +} diff --git a/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh b/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh index 3f721f2b6fb5b5..0adbd9bed00851 100755 --- a/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh +++ b/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh @@ -41,6 +41,11 @@ source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \ source "$(rlocation "io_bazel/src/main/starlark/tests/builtins_bzl/builtin_test_setup.sh")" \ || { echo "builtin_test_setup.sh not found!" >&2; exit 1; } +# `uname` returns the current platform, e.g "MSYS_NT-10.0" or "Linux". +# `tr` converts all upper case letters to lower case. +# `case` matches the result if the `uname | tr` expression to string prefixes +# that use the same wildcards as names do in Bash, i.e. "msys*" matches strings +# starting with "msys", and "*" matches everything (it's the default case). case "$(uname -s | tr [:upper:] [:lower:])" in msys*) # As of 2019-01-15, Bazel on Windows only supports MSYS Bash. @@ -51,6 +56,13 @@ msys*) ;; esac +if "$is_windows"; then + # Disable MSYS path conversion that converts path-looking command arguments to + # Windows paths (even if they arguments are not in fact paths). + export MSYS_NO_PATHCONV=1 + export MSYS2_ARG_CONV_EXCL="*" +fi + function test_starlark_cc() { setup_tests src/main/starlark/tests/builtins_bzl/cc mkdir -p "src/conditions" @@ -70,7 +82,139 @@ EOF fi bazel $START_OPTS test --define=is_bazel=true --test_output=streamed \ + --experimental_cc_static_library \ //src/main/starlark/tests/builtins_bzl/cc/... || fail "expected success" } +function test_cc_static_library_duplicate_symbol() { + mkdir -p pkg + cat > pkg/BUILD<<'EOF' +cc_static_library( + name = "static", + deps = [ + ":direct1", + ":direct2", + ], +) +cc_library( + name = "direct1", + srcs = ["direct1.cc"], +) +cc_library( + name = "direct2", + srcs = ["direct2.cc"], + deps = [":indirect"], +) +cc_library( + name = "indirect", + srcs = ["indirect.cc"], +) +EOF + cat > pkg/direct1.cc<<'EOF' +int foo() { return 42; } +EOF + cat > pkg/direct2.cc<<'EOF' +int bar() { return 21; } +EOF + cat > pkg/indirect.cc<<'EOF' +int foo() { return 21; } +EOF + + bazel build --experimental_cc_static_library //pkg:static \ + &> $TEST_log && fail "Expected build to fail" + if "$is_windows"; then + expect_log "direct1.obj" + expect_log "indirect.obj" + expect_log " foo(" + elif is_darwin; then + expect_log "Duplicate symbols found in .*/pkg/libstatic.a:" + expect_log "direct1.o: T foo()" + expect_log "indirect.o: T foo()" + else + expect_log "Duplicate symbols found in .*/pkg/libstatic.a:" + expect_log "direct1.pic.o: T foo()" + expect_log "indirect.pic.o: T foo()" + fi + + bazel build --experimental_cc_static_library //pkg:static \ + --features=-symbol_check \ + &> $TEST_log || fail "Expected build to succeed" +} + +function test_cc_static_library_duplicate_symbol_mixed_type() { + mkdir -p pkg + cat > pkg/BUILD<<'EOF' +cc_static_library( + name = "static", + deps = [ + ":direct1", + ":direct2", + ], +) +cc_library( + name = "direct1", + srcs = ["direct1.cc"], +) +cc_library( + name = "direct2", + srcs = ["direct2.cc"], + deps = [":indirect"], +) +cc_library( + name = "indirect", + srcs = ["indirect.cc"], +) +EOF + cat > pkg/direct1.cc<<'EOF' +int foo; +EOF + cat > pkg/direct2.cc<<'EOF' +int bar = 21; +EOF + cat > pkg/indirect.cc<<'EOF' +int foo = 21; +EOF + + bazel build --experimental_cc_static_library //pkg:static \ + &> $TEST_log && fail "Expected build to fail" + if "$is_windows"; then + expect_log "direct1.obj" + expect_log "indirect.obj" + expect_log " foo" + elif is_darwin; then + expect_log "Duplicate symbols found in .*/pkg/libstatic.a:" + expect_log "direct1.o: S _foo" + expect_log "indirect.o: D _foo" + else + expect_log "Duplicate symbols found in .*/pkg/libstatic.a:" + expect_log "direct1.pic.o: B foo" + expect_log "indirect.pic.o: D foo" + fi + + bazel build --experimental_cc_static_library //pkg:static \ + --features=-symbol_check \ + &> $TEST_log || fail "Expected build to succeed" +} + +function test_cc_static_library_protobuf() { + if "$is_windows"; then + # Fails on Windows due to long paths of the test workspace. + return 0 + fi + + cat > MODULE.bazel<<'EOF' +bazel_dep(name = "protobuf", version = "23.1") +EOF + mkdir -p pkg + cat > pkg/BUILD<<'EOF' +cc_static_library( + name = "protobuf", + deps = ["@protobuf"], +) +EOF + + bazel build --experimental_cc_static_library //pkg:protobuf \ + &> $TEST_log || fail "Expected build to fail" +} + run_suite "cc_* built starlark test" diff --git a/tools/build_defs/cc/action_names.bzl b/tools/build_defs/cc/action_names.bzl index f377ecafb19660..c57bfb5aa591c3 100644 --- a/tools/build_defs/cc/action_names.bzl +++ b/tools/build_defs/cc/action_names.bzl @@ -91,6 +91,9 @@ CLIF_MATCH_ACTION_NAME = "clif-match" # A string constant for the obj copy actions. OBJ_COPY_ACTION_NAME = "objcopy_embed_data" +# A string constant for the validation action for cc_static_library. +VALIDATE_STATIC_LIBRARY = "validate-static-library" + ACTION_NAMES = struct( c_compile = C_COMPILE_ACTION_NAME, cpp_compile = CPP_COMPILE_ACTION_NAME, @@ -118,4 +121,5 @@ ACTION_NAMES = struct( objcpp_compile = OBJCPP_COMPILE_ACTION_NAME, clif_match = CLIF_MATCH_ACTION_NAME, objcopy_embed_data = OBJ_COPY_ACTION_NAME, + validate_static_library = VALIDATE_STATIC_LIBRARY, ) diff --git a/tools/cpp/BUILD.tpl b/tools/cpp/BUILD.tpl index 40e3f7a1ef1577..b4b5c8baf46a7e 100644 --- a/tools/cpp/BUILD.tpl +++ b/tools/cpp/BUILD.tpl @@ -54,6 +54,11 @@ filegroup( srcs = ["cc_wrapper.sh"], ) +filegroup( + name = "validate_static_library", + srcs = ["validate_static_library.sh"], +) + filegroup( name = "compiler_deps", srcs = glob(["extra_tools/**"], allow_empty = True) + [%{cc_compiler_deps}], diff --git a/tools/cpp/unix_cc_configure.bzl b/tools/cpp/unix_cc_configure.bzl index 0cade4df389ca3..bbbba7e50d87b5 100644 --- a/tools/cpp/unix_cc_configure.bzl +++ b/tools/cpp/unix_cc_configure.bzl @@ -91,6 +91,7 @@ def _get_tool_paths(repository_ctx, overriden_tools): "objcopy", "objdump", "strip", + "c++filt", ] }.items()) @@ -329,6 +330,7 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): "@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl", "@bazel_tools//tools/cpp:unix_cc_toolchain_config.bzl", "@bazel_tools//tools/cpp:linux_cc_wrapper.sh.tpl", + "@bazel_tools//tools/cpp:validate_static_library.sh.tpl", "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", ]) @@ -398,6 +400,19 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): False, )) + if "nm" in tool_paths and "c++filt" in tool_paths: + repository_ctx.template( + "validate_static_library.sh", + paths["@bazel_tools//tools/cpp:validate_static_library.sh.tpl"], + { + "%{nm}": escape_string(str(repository_ctx.path(tool_paths["nm"]))), + # Certain weak symbols are otherwise listed with type T in the output of nm on macOS. + "%{nm_extra_args}": "--no-weak" if darwin else "", + "%{c++filt}": escape_string(str(repository_ctx.path(tool_paths["c++filt"]))), + }, + ) + tool_paths["validate_static_library"] = "validate_static_library.sh" + cc_wrapper_src = ( "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl" if darwin else "@bazel_tools//tools/cpp:linux_cc_wrapper.sh.tpl" ) @@ -569,7 +584,9 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): "%{cc_compiler_deps}": get_starlark_list([ ":builtin_include_directory_paths", ":cc_wrapper", - ]), + ] + ( + [":validate_static_library"] if "validate_static_library" in tool_paths else [] + )), "%{compiler}": escape_string(get_env_var( repository_ctx, "BAZEL_COMPILER", diff --git a/tools/cpp/unix_cc_toolchain_config.bzl b/tools/cpp/unix_cc_toolchain_config.bzl index b25f26e4e0655d..14d1ee0c25784e 100644 --- a/tools/cpp/unix_cc_toolchain_config.bzl +++ b/tools/cpp/unix_cc_toolchain_config.bzl @@ -249,6 +249,25 @@ def _impl(ctx): action_configs.append(llvm_cov_action) action_configs.append(objcopy_action) + validate_static_library = ctx.attr.tool_paths.get("validate_static_library") + if validate_static_library: + validate_static_library_action = action_config( + action_name = ACTION_NAMES.validate_static_library, + tools = [ + tool( + path = validate_static_library, + ), + ], + ) + action_configs.append(validate_static_library_action) + + symbol_check = feature( + name = "symbol_check", + implies = [ACTION_NAMES.validate_static_library], + ) + else: + symbol_check = None + supports_pic_feature = feature( name = "supports_pic", enabled = True, @@ -1535,6 +1554,9 @@ def _impl(ctx): action_configs += parse_headers_action_configs features += parse_headers_features + if symbol_check: + features.append(symbol_check) + return cc_common.create_cc_toolchain_config_info( ctx = ctx, features = features, diff --git a/tools/cpp/validate_static_library.sh.tpl b/tools/cpp/validate_static_library.sh.tpl new file mode 100755 index 00000000000000..d769408465a3ce --- /dev/null +++ b/tools/cpp/validate_static_library.sh.tpl @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# Copyright 2023 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. +set -euo pipefail + +# Find all duplicate symbols in the given static library: +# 1. Use nm to list all global symbols in the library in POSIX format: +# libstatic.a[my_object.o]: my_function T 1234 abcd +# 2. Use sed to transform the output to a format that can be sorted by symbol +# name and is readable by humans: +# my_object.o: T my_function +# By using the `t` and `d` commands, lines for symbols of type U (undefined) +# as well as V and W (weak) and their local lowercase variants are removed. +# 3. Use sort to sort the lines by symbol name. +# 4. Use uniq to only keep the lines corresponding to duplicate symbols. +# 5. Use c++filt to demangle the symbol names. +# c++filt is applied to the duplicated symbols instead of using the -C flag +# of nm because it is not in POSIX and demangled names may not be unique +# (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=35201). +DUPLICATE_SYMBOLS=$( + "%{nm}" -A -g -P %{nm_extra_args} "$1" | + sed -E -e 's/.*\[([^][]+)\]: (.+) ([A-TX-Z]) [a-f0-9]+ [a-f0-9]+/\1: \3 \2/g' -e t -e d | + LC_ALL=C sort -k 3 | + LC_ALL=C uniq -D -f 2 | + "%{c++filt}") +if [[ -n "$DUPLICATE_SYMBOLS" ]]; then + >&2 echo "Duplicate symbols found in $1:" + >&2 echo "$DUPLICATE_SYMBOLS" + exit 1 +else + touch "$2" +fi diff --git a/tools/cpp/windows_cc_toolchain_config.bzl b/tools/cpp/windows_cc_toolchain_config.bzl index 183fedad041c99..5a470eabda8af1 100644 --- a/tools/cpp/windows_cc_toolchain_config.bzl +++ b/tools/cpp/windows_cc_toolchain_config.bzl @@ -1124,6 +1124,17 @@ def _impl(ctx): ], implies = ["msvc_compile_env", "msvc_link_env"], ) + + symbol_check_feature = feature( + name = "symbol_check", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.cpp_link_static_library], + flag_groups = [flag_group(flags = ["/WX:4006"])], + ), + ], + ) + features = [ no_legacy_features_feature, nologo_feature, @@ -1174,6 +1185,7 @@ def _impl(ctx): no_windows_export_all_symbols_feature, supports_dynamic_linker_feature, supports_interface_shared_libraries_feature, + symbol_check_feature, ] else: targets_windows_feature = feature(