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,
}