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 c29b1e6da251bc..909bcc591c8a5b 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
@@ -64,6 +64,7 @@ public void init(ConfiguredRuleClassProvider.Builder builder) {
builder.addRuleDefinition(new CcToolchainRequiringRule());
builder.addRuleDefinition(new BaseRuleClasses.EmptyRule("cc_binary") {});
builder.addRuleDefinition(new EmptyRule("cc_shared_library") {});
+ builder.addRuleDefinition(new EmptyRule("cc_static_library") {});
builder.addRuleDefinition(new BaseRuleClasses.EmptyRule("cc_test") {});
builder.addRuleDefinition(new BaseRuleClasses.EmptyRule("cc_library") {});
builder.addRuleDefinition(new EmptyRule("cc_import") {});
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 b2042a6bd95cd8..21890083397bb6 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
@@ -284,6 +284,18 @@ 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",
@@ -755,6 +767,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)
@@ -845,6 +858,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 07d88afb468c6d..f144e2a264d07e 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
@@ -1005,6 +1005,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/starlarkbuildapi/cpp/CcModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java
index 71fde5241c9d7c..60d4f73c35bf9e 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
@@ -1457,6 +1457,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 9f12b882a307ac..92637296de0fb0 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
@@ -592,6 +592,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()
@@ -872,6 +876,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..499071a73ebc9a
--- /dev/null
+++ b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl
@@ -0,0 +1,303 @@
+# 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/cc/semantics.bzl", "semantics")
+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.pic_objects_private() != None:
+ transitive_objects.append(depset(lib.pic_objects_private()))
+ elif lib.objects_private() != None:
+ transitive_objects.append(depset(lib.objects_private()))
+
+ 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_private() != None or lib.objects_private() != None:
+ # 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
.
+
+
+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:
+
+ - Implementing the
ACTION_NAMES.validate_static_library
action and
+ enabling it with the symbol_check
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.
+ - Having the
symbol_check
feature add archiver flags that cause the
+ action creating the static library to fail on duplicate symbols.
+
+""",
+ 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 b8e60e6f74f473..e72cd06f3d8bbf 100755
--- a/src/main/starlark/builtins_bzl/common/exports.bzl
+++ b/src/main/starlark/builtins_bzl/common/exports.bzl
@@ -25,6 +25,7 @@ load("@_builtins//:common/cc/cc_shared_library_hint_info.bzl", "CcSharedLibraryH
load("@_builtins//:common/cc/cc_test.bzl", "cc_test")
load("@_builtins//:common/cc/cc_toolchain.bzl", "cc_toolchain")
load("@_builtins//:common/cc/cc_toolchain_alias.bzl", "cc_toolchain_alias")
+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/j2objc_library.bzl", "j2objc_library")
load("@_builtins//:common/objc/objc_import.bzl", "objc_import")
@@ -80,6 +81,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
index d4956f7e434e72..21ff32bee52bba 100644
--- a/src/main/starlark/docgen/cpp.bzl
+++ b/src/main/starlark/docgen/cpp.bzl
@@ -24,6 +24,8 @@ library_rules = struct(
cc_import = native.cc_import,
cc_proto_library = native.cc_proto_library,
cc_shared_library = native.cc_shared_library,
+ # TODO: Replace with native.cc_static_library after bumping .bazelversion.
+ **({"cc_static_library": native.cc_static_library} if hasattr(native, "cc_static_library") else {})
)
test_rules = struct(
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..6a90412b6f9f51
--- /dev/null
+++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc
@@ -0,0 +1,23 @@
+// 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..3960976eb4a734
--- /dev/null
+++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/lib_only.cc
@@ -0,0 +1,17 @@
+// 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..39568e460a12e8
--- /dev/null
+++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl
@@ -0,0 +1,160 @@
+# 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/cpp:cc_toolchain_config_lib.bzl",
+ "action_config",
+ "artifact_name_pattern",
+ "env_entry",
+ "env_set",
+ "feature",
+ "feature_set",
+ "flag_group",
+ "flag_set",
+ "tool",
+ "tool_path",
+ "variable_with_value",
+ "with_feature_set",
+)
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+load("@rules_testing//lib:util.bzl", "util")
+
+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..da58c800f1c57e 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,145 @@ 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 72910ec4d8d57a..4661f607cc22f5 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 6f0baad25cc689..5a75893772b395 100644
--- a/tools/cpp/unix_cc_toolchain_config.bzl
+++ b/tools/cpp/unix_cc_toolchain_config.bzl
@@ -252,6 +252,25 @@ def _impl(ctx):
)
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,
@@ -1619,6 +1638,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 e7319cf5e4fdb8..fccfcb765f7525 100644
--- a/tools/cpp/windows_cc_toolchain_config.bzl
+++ b/tools/cpp/windows_cc_toolchain_config.bzl
@@ -1136,6 +1136,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,
@@ -1187,6 +1198,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(