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