BEGIN_PUBLIC

Implement ToolchainConfigInfo.

Add support to create the toolchain from feature, action config, and args targets, and validate it to ensure correctness.
END_PUBLIC

PiperOrigin-RevId: 612985448
Change-Id: I7d9086dd1dde07eb0c2484414c9c28c1b8bfb427
This commit is contained in:
Googler 2024-03-05 15:15:07 -08:00 committed by Copybara-Service
parent 9befdcd90e
commit 7250ef4352
7 changed files with 594 additions and 2 deletions

View File

@ -143,7 +143,7 @@ ActionTypeConfigInfo = provider(
"tools": "(Sequence[ToolInfo]) The tool applied to the action will be the first tool in the sequence with a feature set that matches the feature configuration",
"args": "(Sequence[ArgsInfo]) Set of flag sets the action sets",
"implies": "(depset[FeatureInfo]) Set of features implied by this action config",
"files": "(depset[File]) The files required to run these actions",
"files": "(runfiles) The files required to run these actions",
},
)
@ -155,3 +155,15 @@ ActionTypeConfigSetInfo = provider(
"configs": "(dict[ActionTypeInfo, ActionTypeConfigInfo]) A set of action configs",
},
)
ToolchainConfigInfo = provider(
doc = "The configuration for a toolchain",
# @unsorted-dict-items
fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"features": "(Sequence[FeatureInfo]) The features available for this toolchain",
"action_type_configs": "(dict[ActionTypeInfo, ActionTypeConfigInfo]) The configuration of action configs for the toolchain.",
"args": "(Sequence[ArgsInfo]) A list of arguments to be unconditionally applied to the toolchain.",
"files": "(dict[ActionTypeInfo, depset[File]]) Files required for the toolchain, keyed by the action type.",
},
)

View File

