diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 10f0c250c..2d6fdb67c 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -147,9 +147,8 @@ tasks: build_targets: *build_targets test_targets: *test_targets test_flags: *windows_test_flags - # TODO(pzembrod): reenable once Commit #53428ca hits Bazel ROLLING - should be within 08/2026 - # coverage_flags: *windows_coverage_flags - # coverage_targets: *coverage_targets + coverage_flags: *windows_coverage_flags + coverage_targets: *coverage_targets # Bazel 7 ubuntu_bazel_7: @@ -230,7 +229,7 @@ tasks: coverage_targets: *test_targets_bazel_less_than_9 # Bazel 9 - ubuntu: + ubuntu_bazel_9: name: Ubuntu (Bazel 9) bazel: 9.x platform: ubuntu2404 @@ -238,7 +237,7 @@ tasks: test_targets: *test_targets coverage_flags: *common_coverage_flags coverage_targets: *coverage_targets - macos_intel: + macos_intel_bazel_9: name: MacOS Intel (Bazel 9) bazel: 9.x platform: macos @@ -247,7 +246,7 @@ tasks: test_flags: *macos_test_flags coverage_flags: *common_coverage_flags coverage_targets: *coverage_targets - macos: + macos_bazel_9: name: MacOS (Bazel 9) bazel: 9.x platform: macos_arm64 @@ -256,9 +255,50 @@ tasks: test_flags: *macos_test_flags coverage_flags: *common_coverage_flags coverage_targets: *coverage_targets - windows: + windows_bazel_9: name: Windows (Bazel 9) bazel: 9.x + platform: windows + build_targets: *build_targets + test_targets: *test_targets + test_flags: *windows_test_flags + coverage_flags: *windows_coverage_flags + coverage_targets: *test_targets + +# Bazel 10 + ubuntu_bazel_10: + name: Ubuntu (Bazel 10) + bazel: 10.* # TODO(pzembrod): Change to 10.x after 10.0.0 is released. Same for other jobs. + platform: ubuntu2404 + build_targets: *build_targets + test_targets: *test_targets + coverage_flags: *common_coverage_flags + coverage_targets: *coverage_targets + macos_intel_bazel_10: + name: MacOS Intel (Bazel 10) + bazel: 10.* + platform: macos + environment: + ANDROID_HOME: "" + ANDROID_NDK_HOME: "" + build_targets: *build_targets + test_targets: *test_targets + test_flags: *macos_test_flags + coverage_flags: *common_coverage_flags + coverage_targets: *coverage_targets + macos_bazel_10: + name: MacOS (Bazel 10) + bazel: 10.* + platform: macos_arm64 + build_targets: *build_targets + test_targets: *test_targets + test_flags: *macos_test_flags + coverage_flags: *common_coverage_flags + coverage_targets: *coverage_targets + windows_bazel_10: + name: Windows (Bazel 10) + bazel: 10.* + platform: windows build_targets: *build_targets test_targets: *test_targets test_flags: *windows_test_flags diff --git a/.bazelrc b/.bazelrc index 34bed5573..dd2b2bdb1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,7 @@ -# Required by googletest 1.17. -common --cxxopt=-std=c++17 +common --enable_platform_specific_config +# TODO: Remove https://github.com/bazelbuild/bazel/pull/31423 +common:windows --remote_download_all + +common --check_direct_dependencies=off + test --test_env=PATH=/bin:/usr/bin:/usr/local/bin diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json index 1bdf20711..a51b320ad 100644 --- a/.bcr/metadata.template.json +++ b/.bcr/metadata.template.json @@ -10,6 +10,11 @@ "email": "pzembrod@google.com", "github": "pzembrod", "name": "Philip Zembrod" + }, + { + "email": "bgorshenev@google.com", + "github": "lilygorsheneva", + "name": "Lily Gorsheneva" } ], "versions": [], diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 47fba6122..cf7edd610 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,9 +23,29 @@ permissions: attestations: write contents: write jobs: + check-commit-status: + name: Check commit status + runs-on: ubuntu-latest + permissions: + statuses: read + steps: + - name: Require a green commit + env: + GH_TOKEN: ${{ github.token }} + run: | + status="$(gh api \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/status" \ + --jq '.state')" + if [[ "${status}" != "success" ]]; then + echo "::error::Commit ${GITHUB_SHA} is not green (combined status: ${status})." + exit 1 + fi release: + needs: check-commit-status uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.2.3 with: + bazel_test_command: "true" prerelease: false release_files: rules_cc-*.tar.gz tag_name: ${{ inputs.tag_name || github.ref_name }} diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml new file mode 100644 index 000000000..50fff9056 --- /dev/null +++ b/.github/workflows/sty.yaml @@ -0,0 +1,108 @@ +name: Starlark types + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + sty: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: tamird/sty@95e2b424a7f52fa0b0946da98eb2cc328e9b4107 + with: + audit-loads: true + bazel-version: '9.3.0' + advisory-pathspecs: '*.bzl' + advisory-bazel-load-context: '@cross_repo_test//toolchain_include_dirs:toolchain_config' + advisory-require-bazel-load-context: tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/cc_toolchain_config.bzl + files: | + @bazel_skylib//:bzl_library.bzl + @bazel_skylib//lib:paths.bzl + @bazel_skylib//rules:common_settings.bzl + @bazel_skylib//rules/directory:providers.bzl + cc/BUILD + cc/action_names.bzl + cc/cc_import.bzl + cc/cc_postmark.bzl + cc/cc_rules_extension_support.bzl + cc/cc_shared_library.bzl + cc/common/BUILD + cc/common/cc_shared_library_info.bzl + cc/common/debug_package_info.bzl + cc/common/feature_names.bzl + cc/common/objc_info.bzl + cc/extensions.bzl + cc/private/cc_shared_library_hint_info.bzl + cc/private/cc_shared_library_info.bzl + cc/private/debug_package_info.bzl + cc/private/graph_node_info.bzl + cc/private/paths.bzl + cc/private/rules_impl/failing_cc_proto_library.bzl + cc/private/rules_impl/fdo/fdo_prefetch_hints.bzl + cc/private/rules_impl/fdo/fdo_profile.bzl + cc/private/rules_impl/fdo/memprof_profile.bzl + cc/private/rules_impl/fdo/propeller_optimize.bzl + cc/private/rules_impl/native_cc_common.bzl + cc/private/rules_impl/objc_compilation_context_info.bzl + cc/private/toolchain/cc_configure.bzl + cc/private/toolchain/unix_cc_configure.bzl + cc/repositories.bzl + cc/starlark_doc_extract_helper.bzl + cc/system_library.bzl + cc/toolchains/actions.bzl + cc/toolchains/actions/BUILD + cc/toolchains/args/BUILD + cc/toolchains/args/libraries_to_link/private/library_link_args.bzl + cc/toolchains/args_list.bzl + cc/toolchains/artifacts.bzl + cc/toolchains/capabilities/BUILD + cc/toolchains/cc_toolchain.bzl + cc/toolchains/cc_toolchain_alias.bzl + cc/toolchains/cc_toolchain_config_info.bzl + cc/toolchains/directory_tool.bzl + cc/toolchains/fdo_prefetch_hints.bzl + cc/toolchains/fdo_profile.bzl + cc/toolchains/feature.bzl + cc/toolchains/feature_constraint.bzl + cc/toolchains/feature_injection.bzl + cc/toolchains/feature_set.bzl + cc/toolchains/features/BUILD + cc/toolchains/features/legacy/BUILD + cc/toolchains/impl/cc_artifact_category.bzl + cc/toolchains/impl/external_feature.bzl + cc/toolchains/impl/label_utils.bzl + cc/toolchains/impl/libc_version.bzl + cc/toolchains/impl/markdown_helpers.bzl + cc/toolchains/legacy_file_group.bzl + cc/toolchains/legacy_tool.bzl + cc/toolchains/make_variable.bzl + cc/toolchains/memprof_profile.bzl + cc/toolchains/mutually_exclusive_category.bzl + cc/toolchains/nested_args.bzl + cc/toolchains/propeller_optimize.bzl + cc/toolchains/providers.bzl + cc/toolchains/tool.bzl + cc/toolchains/tool_capability.bzl + cc/toolchains/variables/BUILD + cc/use_cc_toolchain.bzl + local_bazel.bzl + tests/cc/testutil/toolchains/additional_toolchains.bzl + tests/cc/testutil/toolchains/features.bzl + tests/load_from_macro/tags.bzl + tests/rule_based_toolchain/action_type_sets + tests/rule_based_toolchain/args/make_var_provider.bzl + tests/rule_based_toolchain/helpers.bzl + tests/rule_based_toolchain/legacy_features_as_args/goldens + tests/rule_based_toolchain/tool/execution_requirements.bzl diff --git a/BUILD b/BUILD index 278e2f585..449b30347 100644 --- a/BUILD +++ b/BUILD @@ -4,7 +4,10 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) -exports_files(["LICENSE"]) +exports_files([ + "LICENSE", + "stubs.toml", +]) exports_files(["copy.bara.sky"]) @@ -51,6 +54,7 @@ filegroup( srcs = [ "BUILD", "MODULE.bazel", + "stubs.toml", "//cc:srcs", ], visibility = ["//:__subpackages__"], diff --git a/MODULE.bazel b/MODULE.bazel index f0077955f..6d274608c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,7 +4,7 @@ module( compatibility_level = 1, ) -bazel_dep(name = "apple_support", version = "2.8.0") +bazel_dep(name = "apple_support", version = "2.9.1") bazel_dep(name = "bazel_features", version = "1.50.0") bazel_dep(name = "bazel_skylib", version = "1.8.0") bazel_dep(name = "platforms", version = "0.0.10") @@ -19,9 +19,7 @@ register_toolchains( ) bazel_dep(name = "rules_shell", version = "0.2.0", dev_dependency = True) - -bazel_dep(name = "rules_python", version = "1.7.0") - +bazel_dep(name = "rules_python", version = "2.0.0", dev_dependency = True) bazel_dep(name = "googletest", version = "1.17.0", dev_dependency = True) single_version_override( module_name = "googletest", diff --git a/cc/BUILD b/cc/BUILD index 615b43002..1b8a32c65 100644 --- a/cc/BUILD +++ b/cc/BUILD @@ -30,6 +30,8 @@ filegroup( ) exports_files([ + "cc_postmark_initializers.bzli", + "cc_toolchain_config_lib.bzli", "defs.bzl", "action_names.bzl", "system_library.bzl", @@ -74,6 +76,7 @@ filegroup( testonly = 1, srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]) + [ "//cc/common:srcs", diff --git a/cc/cc_import.bzl b/cc/cc_import.bzl index 176243646..822250a9f 100644 --- a/cc/cc_import.bzl +++ b/cc/cc_import.bzl @@ -15,5 +15,8 @@ load("@cc_compatibility_proxy//:proxy.bzl", _cc_import = "cc_import") +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Annotate **kwargs +# when Bazel can derive them from the wrapped cc_import rule. def cc_import(**kwargs): + # type: (**Unpack[KeywordArgs[_cc_import]]) -> None _cc_import(**kwargs) diff --git a/cc/cc_postmark.bzl b/cc/cc_postmark.bzl index f9bf9272c..eed942ead 100644 --- a/cc/cc_postmark.bzl +++ b/cc/cc_postmark.bzl @@ -14,20 +14,21 @@ """PostMark is not implemented in Bazel.""" -def _get_postmark_attrs(): - return {} - -def _postmark_initializer(**kwargs): - return kwargs +load("//cc:cc_postmark_initializers.bzl", _get_postmark_attrs = "get_postmark_attrs", _postmark_initializer = "postmark_initializer") def _add_postmark_action( ctx, # @unused cc_toolchain, # @unused binary, # @unused output_binary_for_linking): # @unused + # type: (object, object, object, object) -> None pass +# The public toolchain never enables PostMark. A broader bool makes the +# cc_binary branch that passes internal-only action keywords reachable. +# TODO: Use bool when the public implementation supports that action. def _get_use_postmark(ctx): # @unused + # type: (object) -> Literal[False] return False postmark = struct( diff --git a/cc/cc_postmark_initializers.bzli b/cc/cc_postmark_initializers.bzli new file mode 100644 index 000000000..39f75ad5c --- /dev/null +++ b/cc/cc_postmark_initializers.bzli @@ -0,0 +1,7 @@ +"""Contracts for the PostMark initializer compatibility layer.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move the initializers into source when supported Bazel versions can bind +# K and V on the function. A signature comment cannot bind these type parameters. + +def _get_postmark_attrs[K, V]() -> dict[K, V]: ... +def _postmark_initializer[T](**kwargs: T) -> dict[str, T]: ... diff --git a/cc/cc_shared_library.bzl b/cc/cc_shared_library.bzl index 2cbc47f83..fe1580b36 100644 --- a/cc/cc_shared_library.bzl +++ b/cc/cc_shared_library.bzl @@ -15,5 +15,8 @@ load("@cc_compatibility_proxy//:proxy.bzl", _cc_shared_library = "cc_shared_library") +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Annotate **kwargs +# when Bazel can derive them from the wrapped cc_shared_library rule. def cc_shared_library(**kwargs): + # type: (**Unpack[KeywordArgs[_cc_shared_library]]) -> None _cc_shared_library(**kwargs) diff --git a/cc/cc_toolchain_config_lib.bzl b/cc/cc_toolchain_config_lib.bzl index 77bb554f9..8627534ab 100644 --- a/cc/cc_toolchain_config_lib.bzl +++ b/cc/cc_toolchain_config_lib.bzl @@ -14,30 +14,35 @@ """ A library of functions creating structs for CcToolchainConfigInfo.""" def _check_is_none(obj, parameter_name, method_name): + # type: (object, str, str) -> None if obj != None: fail("{} parameter of {} should be None, found {}." .format(parameter_name, method_name, type(obj))) def _check_is_none_or_right_type(obj, obj_of_right_type, parameter_name, method_name): + # type: (object, object, str, str) -> None if obj != None: _check_same_type(obj, obj_of_right_type, parameter_name, method_name) def _check_right_type(obj, expected_type, parameter_name, method_name): + # type: (object, str, str, str) -> None if type(obj) != expected_type: fail("{} parameter of {} should be a {}, found {}." .format(parameter_name, method_name, expected_type, type(obj))) def _check_same_type(obj, obj_of_right_type, parameter_name, method_name): + # type: (object, object, str, str) -> None _check_right_type(obj, type(obj_of_right_type), parameter_name, method_name) def _check_is_nonempty_string(obj, parameter_name, method_name): + # type: (object, str, str) -> None _check_same_type(obj, "", parameter_name, method_name) if obj == "": fail("{} parameter of {} must be a nonempty string." .format(parameter_name, method_name)) def _check_is_nonempty_list(obj, parameter_name, method_name): - _check_same_type(obj, [], parameter_name, method_name) + _check_right_type(obj, "list", parameter_name, method_name) if len(obj) == 0: fail("{} parameter of {} must be a nonempty list." .format(parameter_name, method_name)) @@ -48,6 +53,7 @@ EnvEntryInfo = provider( ) def env_entry(key, value, expand_if_available = None): + # type: (str, str, str | None) -> EnvEntryInfo """ A key/value pair to be added as an environment variable. The returned EnvEntry provider finds its use in EnvSet creation through @@ -81,6 +87,7 @@ VariableWithValueInfo = provider( ) def variable_with_value(name, value): + # type: (str, str) -> VariableWithValueInfo """ Represents equality check between a variable and a certain value. The returned provider finds its use through flag_group.expand_if_equal, @@ -108,6 +115,7 @@ MakeVariableInfo = provider( ) def make_variable(name, value): + # type: (str, str) -> MakeVariableInfo """ A make variable that is made accessible to rules.""" _check_is_nonempty_string(name, "name", "make_variable") _check_is_nonempty_string(value, "value", "make_variable") @@ -123,6 +131,7 @@ FeatureSetInfo = provider( ) def feature_set(features = []): + # type: (list[str]) -> FeatureSetInfo """ A set of features. Used to support logical 'and' when specifying feature requirements in a @@ -134,7 +143,7 @@ def feature_set(features = []): Returns: A FeatureSetInfo provider. """ - _check_same_type(features, [], "features", "feature_set") + _check_right_type(features, "list", "features", "feature_set") return FeatureSetInfo(features = features, type_name = "feature_set") WithFeatureSetInfo = provider( @@ -143,6 +152,7 @@ WithFeatureSetInfo = provider( ) def with_feature_set(features = [], not_features = []): + # type: (list[str], list[str]) -> WithFeatureSetInfo """ A set of positive and negative features. This stanza will evaluate to true when every 'feature' is enabled, and @@ -155,8 +165,8 @@ def with_feature_set(features = [], not_features = []): Returns: A WithFeatureSetInfo provider. """ - _check_same_type(features, [], "features", "with_feature_set") - _check_same_type(not_features, [], "not_features", "with_feature_set") + _check_right_type(features, "list", "features", "with_feature_set") + _check_right_type(not_features, "list", "not_features", "with_feature_set") return WithFeatureSetInfo( features = features, not_features = not_features, @@ -169,6 +179,7 @@ EnvSetInfo = provider( ) def env_set(actions, env_entries = [], with_features = []): + # type: (list[str], list[EnvEntryInfo], list[WithFeatureSetInfo]) -> EnvSetInfo """ Groups a set of environment variables to apply for certain actions. EnvSet providers are passed to feature() and action_config(), to be applied to @@ -190,8 +201,8 @@ def env_set(actions, env_entries = [], with_features = []): An EnvSetInfo provider. """ _check_is_nonempty_list(actions, "actions", "env_set") - _check_same_type(env_entries, [], "env_entries", "env_set") - _check_same_type(with_features, [], "with_features", "env_set") + _check_right_type(env_entries, "list", "env_entries", "env_set") + _check_right_type(with_features, "list", "with_features", "env_set") return EnvSetInfo( actions = actions, env_entries = env_entries, @@ -223,6 +234,7 @@ def flag_group( expand_if_true = None, expand_if_false = None, expand_if_equal = None): + # type: (list[str], list[FlagGroupInfo], str | None, str | None, str | None, str | None, str | None, VariableWithValueInfo | None) -> FlagGroupInfo """ A group of flags. Supports parametrization via variable expansion. To expand a variable of list type, flag_group has to be annotated with @@ -292,8 +304,8 @@ def flag_group( A FlagGroupInfo provider. """ - _check_same_type(flags, [], "flags", "flag_group") - _check_same_type(flag_groups, [], "flag_groups", "flag_group") + _check_right_type(flags, "list", "flags", "flag_group") + _check_right_type(flag_groups, "list", "flag_groups", "flag_group") if len(flags) > 0 and len(flag_groups) > 0: fail("flag_group must not contain both a flag and another flag_group.") if len(flags) == 0 and len(flag_groups) == 0: @@ -335,6 +347,7 @@ def flag_set( actions = [], with_features = [], flag_groups = []): + # type: (list[str], list[WithFeatureSetInfo], list[FlagGroupInfo]) -> FlagSetInfo """ A set of flags to be expanded in the command line for specific actions. Args: @@ -351,9 +364,9 @@ def flag_set( Returns: A FlagSetInfo provider. """ - _check_same_type(actions, [], "actions", "flag_set") - _check_same_type(with_features, [], "with_features", "flag_set") - _check_same_type(flag_groups, [], "flag_groups", "flag_set") + _check_right_type(actions, "list", "actions", "flag_set") + _check_right_type(with_features, "list", "with_features", "flag_set") + _check_right_type(flag_groups, "list", "flag_groups", "flag_set") return FlagSetInfo( actions = actions, with_features = with_features, @@ -383,6 +396,7 @@ def feature( requires = [], implies = [], provides = []): + # type: (str, bool, list[FlagSetInfo], list[EnvSetInfo], list[FeatureSetInfo], list[str], list[str]) -> FeatureInfo """ Contains all flag specifications for one feature. Args: @@ -421,11 +435,11 @@ def feature( A FeatureInfo provider. """ _check_same_type(enabled, True, "enabled", "feature") - _check_same_type(flag_sets, [], "flag_sets", "feature") - _check_same_type(env_sets, [], "env_sets", "feature") - _check_same_type(requires, [], "requires", "feature") - _check_same_type(provides, [], "provides", "feature") - _check_same_type(implies, [], "implies", "feature") + _check_right_type(flag_sets, "list", "flag_sets", "feature") + _check_right_type(env_sets, "list", "env_sets", "feature") + _check_right_type(requires, "list", "requires", "feature") + _check_right_type(provides, "list", "provides", "feature") + _check_right_type(implies, "list", "implies", "feature") return FeatureInfo( name = name, enabled = enabled, @@ -443,12 +457,14 @@ ToolPathInfo = provider( ) def tool_path(name, path): + # type: (str, str) -> ToolPathInfo """ Tool locations. Args: name: Name of the tool. path: Location of the tool; Can be absolute path (in case of non hermetic - toolchain), or path relative to the cc_toolchain's package. + toolchain), a path relative to the cc_toolchain's package, or the path of a + generated file. Returns: A ToolPathInfo provider. @@ -474,6 +490,7 @@ ToolInfo = provider( ) def tool(path = None, with_features = [], execution_requirements = [], tool = None): + # type: (str | None, list[WithFeatureSetInfo], list[str], File | None) -> ToolInfo """ Describes a tool associated with a crosstool action config. Args: @@ -506,8 +523,8 @@ def tool(path = None, with_features = [], execution_requirements = [], tool = No _check_is_none(path, "path", "tool") _check_right_type(tool, "File", "tool", "tool") - _check_same_type(with_features, [], "with_features", "tool") - _check_same_type(execution_requirements, [], "execution_requirements", "tool") + _check_right_type(with_features, "list", "with_features", "tool") + _check_right_type(execution_requirements, "list", "execution_requirements", "tool") return ToolInfo( path = path, tool = tool, @@ -535,6 +552,7 @@ def action_config( tools = [], flag_sets = [], implies = []): + # type: (str, bool, list[ToolInfo], list[FlagSetInfo], list[str]) -> ActionConfigInfo """ Configuration of a Bazel action. An action config corresponds to a Bazel action, and allows selection of @@ -565,9 +583,9 @@ def action_config( """ _check_is_nonempty_string(action_name, "name", "action_config") _check_same_type(enabled, True, "enabled", "action_config") - _check_same_type(tools, [], "tools", "action_config") - _check_same_type(flag_sets, [], "flag_sets", "action_config") - _check_same_type(implies, [], "implies", "action_config") + _check_right_type(tools, "list", "tools", "action_config") + _check_right_type(flag_sets, "list", "flag_sets", "action_config") + _check_right_type(implies, "list", "implies", "action_config") return ActionConfigInfo( action_name = action_name, enabled = enabled, @@ -588,6 +606,7 @@ ArtifactNamePatternInfo = provider( ) def artifact_name_pattern(category_name, prefix, extension): + # type: (str, str | None, str | None) -> ArtifactNamePatternInfo """ The name for an artifact of a given category of input or output artifacts to an action. Args: @@ -616,7 +635,9 @@ def artifact_name_pattern(category_name, prefix, extension): ) def _is_gcc_compiler(compiler): + # type: (str) -> bool return "gcc" in compiler def get_profile_correction_flags(ctx): + # type: (ctx[object, struct[{'compiler': NotRequired[str]}, ...]]) -> list[str] return ["-fprofile-correction"] if hasattr(ctx.attr, "compiler") and _is_gcc_compiler(ctx.attr.compiler) else [] diff --git a/cc/cc_toolchain_config_lib.bzli b/cc/cc_toolchain_config_lib.bzli new file mode 100644 index 000000000..6ceb58f41 --- /dev/null +++ b/cc/cc_toolchain_config_lib.bzli @@ -0,0 +1,204 @@ +# Copyright 2026 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. +"""Contracts for complete provider values produced by the configuration helpers. + +ActionConfigInfo also accepts the optional legacy config_name field. +""" + +# TODO(https://github.com/bazelbuild/bazel/issues/32207): Declare the constructor and field types on the providers when Bazel +# can express their field schemas. Callers use their result types across modules. + +def _check_is_nonempty_list[T](obj: list[T], parameter_name: str, method_name: str) -> None: ... + +def EnvEntryInfo( + *, + key: str, + value: str, + expand_if_available: str | None, + type_name: Literal["env_entry"], +) -> record[{ + "key": str, + "value": str, + "expand_if_available": str | None, + "type_name": Literal["env_entry"], +}]: ... + +def VariableWithValueInfo( + *, + name: str, + value: str, + type_name: Literal["variable_with_value"], +) -> record[{ + "name": str, + "value": str, + "type_name": Literal["variable_with_value"], +}]: ... + +def MakeVariableInfo( + *, + name: str, + value: str, + type_name: Literal["make_variable"], +) -> record[{ + "name": str, + "value": str, + "type_name": Literal["make_variable"], +}]: ... + +def FeatureSetInfo( + *, + features: list[str], + type_name: Literal["feature_set"], +) -> record[{ + "features": list[str], + "type_name": Literal["feature_set"], +}]: ... + +def WithFeatureSetInfo( + *, + features: list[str], + not_features: list[str], + type_name: Literal["with_feature_set"], +) -> record[{ + "features": list[str], + "not_features": list[str], + "type_name": Literal["with_feature_set"], +}]: ... + +def EnvSetInfo( + *, + actions: list[str], + env_entries: list[EnvEntryInfo], + with_features: list[WithFeatureSetInfo], + type_name: Literal["env_set"], +) -> record[{ + "actions": list[str], + "env_entries": list[EnvEntryInfo], + "with_features": list[WithFeatureSetInfo], + "type_name": Literal["env_set"], +}]: ... + +def FlagGroupInfo( + *, + flags: list[str], + flag_groups: list[FlagGroupInfo], + iterate_over: str | None, + expand_if_available: str | None, + expand_if_not_available: str | None, + expand_if_true: str | None, + expand_if_false: str | None, + expand_if_equal: VariableWithValueInfo | None, + type_name: Literal["flag_group"], +) -> record[{ + "flags": list[str], + "flag_groups": list[FlagGroupInfo], + "iterate_over": str | None, + "expand_if_available": str | None, + "expand_if_not_available": str | None, + "expand_if_true": str | None, + "expand_if_false": str | None, + "expand_if_equal": VariableWithValueInfo | None, + "type_name": Literal["flag_group"], +}]: ... + +def FlagSetInfo( + *, + actions: list[str], + with_features: list[WithFeatureSetInfo], + flag_groups: list[FlagGroupInfo], + type_name: Literal["flag_set"], +) -> record[{ + "actions": list[str], + "with_features": list[WithFeatureSetInfo], + "flag_groups": list[FlagGroupInfo], + "type_name": Literal["flag_set"], +}]: ... + +def FeatureInfo( + *, + name: str, + enabled: bool, + flag_sets: list[FlagSetInfo], + env_sets: list[EnvSetInfo], + requires: list[FeatureSetInfo], + implies: list[str], + provides: list[str], + type_name: Literal["feature"], +) -> record[{ + "name": str, + "enabled": bool, + "flag_sets": list[FlagSetInfo], + "env_sets": list[EnvSetInfo], + "requires": list[FeatureSetInfo], + "implies": list[str], + "provides": list[str], + "type_name": Literal["feature"], +}]: ... + +def ToolPathInfo( + *, + name: str, + path: str, + type_name: Literal["tool_path"], +) -> record[{ + "name": str, + "path": str, + "type_name": Literal["tool_path"], +}]: ... + +def ToolInfo( + *, + path: str | None, + tool: File | None, + with_features: list[WithFeatureSetInfo], + execution_requirements: list[str], + type_name: Literal["tool"], +) -> record[{ + "path": str | None, + "tool": File | None, + "with_features": list[WithFeatureSetInfo], + "execution_requirements": list[str], + "type_name": Literal["tool"], +}]: ... + +def ActionConfigInfo( + *, + action_name: str, + enabled: bool, + tools: list[ToolInfo], + flag_sets: list[FlagSetInfo], + implies: list[str], + type_name: Literal["action_config"], + config_name: str = ..., +) -> record[{ + "action_name": str, + "enabled": bool, + "tools": list[ToolInfo], + "flag_sets": list[FlagSetInfo], + "implies": list[str], + "type_name": Literal["action_config"], +}, ...]: ... + +def ArtifactNamePatternInfo( + *, + category_name: str, + prefix: str | None, + extension: str | None, + type_name: Literal["artifact_name_pattern"], +) -> record[{ + "category_name": str, + "prefix": str | None, + "extension": str | None, + "type_name": Literal["artifact_name_pattern"], +}]: ... diff --git a/cc/common/BUILD b/cc/common/BUILD index 97755ad6e..db5d44f45 100644 --- a/cc/common/BUILD +++ b/cc/common/BUILD @@ -34,13 +34,13 @@ bzl_library( "debug_package_info.bzl", "objc_info.bzl", "semantics.bzl", - "visibility.bzl", ], visibility = ["//visibility:public"], deps = [ ":feature_names_bzl", "//cc/private:cc_internal_bzl", "//cc/private:paths_bzl", + "//cc/private:visibility_bzl", "//cc/private/rules_impl:native_cc_common_bzl", "@bazel_skylib//lib:paths", "@cc_compatibility_proxy//:symbols_bzl", @@ -54,9 +54,9 @@ bzl_library( deps = [ ":cc_helper_internal_bzl", ":common", - ":visibility_bzl", "//cc:find_cc_toolchain_bzl", "//cc/private:paths_bzl", + "//cc/private:visibility_bzl", "//cc/private/rules_impl:objc_common", ], ) @@ -93,17 +93,11 @@ bzl_library( visibility = ["//visibility:public"], deps = [ ":cc_helper_bzl", - ":visibility_bzl", "//cc:find_cc_toolchain_bzl", + "//cc/private:visibility_bzl", ], ) -bzl_library( - name = "visibility_bzl", - srcs = ["visibility.bzl"], - visibility = ["//visibility:private"], -) - filegroup( name = "srcs", srcs = glob([ @@ -126,7 +120,6 @@ filegroup( exclude = [ "cc_helper.bzl", "cc_debug_helper.bzl", - "visibility.bzl", "semantics.bzl", "cc_helper_internal.bzl", "cc_shared_library_info.bzl", diff --git a/cc/common/cc_debug_helper.bzl b/cc/common/cc_debug_helper.bzl index 7caf81c80..d740fa8a2 100644 --- a/cc/common/cc_debug_helper.bzl +++ b/cc/common/cc_debug_helper.bzl @@ -15,11 +15,11 @@ load("//cc:action_names.bzl", "ACTION_NAMES") load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") +load("//cc/private:visibility.bzl", "PUBLIC_IF_NOT_GOOGLE") load(":cc_common.bzl", "cc_common") load(":cc_helper.bzl", "linker_mode") -load(":visibility.bzl", "INTERNAL_VISIBILITY") -visibility(INTERNAL_VISIBILITY) +visibility(PUBLIC_IF_NOT_GOOGLE) def create_debug_packager_actions( ctx, diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl index c849c6c46..d77c5a6dd 100644 --- a/cc/common/cc_helper.bzl +++ b/cc/common/cc_helper.bzl @@ -17,6 +17,7 @@ load("//cc:action_names.bzl", "ACTION_NAMES") load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE") load("//cc/common:feature_names.bzl", "feature_names") load("//cc/private:paths.bzl", "is_path_absolute") +load("//cc/private:visibility.bzl", "PUBLIC_IF_NOT_GOOGLE") load("//cc/private/rules_impl:objc_common.bzl", "objc_common") load(":cc_common.bzl", "cc_common") load( @@ -35,9 +36,8 @@ load( _should_stamp = "should_stamp", ) load(":cc_info.bzl", "CcInfo") -load(":visibility.bzl", "INTERNAL_VISIBILITY") -visibility(INTERNAL_VISIBILITY) +visibility(PUBLIC_IF_NOT_GOOGLE) linker_mode = struct( LINKING_DYNAMIC = "dynamic_linking_mode", @@ -58,6 +58,7 @@ _PIC_ARCHIVE_EXTENSIONS = tuple(extensions.PIC_ARCHIVE) _ARCHIVE_EXTENSIONS = tuple(extensions.ARCHIVE) _ALWAYSLINK_PIC_LIBRARY_EXTENSIONS = tuple(extensions.ALWAYSLINK_PIC_LIBRARY) _ALWAYSLINK_LIBRARY_EXTENSIONS = tuple(extensions.ALWAYSLINK_LIBRARY) +_DISALLOWED_HDRS_EXTENSIONS = {extension: True for extension in extensions.DISALLOWED_HDRS_FILES} def _rule_error(msg): fail(msg) @@ -240,6 +241,21 @@ def _collect_library_hidden_top_level_artifacts( return depset(transitive = artifacts_to_force_builder) +def _create_dynamic_libraries_copy_actions(ctx, binary, dynamic_libraries_for_runtime): + result = [] + for lib in depset(dynamic_libraries_for_runtime).to_list(): + # If the binary and the DLL are not in the same directory, copy the DLL + # to the binary's directory. + if lib.dirname != binary.dirname: + copy = ctx.actions.declare_file(lib.basename, sibling = binary) + ctx.actions.symlink(output = copy, target_file = lib, progress_message = "Copying Execution Dynamic Library") + result.append(copy) + else: + # If the library is already in the same directory as the binary, we don't need to copy it, + # but we still add it to the result. + result.append(lib) + return depset(result) + def _get_dynamic_libraries_for_runtime(cc_linking_context, linking_statically): libraries = [] for linker_input in cc_linking_context.linker_inputs.to_list(): @@ -363,9 +379,13 @@ def _build_precompiled_files(ctx): ) def _check_file_extension(file, allowed_extensions, allow_versioned_shared_libraries): - extension = "." + file.extension - return extension in allowed_extensions or \ - (allow_versioned_shared_libraries and is_versioned_shared_library_extension_valid(file.path)) + extension = file.extension + + # The final extension of a versioned .so or .dylib starts with a digit. + return "." + extension in allowed_extensions or \ + (allow_versioned_shared_libraries and len(extension) > 0 and + extension[0].isdigit() and + is_versioned_shared_library_extension_valid(file.path)) def _check_file_extensions(attr_values, allowed_extensions, attr_name, label, rule_name, allow_versioned_shared_libraries): for attr_value in attr_values: @@ -908,11 +928,21 @@ def _expand_make_variables_for_copts(ctx, tokenization, unexpanded_tokens, addit tokens.append(_expand(ctx, token, additional_make_variable_substitutions, targets = targets)) return tokens +def _matches_disallowed_copt(flag, disallowed_flags): + """Returns True if flag equals a disallowed flag or has a disallowed prefix.""" + for disallowed in disallowed_flags: + if flag == disallowed or ( + disallowed.endswith("=") and flag.startswith(disallowed) + ): + return True + return False + def _verify_no_disallowed_copts( ctx, copts, attr_name, - disallowed_copts_infos = []): + disallowed_copts_infos = [], + command_line_copts = None): """Fails the build if copts contain any disallowed compiler flags.""" if not disallowed_copts_infos: return @@ -922,49 +952,72 @@ def _verify_no_disallowed_copts( if not hasattr(info, "flags") or not info.flags: continue - # Check if target is exempt under this policy's allowlist - allowlist = getattr(info, "allowlist", None) - if allowlist != None and allowlist.contains(ctx.label): + # Pair each set of flags with a description of where it came from. + flag_sources = [("attribute '{}'".format(attr_name), copts)] + if getattr(info, "verify_command_line", False) and command_line_copts: + flag_sources.extend([ + ("'--copt' flag", command_line_copts.copts), + ("'--cxxopt' flag", command_line_copts.cxxopts), + ("'--conlyopt' flag", command_line_copts.conlyopts), + ]) + + matches = [ + (flag, source) + for source, flags in flag_sources + for flag in flags + if _matches_disallowed_copt(flag, info.flags) + ] + if not matches: continue - allowlist_target_label = getattr(info, "allowlist_target_label", None) - error_message = getattr(info, "error_message", None) + # Check the allowlists only after a match: package allowlist lookups + # scan "//pkg/..." entries linearly, and most targets use no disallowed + # flags. + package_allowlist = getattr(info, "package_allowlist", getattr(info, "allowlist", None)) + if package_allowlist != None and package_allowlist.contains(ctx.label): + continue - # Check each expanded flag against disallowed rules - for flag in copts: - for disallowed in info.flags: - if flag == disallowed or ( - disallowed.endswith("=") and flag.startswith(disallowed) - ): - violations.append((flag, allowlist_target_label, error_message)) + target_allowlist = getattr(info, "target_allowlist", None) + + if target_allowlist != None and ( + str(ctx.label) in target_allowlist or + str(ctx.label).lstrip("@") in target_allowlist + ): + continue + + policy_label = getattr(info, "policy_label", getattr(info, "allowlist_target_label", None)) + error_message = getattr(info, "error_message", None) + for flag, source in matches: + violations.append((flag, policy_label, error_message, source)) if violations: formatted_violations = [] - for flag, allowlist_target_label, error_message in violations: - allowlist_clause = ( - " (target is not in the allowlist '{}')".format( - allowlist_target_label, - ) if allowlist_target_label else "" + for flag, policy_label, error_message, source in violations: + policy_clause = ( + " disallowed by '{}'".format( + policy_label, + ) if policy_label else "" ) error_clause = ( ": {}".format(error_message) if error_message else "" ) formatted_violations.append( - "- Flag '{}'{}{}".format( + "- Flag '{}' in {}{}{}".format( flag, - allowlist_clause, + source, + policy_clause, error_clause, ), ) fail( """ -[DISALLOWED COPTS ERROR] Target '{}' specifies disallowed compiler flag(s) in attribute '{}': +[DISALLOWED COPTS ERROR] Target '{}' specifies disallowed compiler flag(s): {} """.format( ctx.label, - attr_name, "\n ".join(formatted_violations), ), + stack_trace = False, ) def _get_copts( @@ -980,8 +1033,19 @@ def _get_copts( requested_features = ctx.features attribute_copts = getattr(ctx.attr, attr) - # If we wanted to include command-line specified flags, we could add - # ctx.fragments.cpp.copts (and cxxopts/conlyopts) here. + # Only read the command-line options when a policy asks for them; this runs + # for every C++ target, once per copts-like attribute. + command_line_copts = None + for info in disallowed_copts_infos: + if getattr(info, "verify_command_line", False): + cpp_fragment = ctx.fragments.cpp + command_line_copts = struct( + copts = cpp_fragment.copts, + cxxopts = cpp_fragment.cxxopts, + conlyopts = cpp_fragment.conlyopts, + ) + break + tokenization = not (feature_configuration.is_enabled(feature_names.NO_COPTS_TOKENIZATION) or feature_names.NO_COPTS_TOKENIZATION in requested_features) expanded_attribute_copts = _expand_make_variables_for_copts(ctx, tokenization, attribute_copts, additional_make_variable_substitutions) @@ -990,6 +1054,7 @@ def _get_copts( expanded_attribute_copts, attr, disallowed_copts_infos = disallowed_copts_infos, + command_line_copts = command_line_copts, ) return expanded_attribute_copts @@ -1033,12 +1098,6 @@ def _defines(ctx, additional_make_variable_substitutions): def _local_defines(ctx, additional_make_variable_substitutions): return _defines_attribute(ctx, additional_make_variable_substitutions, "local_defines", getattr(ctx.attr, "additional_compiler_inputs", [])) -def _map_to_list(m): - result = [] - for k, v in m.items(): - result.append((k, v)) - return result - def _calculate_artifact_label_map(attr_list, attr_name): """ Converts a label_list attribute into a list of (Artifact, Label) tuples. @@ -1066,13 +1125,13 @@ def _get_srcs(ctx): if not hasattr(ctx.attr, "srcs"): return [] artifact_label_map = _calculate_artifact_label_map(ctx.attr.srcs, "srcs") - return _map_to_list(artifact_label_map) + return artifact_label_map.items() def _get_cpp_module_interfaces(ctx): if not hasattr(ctx.attr, "module_interfaces"): return [] artifact_label_map = _calculate_artifact_label_map(ctx.attr.module_interfaces, "module_interfaces") - return _map_to_list(artifact_label_map) + return artifact_label_map.items() # Returns a list of (Artifact, Label) tuples. Each tuple represents an input source # file and the label of the rule that generates it (or the label of the source file itself if it @@ -1086,7 +1145,7 @@ def _get_private_hdrs(ctx): for artifact in src[DefaultInfo].files.to_list(): if "." + artifact.extension in extensions.CC_HEADER: artifact_label_map[artifact] = src.label - return _map_to_list(artifact_label_map) + return artifact_label_map.items() # Returns the files from headers and does some checks. def _get_public_hdrs(ctx): @@ -1096,10 +1155,10 @@ def _get_public_hdrs(ctx): for hdr in ctx.attr.hdrs: if DefaultInfo in hdr: for artifact in hdr[DefaultInfo].files.to_list(): - if _check_file_extension(artifact, extensions.DISALLOWED_HDRS_FILES, True): + if _check_file_extension(artifact, _DISALLOWED_HDRS_EXTENSIONS, True): continue artifact_label_map[artifact] = hdr.label - return _map_to_list(artifact_label_map) + return artifact_label_map.items() def _linkopts(ctx, additional_make_variable_substitutions, cc_toolchain): linkopts = getattr(ctx.attr, "linkopts", []) @@ -1226,6 +1285,7 @@ cc_helper = struct( get_coverage_environment = _get_coverage_environment, create_cc_instrumented_files_info = _create_cc_instrumented_files_info, get_dynamic_libraries_for_runtime = _get_dynamic_libraries_for_runtime, + create_dynamic_libraries_copy_actions = _create_dynamic_libraries_copy_actions, build_output_groups_for_emitting_compile_providers = _build_output_groups_for_emitting_compile_providers, merge_cc_debug_contexts = _merge_cc_debug_contexts, get_providers = _get_providers, diff --git a/cc/common/cc_helper_internal.bzl b/cc/common/cc_helper_internal.bzl index 57922e98d..318049ad2 100644 --- a/cc/common/cc_helper_internal.bzl +++ b/cc/common/cc_helper_internal.bzl @@ -19,8 +19,8 @@ Only use those within C++ implementation. The others need to go through cc_commo load("@bazel_skylib//lib:paths.bzl", "paths") load("//cc/common:feature_names.bzl", "feature_names") -load("//cc/common:visibility.bzl", _CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES = "CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES", _PRIVATE_STARLARKIFICATION_ALLOWLIST = "PRIVATE_STARLARKIFICATION_ALLOWLIST", _check_private_api = "check_private_api", _wrap_with_check_private_api = "wrap_with_check_private_api") load("//cc/private:paths.bzl", "is_path_absolute") +load("//cc/private:visibility.bzl", _CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES = "CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES", _PRIVATE_STARLARKIFICATION_ALLOWLIST = "PRIVATE_STARLARKIFICATION_ALLOWLIST", _check_private_api = "check_private_api", _wrap_with_check_private_api = "wrap_with_check_private_api") check_private_api = _check_private_api wrap_with_check_private_api = _wrap_with_check_private_api diff --git a/cc/coverage/collect_cc_coverage.sh b/cc/coverage/collect_cc_coverage.sh index 577310932..44c929d6d 100755 --- a/cc/coverage/collect_cc_coverage.sh +++ b/cc/coverage/collect_cc_coverage.sh @@ -76,18 +76,18 @@ function llvm_coverage_lcov() { "${LLVM_PROFDATA}" merge -output "${output_file}.data" \ "${COVERAGE_DIR}"/*.profraw - local object_param="" + local object_params=() while read -r line; do if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then while read -r line_runtime_object; do - object_param+=" -object ${line_runtime_object}" + object_params+=(-object "${line_runtime_object}") done < "${line}" fi done < "${COVERAGE_MANIFEST}" "${LLVM_COV}" export -instr-profile "${output_file}.data" -format=lcov \ -ignore-filename-regex='^/tmp/.+' \ - ${object_param} | sed 's#/proc/self/cwd/##' > "${output_file}" + "${object_params[@]}" | sed 's#/proc/self/cwd/##' > "${output_file}" } function llvm_coverage_profdata() { diff --git a/cc/extensions.bzl b/cc/extensions.bzl index 2f3078a73..1356faf35 100644 --- a/cc/extensions.bzl +++ b/cc/extensions.bzl @@ -19,7 +19,9 @@ load("@bazel_features//:features.bzl", "bazel_features") load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge") load("//cc/private/toolchain:cc_configure.bzl", "cc_autoconf", "cc_autoconf_toolchains") +# TODO(https://github.com/bazelbuild/bazel/issues/32020): use a source annotation once Bazel exposes these context and metadata types. def _cc_configure_extension_impl(ctx): + # type: (...) -> extension_metadata | None cc_autoconf_toolchains(name = "local_config_cc_toolchains") cc_autoconf(name = "local_config_cc") if bazel_features.external_deps.extension_metadata_has_reproducible: @@ -30,7 +32,9 @@ def _cc_configure_extension_impl(ctx): cc_configure_extension = module_extension(implementation = _cc_configure_extension_impl) def _compatibility_proxy_repo_impl(rctx): - if _bazel_version_ge("9.0.0-pre.20250911"): + # type: (...) -> repo_metadata | None + # TODO(https://github.com/bazelbuild/rules_cc/pull/960): use the typed public feature flag. + if _bazel_version_ge("9.0.0-pre.20250911"): # ty: ignore[non-static-type] rctx.file( "BUILD", """ @@ -181,9 +185,11 @@ _compatibility_proxy_repo_rule = repository_rule( ) def compatibility_proxy_repo(): + # type: () -> None _compatibility_proxy_repo_rule(name = "cc_compatibility_proxy") def _compat_proxy_impl(module_ctx): + # type: (...) -> extension_metadata | None compatibility_proxy_repo() # module_ctx.extension_metadata has the paramater `reproducible` as of Bazel 7.1.0. We can't diff --git a/cc/private/BUILD b/cc/private/BUILD index 9959fecee..14f2af0d3 100644 --- a/cc/private/BUILD +++ b/cc/private/BUILD @@ -14,6 +14,8 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files(["objc_info.bzli"]) + filegroup( name = "bzl_srcs", srcs = glob([ @@ -34,6 +36,7 @@ filegroup( "**/BUILD", "*.bzl", ]) + [ + "objc_info.bzli", "//cc/private/bazel7:BUILD", "//cc/private/compile:srcs", "//cc/private/link:srcs", @@ -124,3 +127,9 @@ bzl_library( srcs = ["objc_info.bzl"], visibility = ["//cc:__subpackages__"], ) + +bzl_library( + name = "visibility_bzl", + srcs = ["visibility.bzl"], + visibility = ["//cc:__subpackages__"], +) diff --git a/cc/private/cc_common.bzl b/cc/private/cc_common.bzl index 8a289e3c7..ea80a731a 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl @@ -18,6 +18,7 @@ load( _CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES = "CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES", _PRIVATE_STARLARKIFICATION_ALLOWLIST = "PRIVATE_STARLARKIFICATION_ALLOWLIST", ) +load("//cc/common:semantics.bzl", _starlark_cc_semantics = "semantics") load("//cc/private:cc_info.bzl", "CcNativeLibraryInfo", "create_compilation_context", "create_debug_context", "create_linking_context", "create_module_map", "merge_cc_infos", "merge_compilation_contexts", "merge_debug_context", "merge_linking_contexts") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") load("//cc/private:cc_launcher_info.bzl", "CcLauncherInfo") @@ -170,7 +171,7 @@ def _get_environment_variables(*, feature_configuration, action_name, variables) return _cc_common_internal.get_environment_variables(feature_configuration = feature_configuration, action_name = action_name, variables = variables) def _empty_variables(): - return _cc_common_internal.empty_variables() + return _cc_internal.cc_toolchain_variables(vars = {}) def _create_library_to_link( *, @@ -251,7 +252,9 @@ def _create_compilation_context( system_includes = None, includes = None, quote_includes = None, - framework_includes = None, + # TODO(https://github.com/bazelbuild/bazel/issues/32021): make this an + # annotation when supported Bazel versions expose element-typed depsets. + framework_includes = None, # type: depset[str] | None defines = None, local_defines = None, direct_textual_headers = [], @@ -637,19 +640,25 @@ def _create_compile_action( action_name, compilation_context, additional_inputs = None, - additional_outputs = []): + additional_outputs = [], + use_pic = False): _cc_internal.check_private_api(allowlist = _CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES) - return _cc_common_internal.create_compile_action( - actions = actions, + ctx = _cc_internal.actions2ctx_cheat(actions) + return _cc_internal.create_cc_compile_action( + action_construction_context = ctx, + cc_compilation_context = compilation_context, cc_toolchain = cc_toolchain, + configuration = ctx.configuration, feature_configuration = feature_configuration, - source_file = source_file, + source = source_file, + additional_compilation_inputs_set = additional_inputs, output_file = output_file, - variables = variables, + use_pic = use_pic, + compile_build_variables = variables, action_name = action_name, - compilation_context = compilation_context, - additional_inputs = additional_inputs, additional_outputs = additional_outputs, + needs_include_validation = True, + toolchain_type = _starlark_cc_semantics.toolchain, ) def _implementation_deps_allowed_by_allowlist(*, ctx): diff --git a/cc/private/cc_info.bzl b/cc/private/cc_info.bzl index 6b3dcbeda..7d6a5999f 100644 --- a/cc/private/cc_info.bzl +++ b/cc/private/cc_info.bzl @@ -18,8 +18,8 @@ This file is extremely widely loaded, do not load others unless absolutely neces """ load("@bazel_skylib//lib:paths.bzl", "paths") -load("//cc/common:visibility.bzl", "check_private_api") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") +load("//cc/private:visibility.bzl", "check_private_api") load("//cc/private/link:create_extra_link_time_library.bzl", "create_extra_link_time_libraries", "merge_extra_link_time_libraries") CcCompilationContextInfo = provider( @@ -320,7 +320,7 @@ def merge_debug_context(debug_contexts = []): def _create_cc_info( *, - compilation_context = None, + compilation_context = None, # type: CcCompilationContextInfo | None linking_context = None, debug_context = None, cc_native_library_info = None): diff --git a/cc/private/compile/compile.bzl b/cc/private/compile/compile.bzl index af93e1eb6..2643b2aa3 100644 --- a/cc/private/compile/compile.bzl +++ b/cc/private/compile/compile.bzl @@ -875,8 +875,12 @@ def _create_cc_compile_actions_with_cpp20_module_helper( all_module_files = depset(direct_module_files, transitive = [transitive_module_files]) for cpp_source in compilation_unit_sources.values(): source_artifact = cpp_source.file - output_name = output_name_map[source_artifact] + source_type = cpp_source.type source_label = cpp_source.label + if not _cc_internal.is_tree_artifact(source_artifact) and source_type == CPP_SOURCE_TYPE_HEADER: + continue + + output_name = output_name_map[source_artifact] bitcode_output = feature_configuration.is_enabled(feature_names.THIN_LTO) and (("." + source_artifact.extension) in LTO_SOURCE_EXTENSIONS) if use_pic: diff --git a/cc/private/compile/compile_build_variables.bzl b/cc/private/compile/compile_build_variables.bzl index 34500702b..5091c8a52 100644 --- a/cc/private/compile/compile_build_variables.bzl +++ b/cc/private/compile/compile_build_variables.bzl @@ -108,7 +108,9 @@ def create_compile_variables( add_legacy_cxx_options = False, # buildifier: disable=unused-variable variables_extension = {}, strip_opts = _UNBOUND, - input_file = _UNBOUND): + input_file = _UNBOUND, + cpp_module_map = None, + direct_module_maps = depset()): """Returns variables used for compilation actions. Args: @@ -138,6 +140,9 @@ def create_compile_variables( variables_extension: A dictionary of additional variables used by compile actions. strip_opts: (Private API) input_file: (Private API) + cpp_module_map: Optional CppModuleMap of the target being compiled, used to populate + module_name and module_map_file build variables when module_maps is enabled. + direct_module_maps: Depset of direct dependency module map files. Returns: (CcToolchainVariables) common compile build variables @@ -167,6 +172,8 @@ def create_compile_variables( system_include_dirs = system_include_directories or depset(), framework_include_dirs = framework_include_directories or depset(), defines = preprocessor_defines or depset(), + cpp_module_map = cpp_module_map if feature_configuration.is_enabled(feature_names.MODULE_MAPS) else None, + direct_module_maps = direct_module_maps, ) additional_build_variables = {} @@ -479,17 +486,25 @@ def _setup_fdo_build_variables( if (branch_fdo_profile and auxiliary_fdo_inputs_list and - ( - feature_configuration.is_enabled(feature_names.AUTOFDO) or - feature_configuration.is_enabled(feature_names.XBINARYFDO) or - ( - feature_configuration.is_enabled(feature_names.FDO_OPTIMIZE) and - branch_fdo_profile.branch_fdo_mode in ("llvm_fdo", "llvm_cs_fdo") - ) - )): + _uses_branch_fdo_profile(branch_fdo_profile.branch_fdo_mode, feature_configuration)): variables["fdo_profile_path"] = branch_fdo_profile.profile_artifact.path return variables +def _uses_branch_fdo_profile(branch_fdo_mode, feature_configuration): + """Returns whether the enabled features consume the branch FDO profile. + + The profile is only passed to compile actions (as `fdo_profile_path` and as + an input) when the toolchain feature that reads it is enabled. + """ + if feature_configuration.is_enabled(feature_names.AUTOFDO): + return True + if feature_configuration.is_enabled(feature_names.XBINARYFDO): + return True + if (feature_configuration.is_enabled(feature_names.FDO_OPTIMIZE) and + branch_fdo_mode in ("llvm_fdo", "llvm_cs_fdo")): + return True + return False + def _get_auxiliary_fdo_inputs_list( cc_toolchain, fdo_context, @@ -517,8 +532,10 @@ def _get_auxiliary_fdo_inputs_list( branch_fdo_profile = getattr(fdo_context, "branch_fdo_profile", None) - # If --fdo_optimize was not specified, we don't have any additional inputs. - if branch_fdo_profile: + # Only add the profile when the enabled features read it, matching the + # condition under which `fdo_profile_path` is set. + if (branch_fdo_profile and + _uses_branch_fdo_profile(branch_fdo_profile.branch_fdo_mode, feature_configuration)): auxiliary_inputs.append(branch_fdo_profile.profile_artifact) return auxiliary_inputs diff --git a/cc/private/link/cc_linking_helper.bzl b/cc/private/link/cc_linking_helper.bzl index 808a34f42..42746b195 100644 --- a/cc/private/link/cc_linking_helper.bzl +++ b/cc/private/link/cc_linking_helper.bzl @@ -590,9 +590,9 @@ def _create_no_pic_and_pic_static_libs_actions( actions, name, static_link_type, - False, # use_pic - cc_toolchain, - link_action_kwargs, + use_pic = False, + cc_toolchain = cc_toolchain, + link_action_kwargs = link_action_kwargs, ) pic_library_to_link = {} @@ -609,9 +609,9 @@ def _create_no_pic_and_pic_static_libs_actions( actions, name, link_target_type_used_for_naming, - True, # use_pic - cc_toolchain, - link_action_kwargs, + use_pic = True, + cc_toolchain = cc_toolchain, + link_action_kwargs = link_action_kwargs, ) return no_pic_library_to_link | pic_library_to_link diff --git a/cc/private/link/cpp_link_action.bzl b/cc/private/link/cpp_link_action.bzl index 5e48f5632..3f3569ef1 100644 --- a/cc/private/link/cpp_link_action.bzl +++ b/cc/private/link/cpp_link_action.bzl @@ -212,31 +212,31 @@ def link_action( stamping, feature_configuration, cc_toolchain, - "Linking %{output}", # progress_message + progress_message = "Linking %{output}", # Inputs: - object_file_inputs, - non_code_inputs, - libraries_to_link, - linkstamp_map, - linkstamp_object_artifacts, - linkstamp_object_file_inputs, - user_link_flags, + object_file_inputs = object_file_inputs, + non_code_inputs = non_code_inputs, + libraries_to_link = libraries_to_link, + linkstamp_map = linkstamp_map, + linkstamp_object_artifacts = linkstamp_object_artifacts, + linkstamp_object_file_inputs = linkstamp_object_file_inputs, + user_link_flags = user_link_flags, # Custom user input files and variables: - additional_linker_inputs, - build_variables, + additional_linker_inputs = additional_linker_inputs, + additional_build_variables = build_variables, # Outputs: - output, - interface_output, - dynamic_library_solib_symlink_output, - action_outputs, + output = output, + interface_output = interface_output, + dynamic_library_solib_symlink_output = dynamic_library_solib_symlink_output, + action_outputs = action_outputs, # Originating from private APIs: - use_test_only_flags, - whole_archive, - native_deps, - additional_linkstamp_defines, + use_test_only_flags = use_test_only_flags, + whole_archive = whole_archive, + native_deps = native_deps, + additional_linkstamp_defines = additional_linkstamp_defines, # LTO: - lto_mapping, - allow_lto_indexing, + lto_mapping = lto_mapping, + allow_lto_indexing = allow_lto_indexing, ) return output_library, interface_output_library diff --git a/cc/private/link/create_extra_link_time_library.bzl b/cc/private/link/create_extra_link_time_library.bzl index 74165962d..344a83fe8 100644 --- a/cc/private/link/create_extra_link_time_library.bzl +++ b/cc/private/link/create_extra_link_time_library.bzl @@ -22,8 +22,8 @@ This file is extremely widely loaded (due to being used by CcInfo), do not load others unless absolutely necessary. """ -load("//cc/common:visibility.bzl", "check_private_api") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") +load("//cc/private:visibility.bzl", "check_private_api") # An implementation of ExtraLinkTimeLibrary that uses functions and data passed in from Starlark. @@ -57,16 +57,21 @@ def create_extra_link_time_library(*, build_library_func, **kwargs): Any implementations must be immutable (and therefore thread-safe), because this is passed between rules and accessed in a multi-threaded context. + The callback is part of the key used to merge inputs from dependencies. Pass rule-specific + values through kwargs so dependencies can reuse the same callback. + Args: - build_library_func: A function that takes a rule context, static_mode, for_dynamic_library, - and kwargs, and returns a tuple of (linker_input, runtime_library): `tuple[LinkerInputInfo, - File]`. + build_library_func: A top-level Starlark function that takes a rule context, static_mode, + for_dynamic_library, and kwargs. It returns a struct with linker_input (a depset of linker + inputs), runtime_library (a depset of files), and optional additional_stamp_info. The + top-level requirement is checked when the library is built. **kwargs: Additional fields to pass to the build function. Returns: ExtraLinkTimeLibraryInfo. """ - _cc_internal.check_toplevel(build_library_func) + if type(build_library_func) != "function": + fail("build_library_func must be a Starlark function, got %s" % type(build_library_func)) return ExtraLinkTimeLibraryInfo( build_library_func = build_library_func, # Key to identify the "class" of a StarlarkDefinedLinkTimeLibrary. Uses the build function and @@ -173,6 +178,12 @@ def build_libraries(extra_libraries, ctx, static_mode, for_dynamic_library): transitive_runtime_libraries = [] additional_stamp_infos = [] for library in extra_libraries: + # Merging uses build_library_func as part of the key, so require a + # top-level def with the same identity across dependency analysis. + # Args.add_all normally uses this check to keep closures out of execution; + # here an empty, discarded Args validates the function without invoking it + # or registering an action. We call build_library_func below during analysis. + ctx.actions.args().add_all([], map_each = library.build_library_func) kwargs = {} for key in dir(library): if key not in ["build_library_func", "_key"]: diff --git a/cc/private/link/create_linking_context_from_compilation_outputs.bzl b/cc/private/link/create_linking_context_from_compilation_outputs.bzl index 13e492397..f8f1c6047 100644 --- a/cc/private/link/create_linking_context_from_compilation_outputs.bzl +++ b/cc/private/link/create_linking_context_from_compilation_outputs.bzl @@ -111,11 +111,11 @@ def create_linking_context_from_compilation_outputs( cc_toolchain, compilation_outputs, linking_contexts, - user_link_flags, # linkopts - stamp, - additional_inputs, # additional_linker_inputs - [], # linker_outputs - variables_extension, + linkopts = user_link_flags, + stamp = stamp, + additional_linker_inputs = additional_inputs, + linker_outputs = [], + variables_extension = variables_extension, alwayslink = alwayslink, test_only_target = test_only_target, linked_dll_name_suffix = linked_dll_name_suffix, diff --git a/cc/private/link/link.bzl b/cc/private/link/link.bzl index 822506fb4..993aee8df 100644 --- a/cc/private/link/link.bzl +++ b/cc/private/link/link.bzl @@ -174,21 +174,21 @@ def link( cc_toolchain, compilation_outputs, linking_contexts, - user_link_flags, # linkopts - stamp, - additional_inputs, - additional_outputs, # linker_outputs - variables_extension, - use_test_only_flags, - never_link, # neverlink - test_only_target, - whole_archive, - native_deps, - additional_linkstamp_defines, + linkopts = user_link_flags, + stamp = stamp, + additional_linker_inputs = additional_inputs, + linker_outputs = additional_outputs, + variables_extension = variables_extension, + use_test_only_flags = use_test_only_flags, + neverlink = never_link, + test_only_target = test_only_target, + whole_archive = whole_archive, + native_deps = native_deps, + additional_linkstamp_defines = additional_linkstamp_defines, # alwayslink may be deprecated but we're trying to replicate CcBinary as closely as possible for the moment. # TODO(b/331164666): remove alwayslink, the information is in static_link_type already - always_link, - main_output, # linker_output_artifact + alwayslink = always_link, + linker_output_artifact = main_output, emit_interface_shared_libraries = dynamic_link_type == LINK_TARGET_TYPE.DYNAMIC_LIBRARY and (emit_interface_shared_library or feature_configuration.is_enabled(feature_names.TARGETS_WINDOWS)), diff --git a/cc/private/link/link_build_variables.bzl b/cc/private/link/link_build_variables.bzl index 51c4337d6..2e13c38c5 100644 --- a/cc/private/link/link_build_variables.bzl +++ b/cc/private/link/link_build_variables.bzl @@ -155,16 +155,16 @@ def create_link_variables( vars = setup_common_linking_variables( cc_toolchain, feature_configuration, - [], # libraries_to_link - runtime_library_search_directories, - library_search_directories, - user_link_flags, - param_file, - is_using_linker, - is_linking_dynamic_library, - must_keep_debug, - use_test_only_flags, - is_static_linking_mode, + libraries_to_link = [], + runtime_library_search_directories = runtime_library_search_directories, + library_search_directories = library_search_directories, + user_link_flags = user_link_flags, + param_file = param_file, + is_using_linker = is_using_linker, + is_linking_dynamic_library = is_linking_dynamic_library, + must_keep_debug = must_keep_debug, + use_test_only_flags = use_test_only_flags, + is_static_linking_mode = is_static_linking_mode, ) # output exec path diff --git a/cc/private/link/lto_backends.bzl b/cc/private/link/lto_backends.bzl index 4d9629080..6481a31fb 100644 --- a/cc/private/link/lto_backends.bzl +++ b/cc/private/link/lto_backends.bzl @@ -411,7 +411,7 @@ def create_lto_backend_artifacts( all_bitcode_files, dwo_file, use_pic, - None, # bitcode_file_path + bitcode_file_path = None, ) return LtoBackendArtifactsInfo( diff --git a/cc/private/link/lto_indexing_action.bzl b/cc/private/link/lto_indexing_action.bzl index fd3d7b9d4..1685eb46c 100644 --- a/cc/private/link/lto_indexing_action.bzl +++ b/cc/private/link/lto_indexing_action.bzl @@ -263,14 +263,14 @@ def _lto_indexing_action( finalize_link_action( actions, - "CppLTOIndexing", # mnemonic - action_name, - link_type, - linking_mode, - False, # stamping (we don't have linkstamps, nothing to stamp) - feature_configuration, - cc_toolchain, - "LTO indexing %{output}", # progress_message + mnemonic = "CppLTOIndexing", + action_name = action_name, + link_type = link_type, + linking_mode = linking_mode, + stamping = False, # we don't have linkstamps, nothing to stamp + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + progress_message = "LTO indexing %{output}", # Inputs: object_file_inputs = object_file_inputs, libraries_to_link = libraries_to_link, diff --git a/cc/private/objc_info.bzl b/cc/private/objc_info.bzl index c17981b69..8531ae42b 100644 --- a/cc/private/objc_info.bzl +++ b/cc/private/objc_info.bzl @@ -14,6 +14,7 @@ # LINT.IfChange(forked_exports) """The provider ObjcInfo for ObjC rules. For more context see doc param of provider() call.""" +# TODO(https://github.com/bazelbuild/bazel/issues/32021): use a source annotation once depset element types are supported. def _objcinfo_init( j2objc_library = depset(), module_map = depset(), @@ -21,6 +22,7 @@ def _objcinfo_init( strict_include = depset(), umbrella_header = depset(), providers = []): # List of depended-on ObjcInfo providers + # type: (depset[File], depset[File], depset[File], depset[str], depset[File], Collection[ObjcInfo]) -> dict[{'direct_module_maps': list[File], 'direct_sources': list[File], 'j2objc_library': depset[File], 'module_map': depset[File], 'source': depset[File], 'strict_include': depset[str], 'umbrella_header': depset[File]}] direct_module_maps = module_map.to_list() direct_sources = source.to_list() diff --git a/cc/private/objc_info.bzli b/cc/private/objc_info.bzli new file mode 100644 index 000000000..55be7d23b --- /dev/null +++ b/cc/private/objc_info.bzli @@ -0,0 +1,22 @@ +"""Objective-C compilation and linking information.""" + +# TODO(https://github.com/bazelbuild/bazel/issues/32207): Move the field types into the provider declaration when Bazel can +# describe its result independently of the custom initializer inputs. + +# The provider and initializer need separate declarations; keep their signatures and fields in sync. +def ObjcInfo( + j2objc_library: depset[File] = ..., + module_map: depset[File] = ..., + source: depset[File] = ..., + strict_include: depset[str] = ..., + umbrella_header: depset[File] = ..., + providers: Collection[ObjcInfo] = ..., +) -> record[{ + "direct_module_maps": list[File], + "direct_sources": list[File], + "j2objc_library": depset[File], + "module_map": depset[File], + "source": depset[File], + "strict_include": depset[str], + "umbrella_header": depset[File], +}]: ... diff --git a/cc/private/paths.bzl b/cc/private/paths.bzl index 838f4ec13..60f55d106 100644 --- a/cc/private/paths.bzl +++ b/cc/private/paths.bzl @@ -14,6 +14,7 @@ """Paths utility functions.""" def is_path_absolute(path): + # type: (str) -> bool """Checks whether a path is absolute or not. Note that is does not take the execution platform into account. diff --git a/cc/private/rules_impl/BUILD b/cc/private/rules_impl/BUILD index 1e11a5b36..6f8e395d0 100644 --- a/cc/private/rules_impl/BUILD +++ b/cc/private/rules_impl/BUILD @@ -4,6 +4,8 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 +exports_files(["objc_compilation_context_info.bzli"]) + filegroup( name = "bzl_srcs", srcs = glob(["**/*.bzl"]) + [ @@ -15,6 +17,7 @@ filegroup( name = "srcs", srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]) + [ "//cc/private/rules_impl/fdo:srcs", diff --git a/cc/private/rules_impl/attrs.bzl b/cc/private/rules_impl/attrs.bzl index c74d42f0d..cd4cbc99c 100644 --- a/cc/private/rules_impl/attrs.bzl +++ b/cc/private/rules_impl/attrs.bzl @@ -301,7 +301,7 @@ Whether to encode build information into the binary. Possible values:

Stamped binaries are not rebuilt unless their dependencies change.

diff --git a/cc/private/rules_impl/cc_binary_impl.bzl b/cc/private/rules_impl/cc_binary_impl.bzl index a329ae28d..152f5f6f0 100644 --- a/cc/private/rules_impl/cc_binary_impl.bzl +++ b/cc/private/rules_impl/cc_binary_impl.bzl @@ -120,7 +120,7 @@ def _add_transitive_info_providers(ctx, cc_toolchain, cpp_config, feature_config cc_toolchain, feature_configuration, ctx, - False, # generate_hidden_top_level_group + generate_hidden_top_level_group = False, ) cc_info = CcInfo( compilation_context = compilation_context, @@ -178,21 +178,6 @@ def _collect_runfiles(ctx, feature_configuration, cc_toolchain, linking_context, return (builder.merge(ctx.runfiles(files = builder_artifacts, transitive_files = depset(builder_transitive_artifacts))), runtime_objects_for_coverage) -def _create_dynamic_libraries_copy_actions(ctx, binary, dynamic_libraries_for_runtime): - result = [] - for lib in dynamic_libraries_for_runtime: - # If the binary and the DLL are not in the same directory, copy the DLL - # to the binary's directory. - if lib.dirname != binary.dirname: - copy = ctx.actions.declare_file(lib.basename, sibling = binary) - ctx.actions.symlink(output = copy, target_file = lib, progress_message = "Copying Execution Dynamic Library") - result.append(copy) - else: - # If the library is already in the same directory as the binary, we don't need to copy it, - # but we still add it to the result. - result.append(lib) - return depset(result) - def _get_dynamic_library_for_runtime_or_none(library_to_link, link_statically): if library_to_link.dynamic_library == None: return None @@ -718,10 +703,15 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): dwp_exec_group = "cpp_dwp", ) explicit_dwp_file = dwp_file + materialize_dwo_files = False if not cc_helper.should_create_per_object_debug_info(feature_configuration, cpp_config): explicit_dwp_file = None elif cc_helper.should_create_test_dwp_for_statically_linked_test(ctx.attr._is_test, linking_mode, cpp_config): files_to_build_list.append(dwp_file) + elif is_dbg_build: + # If fission is enabled, and this is a debug build, and we are not creating a DWP file, + # materialize DWO files into their own output_group. + materialize_dwo_files = True # If the binary is linked dynamically and COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, collect # all the dynamic libraries we need at runtime. Then copy these libraries next to the binary. @@ -731,7 +721,7 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): libraries = [] for linker_input in linker_inputs: libraries.extend(linker_input.libraries) - copied_runtime_dynamic_libraries = _create_dynamic_libraries_copy_actions(ctx, binary, _get_dynamic_libraries_for_runtime(is_static_mode, libraries)) + copied_runtime_dynamic_libraries = cc_helper.create_dynamic_libraries_copy_actions(ctx, binary, _get_dynamic_libraries_for_runtime(is_static_mode, libraries)) # TODO(b/198254254)(bazel-team): Do we need to put original shared libraries (along with # mangled symlinks) into the RunfilesSupport object? It does not seem @@ -785,6 +775,13 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): if linkmap: output_groups["linkmap"] = depset([linkmap]) + if materialize_dwo_files and dwo_files: + output_groups["dwo_files"] = dwo_files + else: + # Some configurations always request dwo_files. + # If we do not create them, set an empty depset to avoid a warning. + output_groups["dwo_files"] = depset() + if cc_linking_outputs_binary_library != None: # For consistency and readability. library_to_link = cc_linking_outputs_binary_library @@ -818,12 +815,14 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): runfiles = runfiles, executable = binary, ) + + # TODO(https://github.com/bazelbuild/bazel/issues/32085): Use Bazel's type. result = [ cc_info, instrumented_files_provider, debug_package_info, OutputGroupInfo(**output_groups), - ] + ] # type: list[ProviderInstance] if cc_launcher_info != None: result.append(cc_launcher_info) return binary_info, result diff --git a/cc/private/rules_impl/cc_library.bzl b/cc/private/rules_impl/cc_library.bzl index 0bbb93094..5b8e7bb43 100755 --- a/cc/private/rules_impl/cc_library.bzl +++ b/cc/private/rules_impl/cc_library.bzl @@ -394,7 +394,7 @@ the malloc attribute of cc_binary). Dependency linkopts take precedence over dependent linkopts (i.e. dependency linkopts appear later in the command line). Linkopts specified in ---linkopt +--linkopt take precedence over rule linkopts.

diff --git a/cc/private/rules_impl/cc_shared_library.bzl b/cc/private/rules_impl/cc_shared_library.bzl index 7df94c7f8..8566333d2 100644 --- a/cc/private/rules_impl/cc_shared_library.bzl +++ b/cc/private/rules_impl/cc_shared_library.bzl @@ -79,31 +79,8 @@ graph_structure_aspect = aspect( implementation = _graph_structure_aspect_impl, ) -def _cc_shared_library_initializer(**kwargs): - """Converts labels in exports_filter into canonical form relative to the current repository. - - This conversion can only be done in a macro as it requires access to the repository mapping of - the repository containing the cc_shared_library target. This mapping is automatically - applied to label attributes, but exports_filter is a list of strings attribute. - """ - if "exports_filter" not in kwargs: - return kwargs - - raw_exports_filter = kwargs["exports_filter"] - if type(raw_exports_filter) != type([]): - # TODO: Also canonicalize labels in selects once macros can operate on them. - # https://github.com/bazelbuild/bazel/issues/14157 - return kwargs - - canonical_exports_filter = [ - str(native.package_relative_label(s)) - for s in raw_exports_filter - ] - return kwargs | {"exports_filter": canonical_exports_filter} - cc_shared_library = rule( implementation = proxy, - initializer = _cc_shared_library_initializer, doc = """

It produces a shared library.

diff --git a/cc/private/rules_impl/cc_shared_library_impl.bzl b/cc/private/rules_impl/cc_shared_library_impl.bzl index b241381c4..58212ae28 100644 --- a/cc/private/rules_impl/cc_shared_library_impl.bzl +++ b/cc/private/rules_impl/cc_shared_library_impl.bzl @@ -17,7 +17,7 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("//cc:find_cc_toolchain.bzl", "find_cc_toolchain") load("//cc/common:cc_common.bzl", "cc_common") -load("//cc/common:cc_helper.bzl", "cc_helper") +load("//cc/common:cc_helper.bzl", "artifact_category", "cc_helper") load("//cc/common:cc_info.bzl", "CcInfo") load("//cc/common:cc_shared_library_info.bzl", "CcSharedLibraryInfo") load("//cc/common:feature_names.bzl", "feature_names") @@ -293,9 +293,9 @@ def _check_if_target_under_path(value, pattern): return pattern.package == value.package and pattern.name == value.name -def _check_if_target_should_be_exported_with_filter(target, current_label, exports_filter): +def _check_if_target_should_be_exported_with_filter(ctx, target, exports_filter): for export_filter in exports_filter: - export_filter_label = current_label.relative(export_filter) + export_filter_label = ctx.package_relative_label(export_filter) if _check_if_target_under_path(target, export_filter_label): return True @@ -478,8 +478,8 @@ def _filter_inputs( linker_input, ) if _check_if_target_should_be_exported_with_filter( + ctx, linker_input.owner, - ctx.label, ctx.attr.exports_filter, ): exports[owner] = True @@ -690,12 +690,21 @@ def _cc_shared_library_impl(ctx): if library.objects != None: object_files.extend(library.objects) - generated_def_file = None + # The LIBRARY statement of the DEF file determines the DLL name that the import library + # refers to, so it has to match the actual output file. + if main_output: + dll_name = main_output.basename + else: + dll_name = paths.basename(cc_common.get_artifact_name_for_category( + cc_toolchain = cc_toolchain, + category = artifact_category.DYNAMIC_LIBRARY, + output_name = ctx.label.name, + )) generated_def_file = cc_helper.generate_def_file( ctx, ctx.file._def_parser, object_files, - ctx.label.name, + dll_name, cc_toolchain, feature_configuration, ) @@ -736,6 +745,25 @@ def _cc_shared_library_impl(ctx): for dep in ctx.attr.dynamic_deps: runfiles = runfiles.merge(dep[DefaultInfo].data_runfiles) + if feature_configuration.is_enabled(feature_names.COPY_DYNAMIC_LIBRARIES_TO_BINARY): + libraries_to_copy = [] + copy_linking_contexts = [linking_context] + runtimes_linking_contexts + copy_linking_contexts.append(_create_linker_context([ + dep.linker_input + for dep in merged_cc_shared_library_infos_list + ])) + for context in copy_linking_contexts: + libraries_to_copy.extend(cc_helper.get_dynamic_libraries_for_runtime( + context, + linking_statically = True, + )) + copied_runtime_libraries = cc_helper.create_dynamic_libraries_copy_actions( + ctx, + linking_outputs.library_to_link.dynamic_library, + libraries_to_copy, + ) + runfiles = runfiles.merge(ctx.runfiles(transitive_files = copied_runtime_libraries)) + precompiled_only_dynamic_libraries_runfiles = [] for precompiled_dynamic_library in precompiled_only_dynamic_libraries: # precompiled_dynamic_library.dynamic_library could be None if the library to link just contains diff --git a/cc/private/rules_impl/cc_static_library.bzl b/cc/private/rules_impl/cc_static_library.bzl index 041da2afb..7579307b6 100644 --- a/cc/private/rules_impl/cc_static_library.bzl +++ b/cc/private/rules_impl/cc_static_library.bzl @@ -40,8 +40,8 @@ def _flatten_and_get_objects(linker_inputs): for linker_input in linker_inputs.to_list(): for lib in linker_input.libraries: if lib._contains_objects: - transitive_objects.append(depset(lib.pic_objects)) - transitive_objects.append(depset(lib.objects)) + # Select on variant per library, preferring PIC. + transitive_objects.append(depset(lib.pic_objects or lib.objects)) return depset(transitive = transitive_objects, order = "topological") diff --git a/cc/private/rules_impl/cc_toolchain.bzl b/cc/private/rules_impl/cc_toolchain.bzl index ab55e6b35..7a622b536 100644 --- a/cc/private/rules_impl/cc_toolchain.bzl +++ b/cc/private/rules_impl/cc_toolchain.bzl @@ -356,12 +356,12 @@ The label of the rule providing cc_toolchain_config_info.""", default = configuration_field(fragment = "cpp", name = "libc_top"), ), "_interface_library_builder": attr.label( - default = "@bazel_tools//tools/cpp:interface_library_builder", + default = "//cc/private/toolchain:interface_library_builder", allow_single_file = True, cfg = "exec", ), "_link_dynamic_library_tool": attr.label( - default = "@bazel_tools//tools/cpp:link_dynamic_library", + default = "//cc/private/toolchain:link_dynamic_library", allow_single_file = True, cfg = "exec", ), diff --git a/cc/private/rules_impl/cc_toolchain_provider_helper.bzl b/cc/private/rules_impl/cc_toolchain_provider_helper.bzl index 8d042e291..b0dea3f7f 100644 --- a/cc/private/rules_impl/cc_toolchain_provider_helper.bzl +++ b/cc/private/rules_impl/cc_toolchain_provider_helper.bzl @@ -58,17 +58,22 @@ def _additional_make_variables(toolchain_make_vars): make_vars.pop("CC_FLAGS", None) return make_vars -def _compute_tool_paths(toolchain_config_info, crosstool_top_path): +def _compute_tool_paths(toolchain_config_info, crosstool_top_path, output_directory_prefix): tool_paths_collector = {} for tool in toolchain_config_info.tool_paths: path_str = tool.path if not paths.is_normalized(path_str): fail("The include path '" + path_str + "' is not normalized.") - tool_paths_collector[tool.name] = get_relative_path(crosstool_top_path, path_str) + if path_str.startswith(output_directory_prefix): + # The path of a file generated by the build, e.g. a compiler wrapper built in the + # exec configuration, is already relative to the execution root. + tool_paths_collector[tool.name] = path_str + else: + tool_paths_collector[tool.name] = get_relative_path(crosstool_top_path, path_str) return tool_paths_collector -def _resolve_include_dir(target_label, s, sysroot, crosstool_path): +def _resolve_include_dir(ctx, s, sysroot, crosstool_path): """ Resolve the given include directory. If it starts with %sysroot%/, that part is replaced with the actual sysroot. @@ -97,7 +102,7 @@ def _resolve_include_dir(target_label, s, sysroot, crosstool_path): # This is necessary to avoid the hard work of parsing, # and use an already existing API. - dummy_label = target_label.relative(package + ":dummy_target") + dummy_label = ctx.package_relative_label(package + ":dummy_target") repo_prefix = dummy_label.workspace_root path_prefix = get_relative_path(repo_prefix, dummy_label.package) path_start_index = package_end_index + len(_PACKAGE_END) @@ -150,7 +155,14 @@ def get_cc_toolchain_provider(ctx, attributes): attributes.cc_toolchain_label.package, ctx.configuration.is_sibling_repository_layout(), ) - tool_paths = _compute_tool_paths(toolchain_config_info, tools_directory) + bin_dir_path = ctx.bin_dir.path + tool_paths = _compute_tool_paths( + toolchain_config_info, + tools_directory, + # The first segment of the bin directory path, e.g. "bazel-out/", is shared by the output + # directories of all configurations. + bin_dir_path[:bin_dir_path.find("/") + 1], + ) toolchain_features = cc_common.cc_toolchain_features(toolchain_config_info = toolchain_config_info, tools_directory = tools_directory) # action_is_enabled only returns true for action_configs that are in @@ -222,7 +234,7 @@ def get_cc_toolchain_provider(ctx, attributes): builtin_include_directories = [] for s in toolchain_config_info.cxx_builtin_include_directories: - builtin_include_directories.append(_resolve_include_dir(ctx.label, s, sysroot, tools_directory)) + builtin_include_directories.append(_resolve_include_dir(ctx, s, sysroot, tools_directory)) build_variables_dict = _get_cc_toolchain_vars(ctx.fragments.cpp, sysroot) build_variables = cc_common.cc_toolchain_variables(vars = build_variables_dict) diff --git a/cc/private/rules_impl/failing_cc_proto_library.bzl b/cc/private/rules_impl/failing_cc_proto_library.bzl index 263c8a022..499c8d2cc 100644 --- a/cc/private/rules_impl/failing_cc_proto_library.bzl +++ b/cc/private/rules_impl/failing_cc_proto_library.bzl @@ -23,6 +23,7 @@ CC_PROTO_LIBRARY_DEPRECATION = ( ) def _impl(_ctx): + # type: (...) -> Never fail(CC_PROTO_LIBRARY_DEPRECATION) cc_proto_library = rule( diff --git a/cc/private/rules_impl/fdo/fdo_prefetch_hints.bzl b/cc/private/rules_impl/fdo/fdo_prefetch_hints.bzl index 6696b7b3d..c6e693073 100644 --- a/cc/private/rules_impl/fdo/fdo_prefetch_hints.bzl +++ b/cc/private/rules_impl/fdo/fdo_prefetch_hints.bzl @@ -20,6 +20,7 @@ FdoPrefetchHintsInfo = provider( ) def _impl(ctx): + # type: (...) -> FdoPrefetchHintsInfo return FdoPrefetchHintsInfo(artifact = ctx.file.profile) fdo_prefetch_hints = rule( diff --git a/cc/private/rules_impl/fdo/fdo_profile.bzl b/cc/private/rules_impl/fdo/fdo_profile.bzl index d16e94dac..f2bfe2937 100644 --- a/cc/private/rules_impl/fdo/fdo_profile.bzl +++ b/cc/private/rules_impl/fdo/fdo_profile.bzl @@ -26,6 +26,7 @@ FdoProfileInfo = provider( ) def _impl(ctx): + # type: (...) -> FdoProfileInfo return FdoProfileInfo( artifact = ctx.file.profile, proto_profile_artifact = ctx.file.proto_profile, diff --git a/cc/private/rules_impl/fdo/memprof_profile.bzl b/cc/private/rules_impl/fdo/memprof_profile.bzl index 46248613c..3eb12f01a 100644 --- a/cc/private/rules_impl/fdo/memprof_profile.bzl +++ b/cc/private/rules_impl/fdo/memprof_profile.bzl @@ -20,6 +20,7 @@ MemProfProfileInfo = provider( ) def _impl(ctx): + # type: (...) -> MemProfProfileInfo return MemProfProfileInfo( artifact = ctx.file.profile, changelist = ctx.attr.changelist, diff --git a/cc/private/rules_impl/fdo/propeller_optimize.bzl b/cc/private/rules_impl/fdo/propeller_optimize.bzl index 3e3e8ce62..833aa535c 100644 --- a/cc/private/rules_impl/fdo/propeller_optimize.bzl +++ b/cc/private/rules_impl/fdo/propeller_optimize.bzl @@ -20,6 +20,7 @@ PropellerOptimizeInfo = provider( ) def _impl(ctx): + # type: (...) -> PropellerOptimizeInfo return PropellerOptimizeInfo( cc_profile = ctx.file.cc_profile, ld_profile = ctx.file.ld_profile, diff --git a/cc/private/rules_impl/native_cc_common.bzl b/cc/private/rules_impl/native_cc_common.bzl index 00bc8a5dc..75f18abcf 100644 --- a/cc/private/rules_impl/native_cc_common.bzl +++ b/cc/private/rules_impl/native_cc_common.bzl @@ -14,5 +14,7 @@ # """Native implementation of cc_common.""" +# Bazel's API metadata omits private members such as empty_variables and +# legacy_cc_flags_make_variable_do_not_use. # buildifier: disable=native-cc-common -native_cc_common = cc_common +native_cc_common = cc_common # ty: ignore[non-static-type] diff --git a/cc/private/rules_impl/objc_compilation_artifacts_info.bzl b/cc/private/rules_impl/objc_compilation_artifacts_info.bzl index bdb0e77ea..0b72d049f 100644 --- a/cc/private/rules_impl/objc_compilation_artifacts_info.bzl +++ b/cc/private/rules_impl/objc_compilation_artifacts_info.bzl @@ -53,12 +53,13 @@ def _compilation_artifacts_init( # Note: the condition under which we set an archive artifact needs to match the condition for # which we create the archive in compilation_support.bzl. In particular, if srcs are all # headers, we don't generate an archive. + # TODO(https://github.com/bazelbuild/rules_cc/pull/967): handle omitted sources and artifacts. if ( non_arc_srcs or - [s for s in srcs if s.is_directory or + [s for s in srcs if s.is_directory or # ty: ignore[not-iterable] s.extension in COMPILABLE_OR_PRECOMPILED_SRC_EXTENSIONS] ): - archive = intermediate_artifacts.archive() + archive = intermediate_artifacts.archive() # ty: ignore[unresolved-attribute] else: archive = None return { diff --git a/cc/private/rules_impl/objc_compilation_artifacts_info.bzli b/cc/private/rules_impl/objc_compilation_artifacts_info.bzli new file mode 100644 index 000000000..2d7748db4 --- /dev/null +++ b/cc/private/rules_impl/objc_compilation_artifacts_info.bzli @@ -0,0 +1,47 @@ +# 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. + +"""Types for Objective-C compilation artifacts.""" + +type _Files = list[File] | tuple[File, ...] +type _ArchiveFactory = struct[{"archive": Callable[[], File]}, ...] +# TODO(https://github.com/bazelbuild/bazel/issues/31770): annotate the Objective-C artifact dictionary +# in Starlark once Bazel supports dictionaries with named, typed keys. +type _Result = dict[{ + "srcs": _Files | None, + "non_arc_srcs": _Files | None, + "additional_hdrs": _Files | None, + "archive": File | None, +}] + +def _compilation_artifacts_init( + ctx: ctx[object, object] | None = ..., + srcs: _Files | None = ..., + non_arc_srcs: _Files | None = ..., + hdrs: _Files | None = ..., + intermediate_artifacts: _ArchiveFactory | None = ..., +) -> _Result: ... + +def CompilationArtifactsInfo( + ctx: ctx[object, object] | None = ..., + srcs: _Files | None = ..., + non_arc_srcs: _Files | None = ..., + hdrs: _Files | None = ..., + intermediate_artifacts: _ArchiveFactory | None = ..., +) -> record[{ + "srcs": _Files | None, + "non_arc_srcs": _Files | None, + "additional_hdrs": _Files | None, + "archive": File | None, +}]: ... diff --git a/cc/private/rules_impl/objc_compilation_context_info.bzli b/cc/private/rules_impl/objc_compilation_context_info.bzli new file mode 100644 index 000000000..9a417c6ce --- /dev/null +++ b/cc/private/rules_impl/objc_compilation_context_info.bzli @@ -0,0 +1,35 @@ +"""Inputs and fields of the Objective-C compilation context provider.""" + +class _StrictIncludes(Protocol): + @property + def strict_include(self) -> depset[str]: ... + +# TODO(https://github.com/bazelbuild/bazel/issues/31770): annotate the Objective-C provider initializer result +# in Starlark once Bazel supports dictionaries with named, typed keys. +type _Fields = dict[{ + "defines": Sequence[str], + "public_hdrs": Sequence[File], + "public_textual_hdrs": Collection[File], + "private_hdrs": Collection[File], + "includes": Sequence[str], + "system_includes": Sequence[str], + "quote_includes": Sequence[str], + "direct_cc_compilation_contexts": Collection[CompilationContext], + "cc_compilation_contexts": list[CompilationContext], + "implementation_cc_compilation_contexts": Sequence[CompilationContext], + "strict_dependency_includes": list[str], +}] + +def _objc_compilation_context_info_init( + defines: Sequence[str] = ..., + public_hdrs: Sequence[File] = ..., + public_textual_hdrs: Collection[File] = ..., + private_hdrs: Collection[File] = ..., + includes: Sequence[str] = ..., + system_includes: Sequence[str] = ..., + quote_includes: Sequence[str] = ..., + direct_cc_compilation_contexts: Collection[CompilationContext] = ..., + cc_compilation_contexts: list[CompilationContext] = ..., + implementation_cc_compilation_contexts: Sequence[CompilationContext] = ..., + providers: Collection[_StrictIncludes] = ..., +) -> _Fields: ... diff --git a/cc/private/rules_impl/objc_compilation_support.bzl b/cc/private/rules_impl/objc_compilation_support.bzl index 624ee8bfd..46bcd8439 100644 --- a/cc/private/rules_impl/objc_compilation_support.bzl +++ b/cc/private/rules_impl/objc_compilation_support.bzl @@ -22,8 +22,6 @@ load(":objc_common.bzl", "objc_common") load(":objc_compilation_artifacts_info.bzl", "CompilationArtifactsInfo") load(":objc_intermediate_artifacts.bzl", "create_intermediate_artifacts") -XcodeVersionInfo = apple_common.XcodeVersionConfig - def _build_variable_extensions(ctx, arc_enabled): extensions = {} if hasattr(ctx.attr, "pch") and ctx.attr.pch != None: @@ -282,13 +280,7 @@ def _get_non_arc_srcs(ctx): if DefaultInfo in src: for artifact in src[DefaultInfo].files.to_list(): artifact_label_map[artifact] = src.label - return _map_to_list(artifact_label_map) - -def _map_to_list(m): - result = [] - for k, v in m.items(): - result.append((k, v)) - return result + return artifact_label_map.items() def _cc_compile_and_link( srcs, diff --git a/cc/private/toolchain/BUILD b/cc/private/toolchain/BUILD index abd4e6b9d..753bdab3d 100644 --- a/cc/private/toolchain/BUILD +++ b/cc/private/toolchain/BUILD @@ -21,6 +21,11 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 +exports_files([ + "lib_cc_configure.bzli", + "unix_cc_configure.bzli", +]) + # It is frequently necessary to constrain platforms based on the cc compiler type. constraint_setting(name = "cc_compiler") @@ -58,6 +63,12 @@ cc_library( name = "malloc", ) +filegroup( + name = "grep-includes", + srcs = ["empty.cc"], + deprecation = "grep-includes is always empty, you must provide your own.", +) + filegroup( name = "empty", srcs = [], diff --git a/cc/private/toolchain/cc_configure.bzl b/cc/private/toolchain/cc_configure.bzl index c2bf06aaa..fa28a5d81 100644 --- a/cc/private/toolchain/cc_configure.bzl +++ b/cc/private/toolchain/cc_configure.bzl @@ -14,15 +14,14 @@ """Rules for configuring the C++ toolchain (experimental).""" load("@bazel_features//:features.bzl", "bazel_features") -load( - ":lib_cc_configure.bzl", - "get_cpu_value", - "resolve_labels", -) +load(":lib_cc_configure.bzl", "copy_file", "get_cpu_value") load(":unix_cc_configure.bzl", "configure_unix_toolchain") load(":windows_cc_configure.bzl", "configure_windows_toolchain") def _should_disable_toolchain(repository_ctx): + # type: (repository_ctx) -> bool + # TODO(https://github.com/bazelbuild/bazel/issues/32020): make this an + # annotation when supported Bazel versions expose a repository context type. """Returns true if the toolchain should be disabled based on environment variables.""" env = repository_ctx.os.environ if env.get("BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN") == "1": @@ -39,6 +38,7 @@ def _should_disable_toolchain(repository_ctx): return False def cc_autoconf_toolchains_impl(repository_ctx): + # type: (repository_ctx) -> repo_metadata | None """Generate BUILD file with 'toolchain' targets for the local host C++ toolchain. Args: @@ -52,13 +52,10 @@ def cc_autoconf_toolchains_impl(repository_ctx): build_path = "@rules_cc//cc/private/toolchain:BUILD.windows_toolchains.tpl" else: build_path = "@rules_cc//cc/private/toolchain:BUILD.toolchains.tpl" - paths = resolve_labels(repository_ctx, [ - build_path, - ]) repository_ctx.template( "BUILD", - paths[build_path], + Label(build_path), {"%{name}": get_cpu_value(repository_ctx)}, ) else: @@ -78,6 +75,7 @@ cc_autoconf_toolchains = repository_rule( ) def cc_autoconf_impl(repository_ctx, overriden_tools = dict()): + # type: (repository_ctx, Mapping[str, str | path]) -> repo_metadata | None """Generate BUILD file with 'cc_toolchain' targets for the local host C++ toolchain. Args: @@ -89,28 +87,18 @@ def cc_autoconf_impl(repository_ctx, overriden_tools = dict()): """ cpu_value = get_cpu_value(repository_ctx) if _should_disable_toolchain(repository_ctx): - paths = resolve_labels(repository_ctx, [ - "@rules_cc//cc/private/toolchain:BUILD.empty.tpl", - "@rules_cc//cc/private/toolchain:empty_cc_toolchain_config.bzl", - ]) - repository_ctx.symlink(paths["@rules_cc//cc/private/toolchain:empty_cc_toolchain_config.bzl"], "cc_toolchain_config.bzl") - repository_ctx.template("BUILD", paths["@rules_cc//cc/private/toolchain:BUILD.empty.tpl"], { + copy_file(repository_ctx, Label("@rules_cc//cc/private/toolchain:empty_cc_toolchain_config.bzl"), "cc_toolchain_config.bzl") + repository_ctx.template("BUILD", Label("@rules_cc//cc/private/toolchain:BUILD.empty.tpl"), { "%{cpu}": get_cpu_value(repository_ctx), }) elif cpu_value == "freebsd" or cpu_value == "openbsd": - paths = resolve_labels(repository_ctx, [ - "@rules_cc//cc/private/toolchain:BUILD.static.bsd", - "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl", - "@rules_cc//cc/private/toolchain:bsd_cc_toolchain_config.bzl", - ]) - # This is defaulting to a static crosstool. We should eventually # autoconfigure this platform too. Theoretically, FreeBSD and OpenBSD # should be straightforward to add but we cannot run them in a Docker # container so skipping until we have proper tests for these platforms. - repository_ctx.symlink(paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"], "armeabi_cc_toolchain_config.bzl") - repository_ctx.symlink(paths["@rules_cc//cc/private/toolchain:bsd_cc_toolchain_config.bzl"], "cc_toolchain_config.bzl") - repository_ctx.symlink(paths["@rules_cc//cc/private/toolchain:BUILD.static.bsd"], "BUILD") + copy_file(repository_ctx, Label("@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"), "armeabi_cc_toolchain_config.bzl") + copy_file(repository_ctx, Label("@rules_cc//cc/private/toolchain:bsd_cc_toolchain_config.bzl"), "cc_toolchain_config.bzl") + copy_file(repository_ctx, Label("@rules_cc//cc/private/toolchain:BUILD.static.bsd"), "BUILD") elif cpu_value in ["x64_windows", "arm64_windows"]: # TODO(ibiryukov): overriden_tools are only supported in configure_unix_toolchain. # We might want to add that to Windows too(at least for msys toolchain). @@ -180,6 +168,7 @@ cc_autoconf = repository_rule( # buildifier: disable=unnamed-macro def cc_configure(): + # type: () -> None """A C++ configuration rules that generate the crosstool file.""" cc_autoconf_toolchains(name = "local_config_cc_toolchains") cc_autoconf(name = "local_config_cc") diff --git a/cc/private/toolchain/lib_cc_configure.bzl b/cc/private/toolchain/lib_cc_configure.bzl index 464700387..241adae99 100644 --- a/cc/private/toolchain/lib_cc_configure.bzl +++ b/cc/private/toolchain/lib_cc_configure.bzl @@ -17,16 +17,8 @@ def resolve_labels(repository_ctx, labels): """Resolves a collection of labels to their paths. - Label resolution can cause the evaluation of Starlark functions to restart. - For functions with side-effects (like the auto-configuration functions, which - inspect the system and touch the file system), such restarts are costly. - We cannot avoid the restarts, but we can minimize their penalty by resolving - all labels upfront. - - Among other things, doing less work on restarts can cut analysis times by - several seconds and may also prevent tickling kernel conditions that cause - build failures. See https://github.com/bazelbuild/bazel/issues/5196 for - more details. + Deprecated: This function is no longer used by rules_cc and will be removed + in the future. Pass labels to repository_ctx methods directly instead. Args: repository_ctx: The context with which to resolve the labels. @@ -37,14 +29,35 @@ def resolve_labels(repository_ctx, labels): """ return dict([(label, repository_ctx.path(Label(label))) for label in labels]) +def copy_file(repository_ctx, src, dest): + # type: (repository_ctx, Label, str) -> None + """Copies a file into the repository. + + Unlike repository_ctx.symlink, this doesn't require the repository + containing the file to be present on disk. + + Args: + repository_ctx: The repository context. + src: The Label of the file to copy. + dest: The path of the copy relative to the repository root. + """ + repository_ctx.template(dest, src, executable = False) + +def escape_percent(value): + # type: (str) -> str + """Escape percent signs in a known string for the Crosstool.""" + return value.replace("%", "%%") + def escape_string(arg): + # type: (object) -> str | None """Escape percent sign (%) in the string so it can appear in the Crosstool.""" if arg != None: - return str(arg).replace("%", "%%") + return escape_percent(str(arg)) else: return None def split_escaped(string, delimiter): + # type: (str, str) -> list[str] """Split string on the delimiter unless %-escaped. Examples: @@ -75,7 +88,7 @@ def split_escaped(string, delimiter): i = 0 result = [] - accumulator = [] + accumulator = [] # type: list[str] length = len(string) delimiter_length = len(delimiter) @@ -106,12 +119,14 @@ def split_escaped(string, delimiter): return result def auto_configure_fail(msg): + # type: (object) -> Never """Output failure message when auto configuration fails.""" red = "\033[0;31m" no_color = "\033[0m" fail("\n%sAuto-Configuration Error:%s %s\n" % (red, no_color, msg)) def auto_configure_warning(msg): + # type: (object) -> None """Output warning message during auto configuration.""" yellow = "\033[1;33m" no_color = "\033[0m" @@ -137,8 +152,7 @@ def get_env_var(repository_ctx, name, default = None, enable_warning = True): if enable_warning: auto_configure_warning("'%s' environment variable is not set, using '%s' as default" % (name, default)) return default - auto_configure_fail("'%s' environment variable is not set" % name) - return None + return auto_configure_fail("'%s' environment variable is not set" % name) def which(repository_ctx, cmd, default = None): """A wrapper around repository_ctx.which() to provide a fallback value. Doesn't %-escape the value! @@ -170,8 +184,7 @@ def which_cmd(repository_ctx, cmd, default = None): if default != None: auto_configure_warning("Cannot find %s in PATH, using '%s' as default.\nPATH=%s" % (cmd, default, path)) return default - auto_configure_fail("Cannot find %s in PATH, please make sure %s is installed and add its directory in PATH.\nPATH=%s" % (cmd, cmd, path)) - return str(result) + return auto_configure_fail("Cannot find %s in PATH, please make sure %s is installed and add its directory in PATH.\nPATH=%s" % (cmd, cmd, path)) def execute( repository_ctx, @@ -223,6 +236,9 @@ def execute( return stripped_stdout def get_cpu_value(repository_ctx): + # type: (repository_ctx) -> str + # TODO(https://github.com/bazelbuild/bazel/issues/32020): make this an + # annotation when supported Bazel versions expose a repository context type. """Compute the cpu_value based on the OS name. Doesn't %-escape the result! Args: @@ -262,26 +278,31 @@ def get_cpu_value(repository_ctx): return "k8" if arch in ["amd64", "x86_64", "x64"] else "piii" def is_cc_configure_debug(repository_ctx): + # type: (repository_ctx) -> bool """Returns True if CC_CONFIGURE_DEBUG is set to 1.""" env = repository_ctx.os.environ return "CC_CONFIGURE_DEBUG" in env and env["CC_CONFIGURE_DEBUG"] == "1" def build_flags(flags): + # type: (Collection[str]) -> str """Convert `flags` to a string of flag fields.""" return "\n".join([" flag: '" + flag + "'" for flag in flags]) def get_starlark_list(values): + # type: (Collection[str]) -> str """Convert a list of string into a string that can be passed to a rule attribute.""" if not values: return "" return "\"" + "\",\n \"".join(values) + "\"" def auto_configure_warning_maybe(repository_ctx, msg): + # type: (repository_ctx, object) -> None """Output warning message when CC_CONFIGURE_DEBUG is enabled.""" if is_cc_configure_debug(repository_ctx): auto_configure_warning(msg) def write_builtin_include_directory_paths(repository_ctx, cc, directories, file_suffix = ""): + # type: (repository_ctx, object, Collection[str], str) -> None """Generate output file named 'builtin_include_directory_paths' in the root of the repository.""" if get_env_var(repository_ctx, "BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS", "0", False) == "1": repository_ctx.file( diff --git a/cc/private/toolchain/lib_cc_configure.bzli b/cc/private/toolchain/lib_cc_configure.bzli new file mode 100644 index 000000000..621ec3f45 --- /dev/null +++ b/cc/private/toolchain/lib_cc_configure.bzli @@ -0,0 +1,47 @@ +"""Type contracts for C++ toolchain configuration helpers.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move get_env_var and which into source when the supported syntax +# can bind a type parameter for the caller’s default. Comments cannot bind T. + +def resolve_labels[T: str | Label](repository_ctx: repository_ctx, labels: Collection[T]) -> dict[T, path]: ... + +def get_env_var[T = Never]( + repository_ctx: repository_ctx, + name: str, + default: T | None = ..., + enable_warning: bool = ..., +) -> str | T: ... + +def which[T = Never]( + repository_ctx: repository_ctx, + cmd: str, + default: T | None = ..., +) -> str | T | None: ... + +def which_cmd[T = Never]( + repository_ctx: repository_ctx, + cmd: str, + default: T | None = ..., +) -> str | T: ... + +class _EnvironmentSchema[V](TypedDict, extra_items=ReadOnly[V]): ... + +type _Environment[K: str, V] = dict[K, V] | _EnvironmentSchema[V] + +# The wrapper forwards its environment to Bazel. Derive the accepted value +# type from the repository context, since it varies across Bazel releases. +class _Executable[K: str, V](Protocol): + def execute( + self, + __arguments: Sequence[str | Label | path], + *, + environment: _Environment[K, V] = ..., + ) -> exec_result: ... + +def execute[K: str = str, V = str]( + repository_ctx: Intersection[repository_ctx, _Executable[K, V]], + command: Sequence[str | Label | path], + environment: _Environment[K, V] | None = ..., + expect_failure: bool = ..., + expect_empty_output: bool = ..., +) -> str: ... diff --git a/cc/private/toolchain/unix_cc_configure.bzl b/cc/private/toolchain/unix_cc_configure.bzl index 65ac6ce9d..89615b709 100644 --- a/cc/private/toolchain/unix_cc_configure.bzl +++ b/cc/private/toolchain/unix_cc_configure.bzl @@ -17,13 +17,12 @@ load( ":lib_cc_configure.bzl", "auto_configure_fail", - "auto_configure_warning", "auto_configure_warning_maybe", - "escape_string", + "copy_file", + "escape_percent", "execute", "get_env_var", "get_starlark_list", - "resolve_labels", "split_escaped", "which", "write_builtin_include_directory_paths", @@ -32,11 +31,16 @@ load( def _uniq(iterable): """Remove duplicates from a list.""" - unique_elements = {element: None for element in iterable} - return unique_elements.keys() + return {element: None for element in iterable}.keys() -def _generate_system_module_map(repository_ctx, dirs, script_path): - return execute(repository_ctx, [script_path] + dirs) +def _generate_system_module_map(repository_ctx, dirs): + # type: (repository_ctx, list[str]) -> str + # The script has to be present on disk to be run. + script = "generate_system_module_map.sh" + repository_ctx.template(script, Label("@rules_cc//cc/private/toolchain:generate_system_module_map.sh")) + module_map = execute(repository_ctx, [repository_ctx.path(script)] + dirs) + repository_ctx.delete(script) + return module_map def _prepare_include_path(repo_ctx, path): """Resolve include path before outputting it into the crosstool. @@ -78,7 +82,7 @@ def _get_target_libc(repository_ctx, cc, darwin, compile_opts): return "macosx" libc = get_env_var(repository_ctx, "BAZEL_TARGET_LIBC", "", False) if libc: - return escape_string(libc) + return escape_percent(libc) # Preprocessing any standard library header defines macros that identify # most implementations. @@ -115,33 +119,38 @@ def _find_tool(repository_ctx, tool, overridden_tools): return overridden_tools[tool] return which(repository_ctx, tool, "/usr/bin/" + tool) +# TODO(https://github.com/bazelbuild/bazel/issues/32020): use a source annotation once Bazel exposes these context and metadata types. def _get_tool_paths(repository_ctx, overridden_tools): + # type: (repository_ctx, Mapping[str, str | path | None]) -> dict[str, str] """Compute the %-escaped path to the various tools""" - return dict({ - k: escape_string(_find_tool(repository_ctx, k, overridden_tools)) - for k in [ - "ar", - "ld", - "llvm-cov", - "llvm-profdata", - "cpp", - "gcc", - "dwp", - "gcov", - "nm", - "objcopy", - "objdump", - "strip", - "c++filt", - ] - }.items()) + paths = {} + for name in [ + "ar", + "ld", + "llvm-cov", + "llvm-profdata", + "cpp", + "gcc", + "dwp", + "gcov", + "nm", + "objcopy", + "objdump", + "strip", + "c++filt", + ]: + tool = _find_tool(repository_ctx, name, overridden_tools) + if tool != None: + paths[name] = escape_percent(str(tool)) + return paths def _escaped_cplus_include_paths(repository_ctx): + # type: (repository_ctx) -> list[str] """Use ${CPLUS_INCLUDE_PATH} to compute the %-escaped list of flags for cxxflag.""" if "CPLUS_INCLUDE_PATH" in repository_ctx.os.environ: result = [] for p in repository_ctx.os.environ["CPLUS_INCLUDE_PATH"].split(":"): - p = escape_string(str(repository_ctx.path(p))) # Normalize the path + p = escape_percent(str(repository_ctx.path(p))) # Normalize the path result.append("-I" + p) return result else: @@ -154,6 +163,7 @@ _OSX_FRAMEWORK_SUFFIX = " (framework directory)" _OSX_FRAMEWORK_SUFFIX_LEN = len(_OSX_FRAMEWORK_SUFFIX) def _cxx_inc_convert(path): + # type: (str) -> str """Convert path returned by cc -E xc++ in a complete path. Doesn't %-escape the path!""" path = path.strip() if path.endswith(_OSX_FRAMEWORK_SUFFIX): @@ -216,7 +226,7 @@ def _is_linker_option_supported(repository_ctx, cc, force_linker_flags, option, def _is_oso_prefix_supported(repository_ctx, ld): return repository_ctx.execute([ld, "-v", "-oso_prefix", "."]).return_code == 0 -def _find_linker_path(repository_ctx, cc, linker, is_clang): +def _find_linker(repository_ctx, cc, linker, is_clang): """Checks if a given linker is supported by the C compiler. Args: @@ -226,7 +236,8 @@ def _find_linker_path(repository_ctx, cc, linker, is_clang): is_clang: whether the compiler is known to be clang Returns: - String to put as value to -fuse-ld= flag, or None if linker couldn't be found. + A struct with the linker name or path and the flags that select it, or + None if the linker couldn't be found. """ result = repository_ctx.execute([ cc, @@ -245,7 +256,7 @@ def _find_linker_path(repository_ctx, cc, linker, is_clang): return None if not is_clang: - return linker + return struct(path = linker, flags = ["-fuse-ld=" + linker]) # Extract linker path from: # /usr/bin/clang ... @@ -254,9 +265,22 @@ def _find_linker_path(repository_ctx, cc, linker, is_clang): # https://github.com/llvm/llvm-project/blob/85c78274358717e4d5d019a801decba5c1add484/clang/lib/Driver/Job.cpp#L207-L209 invocations = [line for line in result.stderr.splitlines() if line.startswith(" \"")] if not invocations: - return linker + return struct(path = linker, flags = ["-fuse-ld=" + linker]) linker_command = invocations[-1] - return linker_command.strip().split(" ")[0].strip("\"'") + linker_path = linker_command.strip().split(" ")[0].strip("\"'") + + # Clang uses the -fuse-ld= value as the linker flavor. --ld-path= (Clang 12+) + # specifies the path without losing the flavor. + flags = ["-fuse-ld=" + linker, "--ld-path=" + linker_path] + result = repository_ctx.execute([ + cc, + str(repository_ctx.path("tools/cpp/empty.cc")), + "-o", + "/dev/null", + ] + flags) + if result.return_code != 0: + return struct(path = linker_path, flags = ["-fuse-ld=" + linker_path]) + return struct(path = linker_path, flags = flags) def _add_compiler_option_if_supported(repository_ctx, cc, option): """Returns `[option]` if supported, `[]` otherwise. Doesn't %-escape the option.""" @@ -288,6 +312,7 @@ def _get_no_canonical_prefixes_opt(repository_ctx, cc): return opt def get_env(repository_ctx): + # type: (repository_ctx) -> str """Convert the environment in a list of export if in Homebrew. Doesn't %-escape the result! Args: @@ -306,6 +331,7 @@ def get_env(repository_ctx): return "" def _coverage_flags(repository_ctx, darwin): + # type: (repository_ctx, bool) -> tuple[str, str] use_llvm_cov = "1" == get_env_var( repository_ctx, "BAZEL_USE_LLVM_NATIVE_COVERAGE", @@ -346,8 +372,8 @@ def _get_compiler_name(repository_ctx, cc): return "gcc" return "compiler" -def _find_generic(repository_ctx, name, env_name, overridden_tools, warn = False, silent = False): - """Find a generic C++ toolchain tool. Doesn't %-escape the result.""" +def _find_generic(repository_ctx, name, env_name, overridden_tools, on_missing = auto_configure_fail): + """Return the tool without %-escaping it, or call on_missing with the lookup error.""" if name in overridden_tools: return overridden_tools[name] @@ -367,14 +393,17 @@ def _find_generic(repository_ctx, name, env_name, overridden_tools, warn = False if result == None: msg = ("Cannot find %s or %s%s; either correct your path or set the %s" + " environment variable") % (name, env_name, env_value_with_paren, env_name) - if warn: - if not silent: - auto_configure_warning(msg) - else: - auto_configure_fail(msg) + return on_missing(msg) return result +def _allow_missing_tool(_message): + # type: (str) -> None + return None + def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): + # type: (repository_ctx, str, Mapping[str, str | path]) -> None + # TODO(https://github.com/bazelbuild/bazel/issues/32020): make this an + # annotation when supported Bazel versions expose a repository context type. """Configure C++ toolchain on Unix platforms. Args: @@ -382,25 +411,15 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): cpu_value: The CPU value. overridden_tools: A dictionary of overridden tools. """ - paths = resolve_labels(repository_ctx, [ - "@rules_cc//cc/private/toolchain:BUILD.tpl", - "@rules_cc//cc/private/toolchain:generate_system_module_map.sh", - "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl", - "@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl", - "@rules_cc//cc/private/toolchain:linux_cc_wrapper.sh.tpl", - "@rules_cc//cc/private/toolchain:validate_static_library.sh.tpl", - "@rules_cc//cc/private/toolchain:osx_cc_wrapper.sh.tpl", - "@rules_cc//cc/private/toolchain:clang_deps_scanner_wrapper.sh.tpl", - "@rules_cc//cc/private/toolchain:gcc_deps_scanner_wrapper.sh.tpl", - ]) - - repository_ctx.symlink( - paths["@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl"], + copy_file( + repository_ctx, + Label("@rules_cc//cc/private/toolchain:unix_cc_toolchain_config.bzl"), "cc_toolchain_config.bzl", ) - repository_ctx.symlink( - paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"], + copy_file( + repository_ctx, + Label("@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"), "armeabi_cc_toolchain_config.bzl", ) @@ -412,62 +431,37 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): cc = _find_generic(repository_ctx, "gcc", "CC", overridden_tools) is_clang = _is_clang(repository_ctx, cc) - overridden_tools = dict(overridden_tools) - overridden_tools["gcc"] = cc - overridden_tools["gcov"] = _find_generic( - repository_ctx, - "gcov", - "GCOV", - overridden_tools, - warn = True, - silent = True, - ) - overridden_tools["llvm-cov"] = _find_generic( - repository_ctx, - "llvm-cov", - "BAZEL_LLVM_COV", - overridden_tools, - warn = True, - silent = True, - ) - overridden_tools["llvm-profdata"] = _find_generic( - repository_ctx, - "llvm-profdata", - "BAZEL_LLVM_PROFDATA", - overridden_tools, - warn = True, - silent = True, - ) - overridden_tools["ar"] = _find_generic( - repository_ctx, - "ar", - "AR", - overridden_tools, - warn = True, - silent = True, - ) + optional_tools = { + name: _find_generic(repository_ctx, name, env, overridden_tools, on_missing = _allow_missing_tool) + for name, env in [ + ("gcov", "GCOV"), + ("llvm-cov", "BAZEL_LLVM_COV"), + ("llvm-profdata", "BAZEL_LLVM_PROFDATA"), + ("ar", "AR"), + ] + } + tool_overrides = dict(overridden_tools) | {"gcc": cc} | optional_tools libtool = _find_generic( repository_ctx, "libtool", "LIBTOOL", - overridden_tools, - warn = not darwin, - silent = True, + tool_overrides, + on_missing = auto_configure_fail if darwin else _allow_missing_tool, ) if darwin: - overridden_tools["gcc"] = "cc_wrapper.sh" + tool_overrides["gcc"] = "cc_wrapper.sh" xcrun = repository_ctx.which("xcrun") if xcrun: for tool_name in ["llvm-cov", "llvm-profdata"]: - if overridden_tools.get(tool_name) == None: + if tool_overrides.get(tool_name) == None: xcrun_result = repository_ctx.execute([xcrun, "--find", tool_name]) if xcrun_result.return_code == 0: - overridden_tools[tool_name] = xcrun_result.stdout.strip() + tool_overrides[tool_name] = xcrun_result.stdout.strip() auto_configure_warning_maybe(repository_ctx, "CC used: " + str(cc)) - tool_paths = _get_tool_paths(repository_ctx, overridden_tools) + tool_paths = _get_tool_paths(repository_ctx, tool_overrides) if libtool: - tool_paths["libtool"] = escape_string(libtool) + tool_paths["libtool"] = escape_percent(str(libtool)) tool_paths["cpp-module-deps-scanner"] = "deps_scanner_wrapper.sh" toolchain_features = [] @@ -477,7 +471,7 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): # The parse_header tool needs to be a wrapper around the compiler as it has # to touch the output file. tool_paths["parse_headers"] = "cc_wrapper.sh" - cc_toolchain_identifier = escape_string(get_env_var( + cc_toolchain_identifier = escape_percent(get_env_var( repository_ctx, "CC_TOOLCHAIN_NAME", "local", @@ -487,12 +481,12 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): if "nm" in tool_paths and "c++filt" in tool_paths: repository_ctx.template( "validate_static_library.sh", - paths["@rules_cc//cc/private/toolchain:validate_static_library.sh.tpl"], + Label("@rules_cc//cc/private/toolchain:validate_static_library.sh.tpl"), { - "%{c++filt}": escape_string(str(repository_ctx.path(tool_paths["c++filt"]))), + "%{c++filt}": escape_percent(str(repository_ctx.path(tool_paths["c++filt"]))), # Certain weak symbols are otherwise listed with type T in the output of nm on macOS. "%{nm_extra_args}": "--no-weak" if darwin else "", - "%{nm}": escape_string(str(repository_ctx.path(tool_paths["nm"]))), + "%{nm}": escape_percent(str(repository_ctx.path(tool_paths["nm"]))), }, ) tool_paths["validate_static_library"] = "validate_static_library.sh" @@ -502,10 +496,10 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): ) repository_ctx.template( "cc_wrapper.sh", - paths[cc_wrapper_src], + Label(cc_wrapper_src), { - "%{cc}": escape_string(str(cc)), - "%{env}": escape_string(get_env(repository_ctx)), + "%{cc}": escape_percent(str(cc)), + "%{env}": escape_percent(get_env(repository_ctx)), }, ) deps_scanner_wrapper_src = ( @@ -513,17 +507,18 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): ) deps_scanner = "cpp-module-deps-scanner_not_found" if is_clang: - cc_str = str(cc) - path_arr = cc_str.split("/")[:-1] - path_arr.append("clang-scan-deps") - deps_scanner = "/".join(path_arr) + sibling_deps_scanner = repository_ctx.path(cc).dirname.get_child("clang-scan-deps") + if sibling_deps_scanner.exists: + deps_scanner = str(sibling_deps_scanner) + else: + deps_scanner = which(repository_ctx, "clang-scan-deps") or deps_scanner repository_ctx.template( "deps_scanner_wrapper.sh", - paths[deps_scanner_wrapper_src], + Label(deps_scanner_wrapper_src), { - "%{cc}": escape_string(str(cc)), - "%{deps_scanner}": escape_string(deps_scanner), - "%{env}": escape_string(get_env(repository_ctx)), + "%{cc}": escape_percent(str(cc)), + "%{deps_scanner}": escape_percent(deps_scanner), + "%{env}": escape_percent(get_env(repository_ctx)), }, ) @@ -547,24 +542,30 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): False, ), ":") - gold_or_lld_linker_path = ( - _find_linker_path(repository_ctx, cc, "lld", is_clang) or - _find_linker_path(repository_ctx, cc, "gold", is_clang) + linker = ( + _find_linker(repository_ctx, cc, "lld", is_clang) or + _find_linker(repository_ctx, cc, "gold", is_clang) ) cc_path = repository_ctx.path(cc) if not str(cc_path).startswith(str(repository_ctx.path(".")) + "/"): # cc is outside the repository, set -B - bin_search_flags = ["-B" + escape_string(str(cc_path.dirname))] + bin_search_flags = ["-B" + escape_percent(str(cc_path.dirname))] else: # cc is inside the repository, don't set -B. - bin_search_flags = [] - if not gold_or_lld_linker_path: + bin_search_flags = [] # type: list[str] + if not linker: ld_path = repository_ctx.path(tool_paths["ld"]) if ld_path.dirname != cc_path.dirname: - bin_search_flags.append("-B" + str(ld_path.dirname)) + bin_search_flags.append("-B" + escape_percent(str(ld_path.dirname))) + elif not linker.path.startswith("/"): + ld_binary = repository_ctx.which("ld." + linker.path) or repository_ctx.which(linker.path) + if ld_binary: + ld_path = repository_ctx.path(ld_binary) + if ld_path.dirname != cc_path.dirname: + bin_search_flags.append("-B" + escape_percent(str(ld_path.dirname))) force_linker_flags = [] - if gold_or_lld_linker_path: - force_linker_flags.append("-fuse-ld=" + gold_or_lld_linker_path) + if linker: + force_linker_flags.extend(linker.flags) # TODO: It's unclear why these flags aren't added on macOS. if bin_search_flags and not darwin: @@ -661,7 +662,6 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): repository_ctx.file("module.modulemap", _generate_system_module_map( repository_ctx, builtin_include_directories, - paths["@rules_cc//cc/private/toolchain:generate_system_module_map.sh"], )) extra_flags_per_feature = {} if is_clang: @@ -683,16 +683,16 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): write_builtin_include_directory_paths(repository_ctx, cc, builtin_include_directories) repository_ctx.template( "BUILD", - paths["@rules_cc//cc/private/toolchain:BUILD.tpl"], + Label("@rules_cc//cc/private/toolchain:BUILD.tpl"), # @unsorted-dict-items { - "%{abi_libc_version}": escape_string(get_env_var( + "%{abi_libc_version}": escape_percent(get_env_var( repository_ctx, "ABI_LIBC_VERSION", "local", False, )), - "%{abi_version}": escape_string(get_env_var( + "%{abi_version}": escape_percent(get_env_var( repository_ctx, "ABI_VERSION", "local", @@ -728,7 +728,7 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): "-fno-omit-frame-pointer", ], ), - "%{compiler}": escape_string(get_env_var( + "%{compiler}": escape_percent(get_env_var( repository_ctx, "BAZEL_COMPILER", _get_compiler_name(repository_ctx, cc), @@ -743,7 +743,7 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): "%{fastbuild_compile_flags}": get_starlark_list([]), "%{dbg_compile_flags}": get_starlark_list(["-g"]), "%{extra_flags_per_feature}": repr(extra_flags_per_feature), - "%{host_system_name}": escape_string(get_env_var( + "%{host_system_name}": escape_percent(get_env_var( repository_ctx, "BAZEL_HOST_SYSTEM", "local", @@ -760,11 +760,10 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): ) + ( [ "-headerpad_max_install_names", - ] if darwin else [ + ] if darwin else _add_compiler_option_if_supported( # Gold linker only? Can we enable this by default? # "-Wl,--warn-execstack", # "-Wl,--detect-odr-violations" - ] + _add_compiler_option_if_supported( # Have gcc return the exit code from ld. repository_ctx, cc, @@ -817,21 +816,21 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): "--start-lib", ) else "False", "%{toolchain_features}": get_starlark_list(toolchain_features), - "%{target_cpu}": escape_string(get_env_var( + "%{target_cpu}": escape_percent(get_env_var( repository_ctx, "BAZEL_TARGET_CPU", cpu_value, False, )), "%{target_libc}": _get_target_libc(repository_ctx, cc, darwin, all_compile_opts + conly_opts), - "%{target_system_name}": escape_string(get_env_var( + "%{target_system_name}": escape_percent(get_env_var( repository_ctx, "BAZEL_TARGET_SYSTEM", "local", False, )), "%{tool_paths}": ",\n ".join( - ['"%s": "%s"' % (k, v) for k, v in tool_paths.items() if v != None], + ['"%s": "%s"' % (k, v) for k, v in tool_paths.items()], ), "%{unfiltered_compile_flags}": get_starlark_list( _get_no_canonical_prefixes_opt(repository_ctx, cc) + [ diff --git a/cc/private/toolchain/unix_cc_configure.bzli b/cc/private/toolchain/unix_cc_configure.bzli new file mode 100644 index 000000000..fc4e3078e --- /dev/null +++ b/cc/private/toolchain/unix_cc_configure.bzli @@ -0,0 +1,87 @@ +"""Contracts for discovering and configuring a local Unix C++ toolchain.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move the helpers into source when its function syntax can bind T. +# For example, _uniq returns the same item type that its caller supplies. + +type _Executable = str | Label | path + +def _uniq[T: Hashable](iterable: Collection[T]) -> list[T]: ... + +def _prepare_include_path(repo_ctx: repository_ctx, path: _Executable) -> str: ... + +def _get_target_libc( + repository_ctx: repository_ctx, + cc: _Executable, + darwin: bool, + compile_opts: list[str], +) -> str: ... + +def _find_tool[T]( + repository_ctx: repository_ctx, + tool: str, + overridden_tools: Mapping[str, T], +) -> str | T | None: ... + +def _get_cxx_include_directories( + repository_ctx: repository_ctx, + print_resource_dir_supported: bool, + cc: _Executable, + lang_flag: str, + additional_flags: list[str] = ..., +) -> list[str]: ... + +def _is_compiler_option_supported( + repository_ctx: repository_ctx, + cc: _Executable, + option: str, +) -> bool: ... + +def _is_linker_option_supported( + repository_ctx: repository_ctx, + cc: _Executable, + force_linker_flags: list[str], + option: str, + pattern: str, +) -> bool: ... + +def _is_oso_prefix_supported(repository_ctx: repository_ctx, ld: _Executable) -> bool: ... + +def _find_linker( + repository_ctx: repository_ctx, + cc: _Executable, + linker: str, + is_clang: bool, +) -> struct[{"path": str, "flags": list[str]}] | None: ... + +def _add_compiler_option_if_supported( + repository_ctx: repository_ctx, + cc: _Executable, + option: str, +) -> list[str]: ... + +def _add_linker_option_if_supported( + repository_ctx: repository_ctx, + cc: _Executable, + force_linker_flags: list[str], + option: str, + pattern: str, +) -> list[str]: ... + +def _get_no_canonical_prefixes_opt( + repository_ctx: repository_ctx, + cc: _Executable, +) -> list[str]: ... + +def _is_clang(repository_ctx: repository_ctx, cc: _Executable) -> bool: ... + +def _is_gcc(repository_ctx: repository_ctx, cc: _Executable) -> bool: ... + +def _get_compiler_name(repository_ctx: repository_ctx, cc: _Executable) -> str: ... + +def _find_generic[Override, Missing = Never]( + repository_ctx: repository_ctx, + name: str, + env_name: str, + overridden_tools: Mapping[str, Override], + on_missing: Callable[[str], Missing] = ..., +) -> Override | Missing | str | path: ... diff --git a/cc/private/toolchain/unix_cc_toolchain_config.bzl b/cc/private/toolchain/unix_cc_toolchain_config.bzl index 4724887df..a77ac482b 100644 --- a/cc/private/toolchain/unix_cc_toolchain_config.bzl +++ b/cc/private/toolchain/unix_cc_toolchain_config.bzl @@ -13,6 +13,8 @@ # limitations under the License. """A Starlark cc_toolchain configuration rule""" +load("@apple_support//xcode:providers.bzl", "XcodeVersionInfo") + #buildifier: disable=bzl-visibility load("@bazel_features//private:util.bzl", "ge") load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") @@ -44,7 +46,7 @@ def _cpp_module_extension(compiler): return ".pcm" def _target_os_version(ctx): - xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + xcode_config = ctx.attr._xcode_config[XcodeVersionInfo] return xcode_config.minimum_os_for_platform_type(apple_common.platform_type.macos) def layering_check_features(compiler, extra_flags_per_feature, is_macos): diff --git a/cc/private/toolchain/windows_cc_configure.bzl b/cc/private/toolchain/windows_cc_configure.bzl index 235ebbf76..f37e7f179 100644 --- a/cc/private/toolchain/windows_cc_configure.bzl +++ b/cc/private/toolchain/windows_cc_configure.bzl @@ -19,10 +19,10 @@ load( "auto_configure_fail", "auto_configure_warning", "auto_configure_warning_maybe", - "escape_string", + "copy_file", + "escape_percent", "execute", "get_starlark_list", - "resolve_labels", "split_escaped", "write_builtin_include_directory_paths", ) @@ -30,25 +30,27 @@ load( _targets_archs = {"arm": "amd64_arm", "arm64": "amd64_arm64", "x64": "amd64", "x86": "amd64_x86"} _targets_lib_folder = {"arm": "arm", "arm64": "arm64", "x86": ""} -def _lookup_env_var(env, name, default = None): - """Lookup environment variable case-insensitve. +def _lookup_env_var(env, name): + # type: (Mapping[str, str], str) -> tuple[str, str | None] + """Look up an environment variable case-insensitively. If a matching (case-insensitive) entry is found in the env dict both the key and the value are returned. The returned key might differ from name in casing. - If a matching key was found its value is returned otherwise - the default is returned. - - Return a (key, value) tuple""" + If no entry matches, return (name, None). + """ for key, value in env.items(): if name.lower() == key.lower(): return (key, value) - return (name, default) + return (name, None) -def _get_env_var(repository_ctx, name, default = None): +def _get_env_var(repository_ctx, name): + # type: (repository_ctx, str) -> str | None + # TODO(https://github.com/bazelbuild/bazel/issues/32020): make this an + # annotation when supported Bazel versions expose a repository context type. """Returns a value from an environment variable.""" - return _lookup_env_var(repository_ctx.os.environ, name, default)[1] + return _lookup_env_var(repository_ctx.os.environ, name)[1] def _get_path_env_var(repository_ctx, name): """Returns a path from an environment variable. @@ -91,7 +93,7 @@ def _get_escaped_windows_msys_starlark_content(repository_ctx, use_mingw = False msys_root = tokens[0][:len(tokens[0]) - len("bin")] prefix = "mingw64" if use_mingw else "usr" - tool_path_prefix = escape_string(msys_root) + prefix + tool_path_prefix = escape_percent(msys_root) + prefix tool_bin_path = tool_path_prefix + "/bin" tool_path = {} @@ -113,12 +115,12 @@ def _get_system_root(repository_ctx): repository_ctx, "SYSTEMROOT is not set, using default SYSTEMROOT=C:\\Windows", ) - return escape_string(systemroot) + return escape_percent(systemroot) def _add_system_root(repository_ctx, env): """Running VCVARSALL.BAT and VCVARSQUERYREGISTRY.BAT need %SYSTEMROOT%\\\\system32 in PATH.""" - env_key, env_value = _lookup_env_var(env, "PATH", default = "") - env[env_key] = env_value + ";" + _get_system_root(repository_ctx) + "\\system32" + env_key, env_value = _lookup_env_var(env, "PATH") + env[env_key] = (env_value or "") + ";" + _get_system_root(repository_ctx) + "\\system32" return env def find_vc_path(repository_ctx): @@ -343,7 +345,7 @@ def _get_vc_env_vars(repository_ctx, vc_path, msvc_vars_x64, target_arch): env["INCLUDE"] = msvc_vars_x64["%{msvc_env_include_x64}"] env["LIB"] = lib.replace("x64", target_arch) - env["PATH"] = escape_string(tools_path.replace("\\", "\\\\")) + ";" + msvc_vars_x64["%{msvc_env_path_x64}"] + env["PATH"] = escape_percent(tools_path.replace("\\", "\\\\")) + ";" + msvc_vars_x64["%{msvc_env_path_x64}"] return env def setup_vc_env_vars(repository_ctx, vc_path, envvars = [], allow_empty = False, escape = True): @@ -398,7 +400,7 @@ def setup_vc_env_vars(repository_ctx, vc_path, envvars = [], allow_empty = False env_map = {} for env in envs: key, value = env.split("=", 1) - env_map[key] = escape_string(value.replace("\\", "\\\\")) if escape else value + env_map[key] = escape_percent(value.replace("\\", "\\\\")) if escape else value if not allow_empty: _check_env_vars(env_map, cmd, expected = envvars) @@ -445,7 +447,7 @@ def _get_vc_full_version(repository_ctx, vc_path): def _get_winsdk_full_version(repository_ctx): """Return the value of BAZEL_WINSDK_FULL_VERSION if defined, otherwise an empty string.""" - return _get_env_var(repository_ctx, "BAZEL_WINSDK_FULL_VERSION", default = "") + return _get_env_var(repository_ctx, "BAZEL_WINSDK_FULL_VERSION") or "" def _find_msvc_tools(repository_ctx, vc_path, target_arch = "x64"): """Find the exact paths of the build tools in MSVC for the given target. Doesn't %-escape the result.""" @@ -614,7 +616,7 @@ def find_llvm_tool(repository_ctx, llvm_path, tool): def _use_clang_cl(repository_ctx): """Returns True if USE_CLANG_CL is set to 1.""" - return _get_env_var(repository_ctx, "USE_CLANG_CL", default = "0") == "1" + return _get_env_var(repository_ctx, "USE_CLANG_CL") == "1" def _find_missing_llvm_tools(repository_ctx, llvm_path): """Check if any required tool is missing under given LLVM path.""" @@ -665,7 +667,7 @@ def _get_msys_mingw_vars(repository_ctx): } return msys_mingw_vars -def _get_msvc_vars(repository_ctx, paths, target_arch = "x64", msvc_vars_x64 = None): +def _get_msvc_vars(repository_ctx, target_arch = "x64", msvc_vars_x64 = None): """Get the variables we need to populate the MSVC toolchains.""" vc_path = find_vc_path(repository_ctx) missing_tools = None @@ -673,7 +675,7 @@ def _get_msvc_vars(repository_ctx, paths, target_arch = "x64", msvc_vars_x64 = N if not vc_path: repository_ctx.template( "vc_installation_error_" + target_arch + ".bat", - paths["@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"], + Label("@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"), {"%{vc_error_message}": ""}, ) else: @@ -689,7 +691,7 @@ def _get_msvc_vars(repository_ctx, paths, target_arch = "x64", msvc_vars_x64 = N ]) repository_ctx.template( "vc_installation_error_" + target_arch + ".bat", - paths["@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"], + Label("@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl"), {"%{vc_error_message}": message}, ) @@ -715,8 +717,8 @@ def _get_msvc_vars(repository_ctx, paths, target_arch = "x64", msvc_vars_x64 = N env = _get_vc_env_vars(repository_ctx, vc_path, msvc_vars_x64, target_arch) else: env = setup_vc_env_vars(repository_ctx, vc_path) - escaped_tmp_dir = escape_string(_get_temp_env(repository_ctx).replace("\\", "\\\\")) - escaped_include_paths = escape_string(env["INCLUDE"]) + escaped_tmp_dir = escape_percent(_get_temp_env(repository_ctx).replace("\\", "\\\\")) + escaped_include_paths = escape_percent(env["INCLUDE"]) build_tools = {} llvm_path = "" @@ -752,7 +754,7 @@ def _get_msvc_vars(repository_ctx, paths, target_arch = "x64", msvc_vars_x64 = N clang_include_path = (clang_dir + "\\include").replace("\\", "\\\\") escaped_cxx_include_directories.append("\"%s\"" % clang_include_path) clang_lib_path = (clang_dir + "\\lib\\windows").replace("\\", "\\\\") - env["LIB"] = escape_string(env["LIB"]) + ";" + clang_lib_path + env["LIB"] = escape_percent(env["LIB"]) + ";" + clang_lib_path support_debug_fastlink = _is_support_debug_fastlink(repository_ctx, build_tools["LINK"]) write_builtin_include_directory_paths(repository_ctx, "msvc", escaped_cxx_include_directories, file_suffix = "_msvc") @@ -775,8 +777,8 @@ Fix this by installing the English language pack for the Visual Studio installat "%{msvc_env_tmp_" + target_arch + "}": escaped_tmp_dir, "%{msvc_env_include_" + target_arch + "}": escaped_include_paths, "%{msvc_cxx_builtin_include_directories_" + target_arch + "}": " " + ",\n ".join(escaped_cxx_include_directories), - "%{msvc_env_path_" + target_arch + "}": escape_string(env["PATH"]), - "%{msvc_env_lib_" + target_arch + "}": escape_string(env["LIB"]), + "%{msvc_env_path_" + target_arch + "}": escape_percent(env["PATH"]), + "%{msvc_env_lib_" + target_arch + "}": escape_percent(env["LIB"]), "%{msvc_cl_path_" + target_arch + "}": build_tools["CL"], "%{msvc_ml_path_" + target_arch + "}": build_tools.get("ML", "msvc_arm_toolchain_does_not_support_ml"), "%{msvc_link_path_" + target_arch + "}": build_tools["LINK"], @@ -788,7 +790,7 @@ Fix this by installing the English language pack for the Visual Studio installat } return msvc_vars -def _get_clang_cl_vars(repository_ctx, paths, msvc_vars, target_arch): +def _get_clang_cl_vars(repository_ctx, msvc_vars, target_arch): """Get the variables we need to populate the clang-cl toolchains.""" llvm_path = find_llvm_path(repository_ctx) error_script = None @@ -797,7 +799,7 @@ def _get_clang_cl_vars(repository_ctx, paths, msvc_vars, target_arch): elif not llvm_path: repository_ctx.template( "clang_installation_error.bat", - paths["@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"], + Label("@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"), {"%{clang_error_message}": ""}, ) error_script = "clang_installation_error.bat" @@ -813,7 +815,7 @@ def _get_clang_cl_vars(repository_ctx, paths, msvc_vars, target_arch): ]) repository_ctx.template( "clang_installation_error.bat", - paths["@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"], + Label("@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl"), {"%{clang_error_message}": message}, ) error_script = "clang_installation_error.bat" @@ -865,10 +867,10 @@ def _get_clang_cl_vars(repository_ctx, paths, msvc_vars, target_arch): } return clang_cl_vars -def _get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, target_arch = "x64"): +def _get_msvc_deps_scanner_vars(repository_ctx, template_vars, target_arch = "x64"): repository_ctx.template( "msvc_deps_scanner_wrapper_" + target_arch + ".bat", - paths["@rules_cc//cc/private/toolchain:msvc_deps_scanner_wrapper.bat.tpl"], + Label("@rules_cc//cc/private/toolchain:msvc_deps_scanner_wrapper.bat.tpl"), { "%{cc}": template_vars["%{msvc_cl_path_" + target_arch + "}"], }, @@ -878,33 +880,17 @@ def _get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, target_arc "%{msvc_deps_scanner_wrapper_path_" + target_arch + "}": "msvc_deps_scanner_wrapper_" + target_arch + ".bat", } +def _get_env_options(repository_ctx, name): + # type: (repository_ctx, str) -> list[str] + return split_escaped(_get_env_var(repository_ctx, name) or "", ":") + def _get_copts(repository_ctx): """Get the variables we need to populate the msys/mingw toolchains.""" - conly_opts = split_escaped(_get_env_var( - repository_ctx, - "BAZEL_CONLYOPTS", - "", - ), ":") - all_compile_opts = split_escaped(_get_env_var( - repository_ctx, - "BAZEL_COPTS", - "", - ), ":") - cxx_opts = split_escaped(_get_env_var( - repository_ctx, - "BAZEL_CXXOPTS", - "", - ), ":") - link_opts = split_escaped(_get_env_var( - repository_ctx, - "BAZEL_LINKOPTS", - "", - ), ":") - win32_winnt_opts = _get_env_var( - repository_ctx, - "BAZEL_WIN32_WINNT", - None, - ) + conly_opts = _get_env_options(repository_ctx, "BAZEL_CONLYOPTS") + all_compile_opts = _get_env_options(repository_ctx, "BAZEL_COPTS") + cxx_opts = _get_env_options(repository_ctx, "BAZEL_CXXOPTS") + link_opts = _get_env_options(repository_ctx, "BAZEL_LINKOPTS") + win32_winnt_opts = _get_env_var(repository_ctx, "BAZEL_WIN32_WINNT") copts_vars = { "%{all_compile_flags}": get_starlark_list(all_compile_opts), "%{conly_flags}": get_starlark_list(conly_opts), @@ -912,37 +898,31 @@ def _get_copts(repository_ctx): "%{link_flags}": get_starlark_list(link_opts), # None means the user didn't provide the override. # Fall back to the default in that case. - "%{win32_winnt_flag}": ' win32_winnt_flag = "' + escape_string(win32_winnt_opts) + '", ' if win32_winnt_opts != None else "", + "%{win32_winnt_flag}": ' win32_winnt_flag = "' + escape_percent(win32_winnt_opts) + '", ' if win32_winnt_opts != None else "", } return copts_vars def configure_windows_toolchain(repository_ctx): + # type: (repository_ctx) -> None """Configure C++ toolchain on Windows. Args: repository_ctx: The repository context. """ - paths = resolve_labels(repository_ctx, [ - "@rules_cc//cc/private/toolchain:BUILD.windows.tpl", - "@rules_cc//cc/private/toolchain:windows_cc_toolchain_config.bzl", - "@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl", - "@rules_cc//cc/private/toolchain:vc_installation_error.bat.tpl", - "@rules_cc//cc/private/toolchain:msys_gcc_installation_error.bat", - "@rules_cc//cc/private/toolchain:clang_installation_error.bat.tpl", - "@rules_cc//cc/private/toolchain:msvc_deps_scanner_wrapper.bat.tpl", - ]) - - repository_ctx.symlink( - paths["@rules_cc//cc/private/toolchain:windows_cc_toolchain_config.bzl"], + copy_file( + repository_ctx, + Label("@rules_cc//cc/private/toolchain:windows_cc_toolchain_config.bzl"), "windows_cc_toolchain_config.bzl", ) - repository_ctx.symlink( - paths["@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"], + copy_file( + repository_ctx, + Label("@rules_cc//cc/private/toolchain:armeabi_cc_toolchain_config.bzl"), "armeabi_cc_toolchain_config.bzl", ) - repository_ctx.symlink( - paths["@rules_cc//cc/private/toolchain:msys_gcc_installation_error.bat"], + copy_file( + repository_ctx, + Label("@rules_cc//cc/private/toolchain:msys_gcc_installation_error.bat"), "msys_gcc_installation_error.bat", ) @@ -954,23 +934,23 @@ def configure_windows_toolchain(repository_ctx): # Set outside _get_msvc_vars so the error-stub configs it returns early for # are labelled the same as the working ones. template_vars["%{compiler}"] = "clang-cl" if _use_clang_cl(repository_ctx) else "msvc-cl" - msvc_vars_x64 = _get_msvc_vars(repository_ctx, paths, "x64") + msvc_vars_x64 = _get_msvc_vars(repository_ctx, "x64") template_vars.update(msvc_vars_x64) - template_vars.update(_get_clang_cl_vars(repository_ctx, paths, msvc_vars_x64, "x64")) + template_vars.update(_get_clang_cl_vars(repository_ctx, msvc_vars_x64, "x64")) template_vars.update(_get_msys_mingw_vars(repository_ctx)) template_vars.update(_get_copts(repository_ctx)) - template_vars.update(_get_msvc_vars(repository_ctx, paths, "x86", msvc_vars_x64)) - template_vars.update(_get_msvc_vars(repository_ctx, paths, "arm", msvc_vars_x64)) - msvc_vars_arm64 = _get_msvc_vars(repository_ctx, paths, "arm64", msvc_vars_x64) + template_vars.update(_get_msvc_vars(repository_ctx, "x86", msvc_vars_x64)) + template_vars.update(_get_msvc_vars(repository_ctx, "arm", msvc_vars_x64)) + msvc_vars_arm64 = _get_msvc_vars(repository_ctx, "arm64", msvc_vars_x64) template_vars.update(msvc_vars_arm64) - template_vars.update(_get_clang_cl_vars(repository_ctx, paths, msvc_vars_arm64, "arm64")) + template_vars.update(_get_clang_cl_vars(repository_ctx, msvc_vars_arm64, "arm64")) - template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, "x64")) - template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, "x86")) - template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, "arm")) - template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, paths, template_vars, "arm64")) + template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, template_vars, "x64")) + template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, template_vars, "x86")) + template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, template_vars, "arm")) + template_vars.update(_get_msvc_deps_scanner_vars(repository_ctx, template_vars, "arm64")) repository_ctx.template( "BUILD", - paths["@rules_cc//cc/private/toolchain:BUILD.windows.tpl"], + Label("@rules_cc//cc/private/toolchain:BUILD.windows.tpl"), template_vars, ) diff --git a/cc/private/toolchain/windows_cc_toolchain_config.bzl b/cc/private/toolchain/windows_cc_toolchain_config.bzl index 55b156daf..521a3428d 100644 --- a/cc/private/toolchain/windows_cc_toolchain_config.bzl +++ b/cc/private/toolchain/windows_cc_toolchain_config.bzl @@ -177,6 +177,11 @@ def _impl(ctx): prefix = "", extension = ".exe", ), + artifact_name_pattern( + category_name = "dynamic_library", + prefix = "lib", + extension = ".dll", + ), ] if _use_msvc_toolchain(ctx): @@ -607,15 +612,7 @@ def _impl(ctx): ] if ctx.attr.conly_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_cpp_compile_actions + [ACTION_NAMES.lto_backend], flag_groups = ([ flag_group( flags = ctx.attr.cxx_flags, @@ -1666,15 +1663,7 @@ def _impl(ctx): ] if ctx.attr.conly_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_cpp_compile_actions + [ACTION_NAMES.lto_backend], flag_groups = ([ flag_group( flags = ctx.attr.cxx_flags, @@ -1739,6 +1728,11 @@ def _impl(ctx): if "dumpbin" in ctx.attr.tool_paths: make_variables.append(make_variable(name = "DUMPBIN", value = ctx.attr.tool_paths["dumpbin"])) + windows_quoting_for_param_files_feature = feature( + name = "windows_quoting_for_param_files", + enabled = _use_msvc_toolchain(ctx), + ) + # Tell bazel we support C++ modules now cpp_modules_feature = feature( name = "cpp_modules", @@ -1785,7 +1779,7 @@ def _impl(ctx): ], enabled = True, ) - features.extend([cpp_modules_feature, cpp_module_modmap_file_feature, cpp20_module_compile_flags_feature]) + features.extend([windows_quoting_for_param_files_feature, cpp_modules_feature, cpp_module_modmap_file_feature, cpp20_module_compile_flags_feature]) extra_rules_based_features = depset(ctx.attr.extra_enabled_features + ctx.attr.extra_known_features) features.extend([convert_feature(extra_feature[FeatureInfo], enabled = extra_feature in ctx.attr.extra_enabled_features) for extra_feature in extra_rules_based_features.to_list()]) diff --git a/cc/private/toolchain_config/BUILD b/cc/private/toolchain_config/BUILD index eb5a48d6e..ccfa6c62a 100644 --- a/cc/private/toolchain_config/BUILD +++ b/cc/private/toolchain_config/BUILD @@ -14,6 +14,8 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files(["legacy_features.bzli"]) + bzl_library( name = "toolchain_config_bzl", srcs = glob(["*.bzl"]), @@ -41,6 +43,7 @@ filegroup( name = "srcs", srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]), visibility = ["//visibility:public"], diff --git a/cc/private/toolchain_config/legacy_features.bzl b/cc/private/toolchain_config/legacy_features.bzl index aa5b88a8a..bd7263d93 100644 --- a/cc/private/toolchain_config/legacy_features.bzl +++ b/cc/private/toolchain_config/legacy_features.bzl @@ -27,6 +27,8 @@ load( "with_feature_set", ) +_NO_FLAGS = [] # type: list[str] + def get_legacy_features(ctx, platform, existing_feature_names, linker_tool_path): """The features added to all legacy toolchains @@ -622,7 +624,7 @@ def get_legacy_features(ctx, platform, existing_feature_names, linker_tool_path) expand_if_available = "output_execpath", flags = _platform_specific_value( platform, - linux = [], + linux = _NO_FLAGS, mac = ["-o"], ) + [ "%{output_execpath}", @@ -1371,7 +1373,7 @@ def get_legacy_action_configs( flags = ["-S"] + _platform_specific_value( platform, linux = ["-p"], - mac = [], + mac = _NO_FLAGS, ) + ["-o", "%{output_file}"], ), flag_group( diff --git a/cc/private/toolchain_config/legacy_features.bzli b/cc/private/toolchain_config/legacy_features.bzli new file mode 100644 index 000000000..f6ff42b45 --- /dev/null +++ b/cc/private/toolchain_config/legacy_features.bzli @@ -0,0 +1,42 @@ +# Copyright 2026 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. +"""Legacy feature and action configuration builders.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move _platform_specific_value into source when its signature can bind +# L and M separately to preserve the two platforms’ possible result types. + +load("//cc:cc_toolchain_config_lib.bzl", "ActionConfigInfo", "FeatureInfo") + +type _ExistingNames = Collection[str] | str + +type _Platform = Literal["linux", "mac"] + +def get_legacy_features( + ctx: ctx[object, struct[{"compiler": NotRequired[str]}, ...]], + platform: _Platform, + existing_feature_names: _ExistingNames, + linker_tool_path: str, +) -> list[FeatureInfo]: ... + +def get_features_to_appear_last(existing_feature_names: _ExistingNames) -> list[FeatureInfo]: ... + +def get_legacy_action_configs( + platform: _Platform, + gcc_tool_path: str, + ar_tool_path: str, + strip_tool_path: str, + existing_action_config_names: _ExistingNames, +) -> list[ActionConfigInfo]: ... + +def _platform_specific_value[L, M](platform: str, *, linux: L, mac: M) -> L | M: ... diff --git a/cc/common/visibility.bzl b/cc/private/visibility.bzl similarity index 91% rename from cc/common/visibility.bzl rename to cc/private/visibility.bzl index 4ce7ed032..5eb0c6edc 100644 --- a/cc/common/visibility.bzl +++ b/cc/private/visibility.bzl @@ -3,6 +3,8 @@ # buildifier: disable=bzl-visibility load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") +visibility("//cc/...") + def check_private_api(): _cc_internal.check_private_api(allowlist = PRIVATE_STARLARKIFICATION_ALLOWLIST, depth = 2) @@ -21,7 +23,9 @@ def wrap_with_check_private_api(symbol): return callback -INTERNAL_VISIBILITY = ["public"] +# Things that are publicly visible in the open-source Bazel world, but have +# restricted visibility within Google's monorepo. +PUBLIC_IF_NOT_GOOGLE = ["public"] PRIVATE_RULES_VISIBILITY_FOR_BZL = [] diff --git a/cc/repositories.bzl b/cc/repositories.bzl index 3ff7dbccd..07a2437b0 100644 --- a/cc/repositories.bzl +++ b/cc/repositories.bzl @@ -3,8 +3,10 @@ load("//cc/private/toolchain:cc_configure.bzl", "cc_configure") def rules_cc_dependencies(): + # type: () -> None pass # buildifier: disable=unnamed-macro -def rules_cc_toolchains(*args): - cc_configure(*args) +def rules_cc_toolchains(): + # type: () -> None + cc_configure() diff --git a/cc/starlark_doc_extract_helper.bzl b/cc/starlark_doc_extract_helper.bzl index 6c145f304..fc96c726d 100644 --- a/cc/starlark_doc_extract_helper.bzl +++ b/cc/starlark_doc_extract_helper.bzl @@ -2,7 +2,10 @@ visibility("//cc/...") +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Annotate **kwargs +# when Bazel can derive them from the wrapped starlark_doc_extract rule. def starlark_doc_extract_helper(**kwargs): + # type: (**Unpack[KeywordArgs[native.starlark_doc_extract]]) -> None """Creates a starlark_doc_extract target if the native rule is available Args: diff --git a/cc/system_library.bzl b/cc/system_library.bzl index 68477dace..12c720321 100644 --- a/cc/system_library.bzl +++ b/cc/system_library.bzl @@ -8,13 +8,16 @@ ENV_VAR_SEPARATOR = "," ENV_VAR_ASSIGNMENT = "=" def _make_flags(flag_values, flag): + # type: (Collection[str] | None, str) -> str flags = [] if flag_values: for s in flag_values: flags.append(flag + s) return " ".join(flags) +# TODO(https://github.com/bazelbuild/bazel/issues/32020): use a source annotation once Bazel exposes these context and metadata types. def _split_env_var(repo_ctx, var_name): + # type: (repository_ctx, str) -> dict[str, list[str]] value = repo_ctx.os.environ.get(var_name) if value: assignments = value.split(ENV_VAR_SEPARATOR) @@ -36,12 +39,15 @@ def _split_env_var(repo_ctx, var_name): return {} def _get_list_from_env_var(repo_ctx, var_name, key): + # type: (repository_ctx, str, str) -> list[str] return _split_env_var(repo_ctx, var_name).get(key, default = []) def _execute_bash(repo_ctx, cmd): + # type: (repository_ctx, str) -> str return repo_ctx.execute(["/usr/bin/env", "bash", "-c", cmd]).stdout.strip("\n") def _find_linker(repo_ctx): + # type: (repository_ctx) -> str ld = _execute_bash(repo_ctx, "which ld") lld = _execute_bash(repo_ctx, "which lld") if ld: @@ -52,6 +58,7 @@ def _find_linker(repo_ctx): fail("No linker found") def _find_compiler(repo_ctx): + # type: (repository_ctx) -> str gcc = _execute_bash(repo_ctx, "which g++") clang = _execute_bash(repo_ctx, "which clang++") if gcc: @@ -62,6 +69,7 @@ def _find_compiler(repo_ctx): fail("No compiler found") def _find_lib_path(repo_ctx, lib_name, archive_names, lib_path_hints): + # type: (repository_ctx, str, Collection[str], list[str]) -> tuple[str, str] | tuple[None, None] override_paths = _get_list_from_env_var( repo_ctx, BAZEL_LIB_OVERRIDE_PATHS_ENV_VAR, @@ -96,6 +104,7 @@ def _find_lib_path(repo_ctx, lib_name, archive_names, lib_path_hints): return (None, None) def _find_header_path(repo_ctx, lib_name, header_name, includes): + # type: (repository_ctx, str, str, list[str]) -> str | None override_paths = _get_list_from_env_var( repo_ctx, BAZEL_INCLUDE_OVERRIDE_PATHS_ENV_VAR, @@ -127,7 +136,21 @@ def _find_header_path(repo_ctx, lib_name, header_name, includes): return result return None +# TODO(https://github.com/bazelbuild/bazel/issues/32020): Use a native repository +# context annotation for these attributes when supported Bazel versions expose it. +# type _Attrs = struct[{ +# "name": str, +# "includes": list[str], +# "hdrs": list[str], +# "optional_hdrs": list[str], +# "deps": list[str], +# "lib_path_hints": list[str], +# "static_lib_names": list[str], +# "shared_lib_names": list[str], +# }, ...] + def _system_library_impl(repo_ctx): + # type: (repository_ctx[_Attrs]) -> repo_metadata | None repo_name = repo_ctx.attr.name.split("+")[-1] includes = repo_ctx.attr.includes hdrs = repo_ctx.attr.hdrs @@ -180,7 +203,7 @@ def _system_library_impl(repo_ctx): # The problem is that the quote-includes are assumed to be # in the same directory as the header they are included from - # they have no subdir prefix ("SDL2/") in their paths - include_subdirs = {} + include_subdirs = {} # type: dict[str, None] for hdr in hdr_names: path_segments = hdr.split("/") path_segments.pop() diff --git a/cc/toolchains/BUILD b/cc/toolchains/BUILD index e9ac62b03..ba57417d3 100644 --- a/cc/toolchains/BUILD +++ b/cc/toolchains/BUILD @@ -14,6 +14,14 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files( + [ + "cc_toolchain_info.bzli", + "tool_map.bzli", + ], + visibility = ["//visibility:public"], +) + bzl_library( name = "toolchain_rules", srcs = glob(["*.bzl"]), @@ -44,6 +52,7 @@ filegroup( name = "srcs", srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]) + [ "//cc/toolchains/actions:srcs", diff --git a/cc/toolchains/actions.bzl b/cc/toolchains/actions.bzl index 9a8928e5b..99e23f0a6 100644 --- a/cc/toolchains/actions.bzl +++ b/cc/toolchains/actions.bzl @@ -19,6 +19,7 @@ load(":cc_toolchain_info.bzl", "ActionTypeInfo", "ActionTypeSetInfo") visibility("public") def _cc_action_type_impl(ctx): + # type: (...) -> list[ActionTypeInfo | ActionTypeSetInfo] action_type = ActionTypeInfo(label = ctx.label, name = ctx.attr.action_name) return [ action_type, @@ -62,6 +63,7 @@ cc_action_type( ) def _cc_action_type_set_impl(ctx): + # type: (...) -> list[ActionTypeSetInfo] if not ctx.attr.actions and not ctx.attr.allow_empty: fail("Each cc_action_type_set must contain at least one action type.") actions = collect_action_types(ctx.attr.actions) diff --git a/cc/toolchains/actions/BUILD b/cc/toolchains/actions/BUILD index 80cac26b2..cbf939535 100644 --- a/cc/toolchains/actions/BUILD +++ b/cc/toolchains/actions/BUILD @@ -269,6 +269,10 @@ cc_action_type_set( excludes = [ # `lto_backend` actions do not instantiate the full set of build variables. ":lto_backend", + # `cpp_module_codegen` actions compile a precompiled module and never + # preprocess, so include paths, defines and other preprocessing flags + # don't apply to them and only make Clang warn about unused arguments. + ":cpp_module_codegen", ], ) diff --git a/cc/toolchains/args.bzl b/cc/toolchains/args.bzl index b07e54ec4..0c18c30de 100644 --- a/cc/toolchains/args.bzl +++ b/cc/toolchains/args.bzl @@ -164,6 +164,7 @@ Roughly equivalent to ctx.actions.args() Examples: cc_args( name = "warnings_as_errors", + actions = ["//cc/toolchains/actions:compile_actions"], args = ["-Werror"], ) """, @@ -172,7 +173,7 @@ Examples: def cc_args( *, name, - actions = None, + actions, allowlist_include_directories = None, allowlist_absolute_include_directories = None, args = None, diff --git a/cc/toolchains/args/BUILD b/cc/toolchains/args/BUILD index 9625c3d9e..3edc7f41a 100644 --- a/cc/toolchains/args/BUILD +++ b/cc/toolchains/args/BUILD @@ -36,6 +36,7 @@ cc_feature_set( "//cc/toolchains/args/linkstamp_flags:feature", "//cc/toolchains/args/library_search_directories:feature", "//cc/toolchains/args/dependency_file:feature", + "//cc/toolchains/args/cpp_modules:feature", # NOTE: This should come below default flags so user flags can override them "//cc/toolchains/args/compile_flags:feature", @@ -61,5 +62,33 @@ filegroup( name = "srcs", srcs = glob(["**"]) + [ "//cc/toolchains/args/archiver_flags:srcs", + "//cc/toolchains/args/compile_flags:srcs", + "//cc/toolchains/args/compiler_input_flags:srcs", + "//cc/toolchains/args/compiler_output_flags:srcs", + "//cc/toolchains/args/cpp_modules:srcs", + "//cc/toolchains/args/def_file:srcs", + "//cc/toolchains/args/dependency_file:srcs", + "//cc/toolchains/args/external_include_paths:srcs", + "//cc/toolchains/args/fission_flags:srcs", + "//cc/toolchains/args/generate_linkmap:srcs", + "//cc/toolchains/args/include_flags:srcs", + "//cc/toolchains/args/layering_check:srcs", + "//cc/toolchains/args/libraries_to_link:srcs", + "//cc/toolchains/args/library_search_directories:srcs", + "//cc/toolchains/args/link_flags:srcs", + "//cc/toolchains/args/linker_param_file:srcs", + "//cc/toolchains/args/linkstamp_flags:srcs", + "//cc/toolchains/args/objc_arc_flags:srcs", + "//cc/toolchains/args/pic_flags:srcs", + "//cc/toolchains/args/preprocessor_defines:srcs", + "//cc/toolchains/args/random_seed:srcs", + "//cc/toolchains/args/runtime_library_search_directories:srcs", + "//cc/toolchains/args/sanitize_pwd:srcs", + "//cc/toolchains/args/shared_flag:srcs", + "//cc/toolchains/args/soname_flags:srcs", + "//cc/toolchains/args/static_libgcc:srcs", + "//cc/toolchains/args/strip_debug_symbols:srcs", + "//cc/toolchains/args/strip_flags:srcs", + "//cc/toolchains/args/thin_lto:srcs", ], ) diff --git a/cc/toolchains/args/compile_flags/BUILD b/cc/toolchains/args/compile_flags/BUILD index 123892aaa..aa285c379 100644 --- a/cc/toolchains/args/compile_flags/BUILD +++ b/cc/toolchains/args/compile_flags/BUILD @@ -62,3 +62,9 @@ cc_args( iterate_over = "//cc/toolchains/variables:unfiltered_compile_flags", requires_not_none = "//cc/toolchains/variables:unfiltered_compile_flags", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/compiler_input_flags/BUILD b/cc/toolchains/args/compiler_input_flags/BUILD index 9bfe6a48b..d4a39d0c1 100644 --- a/cc/toolchains/args/compiler_input_flags/BUILD +++ b/cc/toolchains/args/compiler_input_flags/BUILD @@ -5,6 +5,7 @@ cc_feature( name = "feature", args = [ ":flags", + ":module_interface_args", ":header_parsing_args", ], overrides = "//cc/toolchains/features/legacy:compiler_input_flags", @@ -15,7 +16,8 @@ cc_args( name = "flags", actions = [ "//cc/toolchains/actions:compile_actions_without_header_parsing", - "//cc/toolchains/actions:cpp20_module_actions", + "//cc/toolchains/actions:cpp20_module_codegen", + "//cc/toolchains/actions:cpp_module_deps_scanning", ], args = [ "-c", @@ -25,6 +27,21 @@ cc_args( requires_not_none = "//cc/toolchains/variables:source_file", ) +# A C++20 module interface unit compiles as one whatever its extension. `-x` +# only applies to the inputs after it, so it goes right before the source. +cc_args( + name = "module_interface_args", + actions = ["//cc/toolchains/actions:cpp20_module_compile"], + args = [ + "-x", + "c++-module", + "-c", + "{source_file}", + ], + format = {"source_file": "//cc/toolchains/variables:source_file"}, + requires_not_none = "//cc/toolchains/variables:source_file", +) + cc_args( name = "header_parsing_args", actions = ["//cc/toolchains/actions:cpp_header_parsing"], @@ -32,3 +49,9 @@ cc_args( format = {"source_file": "//cc/toolchains/variables:source_file"}, requires_not_none = "//cc/toolchains/variables:source_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/compiler_output_flags/BUILD b/cc/toolchains/args/compiler_output_flags/BUILD index 068d31085..d6bd17e90 100644 --- a/cc/toolchains/args/compiler_output_flags/BUILD +++ b/cc/toolchains/args/compiler_output_flags/BUILD @@ -15,6 +15,7 @@ cc_args_list( ":output_assembly_flags", ":output_preprocess_flags", ":output_flags", + ":deps_scanner_output_env", ], ) @@ -45,3 +46,19 @@ cc_args( format = {"output_file": "//cc/toolchains/variables:output_file"}, requires_not_none = "//cc/toolchains/variables:output_file", ) + +# The C++20 module dependency scanner writes its P1689 output to the file this +# names; the output_flags above name the scanned compile's object. +cc_args( + name = "deps_scanner_output_env", + actions = ["//cc/toolchains/actions:cpp_module_deps_scanning"], + env = {"DEPS_SCANNER_OUTPUT_FILE": "{output_file}"}, + format = {"output_file": "//cc/toolchains/variables:output_file"}, + requires_not_none = "//cc/toolchains/variables:output_file", +) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/cpp_modules/BUILD b/cc/toolchains/args/cpp_modules/BUILD new file mode 100644 index 000000000..6269e5fd9 --- /dev/null +++ b/cc/toolchains/args/cpp_modules/BUILD @@ -0,0 +1,56 @@ +load("//cc/toolchains:args.bzl", "cc_args") +load("//cc/toolchains:feature.bzl", "cc_feature") + +# The args C++20 modules need from a clang toolchain, matching +# unix_cc_toolchain_config. Bazel builds module interfaces only once the +# toolchain knows the `cpp_modules` feature and the build enables it, so +# toolchains that map tools for the C++20 module actions should list +# `:cpp_modules` in their known features and `:feature` in their enabled ones. +# compiler_input_flags marks a module interface unit as one, since `-x` only +# applies to the inputs after it. + +package(default_visibility = ["//visibility:public"]) + +cc_feature( + name = "cpp_modules", + feature_name = "cpp_modules", +) + +cc_feature( + name = "feature", + args = [ + ":modmap_file_args", + ":module_output_args", + ], + feature_name = "cpp20_module_flags", +) + +# The module map file names the BMI of each module a source imports. +cc_args( + name = "modmap_file_args", + actions = [ + "//cc/toolchains/actions:cpp_compile", + "//cc/toolchains/actions:cpp20_module_compile", + "//cc/toolchains/actions:cpp20_module_codegen", + ], + args = ["@{cpp_module_modmap_file}"], + format = {"cpp_module_modmap_file": "//cc/toolchains/variables:cpp_module_modmap_file"}, + requires_not_none = "//cc/toolchains/variables:cpp_module_modmap_file", + visibility = ["//visibility:private"], +) + +# A module interface unit writes its BMI where Bazel declared it. +cc_args( + name = "module_output_args", + actions = ["//cc/toolchains/actions:cpp20_module_compile"], + args = ["-fmodule-output={cpp_module_output_file}"], + format = {"cpp_module_output_file": "//cc/toolchains/variables:cpp_module_output_file"}, + requires_not_none = "//cc/toolchains/variables:cpp_module_output_file", + visibility = ["//visibility:private"], +) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/def_file/BUILD b/cc/toolchains/args/def_file/BUILD index 0b6811f95..6449b22f3 100644 --- a/cc/toolchains/args/def_file/BUILD +++ b/cc/toolchains/args/def_file/BUILD @@ -16,3 +16,9 @@ cc_args( format = {"def_file_path": "//cc/toolchains/variables:def_file_path"}, requires_not_none = "//cc/toolchains/variables:def_file_path", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/dependency_file/BUILD b/cc/toolchains/args/dependency_file/BUILD index 812092b6e..05ac6052c 100644 --- a/cc/toolchains/args/dependency_file/BUILD +++ b/cc/toolchains/args/dependency_file/BUILD @@ -22,3 +22,9 @@ cc_args( format = {"dependency_file": "//cc/toolchains/variables:dependency_file"}, requires_not_none = "//cc/toolchains/variables:dependency_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/external_include_paths/BUILD b/cc/toolchains/args/external_include_paths/BUILD index de2214f3d..f2dfcfab8 100644 --- a/cc/toolchains/args/external_include_paths/BUILD +++ b/cc/toolchains/args/external_include_paths/BUILD @@ -20,3 +20,9 @@ cc_nested_args( }, iterate_over = "//cc/toolchains/variables:external_include_paths", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/fission_flags/BUILD b/cc/toolchains/args/fission_flags/BUILD index 8ceb501c0..f91cdd9b2 100644 --- a/cc/toolchains/args/fission_flags/BUILD +++ b/cc/toolchains/args/fission_flags/BUILD @@ -39,3 +39,9 @@ cc_args( ], requires_not_none = "//cc/toolchains/variables:per_object_debug_info_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/generate_linkmap/BUILD b/cc/toolchains/args/generate_linkmap/BUILD index b44ecf1bb..27f07a98a 100644 --- a/cc/toolchains/args/generate_linkmap/BUILD +++ b/cc/toolchains/args/generate_linkmap/BUILD @@ -20,3 +20,9 @@ cc_args( requires_not_none = "//cc/toolchains/variables:output_execpath", visibility = ["//visibility:private"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/include_flags/BUILD b/cc/toolchains/args/include_flags/BUILD index 1ee790e26..b6d76ad92 100644 --- a/cc/toolchains/args/include_flags/BUILD +++ b/cc/toolchains/args/include_flags/BUILD @@ -81,3 +81,9 @@ cc_args( format = {"framework_include_paths": "//cc/toolchains/variables:framework_include_paths"}, iterate_over = "//cc/toolchains/variables:framework_include_paths", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/layering_check/BUILD b/cc/toolchains/args/layering_check/BUILD index a7672deef..327aaad6e 100644 --- a/cc/toolchains/args/layering_check/BUILD +++ b/cc/toolchains/args/layering_check/BUILD @@ -95,3 +95,9 @@ cc_args( iterate_over = "@rules_cc//cc/toolchains/variables:dependent_module_map_files", requires_not_none = "@rules_cc//cc/toolchains/variables:dependent_module_map_files", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/libraries_to_link/BUILD b/cc/toolchains/args/libraries_to_link/BUILD index ebfec9a44..8228f3588 100644 --- a/cc/toolchains/args/libraries_to_link/BUILD +++ b/cc/toolchains/args/libraries_to_link/BUILD @@ -180,3 +180,12 @@ cc_nested_args( requires_equal = "//cc/toolchains/variables:libraries_to_link.type", requires_equal_value = "versioned_dynamic_library", ) + +filegroup( + name = "srcs", + srcs = [ + "BUILD", + "//cc/toolchains/args/libraries_to_link/private:srcs", + ], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/libraries_to_link/private/BUILD b/cc/toolchains/args/libraries_to_link/private/BUILD index ecb36c3e0..e0b10e88f 100644 --- a/cc/toolchains/args/libraries_to_link/private/BUILD +++ b/cc/toolchains/args/libraries_to_link/private/BUILD @@ -1 +1,10 @@ package(default_visibility = ["//visibility:private"]) + +filegroup( + name = "srcs", + srcs = [ + "BUILD", + "library_link_args.bzl", + ], + visibility = ["//cc/toolchains/args/libraries_to_link:__pkg__"], +) diff --git a/cc/toolchains/args/libraries_to_link/private/library_link_args.bzl b/cc/toolchains/args/libraries_to_link/private/library_link_args.bzl index 85e65814d..bc47b4a7e 100644 --- a/cc/toolchains/args/libraries_to_link/private/library_link_args.bzl +++ b/cc/toolchains/args/libraries_to_link/private/library_link_args.bzl @@ -16,6 +16,7 @@ load("//cc/toolchains:nested_args.bzl", "cc_nested_args") def apple_force_load_library_args(name, variable): + # type: (str, str | Label) -> None """A helper for declaring -force_load argument expansion for a library. This creates an argument expansion that will expand to -Wl,-force_load, @@ -50,6 +51,7 @@ def apple_force_load_library_args(name, variable): ) def library_link_args(name, library_type, from_variable, iterate_over_variable = False): + # type: (str, str | select[str], str | Label, bool) -> None """A helper for declaring a library to link. For most platforms, this expands something akin to the following: diff --git a/cc/toolchains/args/library_search_directories/BUILD b/cc/toolchains/args/library_search_directories/BUILD index 1ed8b1487..d3f468230 100644 --- a/cc/toolchains/args/library_search_directories/BUILD +++ b/cc/toolchains/args/library_search_directories/BUILD @@ -16,3 +16,9 @@ cc_args( iterate_over = "//cc/toolchains/variables:library_search_directories", requires_not_none = "//cc/toolchains/variables:library_search_directories", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/link_flags/BUILD b/cc/toolchains/args/link_flags/BUILD index 58d38a31f..e57c2747a 100644 --- a/cc/toolchains/args/link_flags/BUILD +++ b/cc/toolchains/args/link_flags/BUILD @@ -58,3 +58,9 @@ cc_args( format = {"output_execpath": "//cc/toolchains/variables:output_execpath"}, requires_not_none = "//cc/toolchains/variables:output_execpath", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/linker_param_file/BUILD b/cc/toolchains/args/linker_param_file/BUILD index b9d4096df..ab50ec440 100644 --- a/cc/toolchains/args/linker_param_file/BUILD +++ b/cc/toolchains/args/linker_param_file/BUILD @@ -27,3 +27,9 @@ cc_args( format = {"param_file": "//cc/toolchains/variables:linker_param_file"}, requires_not_none = "//cc/toolchains/variables:linker_param_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/linkstamp_flags/BUILD b/cc/toolchains/args/linkstamp_flags/BUILD index a2e2bf06c..689026950 100644 --- a/cc/toolchains/args/linkstamp_flags/BUILD +++ b/cc/toolchains/args/linkstamp_flags/BUILD @@ -16,3 +16,9 @@ cc_args( iterate_over = "//cc/toolchains/variables:linkstamp_paths", requires_not_none = "//cc/toolchains/variables:linkstamp_paths", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/objc_arc_flags/BUILD b/cc/toolchains/args/objc_arc_flags/BUILD index 06d44c3b8..0262ac2a6 100644 --- a/cc/toolchains/args/objc_arc_flags/BUILD +++ b/cc/toolchains/args/objc_arc_flags/BUILD @@ -24,3 +24,9 @@ cc_args( args = ["-fno-objc-arc"], requires_not_none = "//cc/toolchains/variables:no_objc_arc", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/pic_flags/BUILD b/cc/toolchains/args/pic_flags/BUILD index 4ff86da6c..c0621da1d 100644 --- a/cc/toolchains/args/pic_flags/BUILD +++ b/cc/toolchains/args/pic_flags/BUILD @@ -42,3 +42,9 @@ cc_args( requires_not_none = "//cc/toolchains/variables:force_pic", visibility = ["//:__subpackages__"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/preprocessor_defines/BUILD b/cc/toolchains/args/preprocessor_defines/BUILD index d7c3267dd..e32268ef3 100644 --- a/cc/toolchains/args/preprocessor_defines/BUILD +++ b/cc/toolchains/args/preprocessor_defines/BUILD @@ -16,3 +16,9 @@ cc_args( format = {"preprocessor_defines": "//cc/toolchains/variables:preprocessor_defines"}, iterate_over = "//cc/toolchains/variables:preprocessor_defines", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/random_seed/BUILD b/cc/toolchains/args/random_seed/BUILD index 97f56a33f..0087d2f7f 100644 --- a/cc/toolchains/args/random_seed/BUILD +++ b/cc/toolchains/args/random_seed/BUILD @@ -16,3 +16,9 @@ cc_args( format = {"output_file": "//cc/toolchains/variables:output_file"}, requires_not_none = "//cc/toolchains/variables:output_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/runtime_library_search_directories/BUILD b/cc/toolchains/args/runtime_library_search_directories/BUILD index 5cfadaa9f..20cfaadcf 100644 --- a/cc/toolchains/args/runtime_library_search_directories/BUILD +++ b/cc/toolchains/args/runtime_library_search_directories/BUILD @@ -114,3 +114,9 @@ cc_nested_args( }, iterate_over = "//cc/toolchains/variables:runtime_library_search_directories", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/sanitize_pwd/BUILD b/cc/toolchains/args/sanitize_pwd/BUILD index a280c1751..cb7cbb307 100644 --- a/cc/toolchains/args/sanitize_pwd/BUILD +++ b/cc/toolchains/args/sanitize_pwd/BUILD @@ -22,3 +22,9 @@ cc_args( "//conditions:default": {"PWD": "/proc/self/cwd"}, }), ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/shared_flag/BUILD b/cc/toolchains/args/shared_flag/BUILD index b01c9eafe..7d6cf1a8f 100644 --- a/cc/toolchains/args/shared_flag/BUILD +++ b/cc/toolchains/args/shared_flag/BUILD @@ -16,3 +16,9 @@ cc_args( args = ["-shared"], visibility = ["//visibility:public"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/soname_flags/BUILD b/cc/toolchains/args/soname_flags/BUILD index 304cccecb..10e0baa7e 100644 --- a/cc/toolchains/args/soname_flags/BUILD +++ b/cc/toolchains/args/soname_flags/BUILD @@ -36,3 +36,9 @@ cc_args( target_compatible_with = ["@platforms//os:linux"], visibility = ["//visibility:public"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/static_libgcc/BUILD b/cc/toolchains/args/static_libgcc/BUILD index 422842750..bbdf50d19 100644 --- a/cc/toolchains/args/static_libgcc/BUILD +++ b/cc/toolchains/args/static_libgcc/BUILD @@ -20,3 +20,9 @@ cc_args( args = ["-static-libgcc"], requires_any_of = [":static_link_cpp_runtimes_enabled"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/strip_debug_symbols/BUILD b/cc/toolchains/args/strip_debug_symbols/BUILD index b94c5b19f..d5a4ab9b2 100644 --- a/cc/toolchains/args/strip_debug_symbols/BUILD +++ b/cc/toolchains/args/strip_debug_symbols/BUILD @@ -15,3 +15,9 @@ cc_args( args = ["-Wl,-S"], requires_not_none = "//cc/toolchains/variables:strip_debug_symbols", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/strip_flags/BUILD b/cc/toolchains/args/strip_flags/BUILD index e6c466834..f13961fae 100644 --- a/cc/toolchains/args/strip_flags/BUILD +++ b/cc/toolchains/args/strip_flags/BUILD @@ -66,3 +66,9 @@ cc_args( format = {"input_file": "//cc/toolchains/variables:input_file"}, requires_not_none = "//cc/toolchains/variables:input_file", ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args/thin_lto/BUILD b/cc/toolchains/args/thin_lto/BUILD index 774be376a..b54bbdd84 100644 --- a/cc/toolchains/args/thin_lto/BUILD +++ b/cc/toolchains/args/thin_lto/BUILD @@ -178,3 +178,9 @@ cc_feature( feature_name = "thin_lto", visibility = ["//visibility:public"], ) + +filegroup( + name = "srcs", + srcs = ["BUILD"], + visibility = ["//cc/toolchains/args:__pkg__"], +) diff --git a/cc/toolchains/args_list.bzl b/cc/toolchains/args_list.bzl index 0747acba7..654ea85b0 100644 --- a/cc/toolchains/args_list.bzl +++ b/cc/toolchains/args_list.bzl @@ -20,6 +20,7 @@ load( load(":cc_toolchain_info.bzl", "ArgsListInfo") def _cc_args_list_impl(ctx): + # type: (...) -> list[ArgsListInfo] return [collect_args_lists(ctx.attr.args, ctx.label)] cc_args_list = rule( diff --git a/cc/toolchains/artifacts.bzl b/cc/toolchains/artifacts.bzl index 20e6b84c1..af8d62134 100644 --- a/cc/toolchains/artifacts.bzl +++ b/cc/toolchains/artifacts.bzl @@ -19,6 +19,7 @@ load(":cc_toolchain_info.bzl", "ArtifactCategoryInfo", "ArtifactNamePatternInfo" visibility("public") def _cc_artifact_name_pattern_impl(ctx): + # type: (...) -> list[ArtifactNamePatternInfo] return [ ArtifactNamePatternInfo( label = ctx.label, diff --git a/cc/toolchains/cc_toolchain_info.bzli b/cc/toolchains/cc_toolchain_info.bzli new file mode 100644 index 000000000..fc8129b31 --- /dev/null +++ b/cc/toolchains/cc_toolchain_info.bzli @@ -0,0 +1,294 @@ +"""Provider values shared by rule-based toolchain implementations.""" + +load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo") +load("//cc:cc_toolchain_config_lib.bzli", "FlagGroupInfo") + +type TypeRequirement = struct[{"msg": str, "valid_types": list[str], "after_option_unwrap": bool}] +type ActionArgs = struct[{"action": ActionTypeInfo, "args": Sequence[ArgsInfo], "files": depset[File]}] +# TODO(https://github.com/bazelbuild/bazel/issues/31770): annotate the toolchain variable dictionaries +# in Starlark once Bazel supports dictionaries with named, typed keys. +type LeafVariableType = dict[{"name": Literal["unknown", "void", "string", "bool", "file", "directory"], "repr": str}] +type ElementVariableType = dict[{"name": Literal["option", "list"], "repr": str, "elements": VariableType}] +type StructVariableType = dict[{"name": Literal["struct"], "repr": str, "kv": dict[str, VariableType]}] +type VariableType = LeafVariableType | ElementVariableType | StructVariableType + +def ActionTypeInfo( + *, + label: Label, + name: str, +) -> record[{ + "label": Label, + "name": str, +}]: ... + +def ActionTypeSetInfo( + *, + label: Label, + actions: depset[ActionTypeInfo], +) -> record[{ + "label": Label, + "actions": depset[ActionTypeInfo], +}]: ... + +def ArtifactCategoryInfo( + *, + label: Label, + name: str, +) -> record[{ + "label": Label, + "name": str, +}]: ... + +def ArtifactNamePatternInfo( + *, + label: Label, + category: ArtifactCategoryInfo, + prefix: str, + extension: str, +) -> record[{ + "label": Label, + "category": ArtifactCategoryInfo, + "prefix": str, + "extension": str, +}]: ... + +def VariableInfo( + *, + name: str, + label: Label, + actions: depset[ActionTypeInfo] | None, + type: VariableType, +) -> record[{ + "name": str, + "label": Label, + "actions": depset[ActionTypeInfo] | None, + "type": VariableType, +}]: ... + +def BuiltinVariablesInfo( + *, + variables: dict[str, VariableInfo], +) -> record[{ + "variables": dict[str, VariableInfo], +}]: ... + +def NestedArgsInfo( + *, + label: Label, + nested: Sequence[NestedArgsInfo], + iterate_over: str | None, + files: depset[File], + requires_types: dict[str, list[TypeRequirement]], + legacy_flag_group: FlagGroupInfo, + unwrap_options: list[str], +) -> record[{ + "label": Label, + "nested": Sequence[NestedArgsInfo], + "iterate_over": str | None, + "files": depset[File], + "requires_types": dict[str, list[TypeRequirement]], + "legacy_flag_group": FlagGroupInfo, + "unwrap_options": list[str], +}]: ... + +def EnvInfo( + *, + label: Label, + entries: dict[str, str], + requires_not_none: str | None, +) -> record[{ + "label": Label, + "entries": dict[str, str], + "requires_not_none": str | None, +}]: ... + +def ArgsInfo( + *, + label: Label, + actions: depset[ActionTypeInfo], + requires_any_of: Sequence[FeatureConstraintInfo], + nested: NestedArgsInfo | None, + files: depset[File], + env: EnvInfo, + allowlist_include_directories: depset[DirectoryInfo], + allowlist_absolute_include_directories: depset[str], +) -> record[{ + "label": Label, + "actions": depset[ActionTypeInfo], + "requires_any_of": Sequence[FeatureConstraintInfo], + "nested": NestedArgsInfo | None, + "files": depset[File], + "env": EnvInfo, + "allowlist_include_directories": depset[DirectoryInfo], + "allowlist_absolute_include_directories": depset[str], +}]: ... + +def ArgsListInfo( + *, + label: Label, + args: Sequence[ArgsInfo], + files: depset[File], + by_action: Sequence[ActionArgs], + allowlist_include_directories: depset[DirectoryInfo], + allowlist_absolute_include_directories: depset[str], +) -> record[{ + "label": Label, + "args": Sequence[ArgsInfo], + "files": depset[File], + "by_action": Sequence[ActionArgs], + "allowlist_include_directories": depset[DirectoryInfo], + "allowlist_absolute_include_directories": depset[str], +}]: ... + +def FeatureInfo( + *, + label: Label, + name: str, + enabled: bool, + args: ArgsListInfo, + implies: depset[FeatureInfo], + requires_any_of: Sequence[FeatureSetInfo], + mutually_exclusive: Sequence[MutuallyExclusiveCategoryInfo], + external: bool, + overridable: bool, + overrides: FeatureInfo | None, + allowlist_include_directories: depset[DirectoryInfo], + allowlist_absolute_include_directories: depset[str], +) -> record[{ + "label": Label, + "name": str, + "enabled": bool, + "args": ArgsListInfo, + "implies": depset[FeatureInfo], + "requires_any_of": Sequence[FeatureSetInfo], + "mutually_exclusive": Sequence[MutuallyExclusiveCategoryInfo], + "external": bool, + "overridable": bool, + "overrides": FeatureInfo | None, + "allowlist_include_directories": depset[DirectoryInfo], + "allowlist_absolute_include_directories": depset[str], +}]: ... + +def FeatureSetInfo( + *, + label: Label, + features: depset[FeatureInfo], +) -> record[{ + "label": Label, + "features": depset[FeatureInfo], +}]: ... + +def FeatureConstraintInfo( + *, + label: Label, + all_of: depset[FeatureInfo], + none_of: depset[FeatureInfo], +) -> record[{ + "label": Label, + "all_of": depset[FeatureInfo], + "none_of": depset[FeatureInfo], +}]: ... + +def MakeVariableInfo( + *, + label: Label, + key: str, + value: str, +) -> record[{ + "label": Label, + "key": str, + "value": str, +}]: ... + +def ExecutionRequirementsInfo( + *, + label: Label, + requirements: Sequence[str], +) -> record[{ + "label": Label, + "requirements": Sequence[str], +}]: ... + +def MutuallyExclusiveCategoryInfo( + *, + label: Label, + name: str, +) -> record[{ + "label": Label, + "name": str, +}]: ... + +def ToolInfo( + *, + label: Label, + exe: File, + runfiles: runfiles, + execution_requirements: Sequence[str], + env: dict[str, str], + allowlist_include_directories: depset[DirectoryInfo], + capabilities: Sequence[ToolCapabilityInfo], +) -> record[{ + "label": Label, + "exe": File, + "runfiles": runfiles, + "execution_requirements": Sequence[str], + "env": dict[str, str], + "allowlist_include_directories": depset[DirectoryInfo], + "capabilities": Sequence[ToolCapabilityInfo], +}]: ... + +def ToolCapabilityInfo( + *, + label: Label, + feature: FeatureInfo, +) -> record[{ + "label": Label, + "feature": FeatureInfo, +}]: ... + +def ToolConfigInfo( + *, + label: Label, + configs: dict[ActionTypeInfo, ToolInfo], +) -> record[{ + "label": Label, + "configs": dict[ActionTypeInfo, ToolInfo], +}]: ... + +def LegacyToolInfo( + *, + label: Label, + name: str, + path: str, +) -> record[{ + "label": Label, + "name": str, + "path": str, +}]: ... + +def ToolchainConfigInfo( + *, + label: Label, + features: Sequence[FeatureInfo], + enabled_features: Sequence[FeatureInfo], + tool_map: ToolConfigInfo, + args: ArgsListInfo, + artifact_name_patterns: Sequence[ArtifactNamePatternInfo], + make_variables: Sequence[MakeVariableInfo], + files: dict[ActionTypeInfo, depset[File]], + allowlist_include_directories: depset[DirectoryInfo], + allowlist_absolute_include_directories: depset[str], + legacy_tools: Sequence[LegacyToolInfo], +) -> record[{ + "label": Label, + "features": Sequence[FeatureInfo], + "enabled_features": Sequence[FeatureInfo], + "tool_map": ToolConfigInfo, + "args": ArgsListInfo, + "artifact_name_patterns": Sequence[ArtifactNamePatternInfo], + "make_variables": Sequence[MakeVariableInfo], + "files": dict[ActionTypeInfo, depset[File]], + "allowlist_include_directories": depset[DirectoryInfo], + "allowlist_absolute_include_directories": depset[str], + "legacy_tools": Sequence[LegacyToolInfo], +}]: ... diff --git a/cc/toolchains/directory_tool.bzl b/cc/toolchains/directory_tool.bzl index b4572e737..76391cae2 100644 --- a/cc/toolchains/directory_tool.bzl +++ b/cc/toolchains/directory_tool.bzl @@ -16,7 +16,28 @@ load("@bazel_skylib//rules/directory:glob.bzl", "directory_glob") load(":tool.bzl", "cc_tool") -def cc_directory_tool(name, directory, executable, data = [], exclude = [], allow_empty = False, **kwargs): +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Type **kwargs on the +# declaration when Bazel can refer to the remaining keywords of cc_tool. +# type _Strings = Collection[str] | depset[str] +# type _StringsInput = _Strings | select[_Strings | None] | None +# type _Bool = bool | Literal[0, 1] + +def cc_directory_tool( + # type: str + name, + # type: str | Label | select[str | Label | None] + directory, + # type: str + executable, + # type: _StringsInput + data = [], + # type: _StringsInput + exclude = [], + # type: _Bool | select[_Bool | None] | None + allow_empty = False, + # type: Unpack[KeywordArgs[cc_tool, "name", "src", "data"]] + **kwargs): + # type: (...) -> None """A tool extracted from a directory. Args: diff --git a/cc/toolchains/feature.bzl b/cc/toolchains/feature.bzl index 713b2a102..29f63097d 100644 --- a/cc/toolchains/feature.bzl +++ b/cc/toolchains/feature.bzl @@ -29,6 +29,7 @@ load( ) def _cc_feature_impl(ctx): + # type: (...) -> list[FeatureInfo | FeatureSetInfo | FeatureConstraintInfo | MutuallyExclusiveCategoryInfo] if bool(ctx.attr.feature_name) == (ctx.attr.overrides != None): fail("Exactly one of 'feature_name' and 'overrides' are required") diff --git a/cc/toolchains/feature_constraint.bzl b/cc/toolchains/feature_constraint.bzl index 8a3d60f8d..b08bae7ed 100644 --- a/cc/toolchains/feature_constraint.bzl +++ b/cc/toolchains/feature_constraint.bzl @@ -25,6 +25,7 @@ load( ) def _cc_feature_constraint_impl(ctx): + # type: (...) -> list[FeatureConstraintInfo] if ctx.attr.features: fail("Features is a reserved attribute in bazel. Use the attributes `all_of` and `none_of` for feature constraints") all_of = collect_provider(ctx.attr.all_of, FeatureConstraintInfo) diff --git a/cc/toolchains/feature_set.bzl b/cc/toolchains/feature_set.bzl index c4a075613..db8b1f765 100644 --- a/cc/toolchains/feature_set.bzl +++ b/cc/toolchains/feature_set.bzl @@ -21,6 +21,7 @@ load( ) def _cc_feature_set_impl(ctx): + # type: (...) -> list[FeatureSetInfo | FeatureConstraintInfo] if ctx.attr.features: fail("Features is a reserved attribute in bazel. cc_feature_set takes `all_of` instead.") features = collect_features(ctx.attr.all_of) diff --git a/cc/toolchains/impl/BUILD b/cc/toolchains/impl/BUILD index 98d2dd716..673005103 100644 --- a/cc/toolchains/impl/BUILD +++ b/cc/toolchains/impl/BUILD @@ -12,6 +12,16 @@ exports_files( visibility = ["//docs:__pkg__"], ) +exports_files( + [ + "collect.bzli", + "legacy_converter.bzli", + "nested_args.bzli", + "variables.bzli", + ], + visibility = ["//visibility:public"], +) + bzl_library( name = "toolchain_impl_rules", srcs = glob(["*.bzl"]), diff --git a/cc/toolchains/impl/cc_artifact_category.bzl b/cc/toolchains/impl/cc_artifact_category.bzl index b53a18b9f..680e3e876 100644 --- a/cc/toolchains/impl/cc_artifact_category.bzl +++ b/cc/toolchains/impl/cc_artifact_category.bzl @@ -21,6 +21,7 @@ visibility([ ]) def _cc_artifact_category_impl(ctx): + # type: (...) -> list[ArtifactCategoryInfo] return [ ArtifactCategoryInfo( label = ctx.label, diff --git a/cc/toolchains/impl/collect.bzl b/cc/toolchains/impl/collect.bzl index eb020b0b5..f1f06f04b 100644 --- a/cc/toolchains/impl/collect.bzl +++ b/cc/toolchains/impl/collect.bzl @@ -39,6 +39,7 @@ def collect_provider(targets, provider): return [target[provider] for target in targets] def collect_defaultinfo(targets): + # type: (Sequence[Target]) -> list[DefaultInfo] """Collects DefaultInfo from a label list. Args: @@ -48,21 +49,28 @@ def collect_defaultinfo(targets): """ return collect_provider(targets, DefaultInfo) -def _make_collector(provider, field): +def _make_collector(field): def collector(targets, direct = [], transitive = []): + # type: (Sequence[Target], Sequence[T], list[depset[T]]) -> depset[T] + # TODO(https://github.com/bazelbuild/bazel/issues/32021): make this an + # annotation once supported Bazel versions have element-typed depsets, + # source type parameters, and admit Target values in sequences. + # Target admission: https://github.com/bazelbuild/bazel/issues/32024 + # The first issue covers only the depset requirement. # Avoid mutating what was passed in. transitive = transitive[:] - for value in collect_provider(targets, provider): - transitive.append(getattr(value, field)) + for target in targets: + transitive.append(field(target)) return depset(direct = direct, transitive = transitive) return collector -collect_action_types = _make_collector(ActionTypeSetInfo, "actions") -collect_features = _make_collector(FeatureSetInfo, "features") -collect_files = _make_collector(DefaultInfo, "files") +collect_action_types = _make_collector(lambda target: target[ActionTypeSetInfo].actions) +collect_features = _make_collector(lambda target: target[FeatureSetInfo].features) +collect_files = _make_collector(lambda target: target.files) def collect_data(ctx, targets): + # type: (ctx[object, object], Sequence[Target]) -> runfiles """Collects from a 'data' attribute. This is distinguished from collect_files by the fact that data attributes @@ -83,6 +91,7 @@ def collect_data(ctx, targets): ]) def collect_tools(ctx, targets, fail = fail): + # type: (ctx[object, object], Sequence[Target], Callable[[str], None]) -> list[ToolInfo] """Collects tools from a label_list. Each entry in the label list may either be a cc_tool or a binary. @@ -117,6 +126,7 @@ def collect_tools(ctx, targets, fail = fail): return tools def collect_args_lists(targets, label): + # type: (Sequence[Target], Label) -> ArgsListInfo """Collects a label_list of ArgsListInfo into a single ArgsListInfo Args: @@ -127,16 +137,19 @@ def collect_args_lists(targets, label): together. """ args = [] - by_action = {} + by_action = {} # type: dict[ActionTypeInfo, _ActionAccumulator] transitive_files = [] for target in targets: args_list = target[ArgsListInfo] args.extend(args_list.args) transitive_files.extend([args_info.files for args_info in args_list.args]) for value in args_list.by_action: + action_args = [] # type: list[ArgsInfo] + + action_files = [] # type: list[depset[File]] out = by_action.setdefault( value.action, - struct(args = [], transitive_files = [], action = value.action), + struct(args = action_args, transitive_files = action_files, action = value.action), ) out.args.extend(value.args) out.transitive_files.append(value.files) diff --git a/cc/toolchains/impl/collect.bzli b/cc/toolchains/impl/collect.bzli new file mode 100644 index 000000000..ac74ac156 --- /dev/null +++ b/cc/toolchains/impl/collect.bzli @@ -0,0 +1,14 @@ +"""Typed collections of toolchain provider values.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move collect_provider into source when a function declaration can bind +# P to its provider and result. A comment cannot bind P or declare the collector protocol. + +load("//cc/toolchains:cc_toolchain_info.bzli", "ActionTypeInfo", "ArgsInfo", "ArgsListInfo", "ToolInfo") + +type _ActionAccumulator = struct[{"action": ActionTypeInfo, "args": list[ArgsInfo], "transitive_files": list[depset[File]]}] + +class _Collector[T](Protocol): + def __call__(self, targets: Sequence[Target], direct: Sequence[T] = ..., transitive: list[depset[T]] = ...) -> depset[T]: ... + +def collect_provider[P](targets: Sequence[Target], provider: Provider[P]) -> list[P]: ... +def _make_collector[T](field: Callable[[Target], depset[T]]) -> _Collector[T]: ... diff --git a/cc/toolchains/impl/external_feature.bzl b/cc/toolchains/impl/external_feature.bzl index 6aa3b026a..42fcf7b58 100644 --- a/cc/toolchains/impl/external_feature.bzl +++ b/cc/toolchains/impl/external_feature.bzl @@ -27,6 +27,7 @@ visibility([ ]) def _cc_external_feature_impl(ctx): + # type: (...) -> list[FeatureInfo | FeatureSetInfo | FeatureConstraintInfo] feature = FeatureInfo( label = ctx.label, name = ctx.attr.feature_name, @@ -36,6 +37,8 @@ def _cc_external_feature_impl(ctx): args = (), files = depset([]), by_action = (), + allowlist_include_directories = depset(), + allowlist_absolute_include_directories = depset(), ), implies = depset([]), requires_any_of = (), diff --git a/cc/toolchains/impl/label_utils.bzl b/cc/toolchains/impl/label_utils.bzl index 690bcec8b..dfa0ecfff 100644 --- a/cc/toolchains/impl/label_utils.bzl +++ b/cc/toolchains/impl/label_utils.bzl @@ -14,6 +14,7 @@ """Helpers for passing possibly duplicated labels through rule attributes.""" def deduplicate_label_list(name, labels): + # type: (str, Sequence[str | Label]) -> struct[{'labels': list[Label], 'indexes': list[int]}] """Deduplicates a label list while preserving the original indexes. Args: diff --git a/cc/toolchains/impl/legacy_converter.bzl b/cc/toolchains/impl/legacy_converter.bzl index d0378911a..02fa8bd3b 100644 --- a/cc/toolchains/impl/legacy_converter.bzl +++ b/cc/toolchains/impl/legacy_converter.bzl @@ -155,7 +155,7 @@ def convert_capability(capability): def _convert_tool_map(tool_map, args_by_action): action_configs = [] - caps = {} + caps = {} # type: dict[ToolCapabilityInfo, None] for action_type, tool in tool_map.configs.items(): action_args = args_by_action.get(action_type.name, default = None) flag_sets = action_args.flag_sets if action_args != None else [] @@ -194,9 +194,11 @@ def convert_toolchain(toolchain): # For that reason, there's additional logic in this function to ensure that the args are # attached to the action configs directly, as that is the only way to ensure the correct # ordering. - args_by_action = {} + args_by_action = {} # type: dict[str, LegacyArgs] for a in toolchain.args.by_action: - args = args_by_action.setdefault(a.action.name, struct(flag_sets = [], env_sets = [])) + flag_sets = [] # type: list[FlagSetInfo] + env_sets = [] # type: list[EnvSetInfo] + args = args_by_action.setdefault(a.action.name, struct(flag_sets = flag_sets, env_sets = env_sets)) new_args = _convert_args_sequence(a.args, strip_actions = True) args.flag_sets.extend(new_args.flag_sets) args.env_sets.extend(new_args.env_sets) diff --git a/cc/toolchains/impl/legacy_converter.bzli b/cc/toolchains/impl/legacy_converter.bzli new file mode 100644 index 000000000..732f727cf --- /dev/null +++ b/cc/toolchains/impl/legacy_converter.bzli @@ -0,0 +1,28 @@ +"""Conversion from rule-based toolchain providers to legacy configuration values.""" + +# TODO(https://github.com/bazelbuild/bazel/issues/32207): Move these signatures into source when the native provider declarations +# expose their field types. The converter imports both sets of types from interfaces. + +load("//cc:cc_toolchain_config_lib.bzli", "ActionConfigInfo", "ArtifactNamePatternInfo", "EnvSetInfo", "FeatureInfo", "FlagSetInfo", "MakeVariableInfo", "ToolInfo", "ToolPathInfo", "WithFeatureSetInfo") +load("//cc/toolchains:cc_toolchain_info.bzli", "ActionTypeInfo", "ArgsInfo", "FeatureConstraintInfo", "ToolConfigInfo", "ToolCapabilityInfo", "ToolchainConfigInfo", NewFeatureInfo = "FeatureInfo", NewToolInfo = "ToolInfo") + +type LegacyArgs = struct[{"flag_sets": list[FlagSetInfo], "env_sets": list[EnvSetInfo]}] +type LegacyToolchain = struct[{ + "features": list[FeatureInfo], + "action_configs": list[ActionConfigInfo], + "cxx_builtin_include_directories": list[str], + "artifact_name_patterns": list[ArtifactNamePatternInfo], + "make_variables": list[MakeVariableInfo], + "tool_paths": list[ToolPathInfo], +}] + +def _convert_actions(actions: depset[ActionTypeInfo]) -> list[str]: ... +def convert_feature_constraint(constraint: FeatureConstraintInfo) -> WithFeatureSetInfo: ... +def convert_args(args: ArgsInfo, strip_actions: bool = ...) -> LegacyArgs: ... +def _convert_args_sequence(args_sequence: Sequence[ArgsInfo], strip_actions: bool = ...) -> LegacyArgs: ... +def _convert_tool_env(tool: NewToolInfo, action_name: str) -> EnvSetInfo | None: ... +def convert_feature(feature: NewFeatureInfo, enabled: bool = ...) -> FeatureInfo | None: ... +def convert_tool(tool: NewToolInfo) -> ToolInfo: ... +def convert_capability(capability: NewFeatureInfo) -> FeatureInfo: ... +def _convert_tool_map(tool_map: ToolConfigInfo, args_by_action: dict[str, LegacyArgs]) -> tuple[list[ActionConfigInfo], list[FeatureInfo]]: ... +def convert_toolchain(toolchain: ToolchainConfigInfo) -> LegacyToolchain: ... diff --git a/cc/toolchains/impl/libc_version.bzl b/cc/toolchains/impl/libc_version.bzl index c3f8ef659..55c65da38 100644 --- a/cc/toolchains/impl/libc_version.bzl +++ b/cc/toolchains/impl/libc_version.bzl @@ -20,6 +20,7 @@ visibility([ ]) def libc_without_version(libc): + # type: (str) -> str """Strips a version suffix from a target_libc value. Args: diff --git a/cc/toolchains/impl/markdown_helpers.bzl b/cc/toolchains/impl/markdown_helpers.bzl index 1ae401f99..ee9d78bc6 100644 --- a/cc/toolchains/impl/markdown_helpers.bzl +++ b/cc/toolchains/impl/markdown_helpers.bzl @@ -14,6 +14,7 @@ """A few small helpers for working with Markdown.""" def markdown_link(link_text, href): + # type: (str, str) -> str """Creates a markdown link. Args: @@ -26,6 +27,7 @@ def markdown_link(link_text, href): return "[" + link_text + "](" + href + ")" def xref_substitutions(match_text_patterns): + # type: (Mapping[str, str]) -> dict[str, str] """Creates a dictionary of substitutions for use for linkification of text. Example: diff --git a/cc/toolchains/impl/nested_args.bzl b/cc/toolchains/impl/nested_args.bzl index 8b523ccdb..c559c58a1 100644 --- a/cc/toolchains/impl/nested_args.bzl +++ b/cc/toolchains/impl/nested_args.bzl @@ -82,6 +82,7 @@ directory as additional files. } def _var(target): + # type: (Target | None) -> str | None if target == None: return None return target[VariableInfo].name @@ -301,17 +302,19 @@ def nested_args_provider( ) def _escape(s): + # type: (str) -> str return s.replace("%", "%%") _SUPPORTED_BUILD_SETTING_TYPES = ["string", "bool", "int", "Label"] -def _format_build_setting(value, label, fail = fail): +def _format_build_setting(value, label, report_error = fail): if type(value) in _SUPPORTED_BUILD_SETTING_TYPES: return _escape(str(value)) - fail("%s had an unsupported build setting type %s. Only string, bool, int, or Label values may be formatted." % (label, type(value))) + report_error("%s had an unsupported build setting type %s. Only string, bool, int, or Label values may be formatted." % (label, type(value))) + return "" -def _format_target(target, fail = fail): +def _format_target(target, report_error = fail): # NOTE: This is for use with ctx.var where we don't have a variable, but we # also don't want to allow arbitrary strings. See args.bzl for usage. if hasattr(target, "__raw_string"): @@ -322,7 +325,7 @@ def _format_target(target, fail = fail): return _format_build_setting( target[BuildSettingInfo].value, target.label, - fail = fail, + report_error = report_error, ) path = None @@ -333,9 +336,10 @@ def _format_target(target, fail = fail): if len(files) == 1: path = files[0].path if path == None: - fail("%s should be either a variable, a directory, or a single file." % target.label) + report_error("%s should be either a variable, a directory, or a single file." % target.label) + return "" if "}" in path: - fail("The path %r of target %s used with 'format' cannot contain } because it would interfere with the formatting." % ( + report_error("The path %r of target %s used with 'format' cannot contain } because it would interfere with the formatting." % ( path, target.label, )) @@ -373,7 +377,7 @@ def _format_string(arg, format, used_vars, fail = fail): else: used_vars[variable] = None has_format = True - out.append(_format_target(format[variable], fail = fail)) + out.append(_format_target(format[variable], report_error = fail)) upto += len(variable) + 2 elif arg[upto] == "}": @@ -400,7 +404,7 @@ def format_list(args, format, must_use = [], fail = fail): A string defined to be compatible with flag groups. """ formatted = [] - used_vars = {} + used_vars = {} # type: dict[str, None] for arg in args: formatted.append(_format_string(arg, format, used_vars, fail)) @@ -427,7 +431,7 @@ def format_dict_values(env, format, must_use = [], fail = fail): The environment variables with values defined to be compatible with flag groups. """ formatted = {} - used_vars = {} + used_vars = {} # type: dict[str, None] for key, value in env.items(): formatted[key] = _format_string(value, format, used_vars, fail) diff --git a/cc/toolchains/impl/nested_args.bzli b/cc/toolchains/impl/nested_args.bzli new file mode 100644 index 000000000..44e9b0469 --- /dev/null +++ b/cc/toolchains/impl/nested_args.bzli @@ -0,0 +1,48 @@ +"""Formatting and validation of nested toolchain arguments.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move the nested-argument signatures into source when functions can bind +# the target type used by their attribute schema. A source comment cannot bind T. + +load("//cc/toolchains:cc_toolchain_info.bzli", "NestedArgsInfo") + +type _Fail = Callable[[str], None] +type _RawString = struct[{"__raw_string": str}] +type _Format = Mapping[str, Target | _RawString] +type _Attrs[T: Target] = struct[{ + "args": list[str], + "format": Mapping[T, str], + "nested": Sequence[Target], + "data": Sequence[Target], + "iterate_over": Target | None, + "requires_not_none": Target | None, + "requires_none": Target | None, + "requires_true": Target | None, + "requires_false": Target | None, + "requires_equal": Target | None, + "requires_equal_value": str, +}, ...] + +def nested_args_provider_from_ctx[T: Target](ctx: ctx[object, _Attrs[T]], maybe_used_vars: Sequence[str] = ...) -> NestedArgsInfo: ... +def nested_args_provider[T: Target]( + *, + label: Label, + args: Sequence[str] = ..., + nested: Sequence[NestedArgsInfo] = ..., + format: Mapping[T, str] = ..., + files: depset[File] = ..., + iterate_over: Target | None = ..., + requires_not_none: str | None = ..., + requires_none: str | None = ..., + requires_true: str | None = ..., + requires_false: str | None = ..., + requires_equal: str | None = ..., + requires_equal_value: str = ..., + maybe_used_vars: Sequence[str] = ..., + make_variables: Mapping[str, str] = ..., + fail: _Fail = ..., +) -> NestedArgsInfo: ... +def _format_build_setting(value: object, label: Label, report_error: _Fail = ...) -> str: ... +def _format_target(target: Target | _RawString, report_error: _Fail = ...) -> str: ... +def _format_string(arg: str, format: _Format, used_vars: dict[str, None], fail: _Fail = ...) -> str: ... +def format_list(args: Collection[str], format: _Format, must_use: Sequence[str] = ..., fail: _Fail = ...) -> tuple[list[str], list[str]]: ... +def format_dict_values(env: Mapping[str, str], format: _Format, must_use: Sequence[str] = ..., fail: _Fail = ...) -> tuple[dict[str, str], list[str]]: ... diff --git a/cc/toolchains/impl/variables.bzl b/cc/toolchains/impl/variables.bzl index aab365df0..11f28a5c8 100644 --- a/cc/toolchains/impl/variables.bzl +++ b/cc/toolchains/impl/variables.bzl @@ -49,11 +49,16 @@ types = struct( ), ) +def _decode_variable_type(encoded): + # cc_variable is the sole writer of the private rule's JSON attribute. + return json.decode(encoded) # ty: ignore[non-static-type, unsound-return-statement] + def _cc_variable_impl(ctx): + # type: (...) -> list[VariableInfo] return [VariableInfo( name = ctx.label.name, label = ctx.label, - type = json.decode(ctx.attr.type), + type = _decode_variable_type(ctx.attr.type), actions = collect_action_types(ctx.attr.actions) if ctx.attr.actions else None, )] @@ -66,7 +71,7 @@ _cc_variable = rule( provides = [VariableInfo], ) -def cc_variable(name, type, **kwargs): +def cc_variable(name, type, *, actions = [], **kwargs): """Exposes a toolchain variable to use in toolchain argument expansions. This internal rule exposes [toolchain variables](https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables) @@ -92,11 +97,13 @@ def cc_variable(name, type, **kwargs): name: (str) The name of the outer variable, and the rule. type: The type of the variable, constructed using `types` factory in [@rules_cc//cc/toolchains/impl:variables.bzl](https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/impl/variables.bzl). + actions: The actions for which the variable is available. **kwargs: [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to this rule. """ - _cc_variable(name = name, type = json.encode(type), **kwargs) + _cc_variable(name = name, type = json.encode(type), actions = actions, **kwargs) def _cc_builtin_variables_impl(ctx): + # type: (...) -> list[BuiltinVariablesInfo] return [BuiltinVariablesInfo(variables = { variable.name: variable for variable in collect_provider(ctx.attr.srcs, VariableInfo) @@ -137,12 +144,13 @@ def get_type(*, name, variables, overrides, actions, args_label, nested_label, f # buildifier: disable=unreachable return types.void - if variables[outer].actions != None: - valid_actions = variables[outer].actions.to_list() + variable = variables[outer] + if variable.actions != None: + valid_actions = variable.actions.to_list() for action in actions: if action not in valid_actions: fail("The variable {var} is inaccessible from the action {action}. This is required because it is referenced in {nested_label}, which is included by {args_label}, which references that action".format( - var = variables[outer].label, + var = variable.label, nested_label = nested_label, args_label = args_label, action = action.label, @@ -151,7 +159,7 @@ def get_type(*, name, variables, overrides, actions, args_label, nested_label, f # buildifier: disable=unreachable return types.void - type = overrides.get(outer, variables[outer].type) + type = overrides.get(outer, variable.type) parent = outer for part in name.split(".")[1:]: @@ -176,7 +184,7 @@ def get_type(*, name, variables, overrides, actions, args_label, nested_label, f # buildifier: disable=unreachable return types.void - type = overrides.get(full, type["kv"][part]) + type = overrides[full] if full in overrides else type["kv"][part] parent = full return type diff --git a/cc/toolchains/impl/variables.bzli b/cc/toolchains/impl/variables.bzli new file mode 100644 index 000000000..b0f269512 --- /dev/null +++ b/cc/toolchains/impl/variables.bzli @@ -0,0 +1,52 @@ +"""Toolchain variable descriptors.""" + +# TODO(https://github.com/bazelbuild/bazel/issues/32044): Move the descriptors into source when variable types can refer to their +# recursive element type there. The factory also accepts named fields through its callable protocol. + +load( + "//cc/toolchains:cc_toolchain_info.bzli", + "ActionTypeInfo", + "ElementVariableType", + "LeafVariableType", + "StructVariableType", + "VariableInfo", + "VariableType", +) + +class _StructFactory(Protocol): + def __call__(self, **fields: VariableType) -> StructVariableType: ... + +types: struct[{ + "unknown": LeafVariableType, + "void": LeafVariableType, + "string": LeafVariableType, + "bool": LeafVariableType, + "file": LeafVariableType, + "directory": LeafVariableType, + "option": Callable[[VariableType], ElementVariableType], + "list": Callable[[VariableType], ElementVariableType], + "struct": _StructFactory, +}] + +type _Labels = Collection[Label | str] | depset[Label | str] | None + +def _decode_variable_type(encoded: str) -> VariableType: ... + +def cc_variable( + name: str, + type: VariableType, + *, + actions: _Labels | select[_Labels] = ..., + **kwargs: Unpack[CommonRuleAttrs], +) -> None: ... + +def get_type( + *, + name: str, + variables: Mapping[str, VariableInfo], + overrides: Mapping[str, VariableType], + actions: Collection[ActionTypeInfo], + args_label: Label, + nested_label: Label, + fail: Callable[[str], object], +) -> VariableType: ... diff --git a/cc/toolchains/legacy_file_group.bzl b/cc/toolchains/legacy_file_group.bzl index dc51569ff..a1f184417 100644 --- a/cc/toolchains/legacy_file_group.bzl +++ b/cc/toolchains/legacy_file_group.bzl @@ -16,6 +16,18 @@ # Taken from https://bazel.build/docs/cc-toolchain-config-reference#actions # TODO: This is best-effort. Update this with the correct file groups once we # work out what actions correspond to what file groups. +# TODO(https://github.com/bazelbuild/bazel/issues/31770): Use native key types +# once all supported Bazel versions can express them. +# type: dict[{ +# "ar_files": list[Label], +# "as_files": list[Label], +# "compiler_files": list[Label], +# "coverage_files": list[Label], +# "dwp_files": list[Label], +# "linker_files": list[Label], +# "objcopy_files": list[Label], +# "strip_files": list[Label], +# }] LEGACY_FILE_GROUPS = { "ar_files": [ Label("//cc/toolchains/actions:ar_actions"), @@ -32,8 +44,11 @@ LEGACY_FILE_GROUPS = { Label("//cc/toolchains/actions:cpp20_module_codegen"), Label("//cc/toolchains/actions:cpp_module_deps_scanning"), ], - # There are no actions listed for coverage and objcopy in action_names.bzl. - "coverage_files": [], + "coverage_files": [ + Label("//cc/toolchains/actions:gcov"), + Label("//cc/toolchains/actions:llvm_cov"), + Label("//cc/toolchains/actions:llvm_profdata"), + ], "dwp_files": [ Label("//cc/toolchains/actions:dwp"), ], diff --git a/cc/toolchains/legacy_tool.bzl b/cc/toolchains/legacy_tool.bzl index 4ab9f8611..629730a51 100644 --- a/cc/toolchains/legacy_tool.bzl +++ b/cc/toolchains/legacy_tool.bzl @@ -16,6 +16,7 @@ load(":cc_toolchain_info.bzl", "LegacyToolInfo") def _cc_legacy_tool_impl(ctx): + # type: (...) -> list[LegacyToolInfo] return [LegacyToolInfo( label = ctx.label, name = ctx.attr.tool_name, diff --git a/cc/toolchains/make_variable.bzl b/cc/toolchains/make_variable.bzl index 754dd768b..a1e54917c 100644 --- a/cc/toolchains/make_variable.bzl +++ b/cc/toolchains/make_variable.bzl @@ -19,6 +19,7 @@ load(":cc_toolchain_info.bzl", "MakeVariableInfo") visibility("public") def _cc_make_variable_impl(ctx): + # type: (...) -> list[MakeVariableInfo] return [ MakeVariableInfo( label = ctx.label, diff --git a/cc/toolchains/mutually_exclusive_category.bzl b/cc/toolchains/mutually_exclusive_category.bzl index f83b55499..d6fe831b4 100644 --- a/cc/toolchains/mutually_exclusive_category.bzl +++ b/cc/toolchains/mutually_exclusive_category.bzl @@ -16,6 +16,7 @@ load(":cc_toolchain_info.bzl", "MutuallyExclusiveCategoryInfo") def _cc_mutually_exclusive_category_impl(ctx): + # type: (...) -> list[MutuallyExclusiveCategoryInfo] return [MutuallyExclusiveCategoryInfo( label = ctx.label, name = str(ctx.label), diff --git a/cc/toolchains/nested_args.bzl b/cc/toolchains/nested_args.bzl index d81dd998b..6a86dccdc 100644 --- a/cc/toolchains/nested_args.bzl +++ b/cc/toolchains/nested_args.bzl @@ -41,21 +41,43 @@ Examples: """, ) +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Annotate **kwargs in the +# declaration when Bazel can type the common rule attributes forwarded here. +# type _Label = str | Label +# type _LabelInput = _Label | select[_Label | None] | None +# type _StringListInput = Collection[str] | depset[str] | select[Collection[str] | depset[str] | None] | None +# type _LabelListInput = Collection[_Label] | depset[_Label] | select[Collection[_Label] | depset[_Label] | None] | None +# type _StringInput = str | select[str | None] | None + def cc_nested_args( *, + # type: str name, + # type: _StringListInput args = None, + # type: _LabelListInput data = None, + # type: Mapping[str, _Label] format = {}, + # type: _LabelInput iterate_over = None, + # type: _LabelListInput nested = None, + # type: _LabelInput requires_not_none = None, + # type: _LabelInput requires_none = None, + # type: _LabelInput requires_true = None, + # type: _LabelInput requires_false = None, + # type: _LabelInput requires_equal = None, + # type: _StringInput requires_equal_value = None, + # type: Unpack[CommonRuleAttrs] **kwargs): + # type: (...) -> None """Nested arguments for use in more complex `cc_args` expansions. While this rule is very similar in shape to `cc_args`, it is intended to be used as a diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl index 246d54c55..419dd437a 100644 --- a/cc/toolchains/tool.bzl +++ b/cc/toolchains/tool.bzl @@ -24,7 +24,27 @@ load( "ToolInfo", ) +# TODO(https://github.com/bazelbuild/bazel/issues/32343): Annotate **kwargs in the +# declaration when Bazel can type the forwarded executable-rule attributes. +# type _LabelInput = str | Label | select[str | Label | None] +# type _Labels = Collection[str | Label] | depset[str | Label] +# type _LabelsInput = _Labels | select[_Labels | None] | None +# type _EnvironmentInput = Mapping[str, str] | select[Mapping[str, str] | None] | None +# type _Attrs = struct[{ +# "src": Target, +# "data": Sequence[Target], +# "format_keys": list[str], +# "format_values": Sequence[Target], +# "format_value_indexes": list[int], +# "env": dict[str, str], +# "execution_requirements": Sequence[Target], +# "tags": list[str], +# "allowlist_include_directories": Sequence[Target], +# "capabilities": Sequence[Target], +# }, ...] + def _cc_tool_impl(ctx): + # type: (ctx[object, _Attrs]) -> list[ToolInfo | DefaultInfo] exe_info = ctx.attr.src[DefaultInfo] if exe_info.files_to_run != None and exe_info.files_to_run.executable != None: exe = exe_info.files_to_run.executable @@ -33,7 +53,7 @@ def _cc_tool_impl(ctx): else: fail("Expected cc_tool's src attribute to be either an executable or a single file") - format_targets = {} + format_targets = {} # type: dict[str, Target] for i in range(len(ctx.attr.format_keys)): format_targets[ctx.attr.format_keys[i]] = ctx.attr.format_values[ctx.attr.format_value_indexes[i]] env, _ = format_dict_values( @@ -45,7 +65,9 @@ def _cc_tool_impl(ctx): for info in collect_provider(ctx.attr.execution_requirements, ExecutionRequirementsInfo): execution_requirements.extend(info.requirements) - runfiles = collect_data(ctx, ctx.attr.data + [ctx.attr.src]) + inputs = list(ctx.attr.data) + inputs.append(ctx.attr.src) + runfiles = collect_data(ctx, inputs) tool = ToolInfo( label = ctx.label, exe = exe, @@ -81,6 +103,7 @@ _cc_tool = rule( attrs = { "src": attr.label( allow_files = True, + mandatory = True, doc = """The underlying binary that this tool represents. Usually just a single prebuilt (eg. @toolchain//:bin/clang), but may be any @@ -187,15 +210,25 @@ cc_tool( def cc_tool( *, + # type: str name, - src = None, + # type: _LabelInput + src, + # type: _LabelsInput data = None, + # type: _LabelsInput allowlist_include_directories = None, + # type: _EnvironmentInput env = None, + # type: _LabelsInput execution_requirements = None, + # type: Mapping[str, str | Label] format = {}, + # type: _LabelsInput capabilities = None, + # type: Unpack[CommonExecutableRuleAttrs] **kwargs): + # type: (...) -> None """Declares a tool for use by toolchain actions. `cc_tool` rules are used in a `cc_tool_map` rule to ensure all files and diff --git a/cc/toolchains/tool_capability.bzl b/cc/toolchains/tool_capability.bzl index 3b811a87f..2dec70b27 100644 --- a/cc/toolchains/tool_capability.bzl +++ b/cc/toolchains/tool_capability.bzl @@ -22,6 +22,7 @@ load( ) def _cc_tool_capability_impl(ctx): + # type: (...) -> list[ToolCapabilityInfo | FeatureConstraintInfo] ft = FeatureInfo( name = ctx.attr.feature_name or ctx.label.name, label = ctx.label, @@ -32,6 +33,7 @@ def _cc_tool_capability_impl(ctx): files = depset(), by_action = (), allowlist_include_directories = depset(), + allowlist_absolute_include_directories = depset(), ), implies = depset(), requires_any_of = (), @@ -43,6 +45,7 @@ def _cc_tool_capability_impl(ctx): overridable = True, overrides = None, allowlist_include_directories = depset(), + allowlist_absolute_include_directories = depset(), ) return [ ToolCapabilityInfo(label = ctx.label, feature = ft), diff --git a/cc/toolchains/tool_map.bzl b/cc/toolchains/tool_map.bzl index 6b133e14d..c8c5aece9 100644 --- a/cc/toolchains/tool_map.bzl +++ b/cc/toolchains/tool_map.bzl @@ -30,8 +30,8 @@ def _cc_tool_map_impl(ctx): tools = collect_tools(ctx, ctx.attr.tools) action_sets = collect_provider(ctx.attr.actions, ActionTypeSetInfo) - action_to_tool = {} - action_to_as = {} + action_to_tool = {} # type: dict[ActionTypeInfo, ToolInfo] + action_to_as = {} # type: dict[ActionTypeInfo, ActionTypeSetInfo] for i in range(len(action_sets)): action_set = action_sets[i] tool = tools[ctx.attr.tool_index_for_action[i]] diff --git a/cc/toolchains/tool_map.bzli b/cc/toolchains/tool_map.bzli new file mode 100644 index 000000000..fd7997cfc --- /dev/null +++ b/cc/toolchains/tool_map.bzli @@ -0,0 +1,15 @@ +"""The association between toolchain actions and executable tools.""" + +# TODO(https://github.com/bazelbuild/starlark/pull/362): Move cc_tool_map into source when its signature can bind K to the +# key type of the caller’s tools dictionary. A signature comment cannot bind K. + +load("//cc/toolchains:cc_toolchain_info.bzli", "ActionTypeInfo", "ActionTypeSetInfo", "ToolConfigInfo", "ToolInfo") + +type _Attrs = struct[{ + "actions": Sequence[Target], + "tools": Sequence[Target], + "tool_index_for_action": list[int], +}, ...] + +def _cc_tool_map_impl(ctx: ctx[object, _Attrs]) -> list[ToolConfigInfo]: ... +def cc_tool_map[K: str | Label](name: str, tools: Mapping[K, str | Label], **kwargs: Unpack[CommonRuleAttrs]) -> None: ... diff --git a/cc/use_cc_toolchain.bzl b/cc/use_cc_toolchain.bzl index 8e271105a..a7eee9731 100644 --- a/cc/use_cc_toolchain.bzl +++ b/cc/use_cc_toolchain.bzl @@ -60,6 +60,7 @@ CC_TOOLCHAIN_ATTRS = { } def use_cc_toolchain(mandatory = True): + # type: (bool) -> list[toolchain_type] """ Helper to depend on the cc toolchain. diff --git a/docs/features.md b/docs/features.md index b12569d22..902be1853 100644 --- a/docs/features.md +++ b/docs/features.md @@ -255,7 +255,11 @@ This feature is off by default. Marker features to configure the quoting style of arguments in `@params` files. If neither are enabled, no quoting is applied. -These features must be enabled if desired. +`gcc_quoting_for_param_files` should be enabled by default for Unix like +toolchains. + +`windows_quoting_for_param_files` should be enabled by default for some +Windows toolchains, such as MSVC. ### `generate_submodules` @@ -538,6 +542,23 @@ on Windows to avoid long path issues. This feature must be enabled by default if desired. +### `skip_virtual_includes` + +A marker feature that avoids creating `_virtual_includes` symlinks when +headers only need a `strip_include_prefix` (without an +`include_prefix`). Instead, `rules_cc` adds the stripped directory to +the include paths directly and uses the original headers. This reduces +path lengths and can help avoid long path issues on Windows. + +The only downside of this feature is that if you disable sandboxing +non-declared headers can be included that are in the same directory as +the one being added to the include path. This would still be caught by +[`layering_check`](#layering_check) if enabled. + +See also [`shorten_virtual_includes`](#shorten_virtual_includes). + +This feature must be enabled if desired. + ### `static_link_cpp_runtimes` A marker feature used by `rules_cc` to determine if the toolchain diff --git a/docs/toolchain_api.md b/docs/toolchain_api.md index bd03ee925..914d15ff4 100644 --- a/docs/toolchain_api.md +++ b/docs/toolchain_api.md @@ -611,7 +611,7 @@ For more extensive examples, see the usages here: | Name | Description | Default Value | | :------------- | :------------- | :------------- | | name | (str) The name of the target. | none | -| actions | (List[Label]) A list of labels of [`cc_action_type`](#cc_action_type) or [`cc_action_type_set`](#cc_action_type_set) rules that dictate which actions these arguments should be applied to. | `None` | +| actions | (List[Label]) A list of labels of [`cc_action_type`](#cc_action_type) or [`cc_action_type_set`](#cc_action_type_set) rules that dictate which actions these arguments should be applied to. | none | | allowlist_include_directories | (List[Label]) A list of include paths that are implied by using this rule. These must point to a skylib [directory](https://github.com/bazelbuild/bazel-skylib/tree/main/docs/directory_doc.md#directory) or [subdirectory](https://github.com/bazelbuild/bazel-skylib/tree/main/docs/directory_subdirectory_doc.md#subdirectory) rule. Some flags (e.g. --sysroot) imply certain include paths are available despite not explicitly specifying a normal include path flag (`-I`, `-isystem`, etc.). Bazel checks that all included headers are properly provided by a dependency or allowlisted through this mechanism.

As a rule of thumb, only use this if Bazel is complaining about absolute paths in your toolchain and you've ensured that the toolchain is compiling with the `-no-canonical-prefixes` and/or `-fno-canonical-system-headers` arguments.

These files are not automatically passed to each action. If they need to be, add them to 'data' as well.

This can help work around errors like: `the source file 'main.c' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain)`. | `None` | | allowlist_absolute_include_directories | (List[str]) Allowlists absolute include directories, preventing Bazel from emitting errors when an #include of local system files in the directory occurs. Be careful when adding directories to this list, as it inherently causes leaks in hermeticity. Prefer to reserve use of this for cases like Xcode, where the conventional expectation is to allowlist well-known system-absolute include paths rather than redistributing the SDK. | `None` | | args | (List[str]) The command-line arguments that are applied by using this rule. This is mutually exclusive with [nested](#cc_args-nested). | `None` | @@ -722,7 +722,7 @@ cc_tool( | Name | Description | Default Value | | :------------- | :------------- | :------------- | | name | (str) The name of the target. | none | -| src | (Label) The underlying binary that this tool represents. Usually just a single prebuilt (e.g. @toolchain//:bin/clang), but may be any executable label. | `None` | +| src | (Label) The underlying binary that this tool represents. Usually just a single prebuilt (e.g. @toolchain//:bin/clang), but may be any executable label. | none | | data | (List[Label]) Additional files that are required for this tool to run. Frequently, clang and gcc require additional files to execute as they often shell out to other binaries (e.g. `cc1`). | `None` | | allowlist_include_directories | (List[Label]) Include paths implied by using this tool. Compilers may include a set of built-in headers that are implicitly available unless flags like `-nostdinc` are provided. Bazel checks that all included headers are properly provided by a dependency or allowlisted through this mechanism.

As a rule of thumb, only use this if Bazel is complaining about absolute paths in your toolchain and you've ensured that the toolchain is compiling with the `-no-canonical-prefixes` and/or `-fno-canonical-system-headers` arguments.

These files are not automatically passed to each action. If they need to be, add them to 'data' as well.

This can help work around errors like: `the source file 'main.c' includes the following non-builtin files with absolute paths (if these are builtin files, make sure these paths are in your toolchain)`. | `None` | | env | (Dict[str, str]) Environment variables to apply when running this tool. Format expansion is performed on values using `format`. | `None` | @@ -872,7 +872,7 @@ Generated rules:
 load("@rules_cc//cc/toolchains/impl:documented_api.bzl", "cc_variable")
 
-cc_variable(name, type, **kwargs)
+cc_variable(name, type, *, actions, **kwargs)
 
Exposes a toolchain variable to use in toolchain argument expansions. @@ -904,6 +904,7 @@ cc_variable( | :------------- | :------------- | :------------- | | name | (str) The name of the outer variable, and the rule. | none | | type | The type of the variable, constructed using `types` factory in [@rules_cc//cc/toolchains/impl:variables.bzl](https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/impl/variables.bzl). | none | +| actions | The actions for which the variable is available. | `[]` | | kwargs | [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to this rule. | none | diff --git a/local_bazel.bzl b/local_bazel.bzl index ab937b932..78140ca7c 100644 --- a/local_bazel.bzl +++ b/local_bazel.bzl @@ -15,6 +15,9 @@ """Repository rule for importing the copy of Bazel on the user's PATH.""" def _local_bazel_import_impl(repository_ctx): + # type: (...) -> repo_metadata | None + # TODO(https://github.com/bazelbuild/bazel/issues/32020): make this an + # annotation when supported Bazel versions expose metadata types. if "windows" in repository_ctx.os.name.lower(): bazel_real = "bazel-real.exe" bazel_name = "bazel.exe" diff --git a/stubs.toml b/stubs.toml new file mode 100644 index 000000000..f1b4eff0f --- /dev/null +++ b/stubs.toml @@ -0,0 +1,17 @@ +format-version = 1 + +[files] +"cc/cc_postmark_initializers.bzl" = "cc/cc_postmark_initializers.bzli" +"cc/cc_toolchain_config_lib.bzl" = "cc/cc_toolchain_config_lib.bzli" +"cc/private/objc_info.bzl" = "cc/private/objc_info.bzli" +"cc/private/rules_impl/objc_compilation_artifacts_info.bzl" = "cc/private/rules_impl/objc_compilation_artifacts_info.bzli" +"cc/private/rules_impl/objc_compilation_context_info.bzl" = "cc/private/rules_impl/objc_compilation_context_info.bzli" +"cc/private/toolchain/lib_cc_configure.bzl" = "cc/private/toolchain/lib_cc_configure.bzli" +"cc/private/toolchain/unix_cc_configure.bzl" = "cc/private/toolchain/unix_cc_configure.bzli" +"cc/private/toolchain_config/legacy_features.bzl" = "cc/private/toolchain_config/legacy_features.bzli" +"cc/toolchains/cc_toolchain_info.bzl" = "cc/toolchains/cc_toolchain_info.bzli" +"cc/toolchains/impl/collect.bzl" = "cc/toolchains/impl/collect.bzli" +"cc/toolchains/impl/legacy_converter.bzl" = "cc/toolchains/impl/legacy_converter.bzli" +"cc/toolchains/impl/nested_args.bzl" = "cc/toolchains/impl/nested_args.bzli" +"cc/toolchains/impl/variables.bzl" = "cc/toolchains/impl/variables.bzli" +"cc/toolchains/tool_map.bzl" = "cc/toolchains/tool_map.bzli" diff --git a/stubs/bazel_skylib/bzl_library.bzli b/stubs/bazel_skylib/bzl_library.bzli new file mode 100644 index 000000000..5e3bda8fd --- /dev/null +++ b/stubs/bazel_skylib/bzl_library.bzli @@ -0,0 +1,5 @@ +# TODO(https://github.com/bazelbuild/bazel-skylib/pull/641): Remove this stub +# when the selected Skylib release types the wrapper's forwarded rule attributes. +load("@bazel_skylib//rules/private:bzl_library.bzl", _rule = "bzl_library") + +def bzl_library(name: str, **kwargs: Unpack[KeywordArgs[_rule, "name"]]) -> None: ... diff --git a/stubs/bazel_skylib/common_settings.bzli b/stubs/bazel_skylib/common_settings.bzli new file mode 100644 index 000000000..c98968090 --- /dev/null +++ b/stubs/bazel_skylib/common_settings.bzli @@ -0,0 +1,3 @@ +# TODO(https://github.com/bazelbuild/bazel-skylib/pull/641): Remove this stub +# when the selected Skylib release types BuildSettingInfo.value. +def BuildSettingInfo[T = object](*, value: T) -> record[{"value": T}]: ... diff --git a/stubs/bazel_skylib/directory_providers.bzli b/stubs/bazel_skylib/directory_providers.bzli new file mode 100644 index 000000000..94538f239 --- /dev/null +++ b/stubs/bazel_skylib/directory_providers.bzli @@ -0,0 +1,26 @@ +# TODO(https://github.com/bazelbuild/bazel-skylib/pull/641): Remove this stub +# when the selected Skylib release types DirectoryInfo. +class _Glob(Protocol): + def __call__(self, include: Sequence[str], exclude: Sequence[str] = ..., allow_empty: bool = ...) -> depset[File]: ... + +type _Fields = dict[{ + "entries": dict[str, File | DirectoryInfo], + "transitive_files": depset[File], + "path": str, + "human_readable": str, + "get_path": Callable[[str], File | DirectoryInfo], + "get_file": Callable[[str], File], + "get_subdirectory": Callable[[str], DirectoryInfo], + "glob": _Glob, +}] + +def DirectoryInfo(**kwargs: Unpack[_Fields]) -> record[{ + "entries": dict[str, File | DirectoryInfo], + "transitive_files": depset[File], + "path": str, + "human_readable": str, + "get_path": Callable[[str], File | DirectoryInfo], + "get_file": Callable[[str], File], + "get_subdirectory": Callable[[str], DirectoryInfo], + "glob": _Glob, +}]: ... diff --git a/stubs/bazel_skylib/paths.bzli b/stubs/bazel_skylib/paths.bzli new file mode 100644 index 000000000..ec6d79705 --- /dev/null +++ b/stubs/bazel_skylib/paths.bzli @@ -0,0 +1,12 @@ +# TODO(https://github.com/bazelbuild/bazel-skylib/pull/641): Remove this stub +# when the selected Skylib release types the functions it exports through paths. +def _basename(p: str) -> str: ... +def _dirname(p: str) -> str: ... +def _is_absolute(path: str) -> bool: ... +def _join(path: str, *others: str) -> str: ... +def _normalize(path: str) -> str: ... +def _is_normalized(str: str, look_for_same_level_references: bool = ...) -> bool: ... +def _relativize(path: str, start: str) -> str: ... +def _replace_extension(p: str, new_extension: str) -> str: ... +def _split_extension(p: str) -> tuple[str, str]: ... +def _starts_with(path_a: str, path_b: str) -> bool: ... diff --git a/stubs/bazel_skylib/stubs.toml b/stubs/bazel_skylib/stubs.toml new file mode 100644 index 000000000..36b7a33c9 --- /dev/null +++ b/stubs/bazel_skylib/stubs.toml @@ -0,0 +1,12 @@ +format-version = 1 + +[source] +repository = "@bazel_skylib" +module = "bazel_skylib" +versions = ["1.9.0"] + +[files] +"bzl_library.bzl" = "bzl_library.bzli" +"lib/paths.bzl" = "paths.bzli" +"rules/common_settings.bzl" = "common_settings.bzli" +"rules/directory/providers.bzl" = "directory_providers.bzli" diff --git a/sty.toml b/sty.toml new file mode 100644 index 000000000..0edcbec44 --- /dev/null +++ b/sty.toml @@ -0,0 +1,36 @@ +# TODO(https://github.com/bazelbuild/bazel/pull/30373): Move ordinary signatures +# into function declarations once the minimum Bazel version supports annotation +# syntax. Bazel 7 must still be able to load these files. +[analysis] +strict-generic-narrowing = true +require-valid-suppressions = true + +[rules] +strict = "error" + +# The released provider modules also contain unannotated private helpers. +# Check the provider contracts and report incomplete types in those helpers. +[[overrides]] +include = [ + "@bazel_skylib//rules:common_settings.bzl", + "@bazel_skylib//rules/directory:providers.bzl", +] + +[overrides.rules] +missing-parameter-annotation = "warn" +missing-return-annotation = "warn" +non-static-type = "warn" + +# _relativize assigns [] to start_segments; Skylib 1.9.0 leaves its element +# type unknown. Keep checking the function's inputs and return type. +[[overrides]] +include = ["@bazel_skylib//lib:paths.bzl"] + +[overrides.rules] +non-static-type = "warn" + +[[stub-packages]] +manifest = "stubs.toml" + +[[stub-packages]] +manifest = "stubs/bazel_skylib/stubs.toml" diff --git a/tests/alwayslink_srcs/BUILD b/tests/alwayslink_srcs/BUILD index 833912012..44015de27 100644 --- a/tests/alwayslink_srcs/BUILD +++ b/tests/alwayslink_srcs/BUILD @@ -39,7 +39,7 @@ genrule( name = "copy_registerer_archive_unix", srcs = [":registerer_lib"], outs = ["libregisterer_copied.a"], - cmd = "for f in $(locations :registerer_lib); do case \"$$f\" in *.a) cp \"$$f\" $@;; esac; done", + cmd = "for f in $(execpaths :registerer_lib); do case \"$$f\" in *.a) cp \"$$f\" $@;; esac; done", target_compatible_with = select({ "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [], @@ -50,7 +50,7 @@ genrule( name = "copy_registerer_archive_windows", srcs = [":registerer_lib"], outs = ["registerer_copied.lib"], - cmd = "for f in $(locations :registerer_lib); do case \"$$f\" in *.lib) cp \"$$f\" $@;; esac; done", + cmd = "for f in $(execpaths :registerer_lib); do case \"$$f\" in *.lib) cp \"$$f\" $@;; esac; done", target_compatible_with = ["@platforms//os:windows"], ) diff --git a/tests/builtins_bzl/cc/cc_shared_library/test/BUILD b/tests/builtins_bzl/cc/cc_shared_library/test/BUILD index d6949cdfa..e6129a9c4 100644 --- a/tests/builtins_bzl/cc/cc_shared_library/test/BUILD +++ b/tests/builtins_bzl/cc/cc_shared_library/test/BUILD @@ -135,8 +135,8 @@ cc_shared_library( user_link_flags = select({ "@platforms//os:linux": [ "-Wl,-rpath,kittens", - "-Wl,--version-script=$(location :foo.lds)", - "-Wl,--script=$(location :additional_script.txt)", + "-Wl,--version-script=$(execpath :foo.lds)", + "-Wl,--script=$(execpath :additional_script.txt)", "-Wl,--no-gc-sections", ], "//conditions:default": [], @@ -272,7 +272,7 @@ cc_shared_library( features = ["windows_export_all_symbols"], user_link_flags = select({ "@platforms//os:linux": [ - "-Wl,--version-script=$(location :bar.lds)", + "-Wl,--version-script=$(execpath :bar.lds)", ], "//conditions:default": [], }), @@ -399,7 +399,7 @@ genrule( name = "check_file_named_correctly", srcs = ["just_main_output_renamed"], outs = ["renamed_so_file_copy.so"], - cmd = "cp $$(dirname $(location :just_main_output_renamed))/renamed_so_file.so $@", + cmd = "cp $$(dirname $(execpath :just_main_output_renamed))/renamed_so_file.so $@", ) filegroup( @@ -552,6 +552,19 @@ exports_test( ], ) +exports_test( + name = "external_select_exports_filter_exports_test", + bazel_only = True, + # target = "external_export_so", # copybara-comment-this-out-please + target = "@cross_repo_test//:bar_so", # copybara-uncomment-this-please + targets_that_should_be_claimed_to_be_exported = [ + # buildifier: disable=canonical-repository + "@@test_repo+//:bar", # copybara-uncomment-this-please + # buildifier: disable=canonical-repository + "@@test_repo+//:bar_wrapper", # copybara-uncomment-this-please + ], +) + pdb_test( name = "pdb_test", target = ":foo_so", diff --git a/tests/builtins_bzl/cc/cc_shared_library/test2/BUILD.bazel b/tests/builtins_bzl/cc/cc_shared_library/test2/BUILD.bazel index 88a92df68..133dff65f 100644 --- a/tests/builtins_bzl/cc/cc_shared_library/test2/BUILD.bazel +++ b/tests/builtins_bzl/cc/cc_shared_library/test2/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_shared_library.bzl", "cc_shared_library") licenses(["notice"]) @@ -8,3 +9,17 @@ cc_library( hdrs = ["bar.h"], visibility = ["//visibility:public"], ) + +cc_library( + name = "bar_wrapper", + visibility = ["//visibility:public"], + deps = [":bar"], +) + +cc_shared_library( + name = "bar_so", + exports_filter = select({"//conditions:default": ["@test_repo//:__pkg__"]}), + tags = ["manual"], + visibility = ["//visibility:public"], + deps = [":bar_wrapper"], +) diff --git a/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/BUILD.bazel b/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/BUILD.bazel new file mode 100644 index 000000000..b5605a2b0 --- /dev/null +++ b/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_cc//cc/toolchains:cc_toolchain.bzl", "cc_toolchain") +load(":cc_toolchain_config.bzl", "cc_toolchain_config") + +package(default_visibility = ["//visibility:public"]) + +filegroup(name = "empty") + +cc_toolchain_config( + name = "toolchain_config", + cxx_builtin_include_directories = [ + "%package(@test_repo//toolchain_include_dirs)%/include", + "%package(//toolchain_include_dirs)%/other", + "%package(@rules_cc//cc)%", + ], + tags = ["manual"], +) + +cc_toolchain( + name = "toolchain", + all_files = ":empty", + ar_files = ":empty", + as_files = ":empty", + compiler_files = ":empty", + dwp_files = ":empty", + linker_files = ":empty", + objcopy_files = ":empty", + strip_files = ":empty", + tags = ["manual"], + toolchain_config = ":toolchain_config", + toolchain_identifier = "test-toolchain", +) diff --git a/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/cc_toolchain_config.bzl b/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/cc_toolchain_config.bzl new file mode 100644 index 000000000..2b2c922f0 --- /dev/null +++ b/tests/builtins_bzl/cc/cc_shared_library/test2/toolchain_include_dirs/cc_toolchain_config.bzl @@ -0,0 +1,23 @@ +"""A minimal cc_toolchain_config rule for testing cxx_builtin_include_directories.""" + +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo") + +def _impl(ctx): + return cc_common.create_cc_toolchain_config_info( + ctx = ctx, + toolchain_identifier = "test-toolchain", + compiler = "test-compiler", + target_system_name = "local", + target_cpu = "k8", + target_libc = "unknown", + cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories, + ) + +cc_toolchain_config = rule( + implementation = _impl, + attrs = { + "cxx_builtin_include_directories": attr.string_list(), + }, + provides = [CcToolchainConfigInfo], +) diff --git a/tests/builtins_bzl/cc/cc_static_library/test/BUILD b/tests/builtins_bzl/cc/cc_static_library/test/BUILD index 407853e7d..8c01a4cae 100644 --- a/tests/builtins_bzl/cc/cc_static_library/test/BUILD +++ b/tests/builtins_bzl/cc/cc_static_library/test/BUILD @@ -34,9 +34,15 @@ cc_library( linkstatic = True, ) +filegroup( + name = "lib_only_static", + srcs = [":lib_only"], + output_group = "archive", +) + cc_import( name = "lib_only_static_lib", - static_library = ":lib_only", + static_library = ":lib_only_static", ) cc_import( @@ -84,6 +90,7 @@ sh_test( ], target_compatible_with = select({ "@platforms//os:linux": [], + "@platforms//os:macos": [], "//conditions:default": ["@platforms//:incompatible"], }), ) diff --git a/tests/builtins_bzl/cc/cc_static_library/test/starlark_tests.bzl b/tests/builtins_bzl/cc/cc_static_library/test/starlark_tests.bzl index 3cba57383..f73ef0af3 100644 --- a/tests/builtins_bzl/cc/cc_static_library/test/starlark_tests.bzl +++ b/tests/builtins_bzl/cc/cc_static_library/test/starlark_tests.bzl @@ -19,6 +19,8 @@ load("@rules_testing//lib:util.bzl", "util") load("//cc:cc_import.bzl", "cc_import") load("//cc:cc_library.bzl", "cc_library") load("//cc:cc_static_library.bzl", "cc_static_library") +load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test") +load("//tests/cc/testutil/toolchains:features.bzl", "FEATURE_NAMES") load(":mock_toolchain.bzl", "mock_cc_toolchain") def _set_up_subject(name): @@ -233,6 +235,40 @@ def _test_validation_disabled_impl(env, target): env.expect.that_collection(dir(target[OutputGroupInfo])).contains_none_of(["_validation"]) +#buildifier: disable=unused-variable +def _test_opt_archives_one_object_per_source(name): + util.helper_target( + cc_library, + name = name + "_lib", + srcs = ["file.cc"], + ) + util.helper_target( + cc_static_library, + name = name + "_subject", + deps = [name + "_lib"], + ) + + # With supports_pic in opt mode, cc_library compiles both a PIC and a + # non-PIC object for every source. + cc_analysis_test( + name = name, + impl = _test_opt_archives_one_object_per_source_impl, + target = name + "_subject", + test_features = [FEATURE_NAMES.supports_pic], + config_settings = { + "//command_line_option:compilation_mode": "opt", + }, + ) + +def _test_opt_archives_one_object_per_source_impl(env, target): + action = env.expect.that_target(target).action_named("CppTransitiveArchive") + objects = [ + f.basename + for f in action.actual.inputs.to_list() + if f.extension == "o" + ] + env.expect.that_collection(objects).contains_exactly(["file.pic.o"]) + def analysis_test_suite(name): test_suite( name = name, @@ -241,5 +277,6 @@ def analysis_test_suite(name): _test_output_groups, _test_validation_enabled, _test_validation_disabled, + _test_opt_archives_one_object_per_source, # copybara-uncomment-this-please ], ) diff --git a/tests/cc/bazel/cpp_modules_test.bzl b/tests/cc/bazel/cpp_modules_test.bzl index ce1c50d14..948659221 100644 --- a/tests/cc/bazel/cpp_modules_test.bzl +++ b/tests/cc/bazel/cpp_modules_test.bzl @@ -18,6 +18,37 @@ def _setup_with_features_target(name, rule_fn, suffix): module_interfaces = [name + "/foo.cppm"], ) +def _setup_with_parse_headers_target(name, rule_fn, suffix): + util.empty_file(name + "/bar.cc") + util.empty_file(name + "/bar.h") + util.helper_target( + rule_fn, + name = name + suffix, + srcs = [name + "/bar.cc"], + hdrs = [name + "/bar.h"], + features = ["parse_headers"], + ) + +def _test_cpp_modules_cc_library_with_parse_headers(name, **kwargs): + _setup_with_parse_headers_target(name, cc_library, "_lib") + cc_analysis_test( + name = name, + target = name + "_lib", + impl = _test_cpp_modules_with_parse_headers_impl, + test_features = ["cpp_modules"], + with_action_configs = [ + ACTION_NAMES.cpp_module_deps_scanning, + ACTION_NAMES.cpp20_module_compile, + ], + config_settings = { + "//command_line_option:experimental_cpp_modules": True, + }, + **kwargs + ) + +def _test_cpp_modules_with_parse_headers_impl(env, target): + env.expect.that_target(target).action_named("CppCompile") + def _test_cpp_modules_cc_library_configuration_with_features(name, **kwargs): _setup_with_features_target(name, cc_library, "_lib") cc_analysis_test( @@ -166,6 +197,7 @@ def cpp_modules_tests(name, **kwargs): _test_cpp_modules_cc_library_configuration_with_features, _test_cpp_modules_cc_binary_configuration_with_features, _test_cpp_modules_cc_test_configuration_with_features, + _test_cpp_modules_cc_library_with_parse_headers, _test_same_module_interfaces_file_in_cc_library_twice, _test_same_module_interfaces_file_in_cc_binary_twice, _test_same_module_interfaces_file_in_cc_test_twice, diff --git a/tests/cc/common/BUILD b/tests/cc/common/BUILD index 94535da60..cc4dda4ff 100644 --- a/tests/cc/common/BUILD +++ b/tests/cc/common/BUILD @@ -12,6 +12,7 @@ load(":cc_link_action_test.bzl", "cc_link_action_tests") load(":cc_linkstamp_compile_test.bzl", "cc_linkstamp_compile_tests") load(":cc_objc_library_configured_target_tests.bzl", "cc_objc_library_configured_target_tests") load(":cc_output_groups_test.bzl", "cc_output_groups_tests") +load(":cc_toolchain_include_directories_test.bzl", "cc_toolchain_include_directories_tests") load(":compile_build_variables_tests.bzl", "compile_build_variables_tests") load(":cpp_modules_test.bzl", "cpp_modules_tests") load(":debug_info_packaging_test.bzl", "debug_info_packaging_tests") @@ -55,3 +56,5 @@ cc_link_action_tests(name = "cc_link_action_tests") cc_linkstamp_compile_tests(name = "cc_linkstamp_compile_tests") cc_binary_fdo_tests(name = "cc_binary_fdo_tests") + +cc_toolchain_include_directories_tests(name = "cc_toolchain_include_directories_tests") diff --git a/tests/cc/common/cc_binary_configured_target_tests.bzl b/tests/cc/common/cc_binary_configured_target_tests.bzl index fdde1aeae..75c9ec6d3 100644 --- a/tests/cc/common/cc_binary_configured_target_tests.bzl +++ b/tests/cc/common/cc_binary_configured_target_tests.bzl @@ -337,6 +337,105 @@ def _test_generated_def_file_uses_toolchain_action_impl(env, target): matching.contains("def_parser_tool"), ) +def _cc_shared_library_generated_def_file_test( + name, + expected_dll_name, + shared_lib_name = None, + dynamic_library_pattern = None, + **kwargs): + util.helper_target( + cc_library, + name = name + "/foo", + srcs = ["foo.cc"], + ) + shared_lib_kwargs = {} + if shared_lib_name != None: + shared_lib_kwargs["shared_lib_name"] = shared_lib_name + util.helper_target( + cc_shared_library, + name = name + "/foo_shared", + deps = [name + "/foo"], + **shared_lib_kwargs + ) + config_settings = {} + if dynamic_library_pattern != None: + config_settings[str(Label("//tests/cc/testutil/toolchains:dynamic_library_pattern"))] = dynamic_library_pattern + cc_analysis_test( + name = name, + impl = _cc_shared_library_generated_def_file_test_impl, + target = name + "/foo_shared", + test_features = [ + FEATURE_NAMES.copy_dynamic_libraries_to_binary, + FEATURE_NAMES.targets_windows, + FEATURE_NAMES.windows_export_all_symbols, + ], + with_action_configs = [ACTION_NAMES.generate_def_file], + config_settings = config_settings, + attrs = { + "expected_dll_name": attr.string(), + }, + attr_values = { + "expected_dll_name": expected_dll_name, + }, + **kwargs + ) + +def _cc_shared_library_generated_def_file_test_impl(env, target): + def_file_short_path = "{}/{}.gen.def".format(target.label.package, target.label.name) + def_action = _generated_def_file_action(env, target) + def_action.argv().contains_at_least_predicates([ + matching.contains("def_parser_tool"), + matching.str_endswith(def_file_short_path), + matching.equals_wrapper(env.ctx.attr.expected_dll_name), + ]).in_order() + env.expect.that_target(target).output_group("def_file").contains_exactly([ + "{package}/{name}.gen.def", + ]) + link_action = env.expect.that_target(target).action_named("CppLink") + link_action.inputs().contains("{package}/{name}.gen.def") + link_action.outputs().contains_predicate( + matching.file_basename_equals(env.ctx.attr.expected_dll_name), + ) + +def _test_cc_shared_library_generated_def_file_default_dll_name(name, **kwargs): + _cc_shared_library_generated_def_file_test( + name, + expected_dll_name = "libfoo_shared.so", + **kwargs + ) + +def _test_cc_shared_library_generated_def_file_mingw_dll_pattern(name, **kwargs): + _cc_shared_library_generated_def_file_test( + name, + expected_dll_name = "libfoo_shared.dll", + dynamic_library_pattern = ["lib", ".dll"], + **kwargs + ) + +def _test_cc_shared_library_generated_def_file_msvc_dll_pattern(name, **kwargs): + _cc_shared_library_generated_def_file_test( + name, + expected_dll_name = "foo_shared.dll", + dynamic_library_pattern = ["", ".dll"], + **kwargs + ) + +def _test_cc_shared_library_generated_def_file_shared_lib_name(name, **kwargs): + _cc_shared_library_generated_def_file_test( + name, + expected_dll_name = "custom_foo.pyd", + shared_lib_name = "custom_foo.pyd", + **kwargs + ) + +def _test_cc_shared_library_generated_def_file_shared_lib_name_with_subdir(name, **kwargs): + _cc_shared_library_generated_def_file_test( + name, + expected_dll_name = "custom_foo.dll", + shared_lib_name = "subdir/custom_foo.dll", + **kwargs + ) + def _test_duplicate_linkopts(name, **kwargs): util.helper_target( cc_library, @@ -2088,6 +2187,11 @@ def cc_binary_configured_target_tests(name): _test_system_include_paths_reclassifies_local_includes_without_propagation, _test_generated_def_file_uses_toolchain_action, # copybara-uncomment-this-please _test_generated_def_file_uses_default_tool, # copybara-uncomment-this-please + _test_cc_shared_library_generated_def_file_default_dll_name, + _test_cc_shared_library_generated_def_file_mingw_dll_pattern, + _test_cc_shared_library_generated_def_file_msvc_dll_pattern, + _test_cc_shared_library_generated_def_file_shared_lib_name, + _test_cc_shared_library_generated_def_file_shared_lib_name_with_subdir, _test_linker_toolchain_feature, _test_link_staticness_binary_static_dynamic_mode_default, _test_link_staticness_hello_test_dynamic_mode_default, diff --git a/tests/cc/common/cc_binary_fdo_test.bzl b/tests/cc/common/cc_binary_fdo_test.bzl index 4b64aef65..6f4fb9b40 100644 --- a/tests/cc/common/cc_binary_fdo_test.bzl +++ b/tests/cc/common/cc_binary_fdo_test.bzl @@ -14,6 +14,7 @@ """Tests for cc_binary with FDO.""" +load("@bazel_features//:features.bzl", "bazel_features") load("@rules_testing//lib:analysis_test.bzl", "test_suite") load("@rules_testing//lib:truth.bzl", "matching") load("@rules_testing//lib:util.bzl", "util") @@ -27,6 +28,12 @@ def _test_fdo_profile_action_graph(name, **kwargs): name = name + "/foo", srcs = ["foo.cc"], ) + util.helper_target( + cc_binary, + name = name + "/foo_no_fdo", + srcs = ["foo.cc"], + features = ["-fdo_optimize"], + ) util.helper_target( fdo_profile, @@ -46,6 +53,7 @@ def _test_fdo_profile_action_graph(name, **kwargs): impl = _test_fdo_profile_action_graph_impl, targets = { "foo": name + "/foo", + "foo_no_fdo": name + "/foo_no_fdo", "gen_profraw": name + "/gen_profraw", "compiler": select({ "@platforms//os:macos": "//tests/cc/testutil/toolchains:cc-compiler-macos-compiler", @@ -101,6 +109,11 @@ def _test_fdo_profile_action_graph_impl(env, targets): # these targets so just assert short_path assert_compile_action.inputs().contains(profdata.short_path) + if bazel_features.cc.cc_common_is_in_rules_cc: + env.expect.that_target(targets.foo_no_fdo).action_generating( + "{package}/_objs/{name}/foo.o", + ).inputs().not_contains(profdata.short_path) + def cc_binary_fdo_tests(name): test_suite( name = name, diff --git a/tests/cc/common/cc_common_test.bzl b/tests/cc/common/cc_common_test.bzl index 15af5008b..39be3c329 100644 --- a/tests/cc/common/cc_common_test.bzl +++ b/tests/cc/common/cc_common_test.bzl @@ -7,6 +7,7 @@ load("@rules_testing//lib:util.bzl", "TestingAspectInfo", "util") load("//cc:cc_binary.bzl", "cc_binary") load("//cc:cc_library.bzl", "cc_library") load("//cc/common:cc_info.bzl", "CcInfo") +load("//cc/private/link:create_extra_link_time_library.bzl", "build_libraries", "create_extra_link_time_libraries", "create_extra_link_time_library", "merge_extra_link_time_libraries") # buildifier: disable=bzl-visibility load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test") load("//tests/cc/testutil:cc_info_subject.bzl", "cc_info_subject") @@ -707,6 +708,133 @@ def _test_alwayslink_yields_lo_impl(env, target): files = [f.basename for f in target[DefaultInfo].files.to_list()] env.expect.that_collection(files).contains("libalways_link.lo") +# buildifier: disable=provider-params +_ExtraLinkTimeLibraryTestInfo = provider(fields = ["stamp_infos"]) + +def _build_extra_link_time_library(_ctx, _static_mode, _for_dynamic_library, inputs): + return struct( + linker_input = depset(), + runtime_library = depset(), + additional_stamp_info = inputs.to_list(), + ) + +def _top_level_extra_link_time_library_impl(ctx): + first = create_extra_link_time_library( + build_library_func = _build_extra_link_time_library, + inputs = depset(["first"]), + ) + second = create_extra_link_time_library( + build_library_func = _build_extra_link_time_library, + inputs = depset(["second"]), + ) + libraries = merge_extra_link_time_libraries([ + create_extra_link_time_libraries(first), + create_extra_link_time_libraries(second), + ]) + result = build_libraries(libraries.libraries, ctx, False, False) + return [_ExtraLinkTimeLibraryTestInfo(stamp_infos = result.additional_stamp_infos)] + +_top_level_extra_link_time_library = rule( + implementation = _top_level_extra_link_time_library_impl, +) + +def _nested_extra_link_time_library_impl(ctx): + def nested_build_library(_ctx, _static_mode, _for_dynamic_library): + # buildifier: disable=rule-impl-returns + return struct( + linker_input = depset(), + runtime_library = depset(), + ) + + library = create_extra_link_time_library( + build_library_func = nested_build_library, + ) + build_libraries([library], ctx, False, False) + return [] + +_nested_extra_link_time_library = rule( + implementation = _nested_extra_link_time_library_impl, +) + +def _invalid_extra_link_time_library_impl(ctx): + create_extra_link_time_library(build_library_func = struct if ctx.attr.builtin else None) + return [] + +_invalid_extra_link_time_library = rule( + implementation = _invalid_extra_link_time_library_impl, + attrs = {"builtin": attr.bool()}, +) + +def _test_top_level_extra_link_time_library(name): + util.helper_target( + _top_level_extra_link_time_library, + name = name + "/library", + ) + cc_analysis_test( + name = name, + impl = _test_top_level_extra_link_time_library_impl, + target = name + "/library", + ) + +def _test_top_level_extra_link_time_library_impl(env, target): + env.expect.that_target(target).failures().contains_exactly([]) + stamp_infos = target[_ExtraLinkTimeLibraryTestInfo].stamp_infos + env.expect.that_collection(stamp_infos).has_size(1) + env.expect.that_collection(stamp_infos[0]).contains_exactly(["first", "second"]) + +def _test_nested_extra_link_time_library(name): + util.helper_target( + _nested_extra_link_time_library, + name = name + "/library", + ) + cc_analysis_test( + name = name, + impl = _test_nested_extra_link_time_library_impl, + target = name + "/library", + expect_failure = True, + ) + +def _test_nested_extra_link_time_library_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.custom( + "contains 'must be declared by a top-level def statement'", + lambda message: "must be declared by a top-level def statement" in message, + ), + ) + +def _test_builtin_extra_link_time_library(name): + util.helper_target( + _invalid_extra_link_time_library, + name = name + "/library", + builtin = True, + ) + cc_analysis_test( + name = name, + impl = _test_invalid_extra_link_time_library_impl, + target = name + "/library", + expect_failure = True, + ) + +def _test_none_extra_link_time_library(name): + util.helper_target( + _invalid_extra_link_time_library, + name = name + "/library", + ) + cc_analysis_test( + name = name, + impl = _test_invalid_extra_link_time_library_impl, + target = name + "/library", + expect_failure = True, + ) + +def _test_invalid_extra_link_time_library_impl(env, target): + env.expect.that_target(target).failures().contains_predicate( + matching.custom( + "contains 'build_library_func must be a Starlark function'", + lambda message: "build_library_func must be a Starlark function" in message, + ), + ) + def cc_common_tests(name): tests = [ _test_same_cc_file_twice, @@ -732,6 +860,10 @@ def cc_common_tests(name): ] if bazel_features.cc.cc_common_is_in_rules_cc: tests.extend([ + _test_top_level_extra_link_time_library, + _test_nested_extra_link_time_library, + _test_builtin_extra_link_time_library, + _test_none_extra_link_time_library, _test_strip_include_prefix_uses_virtual_includes_by_default, _test_strip_include_prefix_no_virtual_includes_when_enabled, _test_strip_include_prefix_with_include_prefix_uses_virtual_includes, diff --git a/tests/cc/common/cc_toolchain_include_directories_test.bzl b/tests/cc/common/cc_toolchain_include_directories_test.bzl new file mode 100644 index 000000000..fdfa88756 --- /dev/null +++ b/tests/cc/common/cc_toolchain_include_directories_test.bzl @@ -0,0 +1,37 @@ +"""Tests for the resolution of cxx_builtin_include_directories by cc_toolchain.""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_testing//lib:analysis_test.bzl", "test_suite") +load("//cc/common:cc_common.bzl", "cc_common") +load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test") + +# buildifier: disable=unused-variable +def _test_package_include_directories_use_toolchain_repo_mapping(name): + cc_analysis_test( + name = name, + impl = _test_package_include_directories_use_toolchain_repo_mapping_impl, + target = "@cross_repo_test//toolchain_include_dirs:toolchain", + ) + +def _test_package_include_directories_use_toolchain_repo_mapping_impl(env, target): + test_repo_root = Label("@cross_repo_test//toolchain_include_dirs:toolchain").workspace_root + rules_cc_root = Label("//cc:BUILD").workspace_root + env.expect.that_collection( + target[cc_common.CcToolchainInfo].built_in_include_directories, + ).contains_exactly([ + paths.join(test_repo_root, "toolchain_include_dirs/include"), + paths.join(test_repo_root, "toolchain_include_dirs/other"), + paths.join(rules_cc_root, "cc"), + ]).in_order() + +def cc_toolchain_include_directories_tests(name): + tests = [] + if bazel_features.cc.cc_common_is_in_rules_cc: + tests.append(_test_package_include_directories_use_toolchain_repo_mapping) # copybara-uncomment-this-please + # pass # copybara-comment-this-out-please + + test_suite( + name = name, + tests = tests, + ) diff --git a/tests/cc/common/debug_info_packaging_test.bzl b/tests/cc/common/debug_info_packaging_test.bzl index 662b9c721..3f70ed221 100644 --- a/tests/cc/common/debug_info_packaging_test.bzl +++ b/tests/cc/common/debug_info_packaging_test.bzl @@ -1,10 +1,12 @@ """Tests for debug info packaging (Fission).""" +load("@bazel_features//:features.bzl", "bazel_features") load("@rules_testing//lib:analysis_test.bzl", "test_suite") load("@rules_testing//lib:truth.bzl", "subjects") load("@rules_testing//lib:util.bzl", "util") load("//cc:cc_binary.bzl", "cc_binary") load("//cc:cc_library.bzl", "cc_library") +load("//cc:cc_test.bzl", "cc_test") load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test") load("//tests/cc/testutil/toolchains:features.bzl", "FEATURE_NAMES") @@ -116,7 +118,7 @@ def _test_dwp_action_structure_impl(env, target): ) dwo_files.contains_exactly([expected_dwo]) -def _create_dwo_input_set_tree(name, linkstatic): +def _create_dwo_input_set_tree(name, linkstatic, rule_under_test = cc_binary): util.empty_file(name + "_lib1.cc") util.empty_file(name + "_lib2.cc") util.empty_file(name + "_lib_pregen.o") @@ -143,7 +145,7 @@ def _create_dwo_input_set_tree(name, linkstatic): name = name + "_empty_lib", ) util.helper_target( - cc_binary, + rule_under_test, name = name + "_binary", srcs = [ name + "_main.cc", @@ -169,6 +171,15 @@ def _get_dwo_files_subject(env, target, dwp_path): dwo_files.append(sf.short_path) return env.expect.that_collection(dwo_files) +def _get_dwo_files_output_group_subject(env, target): + package_prefix = target.label.package + "/" + dwo_files = [ + f.short_path + for f in target[OutputGroupInfo].dwo_files.to_list() + if f.short_path.startswith(package_prefix) + ] + return env.expect.that_collection(dwo_files) + def _test_dwo_input_set_pic_mode(name): """Tests that the set of input .dwo files is as expected. @@ -248,15 +259,355 @@ def _test_dwo_input_set_dynamic_mode_impl(env, target): dwo_files.contains_exactly([expected_main]) +def _test_empty_inputs_in_fission_mode(name): + """Tests that a fission build with no .dwo inputs from the target package doesn't contribute any .dwo files. + + Note: When there are zero .dwo inputs overall (as in OSS Bazel, where _stl defaults to None), + the .dwp action is optimized to an empty FileWrite. When implicit runtime dependencies such as + _stl contribute non-package .dwo files, CcGenerateDwp is created and we verify no package-scoped + .dwo files are included. + """ + util.helper_target( + cc_library, + name = name + "_test_malloc", + srcs = [name + "_test_malloc_prebuilt.o"], + ) + util.helper_target( + cc_library, + name = name + "_empty_lib", + ) + util.helper_target( + cc_binary, + name = name + "_fission", + srcs = [name + "_prebuilt.o"], + link_extra_lib = name + "_empty_lib", # copybara:strip + malloc = name + "_test_malloc", + ) + cc_analysis_test( + name = name, + impl = _test_empty_inputs_in_fission_mode_impl, + target = name + "_fission", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_empty_inputs_in_fission_mode_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + action = env.expect.that_target(target).action_generating(dwp_path) + + mnemonic = action.actual.mnemonic + if mnemonic == "CcGenerateDwp": + # Implicit runtime dependencies (such as _stl) can transitively contribute + # non-package .dwo inputs, causing CcGenerateDwp to be emitted. + dwo_files = _get_dwo_files_subject(env, target, dwp_path) + dwo_files.is_empty() + elif mnemonic == "FileWrite": + # Zero .dwo inputs overall: DWP generation is optimized to an empty FileWrite. + action.content().equals("") + else: + env.fail("Expected DWP action mnemonic to be 'CcGenerateDwp' or 'FileWrite', but got '%s'" % mnemonic) + +def _test_dwo_input_set_non_pic_mode(name): + """Same as _test_dwo_input_set_pic_mode, but for non-pic-mode linking. + + That means .o files are linked in over their .pic.o counterparts and the + .dwo inputs should reflect this change. + """ + _create_dwo_input_set_tree(name, linkstatic = True) + cc_analysis_test( + name = name, + impl = _test_dwo_input_set_non_pic_mode_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:fission": "yes", + }, + ) + +def _test_dwo_input_set_non_pic_mode_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + dwo_files = _get_dwo_files_subject(env, target, dwp_path) + + pkg = target.label.package + prefix = target.label.name.replace("_binary", "") + lib_target = prefix + "_lib" + malloc_target = prefix + "_test_malloc" + bin_target = prefix + "_binary" + + expected_templates = [ + "{pkg}/_objs/{lib}/{prefix}_lib1.dwo", + "{pkg}/_objs/{lib}/{prefix}_lib2.dwo", + "{pkg}/_objs/{malloc}/{prefix}_test_malloc.dwo", + "{pkg}/_objs/{bin}/{prefix}_main.dwo", + ] + expected = [t.format(pkg = pkg, lib = lib_target, malloc = malloc_target, bin = bin_target, prefix = prefix) for t in expected_templates] + dwo_files.contains_exactly(expected) + +def _test_dwo_input_set_no_pic_for_binaries_mode(name): + """Same as _test_dwo_input_set_pic_mode, but for pic-mode configurations where binaries are specially linked without pic. + + See CppConfiguration#usePicForBinaries + """ + _create_dwo_input_set_tree(name, linkstatic = True) + cc_analysis_test( + name = name, + impl = _test_dwo_input_set_no_pic_for_binaries_mode_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:fission": "yes", + "//command_line_option:compilation_mode": "opt", + }, + ) + +def _test_dwo_input_set_no_pic_for_binaries_mode_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + dwo_files = _get_dwo_files_subject(env, target, dwp_path) + + pkg = target.label.package + prefix = target.label.name.replace("_binary", "") + lib_target = prefix + "_lib" + malloc_target = prefix + "_test_malloc" + bin_target = prefix + "_binary" + + expected_templates = [ + "{pkg}/_objs/{lib}/{prefix}_lib1.dwo", + "{pkg}/_objs/{lib}/{prefix}_lib2.dwo", + "{pkg}/_objs/{malloc}/{prefix}_test_malloc.dwo", + "{pkg}/_objs/{bin}/{prefix}_main.dwo", + ] + expected = [t.format(pkg = pkg, lib = lib_target, malloc = malloc_target, bin = bin_target, prefix = prefix) for t in expected_templates] + dwo_files.contains_exactly(expected) + +def _create_custom_malloc_tree(name): + _create_dwo_input_set_tree(name, linkstatic = True) + util.helper_target( + cc_library, + name = name + "_custom_malloc", + srcs = [name + "_custom_malloc.cc"], + ) + +def _test_dwo_input_set_custom_malloc(name): + """Tests that when --custom_malloc overrides a rule's default malloc, the custom malloc is the one that makes it into the input set.""" + _create_custom_malloc_tree(name) + custom_malloc_label = Label("//{}:{}_custom_malloc".format(native.package_name(), name)) + cc_analysis_test( + name = name, + impl = _test_dwo_input_set_custom_malloc_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + "//command_line_option:custom_malloc": custom_malloc_label, + }, + ) + +def _test_dwo_input_set_custom_malloc_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + dwo_files = _get_dwo_files_subject(env, target, dwp_path) + + pkg = target.label.package + prefix = target.label.name.replace("_binary", "") + lib_target = prefix + "_lib" + malloc_target = prefix + "_custom_malloc" + bin_target = prefix + "_binary" + + expected_templates = [ + "{pkg}/_objs/{lib}/{prefix}_lib1.pic.dwo", + "{pkg}/_objs/{lib}/{prefix}_lib2.pic.dwo", + "{pkg}/_objs/{malloc}/{prefix}_custom_malloc.pic.dwo", + "{pkg}/_objs/{bin}/{prefix}_main.pic.dwo", + ] + expected = [t.format(pkg = pkg, lib = lib_target, malloc = malloc_target, bin = bin_target, prefix = prefix) for t in expected_templates] + dwo_files.contains_exactly(expected) + +def _test_dwo_files_output_group_dbg_static_mode(name): + """Tests that the dwo_files output group contains direct and transitive .dwo files in dbg static mode.""" + _create_dwo_input_set_tree(name, linkstatic = True) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_dbg_static_mode_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_dbg_static_mode_impl(env, target): + dwo_files = _get_dwo_files_output_group_subject(env, target) + + pkg = target.label.package + prefix = target.label.name.replace("_binary", "") + lib_target = prefix + "_lib" + malloc_target = prefix + "_test_malloc" + bin_target = prefix + "_binary" + + expected = [ + "{pkg}/_objs/{lib}/{prefix}_lib1.pic.dwo".format(pkg = pkg, lib = lib_target, prefix = prefix), + "{pkg}/_objs/{lib}/{prefix}_lib2.pic.dwo".format(pkg = pkg, lib = lib_target, prefix = prefix), + "{pkg}/_objs/{malloc}/{prefix}_test_malloc.pic.dwo".format(pkg = pkg, malloc = malloc_target, prefix = prefix), + "{pkg}/_objs/{bin}/{prefix}_main.pic.dwo".format(pkg = pkg, bin = bin_target, prefix = prefix), + ] + dwo_files.contains_exactly(expected) + +def _test_dwo_files_output_group_dbg_dynamic_mode(name): + """Tests that the dwo_files output group contains only direct .dwo files in dbg dynamic mode.""" + _create_dwo_input_set_tree(name, linkstatic = False) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_dbg_dynamic_mode_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_dbg_dynamic_mode_impl(env, target): + dwo_files = _get_dwo_files_output_group_subject(env, target) + + pkg = target.label.package + prefix = target.label.name.replace("_binary", "") + bin_target = prefix + "_binary" + expected_main = "{pkg}/_objs/{bin}/{prefix}_main.pic.dwo".format(pkg = pkg, bin = bin_target, prefix = prefix) + + dwo_files.contains_exactly([expected_main]) + +def _test_dwo_files_output_group_fission_disabled(name): + """Tests that the dwo_files output group is empty when fission is disabled.""" + _create_fission_binary(name) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_empty_impl, + target = name + "_fission", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "no", + }, + ) + +def _test_dwo_files_output_group_non_dbg_mode(name): + """Tests that the dwo_files output group is empty in non-dbg builds even when fission is enabled.""" + _create_fission_binary(name) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_empty_impl, + target = name + "_fission", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:compilation_mode": "opt", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_empty_impl(env, target): + env.expect.that_target(target).output_group("dwo_files").is_empty() + +def _test_dwo_files_output_group_cc_test_static_with_build_test_dwp(name): + """Tests that a statically linked cc_test with --build_test_dwp builds a .dwp and leaves dwo_files empty.""" + _create_dwo_input_set_tree(name, linkstatic = True, rule_under_test = cc_test) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_cc_test_static_with_build_test_dwp_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:build_test_dwp": True, + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_cc_test_static_with_build_test_dwp_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + env.expect.that_target(target).default_outputs().contains(dwp_path) + env.expect.that_target(target).output_group("dwo_files").is_empty() + +def _test_dwo_files_output_group_cc_test_static_without_build_test_dwp(name): + """Tests that a statically linked cc_test with --nobuild_test_dwp populates the dwo_files output group.""" + _create_dwo_input_set_tree(name, linkstatic = True, rule_under_test = cc_test) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_cc_test_static_without_build_test_dwp_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:build_test_dwp": False, + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_cc_test_static_without_build_test_dwp_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + env.expect.that_target(target).default_outputs().not_contains(dwp_path) + _test_dwo_files_output_group_dbg_static_mode_impl(env, target) + +def _test_dwo_files_output_group_cc_test_dynamic_with_build_test_dwp(name): + """Tests that a dynamically linked cc_test populates the dwo_files output group even with --build_test_dwp.""" + _create_dwo_input_set_tree(name, linkstatic = False, rule_under_test = cc_test) + cc_analysis_test( + name = name, + impl = _test_dwo_files_output_group_cc_test_dynamic_with_build_test_dwp_impl, + target = name + "_binary", + with_features = [FEATURE_NAMES.supports_pic, FEATURE_NAMES.per_object_debug_info], + config_settings = { + "//command_line_option:build_test_dwp": True, + "//command_line_option:compilation_mode": "dbg", + "//command_line_option:fission": "yes", + "//command_line_option:force_pic": True, + }, + ) + +def _test_dwo_files_output_group_cc_test_dynamic_with_build_test_dwp_impl(env, target): + executable = target[DefaultInfo].files_to_run.executable + dwp_path = executable.short_path + ".dwp" + env.expect.that_target(target).default_outputs().not_contains(dwp_path) + _test_dwo_files_output_group_dbg_dynamic_mode_impl(env, target) + def debug_info_packaging_tests(name): + tests = [ + _test_dwp_output_names, + _test_dwp_builds_require_fission_mode_disabled, + _test_dwp_builds_require_fission_mode_enabled, + _test_dwp_action_structure, + _test_dwo_input_set_pic_mode, + _test_dwo_input_set_dynamic_mode, + _test_empty_inputs_in_fission_mode, + _test_dwo_input_set_non_pic_mode, + _test_dwo_input_set_no_pic_for_binaries_mode, + _test_dwo_input_set_custom_malloc, + ] + if bazel_features.cc.cc_common_is_in_rules_cc: + tests.extend([ + _test_dwo_files_output_group_dbg_static_mode, + _test_dwo_files_output_group_dbg_dynamic_mode, + _test_dwo_files_output_group_fission_disabled, + _test_dwo_files_output_group_non_dbg_mode, + _test_dwo_files_output_group_cc_test_static_with_build_test_dwp, + _test_dwo_files_output_group_cc_test_static_without_build_test_dwp, + _test_dwo_files_output_group_cc_test_dynamic_with_build_test_dwp, + ]) test_suite( name = name, - tests = [ - _test_dwp_output_names, - _test_dwp_builds_require_fission_mode_disabled, - _test_dwp_builds_require_fission_mode_enabled, - _test_dwp_action_structure, - _test_dwo_input_set_pic_mode, - _test_dwo_input_set_dynamic_mode, - ], + tests = tests, ) diff --git a/tests/cc/common/disallowed_copts_test.bzl b/tests/cc/common/disallowed_copts_test.bzl index 1ad8441d1..6384acb3a 100644 --- a/tests/cc/common/disallowed_copts_test.bzl +++ b/tests/cc/common/disallowed_copts_test.bzl @@ -6,7 +6,7 @@ load("@rules_testing//lib:truth.bzl", "matching") load("@rules_testing//lib:util.bzl", "util") load("//cc:cc_binary.bzl", "cc_binary") load("//cc:cc_library.bzl", "cc_library") -load("//tests/cc/testutil:cc_analysis_test.bzl", "MOCK_ALLOWLIST_TOOLCHAINS", "MOCK_EMPTY_ALLOWLIST_TOOLCHAINS", "cc_analysis_test") +load("//tests/cc/testutil:cc_analysis_test.bzl", "MOCK_ALLOWLIST_TOOLCHAINS", "MOCK_EMPTY_ALLOWLIST_TOOLCHAINS", "MOCK_TARGET_ALLOWLIST_TOOLCHAINS", "cc_analysis_test") def _test_pass_impl(_env, _target): pass @@ -19,7 +19,9 @@ def _test_fail_impl(env, target): def _test_fail_empty_toolchain_allowlist_impl(env, target): env.expect.that_target(target).failures().contains_predicate( matching.contains( - "- Flag '-Wno-error' (target is not in the allowlist '//custom:toolchain_allowlist'): Custom warning policy guidance.", + "- Flag '-Wno-error' in attribute 'copts' disallowed by '{}': Custom warning policy guidance.".format( + Label("//tests/cc/testutil/toolchains:k8-compiler_config_with_empty_allowlist"), + ), ), ) @@ -111,6 +113,22 @@ def _test_no_copts_target_passes(name, **kwargs): **kwargs ) +def _test_disallowed_copt_in_toolchain_target_allowlist_passes(name, **kwargs): + util.helper_target( + cc_library, + name = name + "/test", + copts = ["-Wno-error"], + ) + cc_analysis_test( + name = name, + impl = _test_pass_impl, + target = name + "/test", + config_settings = { + "//command_line_option:extra_toolchains": ",".join(MOCK_TARGET_ALLOWLIST_TOOLCHAINS), + }, + **kwargs + ) + def disallowed_copts_tests(name): test_suite( name = name, @@ -118,6 +136,7 @@ def disallowed_copts_tests(name): _test_disallowed_copt_fails, _test_disallowed_copt_in_toolchain_allowlist_passes, _test_disallowed_copt_cc_binary_in_toolchain_allowlist_passes, + _test_disallowed_copt_in_toolchain_target_allowlist_passes, _test_disallowed_copt_with_empty_toolchain_allowlist_fails, _test_valid_warning_passes, _test_no_copts_target_passes, diff --git a/tests/cc/testutil/cc_analysis_test.bzl b/tests/cc/testutil/cc_analysis_test.bzl index 5557975f8..53fb8566c 100644 --- a/tests/cc/testutil/cc_analysis_test.bzl +++ b/tests/cc/testutil/cc_analysis_test.bzl @@ -16,6 +16,10 @@ MOCK_EMPTY_ALLOWLIST_TOOLCHAINS = [ "//tests/cc/testutil/toolchains:cc-toolchain-k8-with-empty-allowlist", ] +MOCK_TARGET_ALLOWLIST_TOOLCHAINS = [ + "//tests/cc/testutil/toolchains:cc-toolchain-k8-with-target-allowlist", +] + def cc_analysis_test(name, with_features = None, test_features = [], with_action_configs = [], **kwargs): """Runs an analysis_test with the a mock C++ toolchain. diff --git a/tests/cc/testutil/toolchains/BUILD b/tests/cc/testutil/toolchains/BUILD index 3c750cdf1..0b65bf396 100644 --- a/tests/cc/testutil/toolchains/BUILD +++ b/tests/cc/testutil/toolchains/BUILD @@ -59,6 +59,11 @@ string_flag( build_setting_default = "", ) +string_list_flag( + name = "dynamic_library_pattern", + build_setting_default = [], +) + # "everything" is a filegroup that the implementation of --grte_top (more specifically, the # LibcTopLabelConverter in # third_party/bazel/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java) @@ -449,7 +454,6 @@ cc_toolchain_config( abi_version = "local", action_configs = ["c++-header-analysis"], allowlist_provider = ":mock_allowlist", - allowlist_target_label = "//custom:toolchain_allowlist", artifact_name_patterns = {}, builtin_sysroot = "/usr/grte/v1", cc_target_os = "", @@ -497,7 +501,6 @@ cc_toolchain_config( abi_version = "local", action_configs = ["c++-header-analysis"], allowlist_provider = ":empty_mock_allowlist", - allowlist_target_label = "//custom:toolchain_allowlist", artifact_name_patterns = {}, builtin_sysroot = "/usr/grte/v1", cc_target_os = "", @@ -539,3 +542,52 @@ toolchain( toolchain = ":cc-compiler-k8-with-empty-allowlist", toolchain_type = "//cc:toolchain_type", ) + +cc_toolchain_config( + name = "k8-compiler_config_with_target_allowlist", + abi_libc_version = "local", + abi_version = "local", + action_configs = ["c++-header-analysis"], + artifact_name_patterns = {}, + builtin_sysroot = "/usr/grte/v1", + cc_target_os = "", + compiler = "compiler", + cpu = "k8", + cxx_builtin_include_directories = [], + feature_names = [], + host_system_name = "local", + make_variables = {}, + target_allowlist = [ + "//tests/cc/common:test_disallowed_copt_in_toolchain_target_allowlist_passes/test", + ], + target_libc = "local", + target_system_name = "local", + tool_paths = {}, + toolchain_identifier = "mock-toolchain-k8", +) + +cc_toolchain( + name = "cc-compiler-k8-with-target-allowlist", + all_files = ":every-file", + ar_files = "ar-k8", + as_files = "as-k8", + compiler_files = "compile-k8", + compiler_files_without_includes = "compile-without-includes-k8", + coverage_files = "coverage-file", + dwp_files = "dwp-k8", + licenses = ["unencumbered"], + linker_files = "link-k8", + module_map = "crosstool.cppmap", + objcopy_files = "objcopy-k8", + output_licenses = ["unencumbered"], + strip_files = ":every-file", + supports_header_parsing = 1, + toolchain_config = ":k8-compiler_config_with_target_allowlist", + toolchain_identifier = "mock-toolchain-k8", +) + +toolchain( + name = "cc-toolchain-k8-with-target-allowlist", + toolchain = ":cc-compiler-k8-with-target-allowlist", + toolchain_type = "//cc:toolchain_type", +) diff --git a/tests/cc/testutil/toolchains/additional_toolchains.bzl b/tests/cc/testutil/toolchains/additional_toolchains.bzl index 1a8320cb7..0b2f7c67d 100644 --- a/tests/cc/testutil/toolchains/additional_toolchains.bzl +++ b/tests/cc/testutil/toolchains/additional_toolchains.bzl @@ -1,6 +1,9 @@ """Additional mock toolchains besides the mock C++ toolchain.""" def mock_additional_toolchains(): + # type: () -> None pass -ADDITIONAL_MOCK_TOOLCHAINS = [] +# TODO(https://github.com/bazelbuild/bazel/pull/30373): Use a variable annotation +# when Bazel 8.8 is the minimum supported version. +ADDITIONAL_MOCK_TOOLCHAINS = [] # type: list[str] diff --git a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl index 3ed8bbb70..60c332531 100644 --- a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl +++ b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl @@ -2057,6 +2057,9 @@ def _impl(ctx): object_file_extension = ctx.attr._object_file_extension[BuildSettingInfo].value if object_file_extension: artifact_name_patterns.append(_get_artifact_name_pattern("object_file", "", object_file_extension)) + dynamic_library_pattern = ctx.attr._dynamic_library_pattern[BuildSettingInfo].value + if dynamic_library_pattern: + artifact_name_patterns.append(_get_artifact_name_pattern("dynamic_library", dynamic_library_pattern[0], dynamic_library_pattern[1])) action_configs = [] @@ -2106,11 +2109,12 @@ def _impl(ctx): extra_args = {} if bazel_features.cc.cc_common_is_in_rules_cc: allowlist = ctx.attr.allowlist_provider[PackageSpecificationInfo] if ctx.attr.allowlist_provider != None else None - allowlist_target_label = ctx.attr.allowlist_target_label or (str(ctx.attr.allowlist_provider.label) if ctx.attr.allowlist_provider else None) extra_args["disallowed_copts_infos"] = [struct( flags = ctx.attr.disallowed_copts, + package_allowlist = allowlist, allowlist = allowlist, - allowlist_target_label = allowlist_target_label, + target_allowlist = ctx.attr.target_allowlist if ctx.attr.target_allowlist else None, + policy_label = str(ctx.label), error_message = ctx.attr.error_message or None, )] @@ -2156,7 +2160,7 @@ cc_toolchain_config = rule( "artifact_name_patterns": attr.string_list_dict(), "disallowed_copts": attr.string_list(default = ["-w", "-Wno-error"]), "allowlist_provider": attr.label(providers = [PackageSpecificationInfo]), - "allowlist_target_label": attr.string(), + "target_allowlist": attr.string_list(), "error_message": attr.string(), "cc_target_os": attr.string(), "builtin_sysroot": attr.string(default = "/usr/grte/v1"), @@ -2166,6 +2170,7 @@ cc_toolchain_config = rule( "_with_features": attr.label(default = Label("//tests/cc/testutil/toolchains:with_features")), "_with_action_configs": attr.label(default = Label("//tests/cc/testutil/toolchains:with_action_configs")), "_object_file_extension": attr.label(default = Label("//tests/cc/testutil/toolchains:object_file_extension")), + "_dynamic_library_pattern": attr.label(default = Label("//tests/cc/testutil/toolchains:dynamic_library_pattern")), }, provides = [CcToolchainConfigInfo], executable = True, diff --git a/tests/coverage/BUILD.bazel b/tests/coverage/BUILD.bazel new file mode 100644 index 000000000..c7cc95327 --- /dev/null +++ b/tests/coverage/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +sh_test( + name = "collect_cc_coverage_test", + size = "small", + srcs = ["collect_cc_coverage_test.sh"], + data = ["//cc/coverage:srcs"], + target_compatible_with = ["@platforms//os:linux"], +) diff --git a/tests/coverage/collect_cc_coverage_test.sh b/tests/coverage/collect_cc_coverage_test.sh new file mode 100755 index 000000000..3f380038a --- /dev/null +++ b/tests/coverage/collect_cc_coverage_test.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Copyright 2026 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 + +readonly SCRIPT="${TEST_SRCDIR}/${TEST_WORKSPACE}/cc/coverage/collect_cc_coverage.sh" +readonly WORK_DIR="$(mktemp -d "${TEST_TMPDIR:-/tmp}/cc-coverage-args.XXXXXXXX")" +trap 'rm -rf "${WORK_DIR}"' EXIT + +mkdir -p "${WORK_DIR}/coverage" "${WORK_DIR}/object files" +readonly OBJECT_WITH_SPACE="${WORK_DIR}/object files/runtime.o" +readonly SECOND_OBJECT="${WORK_DIR}/second.o" +touch "${WORK_DIR}/coverage/coverage.profraw" "${OBJECT_WITH_SPACE}" "${SECOND_OBJECT}" +printf '%s\n' "${OBJECT_WITH_SPACE}" "${SECOND_OBJECT}" > "${WORK_DIR}/runtime_objects_list.txt" +printf '%s\n' "${WORK_DIR}/runtime_objects_list.txt" > "${WORK_DIR}/manifest.txt" + +cat > "${WORK_DIR}/llvm-profdata" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +[[ "$#" -eq 4 && "$1" == merge && "$2" == -output ]] +touch "$3" +EOF + +cat > "${WORK_DIR}/llvm-cov" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$@" > "${LLVM_COV_ARGS}" +printf 'TN:\n' +EOF +chmod +x "${WORK_DIR}/llvm-profdata" "${WORK_DIR}/llvm-cov" + +COVERAGE_DIR="${WORK_DIR}/coverage" \ +COVERAGE_MANIFEST="${WORK_DIR}/manifest.txt" \ +GENERATE_LLVM_LCOV=1 \ +LLVM_PROFDATA="${WORK_DIR}/llvm-profdata" \ +LLVM_COV="${WORK_DIR}/llvm-cov" \ +LLVM_COV_ARGS="${WORK_DIR}/llvm-cov.args" \ + bash "${SCRIPT}" + +printf '%s\n' \ + export \ + -instr-profile "${WORK_DIR}/coverage/_cc_coverage.dat.data" \ + -format=lcov \ + '-ignore-filename-regex=^/tmp/.+' \ + -object "${OBJECT_WITH_SPACE}" \ + -object "${SECOND_OBJECT}" > "${WORK_DIR}/expected.args" +diff -u "${WORK_DIR}/expected.args" "${WORK_DIR}/llvm-cov.args" diff --git a/tests/generated_tool_path/BUILD.bazel b/tests/generated_tool_path/BUILD.bazel new file mode 100644 index 000000000..e2eb86cc4 --- /dev/null +++ b/tests/generated_tool_path/BUILD.bazel @@ -0,0 +1,61 @@ +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("//cc/toolchains:cc_toolchain.bzl", "cc_toolchain") +load("//cc/toolchains:cc_toolchain_alias.bzl", "cc_toolchain_alias") +load(":generated_tool_path_test.bzl", "generated_tool_cc_toolchain_config", "maybe_define_generated_tool_path_targets") + +package(default_visibility = ["//visibility:private"]) + +constraint_setting(name = "toolchain_selector") + +constraint_value( + name = "use_test_toolchain", + constraint_setting = ":toolchain_selector", +) + +platform( + name = "test_platform", + constraint_values = [":use_test_toolchain"], +) + +filegroup(name = "empty") + +# A compiler wrapper generated by the build, as used by e.g. the Apple C++ toolchain. +write_file( + name = "gcc", + out = "gcc.sh", + content = [ + "#!/bin/sh", + "exit 0", + ], + is_executable = True, +) + +generated_tool_cc_toolchain_config( + name = "config", + gcc = ":gcc", +) + +cc_toolchain( + name = "cc_toolchain", + all_files = ":gcc", + ar_files = ":empty", + as_files = ":empty", + compiler_files = ":gcc", + dwp_files = ":empty", + linker_files = ":empty", + objcopy_files = ":empty", + strip_files = ":empty", + toolchain_config = ":config", + toolchain_identifier = "generated-tool-path-test", +) + +toolchain( + name = "cc_toolchain_registration", + target_compatible_with = [":use_test_toolchain"], + toolchain = ":cc_toolchain", + toolchain_type = "//cc:toolchain_type", +) + +cc_toolchain_alias(name = "current_cc_toolchain") + +maybe_define_generated_tool_path_targets() diff --git a/tests/generated_tool_path/generated_tool_path_test.bzl b/tests/generated_tool_path/generated_tool_path_test.bzl new file mode 100644 index 000000000..02b5784e5 --- /dev/null +++ b/tests/generated_tool_path/generated_tool_path_test.bzl @@ -0,0 +1,72 @@ +"""Analysis test for tool paths that refer to files generated by the build.""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test") +load("//cc:cc_toolchain_config_lib.bzl", "tool_path") # buildifier: disable=deprecated-function +load("//cc/common:cc_common.bzl", "cc_common") + +def _generated_tool_cc_toolchain_config_impl(ctx): + return cc_common.create_cc_toolchain_config_info( + ctx = ctx, + toolchain_identifier = "generated-tool-path-test", + host_system_name = "local", + target_system_name = "local", + target_cpu = "k8", + target_libc = "local", + compiler = "compiler", + abi_version = "local", + abi_libc_version = "local", + tool_paths = [ + # The path of a generated file is relative to the execution root rather than to the + # package of the toolchain. + tool_path(name = "gcc", path = ctx.file.gcc.path), + tool_path(name = "ar", path = "/usr/bin/ar"), + tool_path(name = "cpp", path = "/usr/bin/cpp"), + tool_path(name = "ld", path = "/usr/bin/ld"), + tool_path(name = "nm", path = "/usr/bin/nm"), + tool_path(name = "objdump", path = "/usr/bin/objdump"), + tool_path(name = "strip", path = "/usr/bin/strip"), + ], + ) + +generated_tool_cc_toolchain_config = rule( + implementation = _generated_tool_cc_toolchain_config_impl, + attrs = { + "gcc": attr.label( + allow_single_file = True, + mandatory = True, + ), + }, +) + +def _generated_tool_path_test_impl(env, target): + all_files = target[cc_common.CcToolchainInfo].all_files.to_list() + gcc = [file for file in all_files if file.basename == "gcc.sh"] + env.expect.that_collection(gcc).has_size(1) + cc = target[platform_common.TemplateVariableInfo].variables["CC"] + env.expect.that_str(cc).equals(gcc[0].path) + +def generated_tool_path_test(name, target): + analysis_test( + name = name, + target = target, + impl = _generated_tool_path_test_impl, + config_settings = { + "//command_line_option:extra_toolchains": [ + Label("//tests/generated_tool_path:cc_toolchain_registration"), + ], + "//command_line_option:platforms": [ + Label("//tests/generated_tool_path:test_platform"), + ], + }, + ) + +def maybe_define_generated_tool_path_targets(): + # cc_toolchain is implemented in rules_cc only for Bazel 9+. For older Bazel versions, the + # native rule is used. + if not bazel_features.cc.cc_common_is_in_rules_cc: + return + generated_tool_path_test( + name = "generated_tool_path_test", + target = ":current_cc_toolchain", + ) diff --git a/tests/integration/BUILD b/tests/integration/BUILD index 02a083ba5..66b49f31f 100644 --- a/tests/integration/BUILD +++ b/tests/integration/BUILD @@ -9,19 +9,28 @@ exports_files( visibility = ["//tests:__subpackages__"], ) +VERSION_GATE = [] if bazel_features.cc.cc_common_is_in_rules_cc else ["@platforms//:incompatible"] # copybara-uncomment-this-please +# VERSION_GATE = ["@platforms//:incompatible"] # copybara-comment-this-out-please + rules_cc_integration_test( name = "cc_tests", shard_count = 4, test_script = "cc_tests.sh", ) +rules_cc_integration_test( + name = "cc_linking_tests", + tags = ["notap"], + target_compatible_with = [] if bazel_features.cc.cc_common_is_in_rules_cc else ["@platforms//:incompatible"], + test_script = "cc_linking_tests.sh", +) + # Tests migrated from bazelbuild/bazel/src/test/shell/bazel/cc_integration_test.sh # In a separate file from cc_tests.sh to make migration clearer. rules_cc_integration_test( name = "cc_integration_test", shard_count = 10, target_compatible_with = select({ - "@platforms//os:macos": ["@platforms//:incompatible"], "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [], }) if bazel_features.cc.cc_common_is_in_rules_cc else ["@platforms//:incompatible"], @@ -40,10 +49,7 @@ rules_cc_integration_test( rules_cc_integration_test( name = "cc_shared_library_tests", - # These could probably be made to work on all CI platforms, - # but the default toolchains are not properly configured for it. target_compatible_with = select({ - "@platforms//os:macos": ["@platforms//:incompatible"], "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [], }), @@ -60,25 +66,20 @@ rules_cc_integration_test( test_script = "fission_tests.sh", ) -STATIC_LIBRARY_VERSION_GATE = [] if bazel_features.cc.cc_common_is_in_rules_cc else ["@platforms//:incompatible"] # copybara-uncomment-this-please -# STATIC_LIBRARY_VERSION_GATE = ["@platforms//:incompatible"] # copybara-comment-this-out-please - rules_cc_integration_test( name = "cc_static_library_failure_tests", tags = ["notap"], target_compatible_with = select({ + # The default Apple toolchain does not provide symbol_check. "@platforms//os:macos": ["@platforms//:incompatible"], - "//conditions:default": STATIC_LIBRARY_VERSION_GATE, + "//conditions:default": VERSION_GATE, }), test_script = "cc_static_library_failure_tests.sh", ) rules_cc_integration_test( name = "cc_shared_library_toolchain_tests", - # These could probably be made to work on all CI platforms, - # but the default toolchains are not properly configured for it. target_compatible_with = select({ - "@platforms//os:macos": ["@platforms//:incompatible"], "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [] if _features_ge("9.2.0") else ["@platforms//:incompatible"], }), diff --git a/tests/integration/cc_integration_test.sh b/tests/integration/cc_integration_test.sh index 0945605a5..4b7e39672 100755 --- a/tests/integration/cc_integration_test.sh +++ b/tests/integration/cc_integration_test.sh @@ -324,6 +324,10 @@ def _cc_lib_impl(ctx): language = "c++", compilation_outputs = compilation_outputs, linking_contexts = linking_contexts, + disallow_dynamic_library = not cc_common.is_enabled( + feature_configuration = feature_configuration, + feature_name = "supports_dynamic_linker", + ), ) library = linking_outputs.library_to_link files = [] @@ -789,13 +793,6 @@ EOF function test_aspect_accessing_args_link_action_with_tree_artifact() { is_bazel || return 0 - # This test assumes the presence of "nodeps" dynamic libraries, which do not - # function on Apple platforms. - if is_darwin; then - return 0 - fi - - local package="${FUNCNAME[0]}" mkdir -p "${package}" cat > "${package}/makes_tree_artifacts.sh" < pkg/BUILD < pkg/BUILD < BUILD <<'EOF' load("@rules_cc//cc:cc_binary.bzl", "cc_binary") @@ -1831,19 +1826,15 @@ int main() { } EOF - export BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 export BAZEL_CXXOPTS=-DEXIT_CODE=0 bazel build //:main_c \ - --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \ --repo_env=BAZEL_CXXOPTS=-DEXIT_CODE=0 && fail "Expected C compilation to fail" bazel run //:main_cpp \ - --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \ --repo_env=BAZEL_CXXOPTS=-DEXIT_CODE=0 || fail "Expected C++ compilation to pass" } function test_bazel_conlyopts() { is_bazel || return 0 - is_darwin && return 0 cat > BUILD <<'EOF' load("@rules_cc//cc:cc_binary.bzl", "cc_binary") @@ -1869,13 +1860,10 @@ int main() { } EOF - export BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 export BAZEL_CONLYOPTS=-DEXIT_CODE=0 bazel build //:main_cpp \ - --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \ --repo_env=BAZEL_CONLYOPTS=-DEXIT_CODE=0 && fail "Expected C++ compilation to fail" bazel run //:main_c \ - --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \ --repo_env=BAZEL_CONLYOPTS=-DEXIT_CODE=0 || fail "Expected C compilation to pass" } @@ -1954,7 +1942,7 @@ function test_cc_toolchain_asan_feature() { __is_installed "lib$feature" || return 0 - mkdir pkg + mkdir -p pkg cat > pkg/BUILD < pkg/BUILD < pkg/BUILD <<'EOF' load("@rules_cc//cc:cc_library.bzl", "cc_library") package(features = ["parse_headers"]) diff --git a/tests/integration/cc_linking_tests.sh b/tests/integration/cc_linking_tests.sh new file mode 100755 index 000000000..4761542de --- /dev/null +++ b/tests/integration/cc_linking_tests.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(rlocation rules_cc/tests/test_utils.sh)" +source "$(rlocation rules_cc/tests/unittest.bash)" + +function test_linker_param_files_with_spaces() { + cat > BUILD <<'EOF' +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "library with spaces", + srcs = ["source with spaces.cc"], +) + +cc_binary( + name = "binary with spaces", + srcs = ["main.cc"], + deps = [":library with spaces"], +) + +cc_binary( + name = "shared library with spaces", + srcs = ["source with spaces.cc"], + linkshared = True, +) +EOF + + cat > 'source with spaces.cc' <<'EOF' +#ifdef _WIN32 +__declspec(dllexport) +#endif +int value() { return 42; } +EOF + cat > main.cc <<'EOF' +int value(); +int main() { return value() == 42 ? 0 : 1; } +EOF + + # TODO: compiler_param_file is broken with spaces on Windows + bazel build --features=-compiler_param_file --min_param_file_size=0 \ + ':library with spaces' ':binary with spaces' ':shared library with spaces' \ + >& "$TEST_log" || fail "Build with spaces in response files failed" + local binary='bazel-bin/binary with spaces' + if is_windows; then + binary+='.exe' + fi + "$binary" >> "$TEST_log" || fail "Binary failed" +} + +run_suite "Integration tests for C++ linking" diff --git a/tests/integration/cc_shared_library_tests.sh b/tests/integration/cc_shared_library_tests.sh index 437fce83e..24471cd70 100755 --- a/tests/integration/cc_shared_library_tests.sh +++ b/tests/integration/cc_shared_library_tests.sh @@ -104,13 +104,22 @@ EOF --experimental_cc_shared_library \ //foo:foo_shared >& "${TEST_log}" || fail "Expected build to succeed" - so_file="bazel-bin/foo/libfoo_shared.so" + local so_file="bazel-bin/foo/libfoo_shared.so" + local nm_flag="-D" + local symbol_prefix="_Z" + if is_darwin; then + so_file="bazel-bin/foo/libfoo_shared.dylib" + symbol_prefix="__Z" + # nm -g reads global symbols rather than the Mach-O export trie. This test + # assumes that defined global symbols are also exported. + nm_flag="-g" + fi - nm -D "$so_file" | grep -q "T _Z3foov" || fail "_Z3foov not linked statically" - nm -D "$so_file" | grep -q "T _Z4foo2v" || fail "_Z4foo2v not linked statically" - nm -D "$so_file" | grep -q "U _Z3barv" || fail "_Z3barv not linked dynamically" - nm -D "$so_file" | grep -q "baz" && fail "baz shoulod not be linked in anyway" - nm -D "$so_file" | grep -q "bar2" && fail "bar2 shoulod not be linked in anyway" + nm "$nm_flag" "$so_file" | grep -q "T ${symbol_prefix}3foov" || fail "${symbol_prefix}3foov not linked statically" + nm "$nm_flag" "$so_file" | grep -q "T ${symbol_prefix}4foo2v" || fail "${symbol_prefix}4foo2v not linked statically" + nm "$nm_flag" "$so_file" | grep -q "U ${symbol_prefix}3barv" || fail "${symbol_prefix}3barv not linked dynamically" + nm "$nm_flag" "$so_file" | grep -q "baz" && fail "baz shoulod not be linked in anyway" + nm "$nm_flag" "$so_file" | grep -q "bar2" && fail "bar2 shoulod not be linked in anyway" return 0 } diff --git a/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzl b/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzl index d9a695de1..59aadcfe9 100644 --- a/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzl +++ b/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzl @@ -20,6 +20,7 @@ load("//cc/toolchains/impl:collect.bzl", "collect_action_types") visibility("private") def _generate_action_type_set_impl(ctx): + # type: (...) -> DefaultInfo out = ctx.actions.declare_file(ctx.attr.output.name) actions = sorted([ action.name @@ -39,7 +40,12 @@ _generate_action_type_set = rule( }, ) +# TODO(https://github.com/bazelbuild/bazel/pull/30373): Use native type syntax +# when Bazel 8.8 is the minimum supported version. +# type _LabelInput = str | Label | select[str | Label] + def action_type_set_diff_test(name, action_type_set, expected): + # type: (str, _LabelInput, _LabelInput) -> None output_filename = name + ".actual.txt" _generate_action_type_set( name = name + "_actual", diff --git a/tests/rule_based_toolchain/action_type_sets/goldens/source_compile_actions.txt b/tests/rule_based_toolchain/action_type_sets/goldens/source_compile_actions.txt index e039c97e7..c59b6f593 100644 --- a/tests/rule_based_toolchain/action_type_sets/goldens/source_compile_actions.txt +++ b/tests/rule_based_toolchain/action_type_sets/goldens/source_compile_actions.txt @@ -1,7 +1,6 @@ assemble c++-compile c++-header-parsing -c++-module-codegen c++-module-compile c-compile clif-match diff --git a/tests/rule_based_toolchain/args/make_var_provider.bzl b/tests/rule_based_toolchain/args/make_var_provider.bzl index 740387397..9f5d0d76a 100644 --- a/tests/rule_based_toolchain/args/make_var_provider.bzl +++ b/tests/rule_based_toolchain/args/make_var_provider.bzl @@ -1,6 +1,7 @@ """Helper rule for providing TemplateVariableInfo in tests.""" def _make_var_provider_impl(ctx): + # type: (...) -> list[TemplateVariableInfo] return [platform_common.TemplateVariableInfo(ctx.attr.variables)] make_var_provider = rule( diff --git a/tests/rule_based_toolchain/features/features_test.bzl b/tests/rule_based_toolchain/features/features_test.bzl index a0d479afa..67f9787bb 100644 --- a/tests/rule_based_toolchain/features/features_test.bzl +++ b/tests/rule_based_toolchain/features/features_test.bzl @@ -143,6 +143,8 @@ def _external_feature_is_a_feature_test(env, targets): FeatureInfo, ) external_feature.name().equals("builtin_feature") + external_feature.args().allowlist_include_directories().contains_exactly([]) + external_feature.args().allowlist_absolute_include_directories().contains_exactly([]) # It's not a string, but we don't have a factory for the type. env.expect.that_str(convert_feature(external_feature.actual)).equals(None) diff --git a/tests/rule_based_toolchain/helpers.bzl b/tests/rule_based_toolchain/helpers.bzl index d0901a310..bffe8ca1b 100644 --- a/tests/rule_based_toolchain/helpers.bzl +++ b/tests/rule_based_toolchain/helpers.bzl @@ -18,6 +18,7 @@ load("@bazel_features//:features.bzl", "bazel_features") visibility("//tests/rule_based_toolchain/...") def path_pattern(p): + # type: (str) -> str """Wraps a path with %{path:...} when supported by Bazel. Mirrors the behavior of cc/toolchains/impl/nested_args.bzl's _format_target. diff --git a/tests/rule_based_toolchain/legacy_features_as_args/BUILD b/tests/rule_based_toolchain/legacy_features_as_args/BUILD index e64f152af..055792630 100644 --- a/tests/rule_based_toolchain/legacy_features_as_args/BUILD +++ b/tests/rule_based_toolchain/legacy_features_as_args/BUILD @@ -5,6 +5,9 @@ load(":goldens/macos/libraries_to_link.bzl", _MACOS_LIBRARIES_TO_LINK = "GOLDEN" load(":goldens/macos/runtime_library_search_directories.bzl", _MACOS_RUNTIME_LIBRARY_SEARCH_DIRECTORIES = "GOLDEN") load(":goldens/macos/thin_lto.bzl", _MACOS_THIN_LTO = "GOLDEN") load(":goldens/unix/archiver_flags.bzl", _UNIX_ARCHIVER_FLAGS = "GOLDEN") +load(":goldens/unix/compiler_input_flags.bzl", _UNIX_COMPILER_INPUT_FLAGS = "GOLDEN") +load(":goldens/unix/compiler_output_flags.bzl", _UNIX_COMPILER_OUTPUT_FLAGS = "GOLDEN") +load(":goldens/unix/cpp20_module_flags.bzl", _UNIX_CPP20_MODULE_FLAGS = "GOLDEN") load(":goldens/unix/force_pic_flags.bzl", _UNIX_FORCE_PIC_FLAGS = "GOLDEN") load(":goldens/unix/libraries_to_link.bzl", _UNIX_LIBRARIES_TO_LINK = "GOLDEN") load(":goldens/unix/linker_param_file.bzl", _UNIX_LINKER_PARAM_FILE = "GOLDEN") @@ -155,3 +158,27 @@ compare_cc_feature_implementation( expected = _WINDOWS_THIN_LTO, platform = ":windows", ) + +# C++20 modules. Bazel's CppActionConfigs.java predates them, so these goldens +# match the features of the same names in unix_cc_toolchain_config. + +compare_cc_feature_implementation( + name = "cpp20_module_flags_unix_test", + actual_implementation = "//cc/toolchains/args/cpp_modules:feature", + expected = _UNIX_CPP20_MODULE_FLAGS, + platform = ":linux", +) + +compare_cc_feature_implementation( + name = "compiler_input_flags_unix_test", + actual_implementation = "//cc/toolchains/args/compiler_input_flags:feature", + expected = _UNIX_COMPILER_INPUT_FLAGS, + platform = ":linux", +) + +compare_cc_feature_implementation( + name = "compiler_output_flags_unix_test", + actual_implementation = "//cc/toolchains/args/compiler_output_flags:feature", + expected = _UNIX_COMPILER_OUTPUT_FLAGS, + platform = ":linux", +) diff --git a/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_input_flags.bzl b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_input_flags.bzl new file mode 100644 index 000000000..8126866ce --- /dev/null +++ b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_input_flags.bzl @@ -0,0 +1,57 @@ +# Copyright 2026 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. +"""Expected compiler_input_flags legacy feature textproto on unix.""" + +visibility("private") + +GOLDEN = """enabled: false +flag_sets { + actions: "assemble" + actions: "c++-compile" + actions: "c++-module-codegen" + actions: "c++-module-compile" + actions: "c++-module-deps-scanning" + actions: "c++20-module-codegen" + actions: "c-compile" + actions: "clif-match" + actions: "linkstamp-compile" + actions: "lto-backend" + actions: "objc++-compile" + actions: "objc-compile" + actions: "preprocess-assemble" + flag_groups { + expand_if_available: "source_file" + flags: "-c" + flags: "%{source_file}" + } +} +flag_sets { + actions: "c++20-module-compile" + flag_groups { + expand_if_available: "source_file" + flags: "-x" + flags: "c++-module" + flags: "-c" + flags: "%{source_file}" + } +} +flag_sets { + actions: "c++-header-parsing" + flag_groups { + expand_if_available: "source_file" + flags: "%{source_file}" + } +} +name: "compiler_input_flags" +""" diff --git a/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_output_flags.bzl b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_output_flags.bzl new file mode 100644 index 000000000..377244e2a --- /dev/null +++ b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/compiler_output_flags.bzl @@ -0,0 +1,86 @@ +# Copyright 2026 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. +"""Expected compiler_output_flags legacy feature textproto on unix.""" + +visibility("private") + +GOLDEN = """enabled: false +env_sets { + actions: "c++-module-deps-scanning" + env_entries { + expand_if_available: "output_file" + key: "DEPS_SCANNER_OUTPUT_FILE" + value: "%{output_file}" + } +} +flag_sets { + actions: "assemble" + actions: "c++-compile" + actions: "c++-header-parsing" + actions: "c++-module-codegen" + actions: "c++-module-compile" + actions: "c-compile" + actions: "clif-match" + actions: "linkstamp-compile" + actions: "lto-backend" + actions: "objc++-compile" + actions: "objc-compile" + actions: "preprocess-assemble" + flag_groups { + expand_if_available: "output_assembly_file" + flags: "-S" + } +} +flag_sets { + actions: "assemble" + actions: "c++-compile" + actions: "c++-header-parsing" + actions: "c++-module-codegen" + actions: "c++-module-compile" + actions: "c-compile" + actions: "clif-match" + actions: "linkstamp-compile" + actions: "lto-backend" + actions: "objc++-compile" + actions: "objc-compile" + actions: "preprocess-assemble" + flag_groups { + expand_if_available: "output_preprocess_file" + flags: "-E" + } +} +flag_sets { + actions: "assemble" + actions: "c++-compile" + actions: "c++-header-parsing" + actions: "c++-module-codegen" + actions: "c++-module-compile" + actions: "c++-module-deps-scanning" + actions: "c++20-module-codegen" + actions: "c++20-module-compile" + actions: "c-compile" + actions: "clif-match" + actions: "linkstamp-compile" + actions: "lto-backend" + actions: "objc++-compile" + actions: "objc-compile" + actions: "preprocess-assemble" + flag_groups { + expand_if_available: "output_file" + flags: "-o" + flags: "%{output_file}" + } +} +name: "compiler_output_flags" +""" diff --git a/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/cpp20_module_flags.bzl b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/cpp20_module_flags.bzl new file mode 100644 index 000000000..3af6099f7 --- /dev/null +++ b/tests/rule_based_toolchain/legacy_features_as_args/goldens/unix/cpp20_module_flags.bzl @@ -0,0 +1,36 @@ +# Copyright 2026 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. +"""Expected cpp20_module_flags legacy feature textproto on unix.""" + +visibility("private") + +GOLDEN = """enabled: false +flag_sets { + actions: "c++-compile" + actions: "c++20-module-codegen" + actions: "c++20-module-compile" + flag_groups { + expand_if_available: "cpp_module_modmap_file" + flags: "@%{cpp_module_modmap_file}" + } +} +flag_sets { + actions: "c++20-module-compile" + flag_groups { + expand_if_available: "cpp_module_output_file" + flags: "-fmodule-output=%{cpp_module_output_file}" + } +} +name: "cpp20_module_flags" +""" diff --git a/tests/rule_based_toolchain/nested_args/BUILD b/tests/rule_based_toolchain/nested_args/BUILD index 180453741..5bd2c15e3 100644 --- a/tests/rule_based_toolchain/nested_args/BUILD +++ b/tests/rule_based_toolchain/nested_args/BUILD @@ -1,3 +1,4 @@ +load("@bazel_skylib//rules:common_settings.bzl", "string_list_setting") load("//cc/toolchains:nested_args.bzl", "cc_nested_args") load("//cc/toolchains/impl:variables.bzl", "cc_variable", "types") load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite") @@ -31,6 +32,13 @@ cc_nested_args( toolchains = [":make_vars"], ) +filegroup(name = "empty_files") + +string_list_setting( + name = "unsupported_setting", + build_setting_default = ["unsupported"], +) + analysis_test_suite( name = "test_suite", targets = TARGETS, diff --git a/tests/rule_based_toolchain/nested_args/nested_args_test.bzl b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl index 319a64fce..ba4d9a047 100644 --- a/tests/rule_based_toolchain/nested_args/nested_args_test.bzl +++ b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl @@ -122,6 +122,12 @@ def _format_args_test(env, targets): "file", ]) + for target, message in [ + (targets.empty_files, "should be either a variable, a directory, or a single file"), + (targets.unsupported_setting, "had an unsupported build setting type list"), + ]: + _expect_that_formatted(env, ["{value}"], {"value": target}).err().contains(message) + res = _expect_that_formatted( env, ["{var}", "{var}"], @@ -276,6 +282,8 @@ def _nested_make_vars_test(env, targets): nested.legacy_flag_group().equals(flag_group(flags = ["--path=/usr/local", "-DFOO"])) TARGETS = [ + ":empty_files", + ":unsupported_setting", ":foo", ":my_list", ":nested_with_make_vars", diff --git a/tests/rule_based_toolchain/tool/execution_requirements.bzl b/tests/rule_based_toolchain/tool/execution_requirements.bzl index 28be39a30..1be64ee9b 100644 --- a/tests/rule_based_toolchain/tool/execution_requirements.bzl +++ b/tests/rule_based_toolchain/tool/execution_requirements.bzl @@ -18,6 +18,7 @@ load("//cc/toolchains:providers.bzl", "ExecutionRequirementsInfo") visibility("private") def _test_execution_requirements_impl(ctx): + # type: (...) -> list[ExecutionRequirementsInfo] return [ ExecutionRequirementsInfo( label = ctx.label, diff --git a/tests/rule_based_toolchain/tool/tool_test.bzl b/tests/rule_based_toolchain/tool/tool_test.bzl index f79565703..ae5f3de16 100644 --- a/tests/rule_based_toolchain/tool/tool_test.bzl +++ b/tests/rule_based_toolchain/tool/tool_test.bzl @@ -58,6 +58,14 @@ def _wrapped_tool_includes_runfiles_test(env, targets): _BIN, ]) +def _tool_capability_has_no_include_directories_test(env, targets): + feature = env.expect.that_value( + value = targets.tool[ToolInfo].capabilities[0].feature, + factory = subjects.FeatureInfo, + ) + feature.allowlist_absolute_include_directories().contains_exactly([]) + feature.args().allowlist_absolute_include_directories().contains_exactly([]) + def _tool_with_allowlist_include_directories_test(env, targets): tool = env.expect.that_target(targets.tool_with_allowlist_include_directories).provider(ToolInfo) tool.allowlist_include_directories().contains_exactly([_TOOL_DIRECTORY]) @@ -145,6 +153,7 @@ TESTS = { "tool_test": lambda env, targets: _tool_test(env, targets.tool), "directory_tool_test": lambda env, targets: _tool_test(env, targets.directory_tool), "wrapped_tool_includes_runfiles_test": _wrapped_tool_includes_runfiles_test, + "tool_capability_has_no_include_directories_test": _tool_capability_has_no_include_directories_test, "collect_tools_collects_tools_test": _collect_tools_collects_tools_test, "collect_tools_collects_binaries_test": _collect_tools_collects_binaries_test, "collect_tools_collects_single_files_test": _collect_tools_collects_single_files_test, diff --git a/tests/rule_based_toolchain/variables/variables_test.bzl b/tests/rule_based_toolchain/variables/variables_test.bzl index 98a64fd38..cd947c2f1 100644 --- a/tests/rule_based_toolchain/variables/variables_test.bzl +++ b/tests/rule_based_toolchain/variables/variables_test.bzl @@ -89,6 +89,7 @@ str_list expect_type("struct.unknown").err().equals("""Unable to find "unknown" in "struct", which had the following attributes: nested_str: string nested_str_list: List[string]""") + expect_type("struct.unknown", overrides = {"struct.unknown": types.string}).ok().equals(types.string) expect_type("struct", actions = [c_compile]).ok() expect_type("struct", actions = [c_compile, cpp_compile]).err().equals(