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
.
+
+
+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.
+
+
+*/
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
.
+
+
+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 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(