• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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", "ToolConfigInfo", "ToolchainConfigInfo")
17load(":args_utils.bzl", "get_action_type")
18load(":collect.bzl", "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, capability_features, fail):
58    feature_names = {}
59    for ft in capability_features + 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_feature_set(self, known_features, fail):
89    return _validate_requires_any_of(
90        lambda feature_set: feature_set.features.to_list(),
91        self,
92        known_features,
93        fail,
94    )
95
96def _validate_implies(self, known_features, fail = fail):
97    for ft in self.implies.to_list():
98        if _feature_key(ft) not in known_features:
99            fail(_UNKNOWN_FEATURE_ERR.format(self = self.label, ft = ft.label))
100
101def _validate_args(self, known_features, fail):
102    return _validate_requires_any_of(
103        lambda constraint: constraint.all_of.to_list(),
104        self,
105        known_features,
106        fail,
107    )
108
109def _validate_feature(self, known_features, fail):
110    _validate_requires_any_of_feature_set(self, known_features, fail = fail)
111    for arg in self.args.args:
112        _validate_args(arg, known_features, fail = fail)
113    _validate_implies(self, known_features, fail = fail)
114
115def _validate_toolchain(self, fail = fail):
116    capabilities = []
117    for tool in self.tool_map.configs.values():
118        capabilities.extend([cap.feature for cap in tool.capabilities])
119    known_features = _get_known_features(self.features, capabilities, fail = fail)
120
121    for feature in self.features:
122        _validate_feature(feature, known_features, fail = fail)
123    for args in self.args.args:
124        _validate_args(args, known_features, fail = fail)
125
126def _collect_files_for_action_type(action_type, tool_map, features, args):
127    transitive_files = [tool_map[action_type].runfiles.files, get_action_type(args, action_type).files]
128    for ft in features:
129        transitive_files.append(get_action_type(ft.args, action_type).files)
130
131    return depset(transitive = transitive_files)
132
133def toolchain_config_info(label, known_features = [], enabled_features = [], args = [], tool_map = None, fail = fail):
134    """Generates and validates a ToolchainConfigInfo from lists of labels.
135
136    Args:
137        label: (Label) The label to apply to the ToolchainConfigInfo
138        known_features: (List[Target]) A list of features that can be enabled.
139        enabled_features: (List[Target]) A list of features that are enabled by
140          default. Every enabled feature is implicitly also a known feature.
141        args: (List[Target]) A list of targets providing ArgsListInfo
142        tool_map: (Target) A target providing ToolMapInfo.
143        fail: A fail function. Use only during tests.
144    Returns:
145        A validated ToolchainConfigInfo
146    """
147
148    # Later features will come after earlier features on the command-line, and
149    # thus override them. Because of this, we ensure that known_features comes
150    # *after* enabled_features, so that if we do enable them, they override the
151    # default feature flags.
152    features = collect_features(enabled_features + known_features).to_list()
153    enabled_features = collect_features(enabled_features).to_list()
154
155    if tool_map == None:
156        fail("tool_map is required")
157
158        # The `return` here is to support testing, since injecting `fail()` has a
159        # side-effect of allowing code to continue.
160        return None  # buildifier: disable=unreachable
161
162    args = collect_args_lists(args, label = label)
163    tools = tool_map[ToolConfigInfo].configs
164    files = {
165        action_type: _collect_files_for_action_type(action_type, tools, features, args)
166        for action_type in tools.keys()
167    }
168    allowlist_include_directories = depset(
169        transitive = [
170            src.allowlist_include_directories
171            for src in features + tools.values()
172        ] + [args.allowlist_include_directories],
173    )
174    toolchain_config = ToolchainConfigInfo(
175        label = label,
176        features = features,
177        enabled_features = enabled_features,
178        tool_map = tool_map[ToolConfigInfo],
179        args = args,
180        files = files,
181        allowlist_include_directories = allowlist_include_directories,
182    )
183    _validate_toolchain(toolchain_config, fail = fail)
184    return toolchain_config
185