@ -0,0 +1,178 @@
# 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.
"""Helper functions to create and validate a ToolchainConfigInfo."""
load("//cc/toolchains:cc_toolchain_info.bzl", "ToolchainConfigInfo")
load(":args_utils.bzl", "get_action_type")
load(":collect.bzl", "collect_action_type_config_sets", "collect_args_lists", "collect_features")
visibility([
"//cc/toolchains/...",
"//tests/rule_based_toolchain/...",
])
_FEATURE_NAME_ERR = """The feature name {name} was defined by both {lhs} and {rhs}.
Possible causes:
* If you're overriding a feature in //cc/toolchains/features/..., then try adding the "overrides" parameter instead of specifying a feature name.
* If you intentionally have multiple features with the same name (eg. one for ARM and one for x86), then maybe you need add select() calls so that they're not defined at the same time.
* Otherwise, this is probably a real problem, and you need to give them different names.
"""
_INVALID_CONSTRAINT_ERR = """It is impossible to enable {provider}.
None of the entries in requires_any_of could be matched. This is required features are not implicitly added to the toolchain. It's likely that the feature that you require needs to be added to the toolchain explicitly.
"""
_UNKNOWN_FEATURE_ERR = """{self} implies the feature {ft}, which was unable to be found.
Implied features are not implicitly added to your toolchain. You likely need to add features = ["{ft}"] to your cc_toolchain rule.
"""
# Equality comparisons with bazel do not evaluate depsets.
# s = struct()
# d = depset([s])
# depset([s]) != depset([s])
# d == d
# This means that complex structs such as FeatureInfo will only compare as equal
# iff they are the *same* object or if there are no depsets inside them.
# Unfortunately, it seems that the FeatureInfo is copied during the
# cc_action_type_config rule. Ideally we'd like to fix that, but I don't really
# know what power we even have over such a thing.
def _feature_key(feature):
# This should be sufficiently unique.
return (feature.label, feature.name)
def _get_known_features(features, fail):
feature_names = {}
for ft in features:
if ft.name in feature_names:
other = feature_names[ft.name]
if other.overrides != ft and ft.overrides != other:
fail(_FEATURE_NAME_ERR.format(
name = ft.name,
lhs = ft.label,
rhs = other.label,
))
feature_names[ft.name] = ft
return {_feature_key(feature): None for feature in features}
def _can_theoretically_be_enabled(requirement, known_features):
return all([
_feature_key(ft) in known_features
for ft in requirement
])
def _validate_requires_any_of(fn, self, known_features, fail):
valid = any([
_can_theoretically_be_enabled(fn(requirement), known_features)
for requirement in self.requires_any_of
])
# No constraints is always valid.
if self.requires_any_of and not valid:
fail(_INVALID_CONSTRAINT_ERR.format(provider = self.label))
def _validate_requires_any_of_constraint(self, known_features, fail):
return _validate_requires_any_of(
lambda constraint: constraint.all_of.to_list(),
self,
known_features,
fail,
)
def _validate_requires_any_of_feature_set(self, known_features, fail):
return _validate_requires_any_of(
lambda feature_set: feature_set.features.to_list(),
self,
known_features,
fail,
)
def _validate_implies(self, known_features, fail = fail):
for ft in self.implies.to_list():
if _feature_key(ft) not in known_features:
fail(_UNKNOWN_FEATURE_ERR.format(self = self.label, ft = ft.label))
def _validate_args(self, known_features, fail):
_validate_requires_any_of_constraint(self, known_features, fail = fail)
def _validate_tool(self, known_features, fail):
_validate_requires_any_of_constraint(self, known_features, fail = fail)
def _validate_action_config(self, known_features, fail):
_validate_implies(self, known_features, fail = fail)
for tool in self.tools:
_validate_tool(tool, known_features, fail = fail)
for args in self.args:
_validate_args(args, known_features, fail = fail)
def _validate_feature(self, known_features, fail):
_validate_requires_any_of_feature_set(self, known_features, fail = fail)
for arg in self.args.args:
_validate_args(arg, known_features, fail = fail)
_validate_implies(self, known_features, fail = fail)
def _validate_toolchain(self, fail = fail):
known_features = _get_known_features(self.features, fail = fail)
for feature in self.features:
_validate_feature(feature, known_features, fail = fail)
for atc in self.action_type_configs.values():
_validate_action_config(atc, known_features, fail = fail)
for args in self.args:
_validate_args(args, known_features, fail = fail)
def _collect_files_for_action_type(atc, features, args):
transitive_files = [atc.files.files, get_action_type(args, atc.action_type).files]
for ft in features:
transitive_files.append(get_action_type(ft.args, atc.action_type).files)
return depset(transitive = transitive_files)
def toolchain_config_info(label, features = [], args = [], action_type_configs = [], fail = fail):
"""Generates and validates a ToolchainConfigInfo from lists of labels.
Args:
label: (Label) The label to apply to the ToolchainConfigInfo
features: (List[Target]) A list of targets providing FeatureSetInfo
args: (List[Target]) A list of targets providing ArgsListInfo
action_type_configs: (List[Target]) A list of targets providing
ActionTypeConfigSetInfo
fail: A fail function. Use only during tests.
Returns:
A validated ToolchainConfigInfo
"""
features = collect_features(features).to_list()
args = collect_args_lists(args, label = label)
action_type_configs = collect_action_type_config_sets(
action_type_configs,
label = label,
fail = fail,
).configs
files = {
atc.action_type: _collect_files_for_action_type(atc, features, args)
for atc in action_type_configs.values()
}
toolchain_config = ToolchainConfigInfo(
label = label,
features = features,
action_type_configs = action_type_configs,
args = args.args,
files = files,
)
_validate_toolchain(toolchain_config, fail = fail)
return toolchain_config

View File

