BEGIN_PUBLIC
Implement cc_tool
END_PUBLIC
PiperOrigin-RevId: 609307150
Change-Id: I2e135a59e06a56ca8ec071254d340ac4b984b234
diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl
index f7a69ba..5efdda9 100644
--- a/cc/toolchains/cc_toolchain_info.bzl
+++ b/cc/toolchains/cc_toolchain_info.bzl
@@ -124,7 +124,7 @@
# @unsorted-dict-items
fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
- "exe": "(Optional[File]) The file corresponding to the tool",
+ "exe": "(File) The file corresponding to the tool",
"runfiles": "(depset[File]) The files required to run the tool",
"requires_any_of": "(Sequence[FeatureConstraintInfo]) A set of constraints, one of which is required to enable the tool. Equivalent to with_features",
"execution_requirements": "(Sequence[str]) A set of execution requirements of the tool",
diff --git a/cc/toolchains/impl/collect.bzl b/cc/toolchains/impl/collect.bzl
index 77979b4..9d97471 100644
--- a/cc/toolchains/impl/collect.bzl
+++ b/cc/toolchains/impl/collect.bzl
@@ -16,15 +16,19 @@
load(
"//cc/toolchains:cc_toolchain_info.bzl",
"ActionTypeSetInfo",
+ "ToolInfo",
)
-visibility("//cc/toolchains/...")
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
def collect_provider(targets, provider):
"""Collects providers from a label list.
Args:
- targets: (list[Target]) An attribute from attr.label_list
+ targets: (List[Target]) An attribute from attr.label_list
provider: (provider) The provider to look up
Returns:
A list of the providers
@@ -35,7 +39,7 @@
"""Collects DefaultInfo from a label list.
Args:
- targets: (list[Target]) An attribute from attr.label_list
+ targets: (List[Target]) An attribute from attr.label_list
Returns:
A list of the associated defaultinfo
"""
@@ -53,3 +57,55 @@
collect_action_types = _make_collector(ActionTypeSetInfo, "actions")
collect_files = _make_collector(DefaultInfo, "files")
+
+def collect_data(ctx, targets):
+ """Collects from a 'data' attribute.
+
+ This is distinguished from collect_files by the fact that data attributes
+ attributes include runfiles.
+
+ Args:
+ ctx: (Context) The ctx for the current rule
+ targets: (List[Target]) A list of files or executables
+
+ Returns:
+ A depset containing all files for each of the targets, and all runfiles
+ required to run them.
+ """
+ return ctx.runfiles(transitive_files = collect_files(targets)).merge_all([
+ info.default_runfiles
+ for info in collect_defaultinfo(targets)
+ if info.default_runfiles != None
+ ])
+
+def collect_tools(ctx, targets, fail = fail):
+ """Collects tools from a label_list.
+
+ Each entry in the label list may either be a cc_tool or a binary.
+
+ Args:
+ ctx: (Context) The ctx for the current rule
+ targets: (List[Target]) A list of targets. Each of these targets may be
+ either a cc_tool or an executable.
+ fail: (function) The fail function. Should only be used in tests.
+
+ Returns:
+ A List[ToolInfo], with regular executables creating custom tool info.
+ """
+ tools = []
+ for target in targets:
+ info = target[DefaultInfo]
+ if ToolInfo in target:
+ tools.append(target[ToolInfo])
+ elif info.files_to_run != None and info.files_to_run.executable != None:
+ tools.append(ToolInfo(
+ label = target.label,
+ exe = info.files_to_run.executable,
+ runfiles = collect_data(ctx, [target]),
+ requires_any_of = tuple(),
+ execution_requirements = tuple(),
+ ))
+ else:
+ fail("Expected %s to be a cc_tool or a binary rule" % target.label)
+
+ return tools
diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl
new file mode 100644
index 0000000..01ea481
--- /dev/null
+++ b/cc/toolchains/tool.bzl
@@ -0,0 +1,100 @@
+# Copyright 2023 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_tool"""
+
+load("//cc/toolchains/impl:collect.bzl", "collect_data", "collect_provider")
+load(
+ ":cc_toolchain_info.bzl",
+ "FeatureConstraintInfo",
+ "ToolInfo",
+)
+
+def _cc_tool_impl(ctx):
+ exe = ctx.executable.executable
+ runfiles = collect_data(ctx, ctx.attr.data + [ctx.attr.executable])
+ tool = ToolInfo(
+ label = ctx.label,
+ exe = exe,
+ runfiles = runfiles,
+ requires_any_of = tuple(collect_provider(
+ ctx.attr.requires_any_of,
+ FeatureConstraintInfo,
+ )),
+ execution_requirements = tuple(ctx.attr.execution_requirements),
+ )
+
+ link = ctx.actions.declare_file(ctx.label.name)
+ ctx.actions.symlink(
+ output = link,
+ target_file = ctx.executable.executable,
+ is_executable = True,
+ )
+ return [
+ tool,
+ # This isn't required, but now we can do "bazel run <tool>", which can
+ # be very helpful when debugging toolchains.
+ DefaultInfo(
+ files = depset([link]),
+ runfiles = runfiles,
+ executable = link,
+ ),
+ ]
+
+cc_tool = rule(
+ implementation = _cc_tool_impl,
+ # @unsorted-dict-items
+ attrs = {
+ "executable": attr.label(
+ allow_files = True,
+ executable = True,
+ cfg = "exec",
+ doc = """The underlying binary that this tool represents.
+
+Usually just a single prebuilt (eg. @sysroot//:bin/clang), but may be any
+executable label.
+""",
+ ),
+ "data": attr.label_list(
+ allow_files = True,
+ doc = "Additional files that are required for this tool to run.",
+ ),
+ "execution_requirements": attr.string_list(
+ doc = "A list of strings that provide hints for execution environment compatibility (e.g. `requires-mac`).",
+ ),
+ "requires_any_of": attr.label_list(
+ providers = [FeatureConstraintInfo],
+ doc = """This will be enabled when any of the constraints are met.
+
+If omitted, this tool will be enabled unconditionally.
+""",
+ ),
+ },
+ provides = [ToolInfo],
+ doc = """Declares a tool that can be bound to action configs.
+
+A tool is a binary with extra metadata for the action config rule to consume
+(eg. execution_requirements).
+
+Example:
+```
+cc_tool(
+ name = "clang_tool",
+ executable = "@llvm_toolchain//:bin/clang",
+ # Suppose clang needs libc to run.
+ data = ["@llvm_toolchain//:lib/x86_64-linux-gnu/libc.so.6"]
+)
+```
+""",
+ executable = True,
+)
diff --git a/tests/rule_based_toolchain/args/args_test.bzl b/tests/rule_based_toolchain/args/args_test.bzl
index 6aab2e2..859cc6c 100644
--- a/tests/rule_based_toolchain/args/args_test.bzl
+++ b/tests/rule_based_toolchain/args/args_test.bzl
@@ -18,7 +18,7 @@
"ArgsInfo",
)
-visibility("//tests/rule_based_toolchain/...")
+visibility("private")
def _test_simple_args_impl(env, targets):
simple = env.expect.that_target(targets.simple).provider(ArgsInfo)
diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl
index 87f6cb4..5e5ca62 100644
--- a/tests/rule_based_toolchain/subjects.bzl
+++ b/tests/rule_based_toolchain/subjects.bzl
@@ -34,6 +34,10 @@
visibility("//tests/rule_based_toolchain/...")
+# The default runfiles subject uses path instead of short_path.
+# This makes it rather awkward for copybara.
+runfiles_subject = lambda value, meta: _subjects.depset_file(value.files, meta = meta)
+
# buildifier: disable=name-conventions
_ActionTypeFactory = generate_factory(
ActionTypeInfo,
@@ -135,7 +139,7 @@
"ToolInfo",
dict(
exe = _subjects.file,
- runfiles = _subjects.depset_file,
+ runfiles = runfiles_subject,
requires_any_of = ProviderSequence(_FeatureConstraintFactory),
execution_requirements = _subjects.collection,
),
@@ -196,5 +200,6 @@
result = result_subject,
optional = optional_subject,
struct = struct_subject,
+ runfiles = runfiles_subject,
) | {factory.name: factory.factory for factory in FACTORIES})
)
diff --git a/tests/rule_based_toolchain/testdata/BUILD b/tests/rule_based_toolchain/testdata/BUILD
index 6007720..4bfb3e6 100644
--- a/tests/rule_based_toolchain/testdata/BUILD
+++ b/tests/rule_based_toolchain/testdata/BUILD
@@ -1,3 +1,5 @@
+load("@bazel_skylib//rules:native_binary.bzl", "native_binary")
+
package(default_visibility = ["//tests/rule_based_toolchain:__subpackages__"])
exports_files(
@@ -7,6 +9,13 @@
),
)
+native_binary(
+ name = "bin_wrapper",
+ src = "bin_wrapper.sh",
+ out = "bin_wrapper",
+ data = [":bin"],
+)
+
filegroup(
name = "multiple",
srcs = [
@@ -14,3 +23,10 @@
"multiple2",
],
)
+
+# Analysis_test is unable to depend on source files directly, but it can depend
+# on a filegroup containing a single file.
+filegroup(
+ name = "bin_filegroup",
+ srcs = ["bin"],
+)
diff --git a/tests/rule_based_toolchain/testdata/bin b/tests/rule_based_toolchain/testdata/bin
new file mode 100755
index 0000000..ff29c83
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/bin
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Running unwrapped tool"
diff --git a/tests/rule_based_toolchain/testdata/bin_wrapper.sh b/tests/rule_based_toolchain/testdata/bin_wrapper.sh
new file mode 100755
index 0000000..ce615a2
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/bin_wrapper.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Running tool wrapper"
diff --git a/tests/rule_based_toolchain/tool/BUILD b/tests/rule_based_toolchain/tool/BUILD
new file mode 100644
index 0000000..a6b01ea
--- /dev/null
+++ b/tests/rule_based_toolchain/tool/BUILD
@@ -0,0 +1,30 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:tool.bzl", "cc_tool")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":tool_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_tool,
+ name = "tool",
+ data = ["//tests/rule_based_toolchain/testdata:bin"],
+ executable = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ execution_requirements = ["requires-mac"],
+)
+
+util.helper_target(
+ cc_tool,
+ name = "wrapped_tool",
+ executable = "//tests/rule_based_toolchain/testdata:bin_wrapper",
+)
+
+util.helper_target(
+ cc_tool,
+ name = "data_with_runfiles",
+ executable = "//tests/rule_based_toolchain/testdata:file1",
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/tool/tool_test.bzl b/tests/rule_based_toolchain/tool/tool_test.bzl
new file mode 100644
index 0000000..840ae84
--- /dev/null
+++ b/tests/rule_based_toolchain/tool/tool_test.bzl
@@ -0,0 +1,104 @@
+# 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.
+"""Tests for the cc_args rule."""
+
+load("//cc/toolchains:cc_toolchain_info.bzl", "ToolInfo")
+load("//cc/toolchains/impl:collect.bzl", _collect_tools = "collect_tools")
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+collect_tools = result_fn_wrapper(_collect_tools)
+collection_result = subjects.result(subjects.collection)
+
+collect_tool = result_fn_wrapper(
+ lambda ctx, target, fail: _collect_tools(ctx, [target], fail = fail)[0],
+)
+tool_result = subjects.result(subjects.ToolInfo)
+
+# Generated by native_binary.
+_BIN_WRAPPER_SYMLINK = "tests/rule_based_toolchain/testdata/bin_wrapper"
+_BIN_WRAPPER = "tests/rule_based_toolchain/testdata/bin_wrapper.sh"
+_BIN = "tests/rule_based_toolchain/testdata/bin"
+
+def _tool_test(env, targets):
+ tool = env.expect.that_target(targets.tool).provider(ToolInfo)
+ tool.exe().short_path_equals(_BIN_WRAPPER)
+ tool.runfiles().contains_exactly([
+ _BIN_WRAPPER,
+ _BIN,
+ ])
+
+def _wrapped_tool_includes_runfiles_test(env, targets):
+ tool = env.expect.that_target(targets.wrapped_tool).provider(ToolInfo)
+ tool.exe().short_path_equals(_BIN_WRAPPER_SYMLINK)
+ tool.runfiles().contains_exactly([
+ _BIN_WRAPPER_SYMLINK,
+ _BIN,
+ ])
+
+def _collect_tools_collects_tools_test(env, targets):
+ env.expect.that_value(
+ value = collect_tools(env.ctx, [targets.tool, targets.wrapped_tool]),
+ factory = collection_result,
+ ).ok().contains_exactly(
+ [targets.tool[ToolInfo], targets.wrapped_tool[ToolInfo]],
+ ).in_order()
+
+def _collect_tools_collects_binaries_test(env, targets):
+ tool_wrapper = env.expect.that_value(
+ value = collect_tool(env.ctx, targets.bin_wrapper),
+ factory = tool_result,
+ ).ok()
+ tool_wrapper.label().equals(targets.bin_wrapper.label)
+ tool_wrapper.exe().short_path_equals(_BIN_WRAPPER_SYMLINK)
+ tool_wrapper.runfiles().contains_exactly([
+ _BIN_WRAPPER_SYMLINK,
+ _BIN,
+ ])
+
+def _collect_tools_collects_single_files_test(env, targets):
+ bin = env.expect.that_value(
+ value = collect_tool(env.ctx, targets.bin_filegroup),
+ factory = tool_result,
+ expr = "bin_filegroup",
+ ).ok()
+ bin.label().equals(targets.bin_filegroup.label)
+ bin.exe().short_path_equals(_BIN)
+ bin.runfiles().contains_exactly([_BIN])
+
+def _collect_tools_fails_on_non_binary_test(env, targets):
+ env.expect.that_value(
+ value = collect_tools(env.ctx, [targets.multiple]),
+ factory = collection_result,
+ expr = "multiple_non_binary",
+ ).err()
+
+TARGETS = [
+ "//tests/rule_based_toolchain/tool:tool",
+ "//tests/rule_based_toolchain/tool:wrapped_tool",
+ "//tests/rule_based_toolchain/testdata:bin_wrapper",
+ "//tests/rule_based_toolchain/testdata:multiple",
+ "//tests/rule_based_toolchain/testdata:bin_filegroup",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "tool_test": _tool_test,
+ "wrapped_tool_includes_runfiles_test": _wrapped_tool_includes_runfiles_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,
+ "collect_tools_fails_on_non_binary_test": _collect_tools_fails_on_non_binary_test,
+}