From a1da7d0a5ac92a4f8d4df04ac2a472552600d5d2 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Wed, 25 Mar 2026 17:23:59 -0700 Subject: [PATCH] Add cc_legacy_tool stopgap Some tools in rules_cc like gcov and llvm-cov, expect absolute paths in the toolchain. This clearly isn't ideal but updating all callers as a blocker to being able to use rules based toolchains also isn't ideal. This allows backfilling those absolute tool paths with the new cc_legacy_tool rule. --- cc/toolchains/cc_toolchain_info.bzl | 11 ++++ cc/toolchains/impl/legacy_converter.bzl | 10 +++ cc/toolchains/impl/toolchain_config.bzl | 4 ++ cc/toolchains/impl/toolchain_config_info.bzl | 8 ++- cc/toolchains/legacy_tool.bzl | 63 +++++++++++++++++++ cc/toolchains/toolchain.bzl | 5 ++ docs/toolchain_api.md | 7 ++- tests/rule_based_toolchain/legacy_tool/BUILD | 16 +++++ .../legacy_tool/legacy_tool_test.bzl | 32 ++++++++++ tests/rule_based_toolchain/subjects.bzl | 1 + .../toolchain_config/BUILD | 16 +++++ .../toolchain_config_test.bzl | 14 +++++ 12 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 cc/toolchains/legacy_tool.bzl create mode 100644 tests/rule_based_toolchain/legacy_tool/BUILD create mode 100644 tests/rule_based_toolchain/legacy_tool/legacy_tool_test.bzl 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, }