@ -63,6 +63,8 @@ def generate_factory(type, name, attrs):
want_keys = sorted(attrs.keys())
def validate(*, value, meta):
if value == None:
meta.add_failure("Wanted a %s but got" % name, value)
got_keys = sorted(structs.to_dict(value).keys())
subjects.collection(got_keys, meta = meta.derive(details = [
"Value was not a %s - it has a different set of fields" % name,

View File

@ -13,6 +13,7 @@
# limitations under the License.
"""Implementation of a result type for use with rules_testing."""
load("@bazel_skylib//lib:structs.bzl", "structs")
load("@rules_testing//lib:truth.bzl", "subjects")
visibility("//tests/rule_based_toolchain/...")
@ -85,7 +86,13 @@ def result_subject(factory):
def err():
if value.err == None:
meta.add_failure("Wanted an error, but got a value", value.ok)
return subjects.str(value.err, meta = meta.derive("err()"))
subject = subjects.str(value.err, meta = meta.derive("err()"))
def contains_all_of(values):
for value in values:
subject.contains(str(value))
return struct(contains_all_of = contains_all_of, **structs.to_dict(subject))
return struct(ok = ok, err = err)

View File

@ -29,6 +29,7 @@ load(
"FeatureSetInfo",
"MutuallyExclusiveCategoryInfo",
"ToolInfo",
"ToolchainConfigInfo",
)
load(":generate_factory.bzl", "ProviderDepset", "ProviderSequence", "generate_factory")
load(":generics.bzl", "dict_key_subject", "optional_subject", "result_subject", "struct_subject", _result_fn_wrapper = "result_fn_wrapper")
@ -185,6 +186,18 @@ _ActionTypeConfigSetFactory = generate_factory(
),
)
# buildifier: disable=name-conventions
_ToolchainConfigFactory = generate_factory(
ToolchainConfigInfo,
"ToolchainConfigInfo",
dict(
features = ProviderDepset(_FeatureFactory),
action_type_configs = dict_key_subject(_ActionTypeConfigFactory.factory),
args = ProviderSequence(_ArgsFactory),
files = dict_key_subject(_subjects.depset_file),
),
)
FACTORIES = [
_ActionTypeFactory,
_ActionTypeSetFactory,
@ -197,6 +210,7 @@ FACTORIES = [
_FeatureSetFactory,
_ToolFactory,
_ActionTypeConfigSetFactory,
_ToolchainConfigFactory,
]
result_fn_wrapper = _result_fn_wrapper

View File

@ -0,0 +1,160 @@
load("@rules_testing//lib:util.bzl", "util")
load("//cc/toolchains:action_type_config.bzl", "cc_action_type_config")
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:tool.bzl", "cc_tool")
load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
load(":toolchain_config_test.bzl", "TARGETS", "TESTS")
util.helper_target(
cc_feature_set,
name = "all_simple_features",
all_of = [
":simple_feature",
"simple_feature2",
],
)
util.helper_target(
cc_external_feature,
name = "builtin_feature",
feature_name = "builtin_feature",
overridable = True,
)
util.helper_target(
cc_args,
name = "c_compile_args",
actions = ["//tests/rule_based_toolchain/actions:c_compile"],
additional_files = ["//tests/rule_based_toolchain/testdata:file1"],
args = ["c_compile_args"],
)
util.helper_target(
cc_args,
name = "compile_args",
actions = ["//tests/rule_based_toolchain/actions:all_compile"],
additional_files = ["//tests/rule_based_toolchain/testdata:file2"],
args = ["compile_args"],
)
util.helper_target(
cc_action_type_config,
name = "compile_config",
action_types = ["//tests/rule_based_toolchain/actions:all_compile"],
tools = [
"//tests/rule_based_toolchain/testdata:bin",
],
)
util.helper_target(
cc_feature,
name = "compile_feature",
args = [":compile_args"],
enabled = True,
feature_name = "compile_feature",
)
util.helper_target(
cc_action_type_config,
name = "c_compile_config",
action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
implies = [":simple_feature"],
tools = [
"//tests/rule_based_toolchain/testdata:bin",
],
)
util.helper_target(
cc_feature,
name = "implies_simple_feature",
args = [":c_compile_args"],
enabled = True,
feature_name = "implies",
implies = [":simple_feature"],
)
util.helper_target(
cc_feature,
name = "overrides_feature",
args = [":c_compile_args"],
enabled = True,
overrides = ":builtin_feature",
)
util.helper_target(
cc_args,
name = "requires_all_simple_args",
actions = ["//tests/rule_based_toolchain/actions:c_compile"],
args = ["--foo"],
requires_any_of = [":all_simple_features"],
)
util.helper_target(
cc_feature,
name = "requires_all_simple_feature",
args = [":c_compile_args"],
enabled = True,
feature_name = "requires_any_simple",
requires_any_of = [":all_simple_features"],
)
util.helper_target(
cc_tool,
name = "requires_all_simple_tool",
executable = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
requires_any_of = [":all_simple_features"],
)
util.helper_target(
cc_action_type_config,
name = "requires_all_simple_action_type_config",
action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
tools = [":requires_all_simple_tool"],
)
util.helper_target(
cc_feature,
name = "requires_any_simple_feature",
args = [":c_compile_args"],
enabled = True,
feature_name = "requires_any_simple",
requires_any_of = [
":simple_feature",
":simple_feature2",
],
)
util.helper_target(
cc_feature,
name = "same_feature_name",
args = [":c_compile_args"],
enabled = False,
feature_name = "simple_feature",
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
util.helper_target(
cc_feature,
name = "simple_feature",
args = [":c_compile_args"],
enabled = False,
feature_name = "simple_feature",
)
util.helper_target(
cc_feature,
name = "simple_feature2",
args = [":c_compile_args"],
enabled = False,
feature_name = "simple_feature2",
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
analysis_test_suite(
name = "test_suite",
targets = TARGETS,
tests = TESTS,
)

View File

@ -0,0 +1,219 @@
# 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", "ActionTypeInfo")
load("//cc/toolchains/impl:toolchain_config_info.bzl", _toolchain_config_info = "toolchain_config_info")
load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
visibility("private")
toolchain_config_info = result_fn_wrapper(_toolchain_config_info)
def _expect_that_toolchain(env, expr = None, **kwargs):
return env.expect.that_value(
value = toolchain_config_info(label = Label("//:toolchain"), **kwargs),
expr = expr,
factory = subjects.result(subjects.ToolchainConfigInfo),
)
def _empty_toolchain_valid_test(env, _targets):
_expect_that_toolchain(env).ok()
def _duplicate_feature_names_invalid_test(env, targets):
_expect_that_toolchain(
env,
features = [targets.simple_feature, targets.same_feature_name],
expr = "duplicate_feature_name",
).err().contains_all_of([
"The feature name simple_feature was defined by",
targets.same_feature_name.label,
targets.simple_feature.label,
])
# Overriding a feature gives it the same name. Ensure this isn't blocked.
_expect_that_toolchain(
env,
features = [targets.builtin_feature, targets.overrides_feature],
expr = "override_feature",
).ok()
def _duplicate_action_type_invalid_test(env, targets):
_expect_that_toolchain(
env,
features = [targets.simple_feature],
action_type_configs = [targets.compile_config, targets.c_compile_config],
).err().contains_all_of([
"The action type %s is configured by" % targets.c_compile.label,
targets.compile_config.label,
targets.c_compile_config.label,
])
def _action_config_implies_missing_feature_invalid_test(env, targets):
_expect_that_toolchain(
env,
features = [targets.simple_feature],
action_type_configs = [targets.c_compile_config],
expr = "action_type_config_with_implies",
).ok()
_expect_that_toolchain(
env,
features = [],
action_type_configs = [targets.c_compile_config],
expr = "action_type_config_missing_implies",
).err().contains(
"%s implies the feature %s" % (targets.c_compile_config.label, targets.simple_feature.label),
)
def _feature_config_implies_missing_feature_invalid_test(env, targets):
_expect_that_toolchain(
env,
expr = "feature_with_implies",
features = [targets.simple_feature, targets.implies_simple_feature],
).ok()
_expect_that_toolchain(
env,
features = [targets.implies_simple_feature],
expr = "feature_missing_implies",
).err().contains(
"%s implies the feature %s" % (targets.implies_simple_feature.label, targets.simple_feature.label),
)
def _feature_missing_requirements_invalid_test(env, targets):
_expect_that_toolchain(
env,
features = [targets.requires_any_simple_feature, targets.simple_feature],
expr = "requires_any_simple_has_simple",
).ok()
_expect_that_toolchain(
env,
features = [targets.requires_any_simple_feature, targets.simple_feature2],
expr = "requires_any_simple_has_simple2",
).ok()
_expect_that_toolchain(
env,
features = [targets.requires_any_simple_feature],
expr = "requires_any_simple_has_none",
).err().contains(
"It is impossible to enable %s" % targets.requires_any_simple_feature.label,
)
_expect_that_toolchain(
env,
features = [targets.requires_all_simple_feature, targets.simple_feature, targets.simple_feature2],
expr = "requires_all_simple_has_both",
).ok()
_expect_that_toolchain(
env,
features = [targets.requires_all_simple_feature, targets.simple_feature],
expr = "requires_all_simple_has_simple",
).err().contains(
"It is impossible to enable %s" % targets.requires_all_simple_feature.label,
)
_expect_that_toolchain(
env,
features = [targets.requires_all_simple_feature, targets.simple_feature2],
expr = "requires_all_simple_has_simple2",
).err().contains(
"It is impossible to enable %s" % targets.requires_all_simple_feature.label,
)
def _args_missing_requirements_invalid_test(env, targets):
_expect_that_toolchain(
env,
args = [targets.requires_all_simple_args],
features = [targets.simple_feature, targets.simple_feature2],
expr = "has_both",
).ok()
_expect_that_toolchain(
env,
args = [targets.requires_all_simple_args],
features = [targets.simple_feature],
expr = "has_only_one",
).err().contains(
"It is impossible to enable %s" % targets.requires_all_simple_args.label,
)
def _tool_missing_requirements_invalid_test(env, targets):
_expect_that_toolchain(
env,
action_type_configs = [targets.requires_all_simple_action_type_config],
features = [targets.simple_feature, targets.simple_feature2],
expr = "has_both",
).ok()
_expect_that_toolchain(
env,
action_type_configs = [targets.requires_all_simple_action_type_config],
features = [targets.simple_feature],
expr = "has_only_one",
).err().contains(
"It is impossible to enable %s" % targets.requires_all_simple_tool.label,
)
def _toolchain_collects_files_test(env, targets):
tc = _expect_that_toolchain(
env,
args = [targets.c_compile_args],
action_type_configs = [targets.compile_config],
features = [targets.compile_feature],
).ok()
tc.files().get(targets.c_compile[ActionTypeInfo]).contains_exactly([
# From :compile_config's tool
"tests/rule_based_toolchain/testdata/bin",
# From :c_compile_args
"tests/rule_based_toolchain/testdata/file1",
# From :compile_feature's args
"tests/rule_based_toolchain/testdata/file2",
])
tc.files().get(targets.cpp_compile[ActionTypeInfo]).contains_exactly([
# From :compile_config's tool
"tests/rule_based_toolchain/testdata/bin",
# From :compile_feature's args
"tests/rule_based_toolchain/testdata/file2",
])
TARGETS = [
"//tests/rule_based_toolchain/actions:c_compile",
"//tests/rule_based_toolchain/actions:cpp_compile",
":builtin_feature",
":compile_config",
":compile_feature",
":c_compile_args",
":c_compile_config",
":implies_simple_feature",
":overrides_feature",
":requires_any_simple_feature",
":requires_all_simple_feature",
":requires_all_simple_args",
":requires_all_simple_action_type_config",
":requires_all_simple_tool",
":simple_feature",
":simple_feature2",
":same_feature_name",
]
# @unsorted-dict-items
TESTS = {
"empty_toolchain_valid_test": _empty_toolchain_valid_test,
"duplicate_feature_names_fail_validation_test": _duplicate_feature_names_invalid_test,
"duplicate_action_type_invalid_test": _duplicate_action_type_invalid_test,
"action_config_implies_missing_feature_invalid_test": _action_config_implies_missing_feature_invalid_test,
"feature_config_implies_missing_feature_invalid_test": _feature_config_implies_missing_feature_invalid_test,
"feature_missing_requirements_invalid_test": _feature_missing_requirements_invalid_test,
"args_missing_requirements_invalid_test": _args_missing_requirements_invalid_test,
"tool_missing_requirements_invalid_test": _tool_missing_requirements_invalid_test,
"toolchain_collects_files_test": _toolchain_collects_files_test,
}