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 d8d8ad3076f6f2..dd27d89cb292af 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 @@ -291,6 +291,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", @@ -759,6 +771,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) @@ -849,6 +862,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 65cdea71d64685..3075f4fc085c8a 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 @@ -1085,6 +1085,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..401e176b13c92d --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStaticLibraryRule.java @@ -0,0 +1,44 @@ +// 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.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; + +/** A dummy rule for cc_static_library rule. */ +public class CcStaticLibraryRule implements RuleDefinition { + @Override + public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { + return builder + .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(); + } +} 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 413af9419b1442..03a8ff72641fb0 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 @@ -1428,6 +1428,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 48205e7f84c045..a05d98e8013fa0 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl @@ -631,6 +631,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() @@ -905,6 +909,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/cc_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl index 10964935c4fd4f..3dc4ae0af8918b 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl @@ -1259,4 +1259,6 @@ cc_helper = struct( proto_output_root = _proto_output_root, package_source_root = _package_source_root, tokenize = _tokenize, + get_base_name = _get_base_name, + replace_name = _replace_name, ) 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..3330888ce2b6d4 --- /dev/null +++ b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl @@ -0,0 +1,318 @@ +# 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. + +"""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") + +_TEMPLATE_CONTENT = "%content%" + +def _declare_static_library(*, name, actions, cc_toolchain): + new_name = cc_toolchain.get_artifact_name_for_category( + category = artifact_category.STATIC_LIBRARY, + output_name = cc_helper.get_base_name(name), + ) + return actions.declare_file(cc_helper.replace_name(name, new_name)) + +def _collect_linker_inputs(deps): + transitive_linker_inputs = [dep[CcInfo].linking_context.linker_inputs for dep in deps] + return depset(transitive = transitive_linker_inputs) + +def _has_objects(linker_input): + for lib in linker_input.libraries: + if lib.pic_objects or lib.objects: + return True + return False + +def _has_libraries(linker_input): + for lib in linker_input.libraries: + if lib.pic_static_library or lib.static_library or lib.dynamic_library or lib.interface_library: + return True + return False + +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) + +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", + ): + 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.cpp_link_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], + mnemonic = "ValidateStaticLibrary", + progress_message = "Validating static library %{input}", + ) + + return validation_output + +def _str_relative_to_repo(label, repo_name): + if label.workspace_name == repo_name: + return "//{}:{}".format(label.package, label.name) + else: + return str(label) + +def _create_linkdeps_file(*, label, actions, template, linker_inputs): + repo_name = label.workspace_name + + def _format_linkdeps(linker_input): + if _has_objects(linker_input): + # Has been added to the archive. + return None + if not _has_libraries(linker_input): + # Does not require linking, but may have contributed linkopts. + return None + return _str_relative_to_repo(linker_input.owner, repo_name) + "\n" + + linkdeps_file = actions.declare_file(label.name + "_linkdeps.txt") + linkdeps_dict = actions.template_dict().add_joined( + _TEMPLATE_CONTENT, + linker_inputs, + join_with = "", + map_each = _format_linkdeps, + allow_closure = True, + ) + actions.expand_template( + template = template, + output = linkdeps_file, + computed_substitutions = linkdeps_dict, + ) + return linkdeps_file + +def _format_linkopts(linker_input): + return [opt + "\n" for opt in linker_input.user_link_flags] + +def _create_linkopts_file(*, label, actions, template, linker_inputs): + linkopts_file = actions.declare_file(label.name + "_linkopts.txt") + linkopts_dict = actions.template_dict().add_joined( + _TEMPLATE_CONTENT, + linker_inputs, + join_with = "", + map_each = _format_linkopts, + ) + actions.expand_template( + template = template, + output = linkopts_file, + computed_substitutions = linkopts_dict, + ) + return linkopts_file + +def _create_targets_file(*, label, actions, template, linker_inputs): + repo_name = label.workspace_name + + def _format_targets(linker_input): + if not _has_objects(linker_input): + return None + return _str_relative_to_repo(linker_input.owner, repo_name) + "\n" + + targets_file = actions.declare_file(label.name + "_targets.txt") + targets_dict = actions.template_dict().add_joined( + _TEMPLATE_CONTENT, + linker_inputs, + join_with = "", + map_each = _format_targets, + uniquify = True, + allow_closure = True, + ) + actions.expand_template( + template = template, + output = targets_file, + computed_substitutions = targets_dict, + ) + return targets_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, + 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), + ) + + validation_output = _validate_static_library( + name = ctx.label.name, + actions = ctx.actions, + cc_toolchain = cc_toolchain, + feature_configuration = feature_configuration, + static_library = static_library, + ) + + linkdeps_file = _create_linkdeps_file( + label = ctx.label, + actions = ctx.actions, + template = ctx.file._lazy_content_template, + linker_inputs = linker_inputs, + ) + + linkopts_file = _create_linkopts_file( + label = ctx.label, + actions = ctx.actions, + template = ctx.file._lazy_content_template, + linker_inputs = linker_inputs, + ) + + targets_file = _create_targets_file( + label = ctx.label, + actions = ctx.actions, + template = ctx.file._lazy_content_template, + linker_inputs = linker_inputs, + ) + + output_groups = { + "linkdeps": depset([linkdeps_file]), + "linkopts": depset([linkopts_file]), + "targets": depset([targets_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, + attrs = { + "deps": attr.label_list(providers = [CcInfo]), + "_cc_toolchain": attr.label( + default = "@" + semantics.get_repo() + "//tools/cpp:current_cc_toolchain", + ), + "_lazy_content_template": attr.label( + default = "@" + semantics.get_repo() + "//tools/cpp:lazy_content_template.txt", + allow_single_file = True, + ), + }, + 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 95151c1c813dd3..1192f5348e629a 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/cc/xcode_version.bzl", "xcode_version") load("@_builtins//:common/java/proto/java_lite_proto_library.bzl", "java_lite_proto_library") load("@_builtins//:common/objc/j2objc_library.bzl", "j2objc_library") @@ -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/tests/builtins_bzl/builtin_test_setup.sh b/src/main/starlark/tests/builtins_bzl/builtin_test_setup.sh index e5763da33ea14b..427a3690d93248 100644 --- a/src/main/starlark/tests/builtins_bzl/builtin_test_setup.sh +++ b/src/main/starlark/tests/builtins_bzl/builtin_test_setup.sh @@ -21,6 +21,7 @@ function setup_tests() { add_rules_testing_to_workspace "WORKSPACE" setup_skylib_support + add_rules_testing_to_workspace WORKSPACE src=$(get_runfiles_dir $1) dest="${2:-$1}" if [ ! -e "$src" ]; then 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..fddf09c31a5f77 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/BUILD.builtin_test @@ -0,0 +1,33 @@ +load(":starlark_tests.bzl", "analysis_test_suite") + +cc_static_library( + name = "static", + deps = [":bar"], +) + +cc_library( + name = "bar", + srcs = ["bar.cc"], + hdrs = ["bar.h"], + deps = [":foo"], +) + +cc_library( + name = "foo", + srcs = ["foo.cc"], + hdrs = ["foo.h"], +) + +cc_import( + name = "static_import", + hdrs = ["bar.h"], + static_library = ":static", +) + +cc_test( + name = "test", + srcs = ["test.cc"], + deps = [":static_import"], +) + +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..1e7097f3d1d6b1 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.cc @@ -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. +#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(); +} 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..6663f021eb18cf --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/bar.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_BAR_H_ +#define EXAMPLES_TEST_CC_STATIC_LIBRARY_BAR_H_ + +int bar(); + +#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/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/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..63ebbc7c28f2d1 --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/mock_toolchain.bzl @@ -0,0 +1,118 @@ +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 = "my_ar", + execution_requirements = ["my_requirement"], + ), + ], + ), + ], + 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", + ), + ], + ), + ], + ), + ], + 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, + provides = [CcToolchainConfigInfo], +) + +def mock_cc_toolchain(name): + _mock_cc_toolchain_config( + name = name + "_config", + ) + + empty = name + "_empty" + native.filegroup( + name = empty, + ) + + archiver = util.empty_file( + name = name + "_my_ar", + ) + + 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..8ab96e8c83236a --- /dev/null +++ b/src/main/starlark/tests/builtins_bzl/cc/cc_static_library/test_cc_static_library/starlark_tests.bzl @@ -0,0 +1,158 @@ +# 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", + ], + ) + util.helper_target( + native.cc_static_library, + name = name + "_subject", + deps = [ + name + "_dep_1", + name + "_linkopts_only", + name + "_imports", + ], + ) + +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 + "_system_import", + base_label + "_static_import", + base_label + "_interface_import", + base_label + "_dynamic_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", + "", + ]).in_order() + + subject.output_group("targets").contains_exactly([path_prefix + "_targets.txt"]) + subject.action_generating(path_prefix + "_targets.txt").content().split("\n").contains_exactly([ + base_label + "_dep_1", + "", + ]).in_order() + +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:incompatible_enable_cc_toolchain_resolution": True, + }, + ) + +def _test_default_outputs_impl(env, target): + subject_path = target.label.package + "/" + "prefix" + target.label.name + ".lib" + + env.expect.that_target(target).default_outputs().contains_exactly([subject_path]) + + action = env.expect.that_target(target).action_generating(subject_path) + action.mnemonic().equals("CppTransitiveArchive") + + action_env = action.env() + action_env.contains_at_least({"MY_KEY": "my_value"}) + action_env.keys().contains("PATH") + + argv = action.argv() + argv.contains("abc") + argv.contains_at_least([file.path for file in action.actual.inputs.to_list() if file.basename.endswith(".o")]).in_order() + +def analysis_test_suite(name): + test_suite( + name = name, + tests = [ + _test_default_outputs, + _test_output_groups, + ], + ) 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 7397961ba4bb04..b83ec093d29b71 100755 --- a/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh +++ b/src/main/starlark/tests/builtins_bzl/cc_builtin_tests.sh @@ -69,7 +69,53 @@ 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" + expect_log "Duplicate symbols found" + expect_log ":direct1" + expect_log ":indirect" + expect_log "T foo()" +} + 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.tools b/tools/cpp/BUILD.tools index 2aa1538b786707..d9094a986ff47e 100644 --- a/tools/cpp/BUILD.tools +++ b/tools/cpp/BUILD.tools @@ -187,3 +187,5 @@ compiler_flag(name = "compiler") load("@bazel_tools//tools/cpp:cc_flags_supplier.bzl", "cc_flags_supplier") cc_flags_supplier(name = "cc_flags") + +exports_files(["lazy_content_template.txt"]) diff --git a/tools/cpp/BUILD.tpl b/tools/cpp/BUILD.tpl index 384b63a12c0c53..0a8959cd40fe59 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}], @@ -112,6 +117,7 @@ cc_toolchain_config( coverage_compile_flags = [%{coverage_compile_flags}], coverage_link_flags = [%{coverage_link_flags}], supports_start_end_lib = %{supports_start_end_lib}, + validate_static_library_path = "%{validate_static_library_path}", ) # Android tooling requires a default toolchain for the armeabi-v7a cpu. diff --git a/tools/cpp/lazy_content_template.txt b/tools/cpp/lazy_content_template.txt new file mode 100644 index 00000000000000..d68f34152b62f5 --- /dev/null +++ b/tools/cpp/lazy_content_template.txt @@ -0,0 +1 @@ +%content% \ No newline at end of file diff --git a/tools/cpp/nm_validate_static_library.sh.tpl b/tools/cpp/nm_validate_static_library.sh.tpl new file mode 100755 index 00000000000000..b95029df19e17d --- /dev/null +++ b/tools/cpp/nm_validate_static_library.sh.tpl @@ -0,0 +1,28 @@ +#!/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 -eu + +DUPLICATE_SYMBOLS=$( + "%{nm}" --defined-only --demangle --print-file-name "$1" | + LC_ALL=C sort --key=3 | + uniq -D --skip-field=2) +if [[ -n "$DUPLICATE_SYMBOLS" ]]; then + echo "Duplicate symbols found in $1:" | tee "$2" + echo "$DUPLICATE_SYMBOLS" | tee --append "$2" + exit 1 +else + touch "$2" +fi diff --git a/tools/cpp/unix_cc_configure.bzl b/tools/cpp/unix_cc_configure.bzl index 365e78ca1d7a58..b05b15f6abf041 100644 --- a/tools/cpp/unix_cc_configure.bzl +++ b/tools/cpp/unix_cc_configure.bzl @@ -329,6 +329,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:nm_validate_static_library.sh.tpl", "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", ]) @@ -538,6 +539,17 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): paths["@bazel_tools//tools/cpp:generate_system_module_map.sh"], )) + validate_static_library_path = None + if tool_paths["nm"]: + repository_ctx.template( + "validate_static_library.sh", + paths["@bazel_tools//tools/cpp:nm_validate_static_library.sh.tpl"], + { + "%{nm}": escape_string(str(repository_ctx.path(tool_paths["nm"]))), + }, + ) + validate_static_library_path = "validate_static_library.sh" + write_builtin_include_directory_paths(repository_ctx, cc, builtin_include_directories) repository_ctx.template( "BUILD", @@ -548,6 +560,8 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): "%{modulemap}": ("\":module.modulemap\"" if generate_modulemap else "None"), "%{cc_compiler_deps}": get_starlark_list([":builtin_include_directory_paths"] + ( [":cc_wrapper"] if darwin else [] + ) + ( + [":validate_static_library"] if validate_static_library_path else [] )), "%{compiler}": escape_string(get_env_var( repository_ctx, @@ -691,5 +705,6 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools): "%{coverage_compile_flags}": coverage_compile_flags, "%{coverage_link_flags}": coverage_link_flags, "%{supports_start_end_lib}": "True" if gold_or_lld_linker_path else "False", + "%{validate_static_library_path}": validate_static_library_path, }, ) diff --git a/tools/cpp/unix_cc_toolchain_config.bzl b/tools/cpp/unix_cc_toolchain_config.bzl index 4273a2f93d0054..94ea87f91845a9 100644 --- a/tools/cpp/unix_cc_toolchain_config.bzl +++ b/tools/cpp/unix_cc_toolchain_config.bzl @@ -209,6 +209,18 @@ def _impl(ctx): ) action_configs.append(objcopy_action) + validate_static_library_action = action_config( + action_name = ACTION_NAMES.validate_static_library, + tools = [tool(path = ctx.attr.validate_static_library_path)], + ) + action_configs.append(validate_static_library_action) + + static_library_validation_feature = feature( + name = "static_library_validation", + implies = [ACTION_NAMES.validate_static_library], + enabled = bool(ctx.attr.validate_static_library_path), + ) + supports_pic_feature = feature( name = "supports_pic", enabled = True, @@ -1420,6 +1432,7 @@ def _impl(ctx): unfiltered_compile_flags_feature, treat_warnings_as_errors_feature, archive_param_file_feature, + static_library_validation_feature, ] + layering_check_features(ctx.attr.compiler) else: # macOS artifact name patterns differ from the defaults only for dynamic @@ -1458,6 +1471,7 @@ def _impl(ctx): unfiltered_compile_flags_feature, treat_warnings_as_errors_feature, archive_param_file_feature, + static_library_validation_feature, ] return cc_common.create_cc_toolchain_config_info( @@ -1505,6 +1519,7 @@ cc_toolchain_config = rule( "coverage_link_flags": attr.string_list(), "supports_start_end_lib": attr.bool(), "builtin_sysroot": attr.string(), + "validate_static_library_path": attr.string(), "_xcode_config": attr.label(default = configuration_field( fragment = "apple", name = "xcode_config_label",