xref: /aosp_15_r20/external/bazelbuild-rules_cc/cc/toolchains/impl/toolchain_config_info.bzl (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
1# Copyright 2024 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Helper functions to create and validate a ToolchainConfigInfo."""
15
16load("//cc/toolchains:cc_toolchain_info.bzl", "ToolchainConfigInfo")
17load(":args_utils.bzl", "get_action_type")
18load(":collect.bzl", "collect_action_type_config_sets", "collect_args_lists", "collect_features")
19
20visibility([
21    "//cc/toolchains/...",
22    "//tests/rule_based_toolchain/...",
23])
24
25_FEATURE_NAME_ERR = """The feature name {name} was defined by both {lhs} and {rhs}.
26
27Possible causes:
28* If you're overriding a feature in //cc/toolchains/features/..., then try adding the "overrides" parameter instead of specifying a feature name.
29* 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.
30* Otherwise, this is probably a real problem, and you need to give them different names.
31"""
32
33_INVALID_CONSTRAINT_ERR = """It is impossible to enable {provider}.
34
35None 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.
36"""
37
38_UNKNOWN_FEATURE_ERR = """{self} implies the feature {ft}, which was unable to be found.
39
40Implied features are not implicitly added to your toolchain. You likely need to add features = ["{ft}"] to your cc_toolchain rule.
41"""
42
43# Equality comparisons with bazel do not evaluate depsets.
44# s = struct()
45# d = depset([s])
46# depset([s]) != depset([s])
47# d == d
48# This means that complex structs such as FeatureInfo will only compare as equal
49# iff they are the *same* object or if there are no depsets inside them.
50# Unfortunately, it seems that the FeatureInfo is copied during the
51# cc_action_type_config rule. Ideally we'd like to fix that, but I don't really
52# know what power we even have over such a thing.
53def _feature_key(feature):
54    # This should be sufficiently unique.
55    return (feature.label, feature.name)
56
57def _get_known_features(features, fail):
58    feature_names = {}
59    for ft in features:
60        if ft.name in feature_names:
61            other = feature_names[ft.name]
62            if other.overrides != ft and ft.overrides != other:
63                fail(_FEATURE_NAME_ERR.format(
64                    name = ft.name,
65                    lhs = ft.label,
66                    rhs = other.label,
67                ))
68        feature_names[ft.name] = ft
69
70    return {_feature_key(feature): None for feature in features}
71
72def _can_theoretically_be_enabled(requirement, known_features):
73    return all([
74        _feature_key(ft) in known_features
75        for ft in requirement
76    ])
77
78def _validate_requires_any_of(fn, self, known_features, fail):
79    valid = any([
80        _can_theoretically_be_enabled(fn(requirement), known_features)
81        for requirement in self.requires_any_of
82    ])
83
84    # No constraints is always valid.
85    if self.requires_any_of and not valid:
86        fail(_INVALID_CONSTRAINT_ERR.format(provider = self.label))
87
88def _validate_requires_any_of_constraint(self, known_features, fail):
89    return _validate_requires_any_of(
90        lambda constraint: constraint.all_of.to_list(),
91        self,
92        known_features,
93        fail,
94    )
95
96def _validate_requires_any_of_feature_set(self, known_features, fail):
97    return _validate_requires_any_of(
98        lambda feature_set: feature_set.features.to_list(),
99        self,
100        known_features,
101        fail,
102    )
103
104def _validate_implies(self, known_features, fail = fail):
105    for ft in self.implies.to_list():
106        if _feature_key(ft) not in known_features:
107            fail(_UNKNOWN_FEATURE_ERR.format(self = self.label, ft = ft.label))
108
109def _validate_args(self, known_features, fail):
110    _validate_requires_any_of_constraint(self, known_features, fail = fail)
111
112def _validate_tool(self, known_features, fail):
113    _validate_requires_any_of_constraint(self, known_features, fail = fail)
114
115def _validate_action_config(self, known_features, fail):
116    _validate_implies(self, known_features, fail = fail)
117    for tool in self.tools:
118        _validate_tool(tool, known_features, fail = fail)
119    for args in self.args:
120        _validate_args(args, known_features, fail = fail)
121
122def _validate_feature(self, known_features, fail):
123    _validate_requires_any_of_feature_set(self, known_features, fail = fail)
124    for arg in self.args.args:
125        _validate_args(arg, known_features, fail = fail)
126    _validate_implies(self, known_features, fail = fail)
127
128def _validate_toolchain(self, fail = fail):
129    known_features = _get_known_features(self.features, fail = fail)
130
131    for feature in self.features:
132        _validate_feature(feature, known_features, fail = fail)
133    for atc in self.action_type_configs.values():
134        _validate_action_config(atc, known_features, fail = fail)
135    for args in self.args:
136        _validate_args(args, known_features, fail = fail)
137
138def _collect_files_for_action_type(atc, features, args):
139    transitive_files = [atc.files.files, get_action_type(args, atc.action_type).files]
140    for ft in features:
141        transitive_files.append(get_action_type(ft.args, atc.action_type).files)
142
143    return depset(transitive = transitive_files)
144
145def toolchain_config_info(label, features = [], args = [], action_type_configs = [], fail = fail):
146    """Generates and validates a ToolchainConfigInfo from lists of labels.
147
148    Args:
149        label: (Label) The label to apply to the ToolchainConfigInfo
150        features: (List[Target]) A list of targets providing FeatureSetInfo
151        args: (List[Target]) A list of targets providing ArgsListInfo
152        action_type_configs: (List[Target]) A list of targets providing
153          ActionTypeConfigSetInfo
154        fail: A fail function. Use only during tests.
155    Returns:
156        A validated ToolchainConfigInfo
157    """
158    features = collect_features(features).to_list()
159    args = collect_args_lists(args, label = label)
160    action_type_configs = collect_action_type_config_sets(
161        action_type_configs,
162        label = label,
163        fail = fail,
164    ).configs
165    files = {
166        atc.action_type: _collect_files_for_action_type(atc, features, args)
167        for atc in action_type_configs.values()
168    }
169
170    toolchain_config = ToolchainConfigInfo(
171        label = label,
172        features = features,
173        action_type_configs = action_type_configs,
174        args = args.args,
175        files = files,
176    )
177    _validate_toolchain(toolchain_config, fail = fail)
178    return toolchain_config
179