BEGIN_PUBLIC

Implement cc_tool
END_PUBLIC

PiperOrigin-RevId: 609307150
Change-Id: I2e135a59e06a56ca8ec071254d340ac4b984b234
This commit is contained in:
Googler 2024-02-22 03:05:41 -08:00 committed by Copybara-Service
parent 9eb790fe47
commit 916074ec32
10 changed files with 323 additions and 6 deletions

View File

@ -124,7 +124,7 @@ ToolInfo = provider(
# @unsorted-dict-items # @unsorted-dict-items
fields = { fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "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", "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", "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", "execution_requirements": "(Sequence[str]) A set of execution requirements of the tool",

View File

@ -16,15 +16,19 @@
load( load(
"//cc/toolchains:cc_toolchain_info.bzl", "//cc/toolchains:cc_toolchain_info.bzl",
"ActionTypeSetInfo", "ActionTypeSetInfo",
"ToolInfo",
) )
visibility("//cc/toolchains/...") visibility([
"//cc/toolchains/...",
"//tests/rule_based_toolchain/...",
])
def collect_provider(targets, provider): def collect_provider(targets, provider):
"""Collects providers from a label list. """Collects providers from a label list.
Args: 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 provider: (provider) The provider to look up
Returns: Returns:
A list of the providers A list of the providers
@ -35,7 +39,7 @@ def collect_defaultinfo(targets):
"""Collects DefaultInfo from a label list. """Collects DefaultInfo from a label list.
Args: Args:
targets: (list[Target]) An attribute from attr.label_list targets: (List[Target]) An attribute from attr.label_list
Returns: Returns:
A list of the associated defaultinfo A list of the associated defaultinfo
""" """
@ -53,3 +57,55 @@ def _make_collector(provider, field):
collect_action_types = _make_collector(ActionTypeSetInfo, "actions") collect_action_types = _make_collector(ActionTypeSetInfo, "actions")
collect_files = _make_collector(DefaultInfo, "files") 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

100
cc/toolchains/tool.bzl Normal file
View File

@ -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,
)

View File

@ -18,7 +18,7 @@ load(
"ArgsInfo", "ArgsInfo",
) )
visibility("//tests/rule_based_toolchain/...") visibility("private")
def _test_simple_args_impl(env, targets): def _test_simple_args_impl(env, targets):
simple = env.expect.that_target(targets.simple).provider(ArgsInfo) simple = env.expect.that_target(targets.simple).provider(ArgsInfo)

View File

@ -34,6 +34,10 @@ load(":generics.bzl", "optional_subject", "result_subject", "struct_subject", _r
visibility("//tests/rule_based_toolchain/...") 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 # buildifier: disable=name-conventions
_ActionTypeFactory = generate_factory( _ActionTypeFactory = generate_factory(
ActionTypeInfo, ActionTypeInfo,
@ -135,7 +139,7 @@ _ToolFactory = generate_factory(
"ToolInfo", "ToolInfo",
dict( dict(
exe = _subjects.file, exe = _subjects.file,
runfiles = _subjects.depset_file, runfiles = runfiles_subject,
requires_any_of = ProviderSequence(_FeatureConstraintFactory), requires_any_of = ProviderSequence(_FeatureConstraintFactory),
execution_requirements = _subjects.collection, execution_requirements = _subjects.collection,
), ),
@ -196,5 +200,6 @@ subjects = struct(
result = result_subject, result = result_subject,
optional = optional_subject, optional = optional_subject,
struct = struct_subject, struct = struct_subject,
runfiles = runfiles_subject,
) | {factory.name: factory.factory for factory in FACTORIES}) ) | {factory.name: factory.factory for factory in FACTORIES})
) )

View File

@ -1,3 +1,5 @@
load("@bazel_skylib//rules:native_binary.bzl", "native_binary")
package(default_visibility = ["//tests/rule_based_toolchain:__subpackages__"]) package(default_visibility = ["//tests/rule_based_toolchain:__subpackages__"])
exports_files( exports_files(
@ -7,6 +9,13 @@ exports_files(
), ),
) )
native_binary(
name = "bin_wrapper",
src = "bin_wrapper.sh",
out = "bin_wrapper",
data = [":bin"],
)
filegroup( filegroup(
name = "multiple", name = "multiple",
srcs = [ srcs = [
@ -14,3 +23,10 @@ filegroup(
"multiple2", "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"],
)

3
tests/rule_based_toolchain/testdata/bin vendored Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
echo "Running unwrapped tool"

View File

@ -0,0 +1,3 @@
#!/bin/bash
echo "Running tool wrapper"

View File

@ -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,
)

View File

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