From 818408bd9179ccb7c3f4d4b8e9d246ff36ddcb3d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 29 Oct 2026 12:55:40 -0400 Subject: [PATCH 1/9] Describe C++ toolchain helpers and providers Give toolchain configuration, provider collectors and rule helpers typed inputs and results. Keep simple signatures in their implementations; represent provider fields and generic functions in companion interfaces. --- cc/cc_import.bzl | 1 + cc/cc_postmark.bzl | 8 +- cc/cc_postmark_initializers.bzli | 4 + cc/cc_shared_library.bzl | 1 + cc/cc_toolchain_config_lib.bzl | 62 ++-- cc/cc_toolchain_config_lib.bzli | 201 ++++++++++++ cc/extensions.bzl | 8 +- cc/private/cc_common.bzl | 4 +- cc/private/cc_info.bzl | 2 +- cc/private/objc_info.bzl | 2 + cc/private/objc_info.bzli | 19 ++ cc/private/paths.bzl | 1 + cc/private/rules_impl/cc_binary_impl.bzl | 4 +- .../rules_impl/failing_cc_proto_library.bzl | 1 + .../rules_impl/fdo/fdo_prefetch_hints.bzl | 1 + cc/private/rules_impl/fdo/fdo_profile.bzl | 1 + cc/private/rules_impl/fdo/memprof_profile.bzl | 1 + .../rules_impl/fdo/propeller_optimize.bzl | 1 + cc/private/rules_impl/native_cc_common.bzl | 4 +- .../objc_compilation_artifacts_info.bzl | 5 +- .../objc_compilation_artifacts_info.bzli | 47 +++ .../objc_compilation_context_info.bzli | 35 +++ cc/private/toolchain/cc_configure.bzl | 6 + cc/private/toolchain/lib_cc_configure.bzl | 28 +- cc/private/toolchain/lib_cc_configure.bzli | 44 +++ cc/private/toolchain/unix_cc_configure.bzl | 172 +++++----- cc/private/toolchain/unix_cc_configure.bzli | 84 +++++ cc/private/toolchain/windows_cc_configure.bzl | 85 +++-- .../toolchain_config/legacy_features.bzl | 6 +- .../toolchain_config/legacy_features.bzli | 39 +++ cc/repositories.bzl | 6 +- cc/starlark_doc_extract_helper.bzl | 1 + cc/system_library.bzl | 11 +- cc/system_library.bzli | 27 ++ cc/toolchains/actions.bzl | 2 + cc/toolchains/args.bzl | 3 +- .../private/library_link_args.bzl | 2 + cc/toolchains/args_list.bzl | 1 + cc/toolchains/artifacts.bzl | 1 + cc/toolchains/cc_toolchain_info.bzli | 294 ++++++++++++++++++ cc/toolchains/directory_tool.bzli | 17 + cc/toolchains/feature.bzl | 1 + cc/toolchains/feature_constraint.bzl | 1 + cc/toolchains/feature_set.bzl | 1 + cc/toolchains/impl/cc_artifact_category.bzl | 1 + cc/toolchains/impl/collect.bzl | 29 +- cc/toolchains/impl/collect.bzli | 11 + cc/toolchains/impl/external_feature.bzl | 3 + cc/toolchains/impl/label_utils.bzl | 1 + cc/toolchains/impl/legacy_converter.bzl | 8 +- cc/toolchains/impl/legacy_converter.bzli | 25 ++ cc/toolchains/impl/libc_version.bzl | 1 + cc/toolchains/impl/markdown_helpers.bzl | 2 + cc/toolchains/impl/nested_args.bzl | 22 +- cc/toolchains/impl/nested_args.bzli | 45 +++ cc/toolchains/impl/variables.bzl | 24 +- cc/toolchains/impl/variables.bzli | 49 +++ cc/toolchains/legacy_file_group.bzli | 12 + cc/toolchains/legacy_tool.bzl | 1 + cc/toolchains/make_variable.bzl | 1 + cc/toolchains/mutually_exclusive_category.bzl | 1 + cc/toolchains/nested_args.bzli | 23 ++ cc/toolchains/tool.bzl | 9 +- cc/toolchains/tool.bzli | 34 ++ cc/toolchains/tool_capability.bzl | 3 + cc/toolchains/tool_map.bzl | 4 +- cc/toolchains/tool_map.bzli | 12 + cc/use_cc_toolchain.bzl | 1 + local_bazel.bzl | 3 + .../toolchains/additional_toolchains.bzl | 1 + .../toolchains/additional_toolchains.bzli | 3 + .../action_type_set_diff_test.bzl | 1 + .../action_type_set_diff_test.bzli | 5 + .../args/make_var_provider.bzl | 1 + .../features/features_test.bzl | 2 + tests/rule_based_toolchain/helpers.bzl | 1 + tests/rule_based_toolchain/nested_args/BUILD | 8 + .../nested_args/nested_args_test.bzl | 8 + .../tool/execution_requirements.bzl | 1 + .../tool/execution_requirements.bzli | 3 + tests/rule_based_toolchain/tool/tool_test.bzl | 9 + .../variables/variables_test.bzl | 1 + 82 files changed, 1393 insertions(+), 221 deletions(-) create mode 100644 cc/cc_postmark_initializers.bzli create mode 100644 cc/cc_toolchain_config_lib.bzli create mode 100644 cc/private/objc_info.bzli create mode 100644 cc/private/rules_impl/objc_compilation_artifacts_info.bzli create mode 100644 cc/private/rules_impl/objc_compilation_context_info.bzli create mode 100644 cc/private/toolchain/lib_cc_configure.bzli create mode 100644 cc/private/toolchain/unix_cc_configure.bzli create mode 100644 cc/private/toolchain_config/legacy_features.bzli create mode 100644 cc/system_library.bzli create mode 100644 cc/toolchains/cc_toolchain_info.bzli create mode 100644 cc/toolchains/directory_tool.bzli create mode 100644 cc/toolchains/impl/collect.bzli create mode 100644 cc/toolchains/impl/legacy_converter.bzli create mode 100644 cc/toolchains/impl/nested_args.bzli create mode 100644 cc/toolchains/impl/variables.bzli create mode 100644 cc/toolchains/legacy_file_group.bzli create mode 100644 cc/toolchains/nested_args.bzli create mode 100644 cc/toolchains/tool.bzli create mode 100644 cc/toolchains/tool_map.bzli create mode 100644 tests/cc/testutil/toolchains/additional_toolchains.bzli create mode 100644 tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli create mode 100644 tests/rule_based_toolchain/tool/execution_requirements.bzli diff --git a/cc/cc_import.bzl b/cc/cc_import.bzl index 176243646..2292ef1f2 100644 --- a/cc/cc_import.bzl +++ b/cc/cc_import.bzl @@ -16,4 +16,5 @@ load("@cc_compatibility_proxy//:proxy.bzl", _cc_import = "cc_import") 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..28c225706 100644 --- a/cc/cc_postmark.bzl +++ b/cc/cc_postmark.bzl @@ -14,20 +14,18 @@ """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 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..fa7ae1b89 --- /dev/null +++ b/cc/cc_postmark_initializers.bzli @@ -0,0 +1,4 @@ +"""Contracts for the PostMark initializer compatibility layer.""" + +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..d66fea01e 100644 --- a/cc/cc_shared_library.bzl +++ b/cc/cc_shared_library.bzl @@ -16,4 +16,5 @@ load("@cc_compatibility_proxy//:proxy.bzl", _cc_shared_library = "cc_shared_library") 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 798a19ff0..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,6 +457,7 @@ ToolPathInfo = provider( ) def tool_path(name, path): + # type: (str, str) -> ToolPathInfo """ Tool locations. Args: @@ -475,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: @@ -507,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, @@ -536,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 @@ -566,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, @@ -589,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: @@ -617,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..62bcd0067 --- /dev/null +++ b/cc/cc_toolchain_config_lib.bzli @@ -0,0 +1,201 @@ +# 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. +""" + +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/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/cc_common.bzl b/cc/private/cc_common.bzl index aa06fdd1c..ea80a731a 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl @@ -252,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 = [], diff --git a/cc/private/cc_info.bzl b/cc/private/cc_info.bzl index 76eca9572..7d6a5999f 100644 --- a/cc/private/cc_info.bzl +++ b/cc/private/cc_info.bzl @@ -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/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..fb43100f4 --- /dev/null +++ b/cc/private/objc_info.bzli @@ -0,0 +1,19 @@ +"""Objective-C compilation and linking information.""" + +# 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/cc_binary_impl.bzl b/cc/private/rules_impl/cc_binary_impl.bzl index 7f08ed98b..152f5f6f0 100644 --- a/cc/private/rules_impl/cc_binary_impl.bzl +++ b/cc/private/rules_impl/cc_binary_impl.bzl @@ -815,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/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/toolchain/cc_configure.bzl b/cc/private/toolchain/cc_configure.bzl index 1d254af8b..fa28a5d81 100644 --- a/cc/private/toolchain/cc_configure.bzl +++ b/cc/private/toolchain/cc_configure.bzl @@ -19,6 +19,9 @@ 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": @@ -35,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: @@ -71,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: @@ -163,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 32a22ac9b..241adae99 100644 --- a/cc/private/toolchain/lib_cc_configure.bzl +++ b/cc/private/toolchain/lib_cc_configure.bzl @@ -30,6 +30,7 @@ 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 @@ -42,14 +43,21 @@ def copy_file(repository_ctx, src, dest): """ 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: @@ -80,7 +88,7 @@ def split_escaped(string, delimiter): i = 0 result = [] - accumulator = [] + accumulator = [] # type: list[str] length = len(string) delimiter_length = len(delimiter) @@ -111,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" @@ -142,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! @@ -175,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, @@ -228,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: @@ -267,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..82430005c --- /dev/null +++ b/cc/private/toolchain/lib_cc_configure.bzli @@ -0,0 +1,44 @@ +"""Type contracts for C++ toolchain configuration helpers.""" + +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 ce1282e7b..c093cdc73 100644 --- a/cc/private/toolchain/unix_cc_configure.bzl +++ b/cc/private/toolchain/unix_cc_configure.bzl @@ -17,10 +17,9 @@ load( ":lib_cc_configure.bzl", "auto_configure_fail", - "auto_configure_warning", "auto_configure_warning_maybe", "copy_file", - "escape_string", + "escape_percent", "execute", "get_env_var", "get_starlark_list", @@ -32,10 +31,10 @@ 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): + # 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")) @@ -83,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. @@ -120,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: @@ -159,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): @@ -307,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: @@ -325,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", @@ -365,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] @@ -386,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: @@ -421,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 = [] @@ -486,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", @@ -498,10 +483,10 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): "validate_static_library.sh", 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" @@ -513,8 +498,8 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): "cc_wrapper.sh", 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 = ( @@ -526,14 +511,14 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): if sibling_deps_scanner.exists: deps_scanner = str(sibling_deps_scanner) else: - deps_scanner = which(repository_ctx, "clang-scan-deps", deps_scanner) + deps_scanner = which(repository_ctx, "clang-scan-deps") or deps_scanner repository_ctx.template( "deps_scanner_wrapper.sh", 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)), }, ) @@ -564,10 +549,10 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): 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 = [] + bin_search_flags = [] # type: list[string] if not linker: ld_path = repository_ctx.path(tool_paths["ld"]) if ld_path.dirname != cc_path.dirname: @@ -577,7 +562,7 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): if ld_binary: ld_path = repository_ctx.path(ld_binary) if ld_path.dirname != cc_path.dirname: - bin_search_flags.append("-B" + escape_string(str(ld_path.dirname))) + bin_search_flags.append("-B" + escape_percent(str(ld_path.dirname))) force_linker_flags = [] if linker: force_linker_flags.extend(linker.flags) @@ -701,13 +686,13 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): 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", @@ -743,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), @@ -758,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", @@ -775,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, @@ -832,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..8243cb829 --- /dev/null +++ b/cc/private/toolchain/unix_cc_configure.bzli @@ -0,0 +1,84 @@ +"""Contracts for discovering and configuring a local Unix C++ toolchain.""" + +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/windows_cc_configure.bzl b/cc/private/toolchain/windows_cc_configure.bzl index 790d41c1f..f37e7f179 100644 --- a/cc/private/toolchain/windows_cc_configure.bzl +++ b/cc/private/toolchain/windows_cc_configure.bzl @@ -20,7 +20,7 @@ load( "auto_configure_warning", "auto_configure_warning_maybe", "copy_file", - "escape_string", + "escape_percent", "execute", "get_starlark_list", "split_escaped", @@ -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.""" @@ -715,8 +717,8 @@ def _get_msvc_vars(repository_ctx, target_arch = "x64", msvc_vars_x64 = None): 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, target_arch = "x64", msvc_vars_x64 = None): 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"], @@ -878,33 +880,17 @@ def _get_msvc_deps_scanner_vars(repository_ctx, template_vars, target_arch = "x6 "%{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,12 +898,13 @@ 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: 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..c7fed2788 --- /dev/null +++ b/cc/private/toolchain_config/legacy_features.bzli @@ -0,0 +1,39 @@ +# 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.""" + +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/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..55460b24d 100644 --- a/cc/starlark_doc_extract_helper.bzl +++ b/cc/starlark_doc_extract_helper.bzl @@ -3,6 +3,7 @@ visibility("//cc/...") 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..f0cdbee71 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, @@ -180,7 +189,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/system_library.bzli b/cc/system_library.bzli new file mode 100644 index 000000000..090f53d7a --- /dev/null +++ b/cc/system_library.bzli @@ -0,0 +1,27 @@ +# 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. +"""System library and header discovery.""" + +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: repository_ctx[_Attrs]) -> repo_metadata | None: ... 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/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/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_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.bzli b/cc/toolchains/directory_tool.bzli new file mode 100644 index 000000000..9804c209f --- /dev/null +++ b/cc/toolchains/directory_tool.bzli @@ -0,0 +1,17 @@ +"""Tools extracted from directories.""" + +load("//cc/toolchains:tool.bzl", _cc_tool = "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( + name: str, + directory: str | Label | select[str | Label | None], + executable: str, + data: _StringsInput = ..., + exclude: _StringsInput = ..., + allow_empty: _Bool | select[_Bool | None] | None = ..., + **kwargs: Unpack[KeywordArgs[_cc_tool, "name", "src", "data"]], +) -> None: ... 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/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..4943c37d4 --- /dev/null +++ b/cc/toolchains/impl/collect.bzli @@ -0,0 +1,11 @@ +"""Typed collections of toolchain provider values.""" + +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..facd81898 --- /dev/null +++ b/cc/toolchains/impl/legacy_converter.bzli @@ -0,0 +1,25 @@ +"""Conversion from rule-based toolchain providers to legacy configuration values.""" + +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..e2b6fb6b8 --- /dev/null +++ b/cc/toolchains/impl/nested_args.bzli @@ -0,0 +1,45 @@ +"""Formatting and validation of nested toolchain arguments.""" + +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..566b3ab12 --- /dev/null +++ b/cc/toolchains/impl/variables.bzli @@ -0,0 +1,49 @@ +"""Toolchain variable descriptors.""" + +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.bzli b/cc/toolchains/legacy_file_group.bzli new file mode 100644 index 000000000..7268adcbb --- /dev/null +++ b/cc/toolchains/legacy_file_group.bzli @@ -0,0 +1,12 @@ +"""Action labels grouped by the legacy attributes of cc_toolchain.""" + +LEGACY_FILE_GROUPS: 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], +}] 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.bzli b/cc/toolchains/nested_args.bzli new file mode 100644 index 000000000..7eac8bda5 --- /dev/null +++ b/cc/toolchains/nested_args.bzli @@ -0,0 +1,23 @@ +"""Nested command-line arguments.""" + +type _Label = str | Label +type _LabelInput = _Label | select[_Label | None] | None +type _ListInput[T] = Collection[T] | depset[T] | select[Collection[T] | depset[T] | None] | None +type _StringInput = str | select[str | None] | None + +def cc_nested_args( + *, + name: str, + args: _ListInput[str] = ..., + data: _ListInput[_Label] = ..., + format: Mapping[str, _Label] = ..., + iterate_over: _LabelInput = ..., + nested: _ListInput[_Label] = ..., + requires_not_none: _LabelInput = ..., + requires_none: _LabelInput = ..., + requires_true: _LabelInput = ..., + requires_false: _LabelInput = ..., + requires_equal: _LabelInput = ..., + requires_equal_value: _StringInput = ..., + **kwargs: Unpack[CommonRuleAttrs], +) -> None: ... diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl index 246d54c55..3fb958fd4 100644 --- a/cc/toolchains/tool.bzl +++ b/cc/toolchains/tool.bzl @@ -33,7 +33,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 +45,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 +83,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 @@ -188,7 +191,7 @@ cc_tool( def cc_tool( *, name, - src = None, + src, data = None, allowlist_include_directories = None, env = None, diff --git a/cc/toolchains/tool.bzli b/cc/toolchains/tool.bzli new file mode 100644 index 000000000..6cfceca24 --- /dev/null +++ b/cc/toolchains/tool.bzli @@ -0,0 +1,34 @@ +"""Tools, their execution inputs, and forwarded rule attributes.""" + +load("//cc/toolchains:cc_toolchain_info.bzli", "ToolInfo") + +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: ctx[object, _Attrs]) -> list[ToolInfo | DefaultInfo]: ... +def cc_tool( + *, + name: str, + src: _LabelInput, + data: _LabelsInput = ..., + allowlist_include_directories: _LabelsInput = ..., + env: _EnvironmentInput = ..., + execution_requirements: _LabelsInput = ..., + format: Mapping[str, str | Label] = ..., + capabilities: _LabelsInput = ..., + **kwargs: Unpack[CommonExecutableRuleAttrs], +) -> None: ... 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..91a62af81 --- /dev/null +++ b/cc/toolchains/tool_map.bzli @@ -0,0 +1,12 @@ +"""The association between toolchain actions and executable tools.""" + +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/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/tests/cc/testutil/toolchains/additional_toolchains.bzl b/tests/cc/testutil/toolchains/additional_toolchains.bzl index 1a8320cb7..ea7c8419f 100644 --- a/tests/cc/testutil/toolchains/additional_toolchains.bzl +++ b/tests/cc/testutil/toolchains/additional_toolchains.bzl @@ -1,6 +1,7 @@ """Additional mock toolchains besides the mock C++ toolchain.""" def mock_additional_toolchains(): + # type: () -> None pass ADDITIONAL_MOCK_TOOLCHAINS = [] diff --git a/tests/cc/testutil/toolchains/additional_toolchains.bzli b/tests/cc/testutil/toolchains/additional_toolchains.bzli new file mode 100644 index 000000000..ce7ebffe8 --- /dev/null +++ b/tests/cc/testutil/toolchains/additional_toolchains.bzli @@ -0,0 +1,3 @@ +"""Additional mock toolchain labels used by the C++ analysis tests.""" + +ADDITIONAL_MOCK_TOOLCHAINS: list[str] 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..4d4b25653 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 diff --git a/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli b/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli new file mode 100644 index 000000000..54f9273eb --- /dev/null +++ b/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli @@ -0,0 +1,5 @@ +"""Action set golden file test declarations.""" + +type _LabelInput = str | Label | select[str | Label] + +def action_type_set_diff_test(name: str, action_type_set: _LabelInput, expected: _LabelInput) -> None: ... 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/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/execution_requirements.bzli b/tests/rule_based_toolchain/tool/execution_requirements.bzli new file mode 100644 index 000000000..4384459a0 --- /dev/null +++ b/tests/rule_based_toolchain/tool/execution_requirements.bzli @@ -0,0 +1,3 @@ +"""Execution requirements supplied to the toolchain tests.""" + +load("//cc/toolchains:providers.bzl", "ExecutionRequirementsInfo") 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( From cb691505207dd1b88717c43afd2704e9e2acb220 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Thu, 29 Oct 2026 12:55:40 -0400 Subject: [PATCH 2/9] Check and distribute rules_cc type contracts Export the interfaces for dependent repositories. Run sty on the implementations and report findings across the remaining Starlark files. --- .github/workflows/sty.yaml | 102 ++++++++++++++++++++++++++++++ BUILD | 6 +- MODULE.bazel | 7 ++ cc/BUILD | 4 ++ cc/private/BUILD | 3 + cc/private/rules_impl/BUILD | 3 + cc/private/toolchain/BUILD | 5 ++ cc/private/toolchain_config/BUILD | 3 + cc/toolchains/BUILD | 12 ++++ cc/toolchains/impl/BUILD | 10 +++ docs/toolchain_api.md | 7 +- stubs.toml | 22 +++++++ sty.toml | 22 +++++++ 13 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/sty.yaml create mode 100644 stubs.toml create mode 100644 sty.toml diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml new file mode 100644 index 000000000..d4db35f21 --- /dev/null +++ b/.github/workflows/sty.yaml @@ -0,0 +1,102 @@ +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@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: List maintained Starlark implementations + run: git ls-files -- '*.bzl' > "$RUNNER_TEMP/sty-files" + - uses: tamird/sty@2ab74c830937d3ca85165ca0e9a02a42400ad9e5 + with: + audit-loads: true + bazel-version: '9.3.0' + advisory-files-from: ${{ runner.temp }}/sty-files + 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: | + 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/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/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/propeller_optimize.bzl + cc/toolchains/providers.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 6d274608c..d6b1394a9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -53,3 +53,10 @@ use_repo(compat, "cc_compatibility_proxy") local_bazel_import = use_repo_rule("//:local_bazel.bzl", "local_bazel_import") local_bazel_import(name = "local_bazel") + +# Use provider contracts while bazelbuild/bazel-skylib#641 is in review. +git_override( + module_name = "bazel_skylib", + commit = "98d84dda6a754a8ee2fb99397cbdb690f98f56a0", + remote = "https://github.com/tamird/bazel-skylib.git", +) diff --git a/cc/BUILD b/cc/BUILD index 615b43002..d9c897f69 100644 --- a/cc/BUILD +++ b/cc/BUILD @@ -30,6 +30,9 @@ filegroup( ) exports_files([ + "cc_postmark_initializers.bzli", + "cc_toolchain_config_lib.bzli", + "system_library.bzli", "defs.bzl", "action_names.bzl", "system_library.bzl", @@ -74,6 +77,7 @@ filegroup( testonly = 1, srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]) + [ "//cc/common:srcs", diff --git a/cc/private/BUILD b/cc/private/BUILD index 02c25bd26..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", 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/toolchain/BUILD b/cc/private/toolchain/BUILD index e3c068058..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") 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/toolchains/BUILD b/cc/toolchains/BUILD index e9ac62b03..2d40c8c2d 100644 --- a/cc/toolchains/BUILD +++ b/cc/toolchains/BUILD @@ -14,6 +14,17 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +exports_files( + [ + "cc_toolchain_info.bzli", + "directory_tool.bzli", + "nested_args.bzli", + "tool.bzli", + "tool_map.bzli", + ], + visibility = ["//visibility:public"], +) + bzl_library( name = "toolchain_rules", srcs = glob(["*.bzl"]), @@ -44,6 +55,7 @@ filegroup( name = "srcs", srcs = glob([ "**/*.bzl", + "**/*.bzli", "**/BUILD", ]) + [ "//cc/toolchains/actions:srcs", 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/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/stubs.toml b/stubs.toml new file mode 100644 index 000000000..07af7ad57 --- /dev/null +++ b/stubs.toml @@ -0,0 +1,22 @@ +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/system_library.bzl" = "cc/system_library.bzli" +"cc/toolchains/cc_toolchain_info.bzl" = "cc/toolchains/cc_toolchain_info.bzli" +"cc/toolchains/directory_tool.bzl" = "cc/toolchains/directory_tool.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/legacy_file_group.bzl" = "cc/toolchains/legacy_file_group.bzli" +"cc/toolchains/nested_args.bzl" = "cc/toolchains/nested_args.bzli" +"cc/toolchains/tool.bzl" = "cc/toolchains/tool.bzli" +"cc/toolchains/tool_map.bzl" = "cc/toolchains/tool_map.bzli" diff --git a/sty.toml b/sty.toml new file mode 100644 index 000000000..262be1c77 --- /dev/null +++ b/sty.toml @@ -0,0 +1,22 @@ +[analysis] +strict-generic-narrowing = true +require-valid-suppressions = true + +[rules] +missing-parameter-annotation = "error" +missing-return-annotation = "error" +missing-type-argument = "error" +unsound-assignment = "error" +unsound-return-statement = "error" +explicit-any = "error" +non-static-type = "error" +blanket-ignore-comment = "error" +possibly-unresolved-reference = "error" +possibly-missing-attribute = "error" +division-by-zero = "error" + +[[stub-packages]] +manifest = "stubs.toml" + +[[stub-packages]] +manifest = "@bazel_skylib//:stubs.toml" From 8dadf5c2e03cb5c6575097c43a9762a6e3bb64bc Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 09:49:54 -0500 Subject: [PATCH 3/9] Keep toolchain contracts beside their source Move four companion declarations into the implementations they describe. Keep the same strict sources and let the shared action select the complete advisory inventory. --- .github/workflows/sty.yaml | 6 ++---- cc/private/toolchain/unix_cc_configure.bzl | 4 ++-- cc/toolchains/legacy_file_group.bzl | 12 ++++++++++++ cc/toolchains/legacy_file_group.bzli | 12 ------------ stubs.toml | 1 - .../cc/testutil/toolchains/additional_toolchains.bzl | 4 +++- .../testutil/toolchains/additional_toolchains.bzli | 3 --- .../action_type_sets/action_type_set_diff_test.bzl | 5 +++++ .../action_type_sets/action_type_set_diff_test.bzli | 5 ----- .../tool/execution_requirements.bzli | 3 --- 10 files changed, 24 insertions(+), 31 deletions(-) delete mode 100644 cc/toolchains/legacy_file_group.bzli delete mode 100644 tests/cc/testutil/toolchains/additional_toolchains.bzli delete mode 100644 tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli delete mode 100644 tests/rule_based_toolchain/tool/execution_requirements.bzli diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml index d4db35f21..6a1e3930f 100644 --- a/.github/workflows/sty.yaml +++ b/.github/workflows/sty.yaml @@ -20,13 +20,11 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - name: List maintained Starlark implementations - run: git ls-files -- '*.bzl' > "$RUNNER_TEMP/sty-files" - - uses: tamird/sty@2ab74c830937d3ca85165ca0e9a02a42400ad9e5 + - uses: tamird/sty@d400a00807b3c7f7b2ac82bf1e1e6a16db0290e3 with: audit-loads: true bazel-version: '9.3.0' - advisory-files-from: ${{ runner.temp }}/sty-files + 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: | diff --git a/cc/private/toolchain/unix_cc_configure.bzl b/cc/private/toolchain/unix_cc_configure.bzl index c093cdc73..89615b709 100644 --- a/cc/private/toolchain/unix_cc_configure.bzl +++ b/cc/private/toolchain/unix_cc_configure.bzl @@ -552,11 +552,11 @@ def configure_unix_toolchain(repository_ctx, cpu_value, overridden_tools): bin_search_flags = ["-B" + escape_percent(str(cc_path.dirname))] else: # cc is inside the repository, don't set -B. - bin_search_flags = [] # type: list[string] + 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" + escape_string(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: diff --git a/cc/toolchains/legacy_file_group.bzl b/cc/toolchains/legacy_file_group.bzl index 48d289a69..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"), diff --git a/cc/toolchains/legacy_file_group.bzli b/cc/toolchains/legacy_file_group.bzli deleted file mode 100644 index 7268adcbb..000000000 --- a/cc/toolchains/legacy_file_group.bzli +++ /dev/null @@ -1,12 +0,0 @@ -"""Action labels grouped by the legacy attributes of cc_toolchain.""" - -LEGACY_FILE_GROUPS: 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], -}] diff --git a/stubs.toml b/stubs.toml index 07af7ad57..b3a24c8c6 100644 --- a/stubs.toml +++ b/stubs.toml @@ -16,7 +16,6 @@ format-version = 1 "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/legacy_file_group.bzl" = "cc/toolchains/legacy_file_group.bzli" "cc/toolchains/nested_args.bzl" = "cc/toolchains/nested_args.bzli" "cc/toolchains/tool.bzl" = "cc/toolchains/tool.bzli" "cc/toolchains/tool_map.bzl" = "cc/toolchains/tool_map.bzli" diff --git a/tests/cc/testutil/toolchains/additional_toolchains.bzl b/tests/cc/testutil/toolchains/additional_toolchains.bzl index ea7c8419f..0b2f7c67d 100644 --- a/tests/cc/testutil/toolchains/additional_toolchains.bzl +++ b/tests/cc/testutil/toolchains/additional_toolchains.bzl @@ -4,4 +4,6 @@ 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/additional_toolchains.bzli b/tests/cc/testutil/toolchains/additional_toolchains.bzli deleted file mode 100644 index ce7ebffe8..000000000 --- a/tests/cc/testutil/toolchains/additional_toolchains.bzli +++ /dev/null @@ -1,3 +0,0 @@ -"""Additional mock toolchain labels used by the C++ analysis tests.""" - -ADDITIONAL_MOCK_TOOLCHAINS: list[str] 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 4d4b25653..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 @@ -40,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/action_type_set_diff_test.bzli b/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli deleted file mode 100644 index 54f9273eb..000000000 --- a/tests/rule_based_toolchain/action_type_sets/action_type_set_diff_test.bzli +++ /dev/null @@ -1,5 +0,0 @@ -"""Action set golden file test declarations.""" - -type _LabelInput = str | Label | select[str | Label] - -def action_type_set_diff_test(name: str, action_type_set: _LabelInput, expected: _LabelInput) -> None: ... diff --git a/tests/rule_based_toolchain/tool/execution_requirements.bzli b/tests/rule_based_toolchain/tool/execution_requirements.bzli deleted file mode 100644 index 4384459a0..000000000 --- a/tests/rule_based_toolchain/tool/execution_requirements.bzli +++ /dev/null @@ -1,3 +0,0 @@ -"""Execution requirements supplied to the toolchain tests.""" - -load("//cc/toolchains:providers.bzl", "ExecutionRequirementsInfo") From 8eec445be9f4ad4c6904946e0b0a10a3c4712410 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 10:29:17 -0500 Subject: [PATCH 4/9] Move tool and repository types into source Declare each macro's inputs at its definition. Derive the directory tool's forwarded keywords from cc_tool so both macros share one contract. Check the repository rule at its implementation. Remove four companion interfaces and their registrations. Document which generic binders and cross-module types the others require. --- .github/workflows/sty.yaml | 4 +++ cc/BUILD | 1 - cc/cc_postmark_initializers.bzli | 3 ++ cc/cc_toolchain_config_lib.bzli | 3 ++ cc/private/objc_info.bzli | 3 ++ cc/private/toolchain/lib_cc_configure.bzli | 3 ++ cc/private/toolchain/unix_cc_configure.bzli | 3 ++ .../toolchain_config/legacy_features.bzli | 3 ++ cc/system_library.bzl | 14 ++++++++ cc/system_library.bzli | 27 --------------- cc/toolchains/BUILD | 3 -- cc/toolchains/directory_tool.bzl | 23 ++++++++++++- cc/toolchains/directory_tool.bzli | 17 ---------- cc/toolchains/impl/collect.bzli | 3 ++ cc/toolchains/impl/legacy_converter.bzli | 3 ++ cc/toolchains/impl/nested_args.bzli | 3 ++ cc/toolchains/impl/variables.bzli | 3 ++ cc/toolchains/nested_args.bzl | 22 ++++++++++++ cc/toolchains/nested_args.bzli | 23 ------------- cc/toolchains/tool.bzl | 30 ++++++++++++++++ cc/toolchains/tool.bzli | 34 ------------------- cc/toolchains/tool_map.bzli | 3 ++ stubs.toml | 4 --- 23 files changed, 125 insertions(+), 110 deletions(-) delete mode 100644 cc/system_library.bzli delete mode 100644 cc/toolchains/directory_tool.bzli delete mode 100644 cc/toolchains/nested_args.bzli delete mode 100644 cc/toolchains/tool.bzli diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml index 6a1e3930f..505e15ad6 100644 --- a/.github/workflows/sty.yaml +++ b/.github/workflows/sty.yaml @@ -56,6 +56,7 @@ jobs: 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 @@ -66,6 +67,7 @@ jobs: 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 @@ -84,8 +86,10 @@ jobs: 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 diff --git a/cc/BUILD b/cc/BUILD index d9c897f69..1b8a32c65 100644 --- a/cc/BUILD +++ b/cc/BUILD @@ -32,7 +32,6 @@ filegroup( exports_files([ "cc_postmark_initializers.bzli", "cc_toolchain_config_lib.bzli", - "system_library.bzli", "defs.bzl", "action_names.bzl", "system_library.bzl", diff --git a/cc/cc_postmark_initializers.bzli b/cc/cc_postmark_initializers.bzli index fa7ae1b89..39f75ad5c 100644 --- a/cc/cc_postmark_initializers.bzli +++ b/cc/cc_postmark_initializers.bzli @@ -1,4 +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_toolchain_config_lib.bzli b/cc/cc_toolchain_config_lib.bzli index 62bcd0067..6ceb58f41 100644 --- a/cc/cc_toolchain_config_lib.bzli +++ b/cc/cc_toolchain_config_lib.bzli @@ -16,6 +16,9 @@ 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( diff --git a/cc/private/objc_info.bzli b/cc/private/objc_info.bzli index fb43100f4..55be7d23b 100644 --- a/cc/private/objc_info.bzli +++ b/cc/private/objc_info.bzli @@ -1,5 +1,8 @@ """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] = ..., diff --git a/cc/private/toolchain/lib_cc_configure.bzli b/cc/private/toolchain/lib_cc_configure.bzli index 82430005c..621ec3f45 100644 --- a/cc/private/toolchain/lib_cc_configure.bzli +++ b/cc/private/toolchain/lib_cc_configure.bzli @@ -1,5 +1,8 @@ """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]( diff --git a/cc/private/toolchain/unix_cc_configure.bzli b/cc/private/toolchain/unix_cc_configure.bzli index 8243cb829..fc4e3078e 100644 --- a/cc/private/toolchain/unix_cc_configure.bzli +++ b/cc/private/toolchain/unix_cc_configure.bzli @@ -1,5 +1,8 @@ """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]: ... diff --git a/cc/private/toolchain_config/legacy_features.bzli b/cc/private/toolchain_config/legacy_features.bzli index c7fed2788..f6ff42b45 100644 --- a/cc/private/toolchain_config/legacy_features.bzli +++ b/cc/private/toolchain_config/legacy_features.bzli @@ -13,6 +13,9 @@ # 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 diff --git a/cc/system_library.bzl b/cc/system_library.bzl index f0cdbee71..12c720321 100644 --- a/cc/system_library.bzl +++ b/cc/system_library.bzl @@ -136,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 diff --git a/cc/system_library.bzli b/cc/system_library.bzli deleted file mode 100644 index 090f53d7a..000000000 --- a/cc/system_library.bzli +++ /dev/null @@ -1,27 +0,0 @@ -# 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. -"""System library and header discovery.""" - -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: repository_ctx[_Attrs]) -> repo_metadata | None: ... diff --git a/cc/toolchains/BUILD b/cc/toolchains/BUILD index 2d40c8c2d..ba57417d3 100644 --- a/cc/toolchains/BUILD +++ b/cc/toolchains/BUILD @@ -17,9 +17,6 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") exports_files( [ "cc_toolchain_info.bzli", - "directory_tool.bzli", - "nested_args.bzli", - "tool.bzli", "tool_map.bzli", ], visibility = ["//visibility:public"], 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/directory_tool.bzli b/cc/toolchains/directory_tool.bzli deleted file mode 100644 index 9804c209f..000000000 --- a/cc/toolchains/directory_tool.bzli +++ /dev/null @@ -1,17 +0,0 @@ -"""Tools extracted from directories.""" - -load("//cc/toolchains:tool.bzl", _cc_tool = "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( - name: str, - directory: str | Label | select[str | Label | None], - executable: str, - data: _StringsInput = ..., - exclude: _StringsInput = ..., - allow_empty: _Bool | select[_Bool | None] | None = ..., - **kwargs: Unpack[KeywordArgs[_cc_tool, "name", "src", "data"]], -) -> None: ... diff --git a/cc/toolchains/impl/collect.bzli b/cc/toolchains/impl/collect.bzli index 4943c37d4..ac74ac156 100644 --- a/cc/toolchains/impl/collect.bzli +++ b/cc/toolchains/impl/collect.bzli @@ -1,5 +1,8 @@ """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]]}] diff --git a/cc/toolchains/impl/legacy_converter.bzli b/cc/toolchains/impl/legacy_converter.bzli index facd81898..732f727cf 100644 --- a/cc/toolchains/impl/legacy_converter.bzli +++ b/cc/toolchains/impl/legacy_converter.bzli @@ -1,5 +1,8 @@ """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") diff --git a/cc/toolchains/impl/nested_args.bzli b/cc/toolchains/impl/nested_args.bzli index e2b6fb6b8..44e9b0469 100644 --- a/cc/toolchains/impl/nested_args.bzli +++ b/cc/toolchains/impl/nested_args.bzli @@ -1,5 +1,8 @@ """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] diff --git a/cc/toolchains/impl/variables.bzli b/cc/toolchains/impl/variables.bzli index 566b3ab12..b0f269512 100644 --- a/cc/toolchains/impl/variables.bzli +++ b/cc/toolchains/impl/variables.bzli @@ -1,5 +1,8 @@ """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", 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/nested_args.bzli b/cc/toolchains/nested_args.bzli deleted file mode 100644 index 7eac8bda5..000000000 --- a/cc/toolchains/nested_args.bzli +++ /dev/null @@ -1,23 +0,0 @@ -"""Nested command-line arguments.""" - -type _Label = str | Label -type _LabelInput = _Label | select[_Label | None] | None -type _ListInput[T] = Collection[T] | depset[T] | select[Collection[T] | depset[T] | None] | None -type _StringInput = str | select[str | None] | None - -def cc_nested_args( - *, - name: str, - args: _ListInput[str] = ..., - data: _ListInput[_Label] = ..., - format: Mapping[str, _Label] = ..., - iterate_over: _LabelInput = ..., - nested: _ListInput[_Label] = ..., - requires_not_none: _LabelInput = ..., - requires_none: _LabelInput = ..., - requires_true: _LabelInput = ..., - requires_false: _LabelInput = ..., - requires_equal: _LabelInput = ..., - requires_equal_value: _StringInput = ..., - **kwargs: Unpack[CommonRuleAttrs], -) -> None: ... diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl index 3fb958fd4..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 @@ -190,15 +210,25 @@ cc_tool( def cc_tool( *, + # type: str name, + # 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.bzli b/cc/toolchains/tool.bzli deleted file mode 100644 index 6cfceca24..000000000 --- a/cc/toolchains/tool.bzli +++ /dev/null @@ -1,34 +0,0 @@ -"""Tools, their execution inputs, and forwarded rule attributes.""" - -load("//cc/toolchains:cc_toolchain_info.bzli", "ToolInfo") - -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: ctx[object, _Attrs]) -> list[ToolInfo | DefaultInfo]: ... -def cc_tool( - *, - name: str, - src: _LabelInput, - data: _LabelsInput = ..., - allowlist_include_directories: _LabelsInput = ..., - env: _EnvironmentInput = ..., - execution_requirements: _LabelsInput = ..., - format: Mapping[str, str | Label] = ..., - capabilities: _LabelsInput = ..., - **kwargs: Unpack[CommonExecutableRuleAttrs], -) -> None: ... diff --git a/cc/toolchains/tool_map.bzli b/cc/toolchains/tool_map.bzli index 91a62af81..fd7997cfc 100644 --- a/cc/toolchains/tool_map.bzli +++ b/cc/toolchains/tool_map.bzli @@ -1,5 +1,8 @@ """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[{ diff --git a/stubs.toml b/stubs.toml index b3a24c8c6..f1b4eff0f 100644 --- a/stubs.toml +++ b/stubs.toml @@ -9,13 +9,9 @@ format-version = 1 "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/system_library.bzl" = "cc/system_library.bzli" "cc/toolchains/cc_toolchain_info.bzl" = "cc/toolchains/cc_toolchain_info.bzli" -"cc/toolchains/directory_tool.bzl" = "cc/toolchains/directory_tool.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/nested_args.bzl" = "cc/toolchains/nested_args.bzli" -"cc/toolchains/tool.bzl" = "cc/toolchains/tool.bzli" "cc/toolchains/tool_map.bzl" = "cc/toolchains/tool_map.bzli" From d3c9dc8362636beffc2588141a29f12a25ffba79 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 10:38:38 -0500 Subject: [PATCH 5/9] Document the type source for forwarded keywords Link the three wrapper signatures to the Bazel proposal for deriving keyword arguments from a wrapped rule. --- cc/cc_import.bzl | 2 ++ cc/cc_shared_library.bzl | 2 ++ cc/starlark_doc_extract_helper.bzl | 2 ++ 3 files changed, 6 insertions(+) diff --git a/cc/cc_import.bzl b/cc/cc_import.bzl index 2292ef1f2..822250a9f 100644 --- a/cc/cc_import.bzl +++ b/cc/cc_import.bzl @@ -15,6 +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_shared_library.bzl b/cc/cc_shared_library.bzl index d66fea01e..fe1580b36 100644 --- a/cc/cc_shared_library.bzl +++ b/cc/cc_shared_library.bzl @@ -15,6 +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/starlark_doc_extract_helper.bzl b/cc/starlark_doc_extract_helper.bzl index 55460b24d..fc96c726d 100644 --- a/cc/starlark_doc_extract_helper.bzl +++ b/cc/starlark_doc_extract_helper.bzl @@ -2,6 +2,8 @@ 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 From 99a0753cdf7e9ade5809fc1a065334df75fc0bab Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 10:50:56 -0500 Subject: [PATCH 6/9] Explain the source annotation limits Identify the native types each exceptional comment needs. Document the language boundary beside its use. --- cc/cc_postmark.bzl | 3 +++ sty.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cc/cc_postmark.bzl b/cc/cc_postmark.bzl index 28c225706..eed942ead 100644 --- a/cc/cc_postmark.bzl +++ b/cc/cc_postmark.bzl @@ -24,6 +24,9 @@ def _add_postmark_action( # 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 diff --git a/sty.toml b/sty.toml index 262be1c77..cb4dce718 100644 --- a/sty.toml +++ b/sty.toml @@ -1,3 +1,6 @@ +# 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 From 826ec97ddd6542ce1501df62c4ee83ff965b8738 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 10:50:57 -0500 Subject: [PATCH 7/9] Reference sty by release tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow the repository’s release-tag convention for external actions. Use the same checkout and checker revisions as the previous workflow. --- .github/workflows/sty.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml index 505e15ad6..325c68eba 100644 --- a/.github/workflows/sty.yaml +++ b/.github/workflows/sty.yaml @@ -17,10 +17,10 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/checkout@v4 with: persist-credentials: false - - uses: tamird/sty@d400a00807b3c7f7b2ac82bf1e1e6a16db0290e3 + - uses: tamird/sty@sty-action-v0.2.0 with: audit-loads: true bazel-version: '9.3.0' From 4f9860e0da3e473e9f5a54976e35b97395180684 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 12:57:51 -0500 Subject: [PATCH 8/9] Select the strict sty rules as a group Set one severity for the eleven strict diagnostics. Let the pinned checker define the group so an upgrade can extend it without edits to the project configuration. --- sty.toml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sty.toml b/sty.toml index cb4dce718..40cc7da5f 100644 --- a/sty.toml +++ b/sty.toml @@ -6,17 +6,7 @@ strict-generic-narrowing = true require-valid-suppressions = true [rules] -missing-parameter-annotation = "error" -missing-return-annotation = "error" -missing-type-argument = "error" -unsound-assignment = "error" -unsound-return-statement = "error" -explicit-any = "error" -non-static-type = "error" -blanket-ignore-comment = "error" -possibly-unresolved-reference = "error" -possibly-missing-attribute = "error" -division-by-zero = "error" +strict = "error" [[stub-packages]] manifest = "stubs.toml" From bbec9dfcba1604fa7c42810edec3b617a62f872f Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Tue, 3 Nov 2026 13:40:16 -0500 Subject: [PATCH 9/9] Check rules_cc against released Skylib Since 60dc620ac, the root module replaces Skylib with a fork to get type information. Bazel also uses that fork for ordinary builds. Declare the build macro, path helpers and two providers against Skylib 1.9.0. Check their signatures against the installed sources. Report incomplete types in three files from the release while checking their contracts and callers as errors. --- .github/workflows/sty.yaml | 6 ++++- MODULE.bazel | 7 ------ stubs/bazel_skylib/bzl_library.bzli | 5 ++++ stubs/bazel_skylib/common_settings.bzli | 3 +++ stubs/bazel_skylib/directory_providers.bzli | 26 +++++++++++++++++++++ stubs/bazel_skylib/paths.bzli | 12 ++++++++++ stubs/bazel_skylib/stubs.toml | 12 ++++++++++ sty.toml | 23 +++++++++++++++++- 8 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 stubs/bazel_skylib/bzl_library.bzli create mode 100644 stubs/bazel_skylib/common_settings.bzli create mode 100644 stubs/bazel_skylib/directory_providers.bzli create mode 100644 stubs/bazel_skylib/paths.bzli create mode 100644 stubs/bazel_skylib/stubs.toml diff --git a/.github/workflows/sty.yaml b/.github/workflows/sty.yaml index 325c68eba..50fff9056 100644 --- a/.github/workflows/sty.yaml +++ b/.github/workflows/sty.yaml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - uses: tamird/sty@sty-action-v0.2.0 + - uses: tamird/sty@95e2b424a7f52fa0b0946da98eb2cc328e9b4107 with: audit-loads: true bazel-version: '9.3.0' @@ -28,6 +28,10 @@ jobs: 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 diff --git a/MODULE.bazel b/MODULE.bazel index d6b1394a9..6d274608c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -53,10 +53,3 @@ use_repo(compat, "cc_compatibility_proxy") local_bazel_import = use_repo_rule("//:local_bazel.bzl", "local_bazel_import") local_bazel_import(name = "local_bazel") - -# Use provider contracts while bazelbuild/bazel-skylib#641 is in review. -git_override( - module_name = "bazel_skylib", - commit = "98d84dda6a754a8ee2fb99397cbdb690f98f56a0", - remote = "https://github.com/tamird/bazel-skylib.git", -) 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 index 40cc7da5f..0edcbec44 100644 --- a/sty.toml +++ b/sty.toml @@ -8,8 +8,29 @@ 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 = "@bazel_skylib//:stubs.toml" +manifest = "stubs/bazel_skylib/stubs.toml"