diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl index a295e62..158dd2f 100644 --- a/cc/toolchains/cc_toolchain_info.bzl +++ b/cc/toolchains/cc_toolchain_info.bzl @@ -150,11 +150,20 @@ ToolInfo = provider( fields = { "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "exe": "(File) The file corresponding to the tool", - "runfiles": "(depset[File]) The files required to run the tool", + "runfiles": "(runfiles) The files required to run the tool", "execution_requirements": "(Sequence[str]) A set of execution requirements of the tool", }, ) +ToolConfigInfo = provider( + doc = "A mapping from action to tool", + # @unsorted-dict-items + fields = { + "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", + "configs": "(dict[ActionTypeInfo, ToolInfo]) A mapping from action to tool.", + }, +) + ActionTypeConfigInfo = provider( doc = "Configuration of a Bazel action.", # @unsorted-dict-items diff --git a/cc/toolchains/tool_map.bzl b/cc/toolchains/tool_map.bzl new file mode 100644 index 0000000..300523d --- /dev/null +++ b/cc/toolchains/tool_map.bzl @@ -0,0 +1,86 @@ +# 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. +"""Implementation of cc_tool_map.""" + +load( + "//cc/toolchains/impl:collect.bzl", + "collect_provider", + "collect_tools", +) +load( + ":cc_toolchain_info.bzl", + "ActionTypeSetInfo", + "ToolConfigInfo", +) + +def _cc_tool_map_impl(ctx): + tools = collect_tools(ctx, ctx.attr.tools) + action_sets = collect_provider(ctx.attr.actions, ActionTypeSetInfo) + + action_to_tool = {} + action_to_as = {} + for i in range(len(action_sets)): + action_set = action_sets[i] + tool = tools[i] + + for action in action_set.actions.to_list(): + if action in action_to_as: + fail("The action %s appears multiple times in your tool_map (as %s and %s)" % (action.label, action_set.label, action_to_as[action].label)) + action_to_as[action] = action_set + action_to_tool[action] = tool + + return [ToolConfigInfo(label = ctx.label, configs = action_to_tool)] + +_cc_tool_map = rule( + implementation = _cc_tool_map_impl, + # @unsorted-dict-items + attrs = { + "actions": attr.label_list( + providers = [ActionTypeSetInfo], + mandatory = True, + doc = """A list of action names to apply this action to. + +See @toolchain//actions:all for valid options. +""", + ), + "tools": attr.label_list( + mandatory = True, + cfg = "exec", + allow_files = True, + doc = """The tool to use for the specified actions. + +A tool is usually a binary, but may be a `cc_tool`. + +If multiple tools are specified, the first tool that has `with_features` that +satisfy the currently enabled feature set is used. +""", + ), + }, + provides = [ToolConfigInfo], +) + +def cc_tool_map(name, tools, **kwargs): + """Configuration for which actions require which tools. + + Args: + name: (str) The name of the target + tools: (Dict[Action target, Executable target]) + **kwargs: kwargs to be passed to the underlying rule. + """ + _cc_tool_map( + name = name, + actions = tools.keys(), + tools = tools.values(), + **kwargs + ) diff --git a/tests/rule_based_toolchain/generics.bzl b/tests/rule_based_toolchain/generics.bzl index 17bd3a6..c4ec9e8 100644 --- a/tests/rule_based_toolchain/generics.bzl +++ b/tests/rule_based_toolchain/generics.bzl @@ -136,4 +136,5 @@ dict_key_subject = lambda factory: lambda value, *, meta: struct( value[key], meta = meta.derive("get({})".format(key)), ), + contains = lambda key: subjects.bool(key in value, meta = meta.derive("contains({})".format(key))), ) diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl index bdfaae5..bae87e1 100644 --- a/tests/rule_based_toolchain/subjects.bzl +++ b/tests/rule_based_toolchain/subjects.bzl @@ -28,6 +28,7 @@ load( "FeatureSetInfo", "MutuallyExclusiveCategoryInfo", "NestedArgsInfo", + "ToolConfigInfo", "ToolInfo", "ToolchainConfigInfo", ) @@ -183,6 +184,15 @@ _ToolFactory = generate_factory( ), ) +# buildifier: disable=name-conventions +_ToolConfigFactory = generate_factory( + ToolConfigInfo, + "ToolConfigInfo", + dict( + configs = dict_key_subject(_ToolFactory.factory), + ), +) + # buildifier: disable=name-conventions _ActionTypeConfigFactory = generate_factory( ActionTypeConfigInfo, @@ -227,6 +237,7 @@ FACTORIES = [ _FeatureConstraintFactory, _FeatureSetFactory, _ToolFactory, + _ToolConfigFactory, _ActionTypeConfigSetFactory, _ToolchainConfigFactory, ] diff --git a/tests/rule_based_toolchain/testing_rules.bzl b/tests/rule_based_toolchain/testing_rules.bzl new file mode 100644 index 0000000..0e0968f --- /dev/null +++ b/tests/rule_based_toolchain/testing_rules.bzl @@ -0,0 +1,48 @@ +# 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. +"""Helpers for creating tests for the rule based toolchain.""" + +load("@rules_testing//lib:analysis_test.bzl", _analysis_test = "analysis_test") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", "util") +load(":subjects.bzl", "FACTORIES") + +visibility("//tests/rule_based_toolchain/...") + +helper_target = util.helper_target + +def analysis_test(*, name, **kwargs): + """An analysis test for the toolchain rules. + + Args: + name: (str) The name of the test suite. + **kwargs: Kwargs to be passed to rules_testing's analysis_test. + """ + + _analysis_test( + name = name, + provider_subject_factories = FACTORIES, + **kwargs + ) + +def expect_failure_test(*, name, target, failure_message): + def _impl(env, target): + env.expect.that_target(target).failures().contains_predicate(matching.contains(failure_message)) + + _analysis_test( + name = name, + expect_failure = True, + impl = _impl, + target = target, + ) diff --git a/tests/rule_based_toolchain/tool_config/BUILD b/tests/rule_based_toolchain/tool_config/BUILD new file mode 100644 index 0000000..2ebbaea --- /dev/null +++ b/tests/rule_based_toolchain/tool_config/BUILD @@ -0,0 +1,9 @@ +load( + ":tool_map_test.bzl", + "duplicate_tool_test", + "valid_config_test", +) + +duplicate_tool_test(name = "duplicate_tool_test") + +valid_config_test(name = "valid_config_test") diff --git a/tests/rule_based_toolchain/tool_config/tool_map_test.bzl b/tests/rule_based_toolchain/tool_config/tool_map_test.bzl new file mode 100644 index 0000000..1bfdf3d --- /dev/null +++ b/tests/rule_based_toolchain/tool_config/tool_map_test.bzl @@ -0,0 +1,72 @@ +# 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_tool_map rule.""" + +load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "ToolConfigInfo") +load("//cc/toolchains:tool_map.bzl", "cc_tool_map") +load("//tests/rule_based_toolchain:subjects.bzl", "subjects") +load("//tests/rule_based_toolchain:testing_rules.bzl", "analysis_test", "expect_failure_test", "helper_target") + +_ALL_ACTIONS = "//cc/toolchains/actions:all_actions" +_C_COMPILE = "//cc/toolchains/actions:c_compile" +_CPP_COMPILE = "//cc/toolchains/actions:cpp_compile" +_ALL_CPP_COMPILE = "//cc/toolchains/actions:cpp_compile_actions" +_STRIP = "//cc/toolchains/actions:strip" +_BIN = "//tests/rule_based_toolchain/testdata:bin" +_BIN_WRAPPER = "//tests/rule_based_toolchain/testdata:bin_wrapper" + +def valid_config_test(name): + subject_name = "_%s_subject" % name + cc_tool_map( + name = subject_name, + tools = { + _C_COMPILE: _BIN_WRAPPER, + _ALL_CPP_COMPILE: _BIN, + }, + ) + + analysis_test( + name = name, + impl = _valid_config_test_impl, + targets = { + "c_compile": _C_COMPILE, + "cpp_compile": _CPP_COMPILE, + "strip": _STRIP, + "subject": subject_name, + }, + ) + +def _valid_config_test_impl(env, targets): + configs = env.expect.that_target(targets.subject).provider(ToolConfigInfo).configs() + + configs.contains(targets.strip[ActionTypeInfo]).equals(False) + configs.get(targets.c_compile[ActionTypeInfo]).exe().path().split("/").offset(-1, subjects.str).equals("bin_wrapper") + configs.get(targets.cpp_compile[ActionTypeInfo]).exe().path().split("/").offset(-1, subjects.str).equals("bin") + +def duplicate_tool_test(name): + subject_name = "_%s_subject" % name + helper_target( + cc_tool_map, + name = subject_name, + tools = { + _C_COMPILE: _BIN_WRAPPER, + _ALL_ACTIONS: _BIN, + }, + ) + + expect_failure_test( + name = name, + target = subject_name, + failure_message = "appears multiple times in your tool_map", + )