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