diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl index 547447639..6a7043673 100644 --- a/cc/toolchains/cc_toolchain_info.bzl +++ b/cc/toolchains/cc_toolchain_info.bzl @@ -182,6 +182,15 @@ MakeVariableInfo = provider( }, ) +ExecutionRequirementsInfo = provider( + doc = "Provider for execution requirements attached to tool actions.", + # @unsorted-dict-items + fields = { + "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", + "requirements": "(Sequence[str]) The execution requirements to apply.", + }, +) + MutuallyExclusiveCategoryInfo = provider( doc = "Multiple features with the category will be mutally exclusive", # @unsorted-dict-items diff --git a/cc/toolchains/providers.bzl b/cc/toolchains/providers.bzl new file mode 100644 index 000000000..6cd84a9fd --- /dev/null +++ b/cc/toolchains/providers.bzl @@ -0,0 +1,20 @@ +# 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. +"""Public providers for rule-based toolchain configuration.""" + +load(":cc_toolchain_info.bzl", _ExecutionRequirementsInfo = "ExecutionRequirementsInfo") + +visibility("public") + +ExecutionRequirementsInfo = _ExecutionRequirementsInfo diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl index d4481e0e6..246d54c55 100644 --- a/cc/toolchains/tool.bzl +++ b/cc/toolchains/tool.bzl @@ -19,6 +19,7 @@ load("//cc/toolchains/impl:label_utils.bzl", "deduplicate_label_list") load("//cc/toolchains/impl:nested_args.bzl", "format_dict_values") load( ":cc_toolchain_info.bzl", + "ExecutionRequirementsInfo", "ToolCapabilityInfo", "ToolInfo", ) @@ -40,13 +41,16 @@ def _cc_tool_impl(ctx): format = format_targets, must_use = format_targets.keys(), ) + execution_requirements = [] + 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]) tool = ToolInfo( label = ctx.label, exe = exe, runfiles = runfiles, - execution_requirements = tuple(ctx.attr.tags), + execution_requirements = tuple(ctx.attr.tags + execution_requirements), env = env, allowlist_include_directories = depset( direct = [d[DirectoryInfo] for d in ctx.attr.allowlist_include_directories], @@ -116,6 +120,13 @@ This can help work around errors like: doc = """Environment variables to apply when running this tool. Format expansion is performed on values using the format attribute. +""", + ), + "execution_requirements": attr.label_list( + providers = [ExecutionRequirementsInfo], + doc = """Additional execution requirements for actions that run this tool. + +Each label must provide `ExecutionRequirementsInfo`. For fixed execution requirements, use `tags`. """, ), "format_keys": attr.string_list( @@ -181,6 +192,7 @@ def cc_tool( data = None, allowlist_include_directories = None, env = None, + execution_requirements = None, format = {}, capabilities = None, **kwargs): @@ -235,6 +247,9 @@ def cc_tool( (if these are builtin files, make sure these paths are in your toolchain)`. env: (Dict[str, str]) Environment variables to apply when running this tool. Format expansion is performed on values using `format`. + execution_requirements: (List[Label]) Additional execution requirements for actions that + run this tool. Each label must provide `ExecutionRequirementsInfo`. For fixed + execution requirements, use `tags`. format: (Dict[str, Label]) A mapping of format strings to the label of a corresponding target. This target can be a `directory`, `subdirectory`, or a single file that the value should be pulled from. All instances of `{variable_name}` in the `env` dictionary @@ -255,6 +270,7 @@ def cc_tool( data = data, allowlist_include_directories = allowlist_include_directories, env = env, + execution_requirements = execution_requirements, format_keys = format_keys, format_values = format_values.labels, format_value_indexes = format_values.indexes, diff --git a/docs/toolchain_api.md b/docs/toolchain_api.md index 1e0f5c798..7bdad1e67 100755 --- a/docs/toolchain_api.md +++ b/docs/toolchain_api.md @@ -685,7 +685,8 @@ use this rule.
 load("@rules_cc//cc/toolchains/impl:documented_api.bzl", "cc_tool")
 
-cc_tool(*, name, src, data, allowlist_include_directories, env, format, capabilities, **kwargs)
+cc_tool(*, name, src, data, allowlist_include_directories, env, execution_requirements, format,
+        capabilities, **kwargs)
 
Declares a tool for use by toolchain actions. @@ -724,6 +725,7 @@ cc_tool( | 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` | +| execution_requirements | (List[Label]) Additional execution requirements for actions that run this tool. Each label must provide `ExecutionRequirementsInfo`. For fixed execution requirements, use `tags`. | `None` | | format | (Dict[str, Label]) A mapping of format strings to the label of a corresponding target. This target can be a `directory`, `subdirectory`, or a single file that the value should be pulled from. All instances of `{variable_name}` in the `env` dictionary values will be replaced with the expanded value in this dictionary. | `{}` | | capabilities | (List[Label]) Declares that a tool is capable of doing something. For example, `@rules_cc//cc/toolchains/capabilities:supports_pic`. | `None` | | kwargs | [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to this rule. | none | diff --git a/tests/rule_based_toolchain/tool/BUILD b/tests/rule_based_toolchain/tool/BUILD index f6d07104e..cf287ac18 100644 --- a/tests/rule_based_toolchain/tool/BUILD +++ b/tests/rule_based_toolchain/tool/BUILD @@ -1,6 +1,7 @@ load("//cc/toolchains:directory_tool.bzl", "cc_directory_tool") load("//cc/toolchains:tool.bzl", "cc_tool") load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite") +load(":execution_requirements.bzl", "test_execution_requirements") load(":tool_test.bzl", "TARGETS", "TESTS") cc_tool( @@ -44,6 +45,22 @@ cc_tool( visibility = ["//tests/rule_based_toolchain:__subpackages__"], ) +test_execution_requirements( + name = "execution_requirements", + requirements = [ + "example-requires-network", + "example-requires-darwin", + ], +) + +cc_tool( + name = "tool_with_execution_requirements", + src = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh", + execution_requirements = [":execution_requirements"], + tags = ["example-hardcoded-tag"], + visibility = ["//tests/rule_based_toolchain:__subpackages__"], +) + cc_directory_tool( name = "directory_tool", data = ["bin"], diff --git a/tests/rule_based_toolchain/tool/execution_requirements.bzl b/tests/rule_based_toolchain/tool/execution_requirements.bzl new file mode 100644 index 000000000..28be39a30 --- /dev/null +++ b/tests/rule_based_toolchain/tool/execution_requirements.bzl @@ -0,0 +1,34 @@ +# 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. +"""Test helper for tool execution requirements.""" + +load("//cc/toolchains:providers.bzl", "ExecutionRequirementsInfo") + +visibility("private") + +def _test_execution_requirements_impl(ctx): + return [ + ExecutionRequirementsInfo( + label = ctx.label, + requirements = tuple(ctx.attr.requirements), + ), + ] + +test_execution_requirements = rule( + implementation = _test_execution_requirements_impl, + attrs = { + "requirements": attr.string_list(), + }, + provides = [ExecutionRequirementsInfo], +) diff --git a/tests/rule_based_toolchain/tool/tool_test.bzl b/tests/rule_based_toolchain/tool/tool_test.bzl index fc445da5a..f79565703 100644 --- a/tests/rule_based_toolchain/tool/tool_test.bzl +++ b/tests/rule_based_toolchain/tool/tool_test.bzl @@ -74,6 +74,21 @@ def _tool_env_expansion_test(env, targets): "TOOL_ENV_AGAIN": path_pattern("tests/rule_based_toolchain/testdata/file1"), }) +def _tool_execution_requirements_test(env, targets): + tool = env.expect.that_target(targets.tool_with_execution_requirements).provider(ToolInfo) + tool.execution_requirements().contains_exactly([ + "example-hardcoded-tag", + "example-requires-darwin", + "example-requires-network", + ]) + + legacy = convert_tool(tool.actual) + env.expect.that_collection(legacy.execution_requirements).contains_exactly([ + "example-hardcoded-tag", + "example-requires-darwin", + "example-requires-network", + ]) + def _collect_tools_collects_tools_test(env, targets): env.expect.that_value( value = collect_tools(env.ctx, [targets.tool, targets.wrapped_tool]), @@ -118,6 +133,7 @@ TARGETS = [ "//tests/rule_based_toolchain/tool:directory_tool", "//tests/rule_based_toolchain/tool:tool_with_allowlist_include_directories", "//tests/rule_based_toolchain/tool:tool_with_env", + "//tests/rule_based_toolchain/tool:tool_with_execution_requirements", "//tests/rule_based_toolchain/testdata:bin_wrapper", "//tests/rule_based_toolchain/testdata:multiple", "//tests/rule_based_toolchain/testdata:bin_filegroup", @@ -135,4 +151,5 @@ TESTS = { "collect_tools_fails_on_non_binary_test": _collect_tools_fails_on_non_binary_test, "tool_with_allowlist_include_directories_test": _tool_with_allowlist_include_directories_test, "tool_env_expansion_test": _tool_env_expansion_test, + "tool_execution_requirements_test": _tool_execution_requirements_test, }