1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# This file is part of the openHiTLS project. 4# 5# openHiTLS is licensed under the Mulan PSL v2. 6# You can use this software according to the terms and conditions of the Mulan PSL v2. 7# You may obtain a copy of Mulan PSL v2 at: 8# 9# http://license.coscl.org.cn/MulanPSL2 10# 11# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 12# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 13# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14# See the Mulan PSL v2 for more details. 15import sys 16sys.dont_write_bytecode = True 17import json 18import os 19import re 20sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__)))) 21from methods import trans2list, save_json_file 22 23 24class Feature: 25 def __init__(self, name, target, parent, children, deps, opts, impl, ins_set): 26 self.name = name 27 28 self.target = target 29 30 self.parent = parent 31 self.children = children 32 33 self.deps = deps 34 self.opts = opts 35 36 self.impl = impl # Implementation mode 37 self.ins_set = ins_set # Instruction Set 38 39 40 @classmethod 41 def simple(cls, name, target, parent, impl): 42 return Feature(name, target, parent, [], [], [], impl, []) 43 44 45class FeatureParser: 46 """ Parsing feature files """ 47 lib_dir_map = { 48 "hitls_bsl": "bsl", 49 "hitls_crypto": "crypto", 50 "hitls_tls": "tls", 51 "hitls_pki": "pki", 52 "hitls_auth": "auth" 53 } 54 55 def __init__(self, file_path): 56 self._fp = file_path 57 with open(file_path, 'r', encoding='utf-8') as f: 58 self._cfg = json.loads(f.read()) 59 self._file_check() 60 61 # Features and related information. 62 self._feas_info = self._get_feas_info() 63 # Assembly type supported by the openHiTLS. 64 self._asm_types = self._get_asm_types() 65 66 @property 67 def libs(self): 68 return self._cfg['libs'] 69 70 @property 71 def modules(self): 72 return self._cfg['modules'] 73 74 @property 75 def asm_types(self): 76 return self._asm_types 77 78 @property 79 def feas_info(self): 80 return self._feas_info 81 82 def _file_check(self): 83 if 'libs' not in self._cfg or 'modules' not in self._cfg: 84 raise FileNotFoundError("The format of file %s is incorrect." % self._fp) 85 86 @staticmethod 87 def _add_key_value(obj, key, value): 88 if value: 89 obj[key] = value 90 91 def _add_fea(self, feas_info, feature: Feature): 92 fea_name = feature.name 93 feas_info.setdefault(fea_name, {}) 94 self._add_key_value(feas_info[fea_name], 'lib', feature.target) 95 self._add_key_value(feas_info[fea_name], 'parent', feature.parent) 96 self._add_key_value(feas_info[fea_name], 'children', feature.children) 97 self._add_key_value(feas_info[fea_name], 'opts', feature.opts) 98 self._add_key_value(feas_info[fea_name], 'deps', feature.deps) 99 100 feas_info[fea_name].setdefault('impl', {}) 101 feas_info[fea_name]['impl'][feature.impl] = feature.ins_set if feature.ins_set else [] 102 103 def _parse_fea_obj(self, name, target, parent, impl, fea_obj, feas_info): 104 feature = Feature.simple(name, target, parent, impl) 105 if not fea_obj: 106 self._add_fea(feas_info, feature) 107 return 108 109 feature.deps = fea_obj.get('deps', None) 110 feature.opts = fea_obj.get('opts', None) 111 feature.ins_set = fea_obj.get('ins_set', None) 112 113 non_sub_keys = ['opts', 'deps', 'ins_set', 'help'] 114 for key, obj in fea_obj.items(): 115 if key not in non_sub_keys: 116 feature.children.append(key) 117 self._parse_fea_obj(key, target, name, impl, obj, feas_info) 118 119 self._add_fea(feas_info, feature) 120 def parse_fearuers(self, all_feas, tmp_feas_info, target, target_obj): 121 tmp_feas_info[target] = {} 122 for impl, impl_obj in target_obj['features'].items(): 123 for fea, fea_obj in impl_obj.items(): 124 self._parse_fea_obj(fea, target, None, impl, fea_obj, tmp_feas_info[target]) 125 126 # Check that feature names in different target are unique. 127 tgt_feas = set(tmp_feas_info[target].keys()) 128 repeat_feas = all_feas.intersection(tgt_feas) 129 if len(repeat_feas) != 0: 130 raise ValueError("Error: feature '%s' has been defined in other target." % (repeat_feas)) 131 all_feas.update(tgt_feas) 132 133 def _get_feas_info(self): 134 """ 135 description: Parse the feature.json file to obtain feature information 136 and check that feature names in different libraries are unique. 137 return: 138 feas_info: { 139 "children":[], "parent":[], "deps":[], 140 "opts":[[],[]], "lib":"", 141 "impl":{"c":[], "armv8:[], ...}, # [] lists the instruction sets supported by the feature. 142 } 143 """ 144 all_feas = set() 145 tmp_feas_info = {} 146 for lib, lib_obj in self._cfg['libs'].items(): 147 self.parse_fearuers(all_feas, tmp_feas_info, lib, lib_obj) 148 149 feas_info = {} 150 for obj in tmp_feas_info.values(): 151 feas_info.update(obj) 152 self._fill_fea_modules(feas_info) 153 self._correct_impl(feas_info) 154 return feas_info 155 156 def _fill_fea_modules(self, feas_info): 157 for top_mod in self.modules: 158 for mod, mod_obj in self.modules[top_mod].items(): 159 formated_mod = "{}::{}".format(top_mod, mod) 160 for fea in mod_obj.get('.features', []): 161 if fea not in feas_info: 162 raise ValueError("Unrecognized '%s' in '.features' of '%s::%s'" % (fea, top_mod, mod)) 163 if 'modules' not in feas_info[fea]: 164 feas_info[fea]['modules'] = [formated_mod] 165 else: 166 feas_info[fea]['modules'].append(formated_mod) 167 168 @staticmethod 169 def _correct_impl(feas_info): 170 """Updated the implementation modes of sub-features based on the parent feature.""" 171 for fea in feas_info.keys(): 172 parent = feas_info[fea].get('parent', '') 173 if not parent: 174 continue 175 if len(feas_info[fea]['impl'].keys()) == 1 and 'c' in feas_info[fea]['impl']: 176 feas_info[fea]['impl'] = feas_info[parent]['impl'] 177 178 def _get_asm_types(self): 179 asm_type_set = set() 180 [asm_type_set.update(self.libs[lib]['features'].keys()) for lib in self.libs.keys()] 181 asm_type_set.discard('c') 182 asm_type_set.add('no_asm') 183 return asm_type_set 184 185 def get_module_deps(self, module, dep_list, result): 186 return self._get_module_deps(module, dep_list, result) 187 188 def _get_module_deps(self, module, dep_list, result): 189 """ 190 Recursively obtains the modules on which the modules depend. 191 module: [IN] module name, such as crypto::sha2 192 dep_list: [OUT] Dependency list, which is an intermediate variable 193 result: [OUT] result 194 """ 195 top_module, sub_module = module.split('::') 196 mod_obj = self.modules[top_module][sub_module] 197 198 if '.deps' not in mod_obj: 199 result.update(dep_list) 200 return 201 202 for dep_mod in mod_obj['.deps']: 203 if dep_mod in dep_list: 204 # A dependency that already exists in a dependency chain is a circular dependency. 205 raise Exception("Cyclic dependency") 206 dep_list.append(dep_mod) 207 self._get_module_deps(dep_mod, dep_list, result) 208 dep_list.pop() 209 210 def get_mod_srcs(self, top_mod, sub_mod, mod_obj): 211 srcs = self._cfg['modules'][top_mod][sub_mod]['.srcs'] 212 asm_type = mod_obj.get('asmType', 'c') 213 inc = mod_obj.get('incSet', '') 214 215 blurred_srcs = [] 216 if not isinstance(srcs, dict): 217 blurred_srcs.extend(trans2list(srcs)) 218 return blurred_srcs 219 220 blurred_srcs.extend(trans2list(srcs.get('public', []))) 221 if asm_type == 'c': 222 blurred_srcs.extend(trans2list(srcs.get('no_asm', []))) 223 return blurred_srcs 224 225 if asm_type not in srcs: 226 raise ValueError("Missing '.srcs[%s]' in modules '%s::%s'" % (asm_type, top_mod, sub_mod)) 227 if not isinstance(srcs[asm_type], dict): 228 blurred_srcs.extend(trans2list(srcs[asm_type])) 229 return blurred_srcs 230 231 if inc: 232 blurred_srcs.extend(trans2list(srcs[asm_type][inc])) 233 else: 234 first_key = list(srcs[asm_type].keys())[0] 235 blurred_srcs.extend(trans2list(srcs[asm_type][first_key])) 236 return blurred_srcs 237 238class FeatureConfigParser: 239 """ Parses the user feature configuration file. """ 240 # Specifications of keys and values in the file. 241 key_value = { 242 "system": {"require": False, "type": str, "choices": ["linux", ""], "default": "linux"}, 243 "bits": {"require": False, "type": int, "choices": [32, 64], "default": 64}, 244 "endian": {"require": True, "type": str, "choices": ["little", "big"], "default": "little"}, 245 "libType": { 246 "require": True, 247 "type": list, 248 "choices": ["static", "shared", "object"], 249 "default": ["static", "shared", "object"] 250 }, 251 "asmType":{"require": True, "type": str, "choices": [], "default": "no_asm"}, 252 "libs":{"require": True, "type": dict, "choices": [], "default": {}}, 253 "bundleLibs":{"require": False, "type": bool, "choices": [True, False], "default": False}, 254 "securecLib":{"require": False, "type": str, "choices": ["boundscheck", "securec", "sec_shared.z", ""], "default": "boundscheck"} 255 } 256 257 def __init__(self, features: FeatureParser, file_path): 258 self._features = features 259 self._config_file = file_path 260 with open(file_path, 'r', encoding='utf-8') as f: 261 self._cfg = json.loads(f.read()) 262 self.key_value['asmType']['choices'] = list(features.asm_types) 263 self.key_value['libs']['choices'] = list(features.libs) 264 self._file_check() 265 266 @classmethod 267 def default_cfg(cls): 268 config = {} 269 for key in cls.key_value.keys(): 270 if cls.key_value[key]["require"]: 271 config[key] = cls.key_value[key]["default"] 272 return config 273 274 @property 275 def libs(self): 276 return self._cfg['libs'] 277 278 @property 279 def lib_type(self): 280 return trans2list(self._cfg['libType']) 281 282 @property 283 def asm_type(self): 284 return self._cfg['asmType'] 285 286 @property 287 def bundle_libs(self): 288 if 'bundleLibs' in self._cfg: 289 return self._cfg['bundleLibs'] 290 return self.key_value['bundleLibs']['default'] 291 292 @property 293 def securec_lib(self): 294 if 'securecLib' in self._cfg: 295 return self._cfg['securecLib'] 296 return self.key_value['securecLib']['default'] 297 298 @staticmethod 299 def _get_fea_and_inc(asm_fea): 300 if '::' in asm_fea: 301 return asm_fea.split('::') 302 else: 303 return asm_fea, '' 304 305 def _asm_fea_check(self, asm_fea, asm_type, info): 306 fea, inc = self._get_fea_and_inc(asm_fea) 307 feas_info = self._features.feas_info 308 if fea not in feas_info: 309 raise ValueError("Unsupported '%s' in %s" % (fea, info)) 310 if asm_type not in feas_info[fea]['impl']: 311 raise ValueError("Feature '%s' has no assembly implementation of type '%s' in %s" % (fea, asm_type, info)) 312 if inc: 313 if inc not in feas_info[fea]['impl'] and inc not in feas_info[fea]['impl'][asm_type]: 314 raise ValueError("Unsupported instruction set of '%s' in %s" % (asm_fea, info)) 315 return fea, inc 316 317 def _file_check(self): 318 for key, value in self.key_value.items(): 319 if value['require']: 320 if key not in self._cfg.keys(): 321 raise ValueError("Error feature_config file: missing '%s'" % key) 322 323 for key, value in self._cfg.items(): 324 if key not in self.key_value.keys(): 325 raise ValueError("Error feature_config file: unsupported config '%s'" % key) 326 if not isinstance(value, self.key_value.get(key).get("type")): 327 raise ValueError("Error feature_config file: wrong type of '%s'" % key) 328 329 value_type = type(value) 330 if value_type == str or value_type == str: 331 if value not in self.key_value.get(key).get("choices"): 332 if key == "system": 333 print("Info: There is no {} implementation by default, you should set its SAL callbacks to make it work.".format(value)) 334 continue 335 raise ValueError("Error feature_config file: wrong value of '%s'" % key) 336 elif value_type == list: 337 choices = set(self.key_value[key]["choices"]) 338 if not set(value).issubset(choices): 339 raise ValueError("Error feature_config file: wrong value of '%s'" % key) 340 341 for lib, lib_obj in self._cfg['libs'].items(): 342 if lib not in self._features.libs: 343 raise ValueError("Error feature_config file: unsupported lib '%s'" % lib) 344 for fea in lib_obj.get('c', []): 345 if fea not in self._features.feas_info: 346 raise ValueError("Error feature_config file: unsupported fea '%s' in lib '%s'" % (fea, lib)) 347 asm_feas = [] 348 for asm_fea in lib_obj.get('asm', []): 349 fea, _ = self._asm_fea_check(asm_fea, self.asm_type, 'feature_config file') 350 if fea in asm_feas: 351 raise ValueError("Error feature_config file: duplicate assembly feature '%s'" % fea) 352 asm_feas.append(fea) 353 354 def set_param(self, key, value, set_default=True): 355 if key == 'bundleLibs': 356 self._cfg[key] = value 357 return 358 if value: 359 self._cfg[key] = value 360 return 361 if not set_default: 362 return 363 if key not in self._cfg or not self._cfg[key]: 364 print("Warning: Configuration item '{}' is missing and has been set to the default value '{}'.".format( 365 key, self.key_value.get(key).get('default'))) 366 self._cfg[key] = self.key_value.get(key).get('default') 367 368 def _get_related_feas(self, fea, feas_info, related: set): 369 related.add(fea) 370 if 'parent' in feas_info[fea]: 371 parent = feas_info[fea]['parent'] 372 for dep in feas_info[parent].get('deps', []): 373 self._get_related_feas(dep, feas_info, related) 374 if 'children' in feas_info[fea]: 375 for child in feas_info[fea]['children']: 376 self._get_related_feas(child, feas_info, related) 377 if 'deps' in feas_info[fea]: 378 for dep in feas_info[fea]['deps']: 379 self._get_related_feas(dep, feas_info, related) 380 381 def _get_parents(self, disables): 382 parents = set() 383 for d in disables: 384 relation = self._features.feas_info.get(d) 385 if relation and 'parent' in relation: 386 parents.add(relation['parent']) 387 return parents 388 389 def _add_depend_feas(self, enable_feas, feas_info): 390 related = set() 391 for f in enable_feas: 392 fea, inc = self._get_fea_and_inc(f) 393 self._get_related_feas(fea, feas_info, related) 394 395 enable_feas.update(related) 396 397 def _check_asm_fea_enable(self, enable_feas, feas, feas_info): 398 not_in_enable = [] 399 for f in feas: 400 fea, _ = self._get_fea_and_inc(f) 401 if fea in enable_feas: # This feature is already in the enable list. 402 continue 403 404 rel = feas_info[fea] 405 is_enable = False 406 while('parent' in rel): 407 parent = rel['parent'] 408 if parent in enable_feas: # This feature is already in the enable list. 409 is_enable = True 410 break 411 rel = feas_info[parent] 412 if not is_enable: 413 not_in_enable.append(fea) 414 if not_in_enable: 415 raise ValueError("To add '%s' assembly requires add it to 'enable' list" % not_in_enable) 416 417 def get_enable_feas(self, arg_enable, arg_asm): 418 """ 419 Get the enabled features form: 420 1. build/feature_config.json 421 2. argument: enable list 422 3. argument: asm list 423 """ 424 enable_feas = set() 425 enable_asm_feas = set() 426 # 1. Exist feas in build/feature_config.json 427 for _, lib_obj in self._cfg['libs'].items(): 428 enable_feas.update(lib_obj.get('c', [])) 429 enable_asm_feas.update(lib_obj.get('asm', [])) 430 431 # 2. Obtains the properties from the input parameter: enable list. 432 feas_info = self._features.feas_info 433 if 'all' in arg_enable: # all features 434 enable_feas.update(set(x for x in feas_info.keys())) 435 else: 436 for enable in arg_enable: 437 if enable in self._features.libs: # features in a lib 438 enable_feas.update(set(x for x, y in feas_info.items() if enable == y.get('lib', ''))) 439 else: # The feature is not lib and needs to be added separately. 440 enable_feas.add(enable) 441 442 enable_feas.update(enable_asm_feas) 443 self._add_depend_feas(enable_feas, feas_info) 444 self._check_asm_fea_enable(enable_feas, arg_asm, feas_info) 445 enable_asm_feas.update(arg_asm) 446 return enable_feas, enable_asm_feas 447 448 def _add_feature(self, fea, impl_type, inc=''): 449 add_fea = fea if inc == '' else '{}::{}'.format(fea, inc) 450 lib = self._features.feas_info[fea]['lib'] 451 if lib not in self._cfg['libs']: 452 self._cfg['libs'][lib] = {impl_type: [add_fea]} 453 elif impl_type not in self._cfg['libs'][lib]: 454 self._cfg['libs'][lib][impl_type] = [add_fea] 455 elif fea not in self._cfg['libs'][lib][impl_type]: 456 self._cfg['libs'][lib][impl_type].append(add_fea) 457 458 def set_asm_type(self, asm_type): 459 if self._cfg['asmType'] == 'no_asm': 460 self._cfg['asmType'] = asm_type 461 elif self._cfg['asmType'] != asm_type: 462 raise ValueError('Error asmType: %s is different from feature_config file.' % (asm_type)) 463 464 def set_asm_features(self, enable_feas, asm_feas, asm_type): 465 feas_info = self._features.feas_info 466 # Clear the assembly features first. 467 for lib in self._cfg['libs']: 468 if 'asm' in self._cfg['libs'][lib]: 469 self._cfg['libs'][lib]['asm'] = [] 470 # Add assembly features. 471 if asm_feas: 472 for asm_feature in asm_feas: 473 fea, inc = self._asm_fea_check(asm_feature, asm_type, 'input asm list') 474 if inc and inc != asm_type: 475 raise ValueError("Input instruction '%s' is not the same as 'asm_type' '%s'" % (inc, asm_type)) 476 self._add_feature(fea, 'asm', inc) 477 else: 478 for fea in enable_feas: 479 if asm_type not in feas_info[fea]['impl']: 480 continue 481 self._add_feature(fea, 'asm') 482 483 def set_c_features(self, enable_feas): 484 for fea in enable_feas: 485 if 'c' in self._features.feas_info[fea]['impl']: 486 self._add_feature(fea, 'c') 487 488 def _update_enable_feature(self, features, disables): 489 """ 490 The sub-feature macro is derived from the parent feature macro in the code. 491 Therefore, the sub-feature is removed and the parent feature is retained. 492 """ 493 disable_parents = self._get_parents(disables) 494 tmp_feas = features.copy() 495 enable_set = set() 496 feas_info = self._features.feas_info 497 for f in tmp_feas: 498 fea, _ = self._get_fea_and_inc(f) 499 rel = feas_info[fea] 500 if fea in disable_parents: 501 if 'children' in rel: 502 enable_set.update(rel['children']) 503 enable_set.discard(fea) 504 else: 505 is_fea_contained = False 506 while 'parent' in rel: 507 if rel['parent'] in disables: 508 raise Exception("The 'disables' features {} and 'enables' featrues {} conflict".format(fea, disables)) 509 510 if rel['parent'] in features: 511 is_fea_contained = True 512 break 513 rel = feas_info[rel['parent']] 514 if not is_fea_contained: 515 enable_set.add(fea) 516 enable_set.difference_update(set(disables)) 517 return list(enable_set) 518 519 def check_bn_config(self): 520 lib = 'hitls_crypto' 521 if lib not in self._cfg['libs']: 522 return 523 524 has_bn = False 525 bn_pattern = "bn_" 526 for impl_type in self._cfg['libs'][lib]: 527 if 'bn' in self._cfg['libs'][lib][impl_type]: 528 has_bn = True 529 break 530 for fea in self._cfg['libs'][lib][impl_type]: 531 if re.match(bn_pattern, fea) : 532 has_bn = True 533 break 534 535 if has_bn and 'bits' not in self._cfg: 536 raise ValueError("If 'bn' is used, the 'bits' of the system must be configured.") 537 538 def _re_sort_lib(self): 539 # Change the key sequence of the 'libs' dictionary. Otherwise, the compilation fails. 540 lib_sort = ['hitls_bsl', 'hitls_crypto', 'hitls_tls', "hitls_pki", "hitls_auth"] 541 libs = self.libs.copy() 542 self._cfg['libs'].clear() 543 544 for lib in lib_sort: 545 if lib in libs: 546 self._cfg['libs'][lib] = libs[lib].copy() 547 548 def update_feature(self, enables, disables, gen_cmake): 549 ''' 550 update feature: 551 1. Add the default lib and features: hitls_bsl: sal 552 2. Delete features based on the relationship between features. 553 ''' 554 libs = self._cfg['libs'] 555 if len(libs) == 0: 556 if gen_cmake: 557 raise ValueError("No features are enabled.") 558 else: 559 return 560 561 libs.setdefault('hitls_bsl', {'c':['sal']}) 562 if 'hitls_bsl' not in libs: 563 libs['hitls_bsl'] = {'c':['sal']} 564 elif 'c' not in libs['hitls_bsl']: 565 libs['hitls_bsl']['c'] = ['sal'] 566 elif 'sal' not in libs['hitls_bsl']['c']: 567 libs['hitls_bsl']['c'].append('sal') 568 569 for lib in libs: 570 if 'c' in libs[lib]: 571 libs[lib]['c'] = self._update_enable_feature(libs[lib]['c'], disables) 572 libs[lib]['c'].sort() 573 if 'asm' in libs[lib]: 574 libs[lib]['asm'] = self._update_enable_feature(libs[lib]['asm'], disables) 575 libs[lib]['asm'].sort() 576 577 self._re_sort_lib() 578 579 if 'all' in enables: 580 self.set_param('system', None) 581 self.set_param('bits', None) 582 583 def save(self, path): 584 save_json_file(self._cfg, path) 585 586 def get_fea_macros(self): 587 macros = set() 588 for lib, lib_value in self.libs.items(): 589 lib_upper = lib.upper() 590 for fea in lib_value.get('c', []): 591 macros.add("-D%s_%s" % (lib_upper, fea.upper())) 592 for fea in lib_value.get('asm', []): 593 fea = fea.split('::')[0] 594 macros.add("-D%s_%s" % (lib_upper, fea.upper())) 595 if 'bn' in fea: 596 macros.add("-D%s_%s_%s" % (lib_upper, 'BN', self.asm_type.upper())) 597 else: 598 macros.add("-D%s_%s_%s" % (lib_upper, fea.upper(), self.asm_type.upper())) 599 if lib_upper not in macros: 600 macros.add("-D%s" % lib_upper) 601 602 if self._cfg['endian'] == 'big': 603 macros.add("-DHITLS_BIG_ENDIAN") 604 if self._cfg.get('system', "") == "linux": 605 macros.add("-DHITLS_BSL_SAL_LINUX") 606 607 bits = self._cfg.get('bits', 0) 608 if bits == 32: 609 macros.add("-DHITLS_THIRTY_TWO_BITS") 610 elif bits == 64: 611 macros.add("-DHITLS_SIXTY_FOUR_BITS") 612 613 return list(macros) 614 615 def _re_get_fea_modules(self, fea, feas_info, asm_type, inc, modules): 616 """Obtain the modules on which the current feature and subfeature depend.""" 617 for mod in feas_info[fea].get('modules', []): 618 modules.setdefault(mod, {}) 619 modules[mod]["asmType"] = asm_type 620 if inc: 621 modules[mod]["incSet"] = inc 622 623 for child in feas_info[fea].get('children', []): 624 self._re_get_fea_modules(child, feas_info, asm_type, inc, modules) 625 626 def _get_target_modules(self, target): 627 modules = {} 628 feas_info = self._features.feas_info 629 obj = self.libs 630 for fea in obj[target].get('c', []): 631 self._re_get_fea_modules(fea, feas_info, 'c', '', modules) 632 633 for asm_fea in obj[target].get('asm', []): 634 fea, inc = self._get_fea_and_inc(asm_fea) 635 self._re_get_fea_modules(fea, feas_info, self.asm_type, inc, modules) 636 637 for mod in modules: 638 mod_dep_mods = set() 639 self._features.get_module_deps(mod, [], mod_dep_mods) 640 modules[mod]['deps'] = list(mod_dep_mods) 641 if len(modules.keys()) == 0: 642 raise ValueError("Error: no module is enabled in %s" % target) 643 644 return modules 645 646 def get_enable_modules(self): 647 """ 648 Obtain the modules required for compiling each lib features 649 and the modules on which the lib feature depends (for obtaining the include directory). 650 1. Add modules and their dependent modules based on features. 651 2. Check whether the dependent modules are enabled. 652 return: {'lib':{"mod1":{"deps":[], "asmType":"", "incSet":""}}} 653 Module format: top_dir::sub_dir 654 """ 655 enable_libs_mods = {} 656 enable_mods = set() 657 for lib in self.libs.keys(): 658 enable_libs_mods[lib] = self._get_target_modules(lib) 659 enable_mods.update(enable_libs_mods[lib]) 660 661 # Check whether the dependent module is enabled. 662 for lib in enable_libs_mods.keys(): 663 for mod in enable_libs_mods[lib]: 664 for dep_mod in enable_libs_mods[lib][mod].get('deps', []): 665 if dep_mod == "platform::Secure_C": 666 continue 667 if dep_mod not in enable_mods: 668 raise ValueError("Error: '%s' depends on '%s', but '%s' is disabled." % (mod, dep_mod, dep_mod)) 669 return enable_libs_mods 670 671 def filter_no_asm_config(self): 672 self._cfg['asmType'] = 'no_asm' 673 for lib in self._cfg['libs']: 674 if 'asm' in self._cfg['libs'][lib]: 675 self._cfg['libs'][lib]['asm'] = [] 676 677 def _check_fea_opts_arr(self, opts, fea, enable_feas): 678 for opt_arr in opts: 679 has_opt = False 680 for opt_fea in opt_arr: 681 if opt_fea in enable_feas: 682 has_opt = True 683 break 684 parent = self._features.feas_info[opt_fea].get('parent', '') 685 while parent: 686 if parent in enable_feas: 687 has_opt = True 688 break 689 parent = self._features.feas_info[parent].get('parent', '') 690 if has_opt: 691 break 692 if not has_opt: 693 raise ValueError("At leaset one fea in %s must be enabled for '%s*'" % (opt_arr, fea)) 694 695 def _check_opts(self, fea, enable_feas): 696 if 'opts' not in self._features.feas_info[fea]: 697 return 698 opts = self._features.feas_info[fea]['opts'] 699 if not isinstance(opts[0], list): 700 opts = [opts] 701 702 self._check_fea_opts_arr(opts, fea, enable_feas) 703 704 def _check_family_opts(self, fea, key, enable_feas): 705 values = self._features.feas_info[fea].get(key, []) 706 if not isinstance(values, list): 707 values = [values] 708 for value in values: 709 self._check_opts(value, enable_feas) 710 self._check_family_opts(value, key, enable_feas) 711 712 def check_fea_opts(self): 713 enable_feas = set() 714 for _, lib_obj in self.libs.items(): 715 enable_feas.update(lib_obj.get('c', [])) 716 enable_feas.update(lib_obj.get('asm', [])) 717 for fea in enable_feas: 718 fea = fea.split("::")[0] 719 self._check_opts(fea, enable_feas) 720 self._check_family_opts(fea, 'parent', enable_feas) 721 self._check_family_opts(fea, 'children', enable_feas) 722 723class CompleteOptionParser: 724 """ Parses all compilation options. """ 725 # Sequence in which compilation options are added, including all compilation option types. 726 option_order = [ 727 "CC_DEBUG_FLAGS", 728 "CC_OPT_LEVEL", # Optimization Level 729 "CC_OVERALL_FLAGS", # Overall Options 730 "CC_WARN_FLAGS", # Warning options 731 "CC_LANGUAGE_FLAGS", # Language Options 732 "CC_CDG_FLAGS", # Code Generation Options 733 "CC_MD_DEPENDENT_FLAGS", # Machine-Dependent Options 734 "CC_OPT_FLAGS", # Optimization Options 735 "CC_SEC_FLAGS", # Secure compilation options 736 "CC_DEFINE_FLAGS", # Custom Macro 737 "CC_USER_DEFINE_FLAGS", # User-defined compilation options are reserved. 738 ] 739 740 def __init__(self, file_path): 741 self._fp = file_path 742 with open(file_path, 'r') as f: 743 self._cfg = json.loads(f.read()) 744 self._file_check() 745 746 self._option_type_map = {} 747 for option_type in self._cfg['compileFlag']: 748 for option in trans2list(self._cfg['compileFlag'][option_type]): 749 self._option_type_map[option] = option_type 750 751 @property 752 def option_type_map(self): 753 return self._option_type_map 754 755 @property 756 def type_options_map(self): 757 return self._cfg['compileFlag'] 758 759 def _file_check(self): 760 if 'compileFlag' not in self._cfg or 'linkFlag' not in self._cfg: 761 raise FileNotFoundError("The format of file %s is incorrect." % self._fp) 762 for option_type in self._cfg['compileFlag']: 763 if option_type not in self.option_order: 764 raise FileNotFoundError("The format of file %s is incorrect." % self._fp) 765 766class CompileConfigParser: 767 """ Parse the user compilation configuration file. """ 768 769 def __init__(self, all_options: CompleteOptionParser, file_path=''): 770 with open(file_path, 'r') as f: 771 self._cfg = json.loads(f.read()) 772 self._all_options = all_options 773 774 @property 775 def options(self): 776 return self._cfg['compileFlag'] 777 778 @property 779 def link_flags(self): 780 return self._cfg['linkFlag'] 781 782 @classmethod 783 def default_cfg(cls): 784 config = { 785 'compileFlag': {}, 786 'linkFlag': {} 787 } 788 return config 789 790 def save(self, path): 791 save_json_file(self._cfg, path) 792 793 def change_options(self, options, is_add): 794 option_op = 'CC_FLAGS_ADD' if is_add else 'CC_FLAGS_DEL' 795 for option in options: 796 option_type = 'CC_USER_DEFINE_FLAGS' 797 if option in self._all_options.option_type_map: 798 option_type = self._all_options.option_type_map[option] 799 800 if option_type not in self._cfg['compileFlag']: 801 self._cfg['compileFlag'][option_type] = {} 802 803 flags = self._cfg['compileFlag'][option_type] 804 flags[option_op] = list(set(flags.get(option_op, []) + [option])) 805 806 def change_link_flags(self, flags, is_add): 807 link_op = 'LINK_FLAG_ADD' if is_add else 'LINK_FLAG_DEL' 808 new_flags = self._cfg['linkFlag'].get(link_op, []) + flags 809 self._cfg['linkFlag'][link_op] = list(set(new_flags)) 810 811 def add_debug_options(self): 812 flags_add = {'CC_FLAGS_ADD': ['-g3', '-gdwarf-2']} 813 flags_del = {'CC_FLAGS_DEL': ['-O2', '-D_FORTIFY_SOURCE=2']} 814 815 self._cfg['compileFlag']['CC_DEBUG_FLAGS'] = flags_add 816 self._cfg['compileFlag']['CC_OPT_LEVEL'] = flags_del 817 818 def filter_hitls_defines(self): 819 for flag in list(self.link_flags.keys()): 820 del self.link_flags[flag] 821 822 for flag in list(self.options.keys()): 823 if flag != 'CC_USER_DEFINE_FLAGS' and flag != 'CC_DEFINE_FLAGS': 824 del self.options[flag] 825 826class CompileParser: 827 """ 828 Parse the compile.json file. 829 json key and value: 830 compileFlag: compilation options 831 linkFlag: link option 832 """ 833 834 def __init__(self, all_options: CompleteOptionParser, file_path): 835 self._fp = file_path 836 self._all_options = all_options 837 with open(file_path, 'r') as f: 838 self._cfg = json.loads(f.read()) 839 self._file_check() 840 841 @property 842 def options(self): 843 return self._cfg["compileFlag"] 844 845 @property 846 def link_flags(self): 847 return self._cfg["linkFlag"] 848 849 def _file_check(self): 850 if 'compileFlag' not in self._cfg or 'linkFlag' not in self._cfg: 851 raise FileNotFoundError("Error compile file: missing 'compileFlag' or 'linkFlag'") 852 for option_type in self.options: 853 if option_type == 'CC_USER_DEFINE_FLAGS': 854 continue 855 if option_type not in self._all_options.type_options_map: 856 raise ValueError("no '{}' option type in complete_options.json".format(option_type)) 857 858 for option in self.options[option_type]: 859 if option not in self._all_options.type_options_map[option_type]: 860 raise ValueError("unrecognized option '{}' in type {}.".format(option, option_type)) 861 for option_type in self._cfg['linkFlag']: 862 if option_type not in ['PUBLIC', 'SHARED', 'EXE']: 863 raise FileNotFoundError('Incorrect file format: %s' % self._fp) 864 865 def union_options(self, custom_cfg: CompileConfigParser): 866 options = [] 867 for option_type in CompleteOptionParser.option_order: 868 options.extend(self.options.get(option_type, [])) 869 if option_type not in custom_cfg.options: 870 continue 871 for option in custom_cfg.options[option_type].get('CC_FLAGS_ADD', []): 872 if option not in options: 873 options.append(option) 874 for option in custom_cfg.options[option_type].get('CC_FLAGS_DEL', []): 875 if option in options: 876 options.remove(option) 877 878 flags = self.link_flags 879 for flag in custom_cfg.link_flags.get('LINK_FLAG_ADD', []): 880 if flag not in flags['PUBLIC']: 881 flags['PUBLIC'].append(flag) 882 if flag not in flags['EXE']: 883 flags['EXE'].append(flag) 884 if flag not in flags['SHARED']: 885 flags['SHARED'].append(flag) 886 for flag in custom_cfg.link_flags.get('LINK_FLAG_DEL', []): 887 if flag in flags['PUBLIC']: 888 flags['PUBLIC'].remove(flag) 889 if flag in flags['EXE']: 890 flags['EXE'].remove(flag) 891 if flag in flags['SHARED']: 892 flags['SHARED'].remove(flag) 893 894 return options, flags 895