1# Copyright 2023 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://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, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Private utilities and global variables.""" 15 16load( 17 "@rules_cc//cc:cc_toolchain_config_lib.bzl", 18 rules_cc_action_config = "action_config", 19 rules_cc_env_entry = "env_entry", 20 rules_cc_env_set = "env_set", 21 rules_cc_feature = "feature", 22 rules_cc_feature_set = "feature_set", 23 rules_cc_flag_set = "flag_set", 24 rules_cc_with_feature_set = "with_feature_set", 25) 26 27visibility(["//cc_toolchain/tests/..."]) 28 29ALL_FILE_GROUPS = { 30 "ar_files": ["@pw_toolchain//actions:all_ar_actions"], 31 "as_files": ["@pw_toolchain//actions:all_asm_actions"], 32 "compiler_files": ["@pw_toolchain//actions:all_compiler_actions"], 33 "coverage_files": ["@pw_toolchain//actions:llvm_cov"], 34 "dwp_files": [], 35 "linker_files": ["@pw_toolchain//actions:all_link_actions"], 36 "objcopy_files": ["@pw_toolchain//actions:objcopy_embed_data"], 37 "strip_files": ["@pw_toolchain//actions:strip"], 38} 39 40def _ensure_fulfillable(any_of, known, label, fail = fail): 41 # Requirements can be fulfilled if there are no requirements. 42 fulfillable = not any_of 43 for group in any_of: 44 all_met = True 45 for entry in group.to_list(): 46 if entry.label not in known: 47 all_met = False 48 break 49 if all_met: 50 fulfillable = True 51 break 52 53 if not fulfillable: 54 fail("%s cannot possibly be enabled (none of the constraints it requires fully exist). Either remove it from your toolchain, or add the requirements." % label) 55 56def _to_untyped_flag_set(flag_set, known, fail = fail): 57 """Converts a PwFlagSet to rules_cc's flag set.""" 58 _ensure_fulfillable( 59 any_of = [constraint.all_of for constraint in flag_set.requires_any_of], 60 known = known, 61 label = flag_set.label, 62 fail = fail, 63 ) 64 actions = list(flag_set.actions) 65 with_features = [ 66 _to_untyped_feature_constraint(fc) 67 for fc in flag_set.requires_any_of 68 ] 69 70 out_flag_set = None 71 if flag_set.flag_groups: 72 out_flag_set = rules_cc_flag_set( 73 flag_groups = list(flag_set.flag_groups), 74 actions = actions, 75 with_features = with_features, 76 ) 77 78 out_env_set = None 79 if flag_set.env: 80 out_env_set = rules_cc_env_set( 81 env_entries = [ 82 rules_cc_env_entry( 83 key = key, 84 value = value, 85 expand_if_available = flag_set.env_expand_if_available, 86 ) 87 for key, value in flag_set.env.items() 88 ], 89 actions = actions, 90 with_features = with_features, 91 ) 92 return struct( 93 flag_set = out_flag_set, 94 env_set = out_env_set, 95 ) 96 97def _to_untyped_flag_sets(flag_sets, known, fail): 98 out_flag_sets = [] 99 out_env_sets = [] 100 out = [_to_untyped_flag_set(flag_set, known, fail) for flag_set in flag_sets] 101 for entry in out: 102 if entry.flag_set != None: 103 out_flag_sets.append(entry.flag_set) 104 if entry.env_set != None: 105 out_env_sets.append(entry.env_set) 106 return struct(flag_sets = out_flag_sets, env_sets = out_env_sets) 107 108def _to_untyped_feature_set(feature_set): 109 return rules_cc_feature_set([ 110 feature.name 111 for feature in feature_set.features.to_list() 112 ]) 113 114def _to_untyped_feature_constraint(feature_constraint): 115 return rules_cc_with_feature_set( 116 features = [ft.name for ft in feature_constraint.all_of.to_list()], 117 not_features = [ft.name for ft in feature_constraint.none_of.to_list()], 118 ) 119 120def _to_untyped_implies(provider, known, fail = fail): 121 implies = [] 122 for feature in provider.implies_features.to_list(): 123 if feature.label not in known: 124 fail("%s implies %s, which is not explicitly mentioned in your toolchain configuration" % (provider.label, feature.label)) 125 implies.append(feature.name) 126 for action_config in provider.implies_action_configs.to_list(): 127 if action_config.label not in known: 128 fail("%s implies %s, which is not explicitly mentioned in your toolchain configuration" % (provider.label, action_config.label)) 129 implies.append(action_config.action_name) 130 return implies 131 132def _to_untyped_feature(feature, known, fail = fail): 133 if feature.known: 134 return None 135 136 _ensure_fulfillable( 137 any_of = [fs.features for fs in feature.requires_any_of], 138 known = known, 139 label = feature.label, 140 fail = fail, 141 ) 142 143 flags = _to_untyped_flag_sets(feature.flag_sets.to_list(), known, fail = fail) 144 145 return rules_cc_feature( 146 name = feature.name, 147 enabled = feature.enabled, 148 flag_sets = flags.flag_sets, 149 env_sets = flags.env_sets, 150 implies = _to_untyped_implies(feature, known, fail = fail), 151 requires = [_to_untyped_feature_set(requirement) for requirement in feature.requires_any_of], 152 provides = list(feature.provides), 153 ) 154 155def _to_untyped_tool(tool, known, fail = fail): 156 _ensure_fulfillable( 157 any_of = [constraint.all_of for constraint in tool.requires_any_of], 158 known = known, 159 label = tool.label, 160 fail = fail, 161 ) 162 163 # Rules_cc is missing the "tool" parameter. 164 return struct( 165 path = tool.path, 166 tool = tool.exe, 167 execution_requirements = list(tool.execution_requirements), 168 with_features = [ 169 _to_untyped_feature_constraint(fc) 170 for fc in tool.requires_any_of 171 ], 172 type_name = "tool", 173 ) 174 175def _to_untyped_action_config(action_config, extra_flag_sets, known, fail = fail): 176 # De-dupe, in case the same flag set was specified for both unconditional 177 # and for a specific action config. 178 flag_sets = depset( 179 list(action_config.flag_sets) + extra_flag_sets, 180 order = "preorder", 181 ).to_list() 182 untyped_flags = _to_untyped_flag_sets(flag_sets, known = known, fail = fail) 183 implies = _to_untyped_implies(action_config, known, fail = fail) 184 185 # Action configs don't take in an env like they do a flag set. 186 # In order to support them, we create a feature with the env that the action 187 # config will enable, and imply it in the action config. 188 feature = None 189 if untyped_flags.env_sets: 190 feature = rules_cc_feature( 191 name = "implied_by_%s" % action_config.action_name, 192 env_sets = untyped_flags.env_sets, 193 ) 194 implies.append(feature.name) 195 196 return struct( 197 action_config = rules_cc_action_config( 198 action_name = action_config.action_name, 199 enabled = action_config.enabled, 200 tools = [ 201 _to_untyped_tool(tool, known, fail = fail) 202 for tool in action_config.tools 203 ], 204 flag_sets = [ 205 # Make the flag sets actionless. 206 rules_cc_flag_set( 207 actions = [], 208 with_features = fs.with_features, 209 flag_groups = fs.flag_groups, 210 ) 211 for fs in untyped_flags.flag_sets 212 ], 213 implies = implies, 214 ), 215 features = [feature] if feature else [], 216 ) 217 218def to_untyped_config(feature_set, action_config_set, flag_sets, extra_action_files, fail = fail): 219 """Converts Pigweed providers into a format suitable for rules_cc. 220 221 Args: 222 feature_set: PwFeatureSetInfo: Features available in the toolchain 223 action_config_set: ActionConfigSetInfo: Set of defined action configs 224 flag_sets: Flag sets that are unconditionally applied 225 extra_action_files: Files to be added to actions 226 fail: The fail function. Only change this during testing. 227 Returns: 228 A struct containing parameters suitable to pass to 229 cc_common.create_cc_toolchain_config_info. 230 """ 231 flag_sets_by_action = {} 232 for flag_set in flag_sets: 233 for action in flag_set.actions: 234 flag_sets_by_action.setdefault(action, []).append(flag_set) 235 236 known_labels = {} 237 known_feature_names = {} 238 feature_list = feature_set.features.to_list() 239 for feature in feature_list: 240 known_labels[feature.label] = None 241 existing_feature = known_feature_names.get(feature.name, None) 242 if existing_feature != None and feature.overrides != existing_feature and existing_feature.overrides != feature: 243 fail("Conflicting features: %s and %s both have feature name %s" % (feature.label, existing_feature.label, feature.name)) 244 245 known_feature_names[feature.name] = feature 246 247 untyped_features = [] 248 for feature in feature_list: 249 untyped_feature = _to_untyped_feature(feature, known = known_labels, fail = fail) 250 if untyped_feature != None: 251 untyped_features.append(untyped_feature) 252 253 acs = action_config_set.action_configs.to_list() 254 known_actions = {} 255 untyped_acs = [] 256 for ac in acs: 257 if ac.action_name in known_actions: 258 fail("In %s, both %s and %s implement %s" % ( 259 action_config_set.label, 260 ac.label, 261 known_actions[ac.action_name], 262 ac.action_name, 263 )) 264 known_actions[ac.action_name] = ac.label 265 out_ac = _to_untyped_action_config( 266 ac, 267 extra_flag_sets = flag_sets_by_action.get(ac.action_name, []), 268 known = known_labels, 269 fail = fail, 270 ) 271 untyped_acs.append(out_ac.action_config) 272 untyped_features.extend(out_ac.features) 273 274 action_to_files = { 275 ac.action_name: [ac.files] 276 for ac in acs 277 } 278 for ffa in extra_action_files.srcs.to_list(): 279 action_to_files.setdefault(ffa.action, []).append(ffa.files) 280 281 return struct( 282 features = untyped_features, 283 action_configs = untyped_acs, 284 action_to_files = {k: depset(transitive = v) for k, v in action_to_files.items()}, 285 ) 286