diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl index 38148b463..15228cc3f 100644 --- a/cc/toolchains/cc_toolchain_info.bzl +++ b/cc/toolchains/cc_toolchain_info.bzl @@ -221,6 +221,16 @@ ToolConfigInfo = provider( }, ) +LegacyToolInfo = provider( + doc = "A tool specified by path rather than by label, for legacy toolchain configurations.", + # @unsorted-dict-items + fields = { + "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", + "name": "(str) The tool name (eg. gcc, ar, ld, strip)", + "path": "(str) The filesystem path to the tool", + }, +) + ToolchainConfigInfo = provider( doc = "The configuration for a toolchain", # @unsorted-dict-items @@ -235,5 +245,6 @@ ToolchainConfigInfo = provider( "files": "(dict[ActionTypeInfo, depset[File]]) Files required for the toolchain, keyed by the action type.", "allowlist_include_directories": "(depset[DirectoryInfo]) Built-in include directories implied by this toolchain's args and tools that should be allowlisted in Bazel's include checker", "allowlist_absolute_include_directories": "(List[str]) Built-in include directories allowed the sandbox. Use with care", + "legacy_tools": "(Sequence[LegacyToolInfo]) Legacy tools specified by path rather than by label", }, ) diff --git a/cc/toolchains/impl/legacy_converter.bzl b/cc/toolchains/impl/legacy_converter.bzl index 5e8a14481..4618a8503 100644 --- a/cc/toolchains/impl/legacy_converter.bzl +++ b/cc/toolchains/impl/legacy_converter.bzl @@ -24,6 +24,7 @@ load( legacy_flag_set = "flag_set", legacy_make_variable = "make_variable", legacy_tool = "tool", + legacy_tool_path = "tool_path", # buildifier: disable=deprecated-function legacy_with_feature_set = "with_feature_set", ) @@ -226,10 +227,19 @@ def convert_toolchain(toolchain): for m in toolchain.make_variables ] + tool_paths = [ + legacy_tool_path( + name = lt.name, + path = lt.path, + ) + for lt in toolchain.legacy_tools + ] + return struct( features = [ft for ft in features if ft != None], action_configs = sorted(action_configs, key = lambda ac: ac.action_name), cxx_builtin_include_directories = cxx_builtin_include_directories, artifact_name_patterns = artifact_name_patterns, make_variables = make_variables, + tool_paths = tool_paths, ) diff --git a/cc/toolchains/impl/toolchain_config.bzl b/cc/toolchains/impl/toolchain_config.bzl index 1f6efefe5..c2bc8d239 100644 --- a/cc/toolchains/impl/toolchain_config.bzl +++ b/cc/toolchains/impl/toolchain_config.bzl @@ -20,6 +20,7 @@ load( "ArgsListInfo", "ArtifactNamePatternInfo", "FeatureSetInfo", + "LegacyToolInfo", "MakeVariableInfo", "ToolConfigInfo", "ToolchainConfigInfo", @@ -62,6 +63,7 @@ def _cc_toolchain_config_impl(ctx): args = ctx.attr.args, artifact_name_patterns = ctx.attr.artifact_name_patterns, make_variables = ctx.attr.make_variables, + legacy_tools = ctx.attr.legacy_tools, ) legacy = convert_toolchain(toolchain_config) @@ -75,6 +77,7 @@ def _cc_toolchain_config_impl(ctx): make_variables = legacy.make_variables, features = legacy.features, cxx_builtin_include_directories = legacy.cxx_builtin_include_directories, + tool_paths = legacy.tool_paths, # toolchain_identifier is deprecated, but setting it to None results # in an error that it expected a string, and for safety's sake, I'd # prefer to provide something unique. @@ -111,6 +114,7 @@ cc_toolchain_config = rule( "enabled_features": attr.label_list(providers = [FeatureSetInfo]), "artifact_name_patterns": attr.label_list(providers = [ArtifactNamePatternInfo]), "make_variables": attr.label_list(providers = [MakeVariableInfo]), + "legacy_tools": attr.label_list(providers = [LegacyToolInfo]), "_builtin_features": attr.label(default = "//cc/toolchains/features:all_builtin_features"), }, provides = [ToolchainConfigInfo], diff --git a/cc/toolchains/impl/toolchain_config_info.bzl b/cc/toolchains/impl/toolchain_config_info.bzl index a0c3cbac0..c23b3d2b6 100644 --- a/cc/toolchains/impl/toolchain_config_info.bzl +++ b/cc/toolchains/impl/toolchain_config_info.bzl @@ -13,7 +13,7 @@ # limitations under the License. """Helper functions to create and validate a ToolchainConfigInfo.""" -load("//cc/toolchains:cc_toolchain_info.bzl", "ArtifactNamePatternInfo", "MakeVariableInfo", "ToolConfigInfo", "ToolchainConfigInfo") +load("//cc/toolchains:cc_toolchain_info.bzl", "ArtifactNamePatternInfo", "LegacyToolInfo", "MakeVariableInfo", "ToolConfigInfo", "ToolchainConfigInfo") load(":args_utils.bzl", "get_action_type") load(":collect.bzl", "collect_args_lists", "collect_features") @@ -162,7 +162,7 @@ def _collect_make_variables(targets, fail): return make_variables.values() -def toolchain_config_info(label, known_features = [], enabled_features = [], args = [], artifact_name_patterns = [], make_variables = [], tool_map = None, fail = fail): +def toolchain_config_info(label, known_features = [], enabled_features = [], args = [], artifact_name_patterns = [], make_variables = [], legacy_tools = [], tool_map = None, fail = fail): """Generates and validates a ToolchainConfigInfo from lists of labels. Args: @@ -173,6 +173,7 @@ def toolchain_config_info(label, known_features = [], enabled_features = [], arg args: (List[Target]) A list of targets providing ArgsListInfo artifact_name_patterns: (List[Target]) A list of targets providing ArtifactNamePatternInfo. make_variables: (List[Target]) A list of targets providing MakeVariableInfo. + legacy_tools: (List[Target]) A list of targets providing LegacyToolInfo. tool_map: (Target) A target providing ToolMapInfo. fail: A fail function. Use only during tests. Returns: @@ -193,6 +194,8 @@ def toolchain_config_info(label, known_features = [], enabled_features = [], arg # side-effect of allowing code to continue. return None # buildifier: disable=unreachable + legacy_tools_infos = tuple([t[LegacyToolInfo] for t in legacy_tools]) + args = collect_args_lists(args, label = label) tools = tool_map[ToolConfigInfo].configs files = { @@ -222,6 +225,7 @@ def toolchain_config_info(label, known_features = [], enabled_features = [], arg allowlist_absolute_include_directories = allowlist_absolute_include_directories, artifact_name_patterns = _collect_artifact_name_patterns(artifact_name_patterns, fail), make_variables = _collect_make_variables(make_variables, fail), + legacy_tools = legacy_tools_infos, ) _validate_toolchain(toolchain_config, fail = fail) return toolchain_config diff --git a/cc/toolchains/legacy_tool.bzl b/cc/toolchains/legacy_tool.bzl new file mode 100644 index 000000000..4ab9f8611 --- /dev/null +++ b/cc/toolchains/legacy_tool.bzl @@ -0,0 +1,63 @@ +# 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. +"""Implementation of cc_legacy_tool""" + +load(":cc_toolchain_info.bzl", "LegacyToolInfo") + +def _cc_legacy_tool_impl(ctx): + return [LegacyToolInfo( + label = ctx.label, + name = ctx.attr.tool_name, + path = ctx.attr.path, + )] + +cc_legacy_tool = rule( + implementation = _cc_legacy_tool_impl, + attrs = { + "tool_name": attr.string( + mandatory = True, + doc = """The name of the tool (eg. "gcc", "ar", "ld", "strip"). + +This corresponds to the tool name used by Bazel's legacy toolchain resolution. +""", + ), + "path": attr.string( + mandatory = True, + doc = """The filesystem path to the tool. + +Can be an absolute path (for non-hermetic toolchains) or a relative path +starting from the package that provides the toolchain. +""", + ), + }, + provides = [LegacyToolInfo], + doc = """Declares a tool by filesystem path for use in legacy toolchain configurations. + +`cc_legacy_tool` allows specifying tools by their filesystem path rather than as +Bazel targets. This is useful where bazel doesn't handle relative paths well. + +These tools are passed via the `legacy_tools` attribute of `cc_toolchain`. + +Example: +``` +load("@rules_cc//cc/toolchains:legacy_tool.bzl", "cc_legacy_tool") + +cc_legacy_tool( + name = "gcov", + tool_name = "gcov", + path = "/usr/bin/gcov", +) +``` +""", +) diff --git a/cc/toolchains/toolchain.bzl b/cc/toolchains/toolchain.bzl index fa41163ab..fa02cf11a 100644 --- a/cc/toolchains/toolchain.bzl +++ b/cc/toolchains/toolchain.bzl @@ -61,6 +61,7 @@ def cc_toolchain( args = [], artifact_name_patterns = [], make_variables = [], + legacy_tools = [], known_features = [], enabled_features = [], libc_top = None, @@ -118,6 +119,9 @@ def cc_toolchain( artifact_name_patterns: (List[Label]) A list of `cc_artifact_name_pattern` defining patterns for names of artifacts created by this toolchain. make_variables: (List[Label]) A list of `cc_make_variable` defining variable substitutions. + legacy_tools: (List[Label]) A list of `cc_legacy_tool` rules that specify tools by filesystem + path. These are used to populate the legacy `tool_paths` parameter of the toolchain + configuration, which is required by some Bazel features (e.g. coverage). known_features: (List[Label]) A list of `cc_feature` rules that this toolchain supports. Whether or not these [features](https://bazel.build/docs/cc-toolchain-config-reference#features) @@ -174,6 +178,7 @@ def cc_toolchain( args = args, artifact_name_patterns = artifact_name_patterns, make_variables = make_variables, + legacy_tools = legacy_tools, known_features = known_features, enabled_features = enabled_features, compiler = compiler, diff --git a/docs/toolchain_api.md b/docs/toolchain_api.md index eee9241f1..30a2c17b6 100755 --- a/docs/toolchain_api.md +++ b/docs/toolchain_api.md @@ -785,9 +785,9 @@ cc_tool_map(
load("@rules_cc//cc/toolchains/impl:documented_api.bzl", "cc_toolchain")
-cc_toolchain(*, name, tool_map, args, artifact_name_patterns, make_variables, known_features,
- enabled_features, libc_top, module_map, dynamic_runtime_lib, static_runtime_lib,
- supports_header_parsing, supports_param_files, compiler, **kwargs)
+cc_toolchain(*, name, tool_map, args, artifact_name_patterns, make_variables, legacy_tools,
+ known_features, enabled_features, libc_top, module_map, dynamic_runtime_lib,
+ static_runtime_lib, supports_header_parsing, supports_param_files, compiler, **kwargs)
A C/C++ toolchain configuration.
@@ -840,6 +840,7 @@ Generated rules:
| args | (List[Label]) A list of [`cc_args`](#cc_args) and `cc_arg_list` to apply across this toolchain. | `[]` |
| artifact_name_patterns | (List[Label]) A list of `cc_artifact_name_pattern` defining patterns for names of artifacts created by this toolchain. | `[]` |
| make_variables | (List[Label]) A list of `cc_make_variable` defining variable substitutions. | `[]` |
+| legacy_tools | (List[Label]) A list of `cc_legacy_tool` rules that specify tools by filesystem path. These are used to populate the legacy `tool_paths` parameter of the toolchain configuration, which is required by some Bazel features (e.g. coverage). | `[]` |
| known_features | (List[Label]) A list of [`cc_feature`](#cc_feature) rules that this toolchain supports. Whether or not these [features](https://bazel.build/docs/cc-toolchain-config-reference#features) are enabled may change over the course of a build. See the documentation for [`cc_feature`](#cc_feature) for more information. | `[]` |
| enabled_features | (List[Label]) A list of [`cc_feature`](#cc_feature) rules whose initial state should be `enabled`. Note that it is still possible for these [features](https://bazel.build/docs/cc-toolchain-config-reference#features) to be disabled over the course of a build through other mechanisms. See the documentation for [`cc_feature`](#cc_feature) for more information. | `[]` |
| libc_top | (Label) A collection of artifacts for libc passed as inputs to compile/linking actions. See [`cc_toolchain.libc_top`](https://bazel.build/reference/be/c-cpp#cc_toolchain.libc_top) for more information. | `None` |
diff --git a/tests/rule_based_toolchain/legacy_tool/BUILD b/tests/rule_based_toolchain/legacy_tool/BUILD
new file mode 100644
index 000000000..7fa6244d0
--- /dev/null
+++ b/tests/rule_based_toolchain/legacy_tool/BUILD
@@ -0,0 +1,16 @@
+load("//cc/toolchains:legacy_tool.bzl", "cc_legacy_tool")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":legacy_tool_test.bzl", "TARGETS", "TESTS")
+
+cc_legacy_tool(
+ name = "gcov_tool",
+ path = "/usr/bin/gcov",
+ tool_name = "gcov",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/legacy_tool/legacy_tool_test.bzl b/tests/rule_based_toolchain/legacy_tool/legacy_tool_test.bzl
new file mode 100644
index 000000000..cb4471763
--- /dev/null
+++ b/tests/rule_based_toolchain/legacy_tool/legacy_tool_test.bzl
@@ -0,0 +1,32 @@
+# 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.
+"""Tests for the cc_legacy_tool rule."""
+
+load("//cc/toolchains:cc_toolchain_info.bzl", "LegacyToolInfo")
+
+visibility("private")
+
+def _legacy_tool_provides_info_test(env, targets):
+ info = targets.gcov_tool[LegacyToolInfo]
+ env.expect.that_str(info.name).equals("gcov")
+ env.expect.that_str(info.path).equals("/usr/bin/gcov")
+
+TARGETS = [
+ ":gcov_tool",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "legacy_tool_provides_info_test": _legacy_tool_provides_info_test,
+}
diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl
index 79e6f1b50..0eaa1eff7 100644
--- a/tests/rule_based_toolchain/subjects.bzl
+++ b/tests/rule_based_toolchain/subjects.bzl
@@ -238,6 +238,7 @@ _ToolchainConfigFactory = generate_factory(
allowlist_absolute_include_directories = ProviderDepset(_subjects.str),
artifact_name_patterns = [],
make_variables = [],
+ legacy_tools = _subjects.collection,
),
)
diff --git a/tests/rule_based_toolchain/toolchain_config/BUILD b/tests/rule_based_toolchain/toolchain_config/BUILD
index 7bf8d3b43..f85c82ddd 100644
--- a/tests/rule_based_toolchain/toolchain_config/BUILD
+++ b/tests/rule_based_toolchain/toolchain_config/BUILD
@@ -2,6 +2,7 @@ load("@rules_testing//lib:util.bzl", "util")
load("//cc/toolchains:args.bzl", "cc_args")
load("//cc/toolchains:feature.bzl", "cc_feature")
load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
+load("//cc/toolchains:legacy_tool.bzl", "cc_legacy_tool")
load("//cc/toolchains:tool.bzl", "cc_tool")
load("//cc/toolchains:tool_map.bzl", "cc_tool_map")
load("//cc/toolchains/args:sysroot.bzl", "cc_sysroot")
@@ -199,6 +200,21 @@ util.helper_target(
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
+cc_legacy_tool(
+ name = "legacy_gcov",
+ path = "/usr/bin/gcov",
+ tool_name = "gcov",
+)
+
+util.helper_target(
+ cc_toolchain_config,
+ name = "toolchain_config_with_legacy_tools",
+ legacy_tools = [
+ ":legacy_gcov",
+ ],
+ tool_map = ":empty_tool_map",
+)
+
analysis_test_suite(
name = "test_suite",
targets = TARGETS,
diff --git a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
index 1047203c4..5e20cfbf5 100644
--- a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
+++ b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
@@ -20,6 +20,7 @@ load(
legacy_flag_group = "flag_group",
legacy_flag_set = "flag_set",
legacy_tool = "tool",
+ legacy_tool_path = "tool_path", # buildifier: disable=deprecated-function
)
load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "ToolchainConfigInfo")
load("//cc/toolchains/impl:legacy_converter.bzl", "convert_toolchain")
@@ -258,6 +259,16 @@ def _toolchain_collects_files_test(env, targets):
),
]).in_order()
+def _legacy_tools_produce_tool_paths_test(env, targets):
+ tc = env.expect.that_target(
+ targets.toolchain_config_with_legacy_tools,
+ ).provider(ToolchainConfigInfo)
+
+ legacy = convert_toolchain(tc.actual)
+ env.expect.that_collection(legacy.tool_paths).contains_exactly([
+ legacy_tool_path(name = "gcov", path = "/usr/bin/gcov"),
+ ])
+
TARGETS = [
"//tests/rule_based_toolchain/actions:c_compile",
"//tests/rule_based_toolchain/actions:cpp_compile",
@@ -271,6 +282,7 @@ TARGETS = [
":c_compile_tool_map",
":empty_tool_map",
":implies_simple_feature",
+ ":legacy_gcov",
":overrides_feature",
":requires_any_simple_feature",
":requires_all_simple_feature",
@@ -278,6 +290,7 @@ TARGETS = [
":simple_feature",
":simple_feature2",
":same_feature_name",
+ ":toolchain_config_with_legacy_tools",
]
# @unsorted-dict-items
@@ -289,4 +302,5 @@ TESTS = {
"feature_missing_requirements_invalid_test": _feature_missing_requirements_invalid_test,
"args_missing_requirements_invalid_test": _args_missing_requirements_invalid_test,
"toolchain_collects_files_test": _toolchain_collects_files_test,
+ "legacy_tools_produce_tool_paths_test": _legacy_tools_produce_tool_paths_test,
}