import sys
sys.dont_write_bytecode = True
import json
import os
import re
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__))))
from methods import trans2list, save_json_file
from platform_utils import CompilerDetector, LinkerDetector, PlatformDetector
class Feature:
def __init__(self, name, target, is_lib, parent, children, deps, opts, impl, ins_set):
self.name = name
self.target = target
self.is_lib = is_lib
self.parent = parent
self.children = children
self.deps = deps
self.opts = opts
self.impl = impl
self.ins_set = ins_set
@classmethod
def simple(cls, name, target, is_lib, parent, impl):
return Feature(name, target, is_lib, parent, [], [], [], impl, [])
class FeatureParser:
""" Parsing feature files """
lib_dir_map = {
"hitls_bsl": "bsl",
"hitls_crypto": "crypto",
"hitls_tls": "tls",
"hitls_pki": "pki",
"hitls_auth": "auth"
}
def __init__(self, file_path):
self._fp = file_path
with open(file_path, 'r', encoding='utf-8') as f:
self._cfg = json.loads(f.read())
self._file_check()
self._feas_info = self._get_feas_info()
self._asm_types = self._get_asm_types()
@property
def libs(self):
return self._cfg['libs']
@property
def executes(self):
return self._cfg['executes']
@property
def modules(self):
return self._cfg['modules']
@property
def asm_types(self):
return self._asm_types
@property
def feas_info(self):
return self._feas_info
def _file_check(self):
if 'libs' not in self._cfg or 'modules' not in self._cfg:
raise FileNotFoundError("The format of file %s is incorrect." % self._fp)
@staticmethod
def _add_key_value(obj, key, value):
if value:
obj[key] = value
def _add_fea(self, feas_info, feature: Feature):
fea_name = feature.name
feas_info.setdefault(fea_name, {})
if feature.is_lib:
self._add_key_value(feas_info[fea_name], 'lib', feature.target)
else:
self._add_key_value(feas_info[fea_name], 'execute', feature.target)
self._add_key_value(feas_info[fea_name], 'parent', feature.parent)
self._add_key_value(feas_info[fea_name], 'children', feature.children)
self._add_key_value(feas_info[fea_name], 'opts', feature.opts)
self._add_key_value(feas_info[fea_name], 'deps', feature.deps)
feas_info[fea_name].setdefault('impl', {})
feas_info[fea_name]['impl'][feature.impl] = feature.ins_set if feature.ins_set else []
def _parse_fea_obj(self, name, target, is_lib, parent, impl, fea_obj, feas_info):
feature = Feature.simple(name, target, is_lib, parent, impl)
if not fea_obj:
self._add_fea(feas_info, feature)
return
feature.deps = fea_obj.get('deps', None)
feature.opts = fea_obj.get('opts', None)
feature.ins_set = fea_obj.get('ins_set', None)
non_sub_keys = ['opts', 'deps', 'ins_set', 'help']
for key, obj in fea_obj.items():
if key not in non_sub_keys:
feature.children.append(key)
self._parse_fea_obj(key, target, is_lib, name, impl, obj, feas_info)
self._add_fea(feas_info, feature)
def parse_fearuers(self, all_feas, tmp_feas_info, target, target_obj, is_lib):
tmp_feas_info[target] = {}
for impl, impl_obj in target_obj['features'].items():
for fea, fea_obj in impl_obj.items():
self._parse_fea_obj(fea, target, is_lib, None, impl, fea_obj, tmp_feas_info[target])
tgt_feas = set(tmp_feas_info[target].keys())
repeat_feas = all_feas.intersection(tgt_feas)
if len(repeat_feas) != 0:
raise ValueError("Error: feature '%s' has been defined in other target." % (repeat_feas))
all_feas.update(tgt_feas)
def _get_feas_info(self):
"""
description: Parse the feature.json file to obtain feature information
and check that feature names in different libraries are unique.
return:
feas_info: {
"children":[], "parent":[], "deps":[],
"opts":[[],[]], "lib":"", "execute":""
"impl":{"c":[], "armv8:[], ...}, # [] lists the instruction sets supported by the feature.
}
"""
all_feas = set()
tmp_feas_info = {}
for lib, lib_obj in self._cfg['libs'].items():
self.parse_fearuers(all_feas, tmp_feas_info, lib, lib_obj, True)
for exe, exe_obj in self._cfg['executes'].items():
self.parse_fearuers(all_feas, tmp_feas_info, exe, exe_obj, False)
feas_info = {}
for obj in tmp_feas_info.values():
feas_info.update(obj)
self._fill_fea_modules(feas_info)
self._correct_impl(feas_info)
return feas_info
def _fill_fea_modules(self, feas_info):
for top_mod in self.modules:
for mod, mod_obj in self.modules[top_mod].items():
formated_mod = "{}::{}".format(top_mod, mod)
for fea in mod_obj.get('.features', []):
if fea not in feas_info:
raise ValueError("Unrecognized '%s' in '.features' of '%s::%s'" % (fea, top_mod, mod))
if 'modules' not in feas_info[fea]:
feas_info[fea]['modules'] = [formated_mod]
else:
feas_info[fea]['modules'].append(formated_mod)
@staticmethod
def _correct_impl(feas_info):
"""Updated the implementation modes of sub-features based on the parent feature."""
for fea in feas_info.keys():
parent = feas_info[fea].get('parent', '')
if not parent:
continue
if len(feas_info[fea]['impl'].keys()) == 1 and 'c' in feas_info[fea]['impl']:
feas_info[fea]['impl'] = feas_info[parent]['impl']
def _get_asm_types(self):
asm_type_set = set()
[asm_type_set.update(self.libs[lib]['features'].keys()) for lib in self.libs.keys()]
asm_type_set.discard('c')
asm_type_set.add('no_asm')
asm_type_set.add('riscv64')
return asm_type_set
def get_module_deps(self, module, dep_list, result):
return self._get_module_deps(module, dep_list, result)
def _get_module_deps(self, module, dep_list, result):
"""
Recursively obtains the modules on which the modules depend.
module: [IN] module name, such as crypto::sha2
dep_list: [OUT] Dependency list, which is an intermediate variable
result: [OUT] result
"""
top_module, sub_module = module.split('::')
mod_obj = self.modules[top_module][sub_module]
if '.deps' not in mod_obj:
result.update(dep_list)
return
for dep_mod in mod_obj['.deps']:
if dep_mod in dep_list:
raise Exception("Cyclic dependency")
dep_list.append(dep_mod)
self._get_module_deps(dep_mod, dep_list, result)
dep_list.pop()
def get_mod_srcs(self, top_mod, sub_mod, mod_obj):
srcs = self._cfg['modules'][top_mod][sub_mod]['.srcs']
asm_type = mod_obj.get('asmType', 'c')
inc = mod_obj.get('incSet', '')
blurred_srcs = []
if not isinstance(srcs, dict):
blurred_srcs.extend(trans2list(srcs))
return blurred_srcs
blurred_srcs.extend(trans2list(srcs.get('public', [])))
if asm_type == 'c':
blurred_srcs.extend(trans2list(srcs.get('no_asm', [])))
return blurred_srcs
if asm_type not in srcs:
raise ValueError("Missing '.srcs[%s]' in modules '%s::%s'" % (asm_type, top_mod, sub_mod))
if not isinstance(srcs[asm_type], dict):
blurred_srcs.extend(trans2list(srcs[asm_type]))
return blurred_srcs
if inc:
blurred_srcs.extend(trans2list(srcs[asm_type][inc]))
else:
first_key = list(srcs[asm_type].keys())[0]
blurred_srcs.extend(trans2list(srcs[asm_type][first_key]))
return blurred_srcs
class FeatureConfigParser:
""" Parses the user feature configuration file. """
key_value = {
"system": {"require": False, "type": str, "choices": ["linux", "darwin", "none", ""], "default": "linux"},
"bits": {"require": False, "type": int, "choices": [32, 64], "default": 64},
"endian": {"require": True, "type": str, "choices": ["little", "big"], "default": "little"},
"libType": {
"require": True,
"type": list,
"choices": ["static", "shared", "object"],
"default": ["static", "shared", "object"]
},
"asmType":{"require": True, "type": str, "choices": [], "default": "no_asm"},
"libs":{"require": True, "type": dict, "choices": [], "default": {}},
"bundleLibs":{"require": False, "type": bool, "choices": [True, False], "default": False},
"securecLib":{"require": False, "type": str, "choices": ["boundscheck", "securec", "sec_shared.z", ""], "default": "boundscheck"},
"executes":{"require": False, "type": dict, "default": {}}
}
def __init__(self, features: FeatureParser, file_path):
self._features = features
self._config_file = file_path
with open(file_path, 'r', encoding='utf-8') as f:
self._cfg = json.loads(f.read())
self.key_value['asmType']['choices'] = list(features.asm_types)
self.key_value['libs']['choices'] = list(features.libs)
self._file_check()
@classmethod
def default_cfg(cls):
config = {}
for key in cls.key_value.keys():
if cls.key_value[key]["require"]:
config[key] = cls.key_value[key]["default"]
return config
@property
def libs(self):
return self._cfg['libs']
@property
def executes(self):
return self._cfg.get('executes', {})
@property
def lib_type(self):
return trans2list(self._cfg['libType'])
@property
def asm_type(self):
return self._cfg['asmType']
@property
def bundle_libs(self):
if 'bundleLibs' in self._cfg:
return self._cfg['bundleLibs']
return self.key_value['bundleLibs']['default']
@property
def securec_lib(self):
if 'securecLib' in self._cfg:
return self._cfg['securecLib']
return self.key_value['securecLib']['default']
@staticmethod
def _get_fea_and_inc(asm_fea):
if '::' in asm_fea:
return asm_fea.split('::')
else:
return asm_fea, ''
def enable_executes(self, targets):
for target in targets:
if 'executes' not in self._cfg:
self._cfg['executes'] = {target: {}}
else:
self._cfg['executes'][target] = {}
exe_objs = []
for fea, fea_obj in self._features.feas_info.items():
if fea_obj.get('execute', '') != target:
continue
for i in fea_obj['impl'].keys():
self._cfg['executes'][target].setdefault(i, [])
self._cfg['executes'][target][i].append(fea)
def _asm_fea_check(self, asm_fea, asm_type, info):
fea, inc = self._get_fea_and_inc(asm_fea)
feas_info = self._features.feas_info
if fea not in feas_info:
raise ValueError("Unsupported '%s' in %s" % (fea, info))
if asm_type not in feas_info[fea]['impl']:
raise ValueError("Feature '%s' has no assembly implementation of type '%s' in %s" % (fea, asm_type, info))
if inc:
if inc not in feas_info[fea]['impl'] and inc not in feas_info[fea]['impl'][asm_type]:
raise ValueError("Unsupported instruction set of '%s' in %s" % (asm_fea, info))
return fea, inc
def _file_check(self):
for key, value in self.key_value.items():
if value['require']:
if key not in self._cfg.keys():
raise ValueError("Error feature_config file: missing '%s'" % key)
for key, value in self._cfg.items():
if key not in self.key_value.keys():
raise ValueError("Error feature_config file: unsupported config '%s'" % key)
if not isinstance(value, self.key_value.get(key).get("type")):
raise ValueError("Error feature_config file: wrong type of '%s'" % key)
value_type = type(value)
if value_type == str or value_type == str:
if value not in self.key_value.get(key).get("choices"):
if key == "system":
print("Info: There is no {} implementation by default, you should set its SAL callbacks to make it work.".format(value))
continue
raise ValueError("Error feature_config file: wrong value of '%s'" % key)
elif value_type == list:
choices = set(self.key_value[key]["choices"])
if not set(value).issubset(choices):
raise ValueError("Error feature_config file: wrong value of '%s'" % key)
for lib, lib_obj in self._cfg['libs'].items():
if lib not in self._features.libs:
raise ValueError("Error feature_config file: unsupported lib '%s'" % lib)
for fea in lib_obj.get('c', []):
if fea not in self._features.feas_info:
raise ValueError("Error feature_config file: unsupported fea '%s' in lib '%s'" % (fea, lib))
asm_feas = []
for asm_fea in lib_obj.get('asm', []):
fea, _ = self._asm_fea_check(asm_fea, self.asm_type, 'feature_config file')
if fea in asm_feas:
raise ValueError("Error feature_config file: duplicate assembly feature '%s'" % fea)
asm_feas.append(fea)
def set_param(self, key, value, set_default=True):
if key == 'bundleLibs':
self._cfg[key] = value
return
if value:
self._cfg[key] = value
return
if not set_default:
return
if key not in self._cfg or not self._cfg[key]:
print("Warning: Configuration item '{}' is missing and has been set to the default value '{}'.".format(
key, self.key_value.get(key).get('default')))
self._cfg[key] = self.key_value.get(key).get('default')
def _get_related_feas(self, fea, feas_info, related: set):
related.add(fea)
if 'parent' in feas_info[fea]:
parent = feas_info[fea]['parent']
for dep in feas_info[parent].get('deps', []):
self._get_related_feas(dep, feas_info, related)
if 'children' in feas_info[fea]:
for child in feas_info[fea]['children']:
self._get_related_feas(child, feas_info, related)
if 'deps' in feas_info[fea]:
for dep in feas_info[fea]['deps']:
self._get_related_feas(dep, feas_info, related)
def _get_parents(self, disables):
parents = set()
for d in disables:
relation = self._features.feas_info.get(d)
if relation and 'parent' in relation:
parents.add(relation['parent'])
return parents
def _add_depend_feas(self, enable_feas, feas_info):
related = set()
for f in enable_feas:
fea, inc = self._get_fea_and_inc(f)
self._get_related_feas(fea, feas_info, related)
enable_feas.update(related)
def _check_asm_fea_enable(self, enable_feas, feas, feas_info):
not_in_enable = []
for f in feas:
fea, _ = self._get_fea_and_inc(f)
if fea in enable_feas:
continue
rel = feas_info[fea]
is_enable = False
while('parent' in rel):
parent = rel['parent']
if parent in enable_feas:
is_enable = True
break
rel = feas_info[parent]
if not is_enable:
not_in_enable.append(fea)
if not_in_enable:
raise ValueError("To add '%s' assembly requires add it to 'enable' list" % not_in_enable)
def get_enable_feas(self, arg_enable, arg_asm):
"""
Get the enabled features form:
1. build/feature_config.json
2. argument: enable list
3. argument: asm list
"""
enable_feas = set()
enable_asm_feas = set()
for _, lib_obj in self._cfg['libs'].items():
enable_feas.update(lib_obj.get('c', []))
enable_asm_feas.update(lib_obj.get('asm', []))
feas_info = self._features.feas_info
if 'all' in arg_enable:
enable_feas.update(set(x for x in feas_info.keys()))
else:
for enable in arg_enable:
if enable in self._features.libs:
enable_feas.update(set(x for x, y in feas_info.items() if enable == y.get('lib', '')))
else:
enable_feas.add(enable)
enable_feas.update(enable_asm_feas)
self._add_depend_feas(enable_feas, feas_info)
self._check_asm_fea_enable(enable_feas, arg_asm, feas_info)
enable_asm_feas.update(arg_asm)
return enable_feas, enable_asm_feas
def _add_feature(self, fea, impl_type, inc=''):
add_fea = fea if inc == '' else '{}::{}'.format(fea, inc)
lib = self._features.feas_info[fea]['lib']
if lib not in self._cfg['libs']:
self._cfg['libs'][lib] = {impl_type: [add_fea]}
elif impl_type not in self._cfg['libs'][lib]:
self._cfg['libs'][lib][impl_type] = [add_fea]
elif fea not in self._cfg['libs'][lib][impl_type]:
self._cfg['libs'][lib][impl_type].append(add_fea)
def set_asm_type(self, asm_type):
if self._cfg['asmType'] == 'no_asm':
self._cfg['asmType'] = asm_type
elif self._cfg['asmType'] != asm_type:
raise ValueError('Error asmType: %s is different from feature_config file.' % (asm_type))
def set_asm_features(self, enable_feas, asm_feas, asm_type):
feas_info = self._features.feas_info
for lib in self._cfg['libs']:
if 'asm' in self._cfg['libs'][lib]:
self._cfg['libs'][lib]['asm'] = []
if asm_feas:
for asm_feature in asm_feas:
fea, inc = self._asm_fea_check(asm_feature, asm_type, 'input asm list')
if inc and inc != asm_type:
raise ValueError("Input instruction '%s' is not the same as 'asm_type' '%s'" % (inc, asm_type))
self._add_feature(fea, 'asm', inc)
else:
for fea in enable_feas:
if asm_type not in feas_info[fea]['impl']:
continue
self._add_feature(fea, 'asm')
def set_c_features(self, enable_feas):
for f in enable_feas:
fea, _ = self._get_fea_and_inc(f)
if self._features.feas_info[fea].get('execute'):
continue
if 'c' in self._features.feas_info[fea]['impl']:
self._add_feature(fea, 'c')
def _update_enable_feature(self, features, disables):
"""
The sub-feature macro is derived from the parent feature macro in the code.
Therefore, the sub-feature is removed and the parent feature is retained.
"""
disable_parents = self._get_parents(disables)
tmp_feas = features.copy()
enable_set = set()
feas_info = self._features.feas_info
for f in tmp_feas:
fea, _ = self._get_fea_and_inc(f)
rel = feas_info[fea]
if fea in disable_parents:
if 'children' in rel:
enable_set.update(rel['children'])
enable_set.discard(fea)
else:
is_fea_contained = False
while 'parent' in rel:
if rel['parent'] in disables or rel['parent'] in features:
is_fea_contained = True
break
rel = feas_info[rel['parent']]
if not is_fea_contained:
enable_set.add(fea)
enable_set.difference_update(set(disables))
return list(enable_set)
def check_bn_config(self):
lib = 'hitls_crypto'
if lib not in self._cfg['libs']:
return
has_bn = False
bn_pattern = "bn_"
for impl_type in self._cfg['libs'][lib]:
if 'bn' in self._cfg['libs'][lib][impl_type]:
has_bn = True
break
for fea in self._cfg['libs'][lib][impl_type]:
if re.match(bn_pattern, fea) :
has_bn = True
break
if has_bn and 'bits' not in self._cfg:
raise ValueError("If 'bn' is used, the 'bits' of the system must be configured.")
def _re_sort_lib(self):
lib_sort = ['hitls_bsl', 'hitls_crypto', 'hitls_tls', "hitls_pki", "hitls_auth"]
libs = self.libs.copy()
self._cfg['libs'].clear()
for lib in lib_sort:
if lib in libs:
self._cfg['libs'][lib] = libs[lib].copy()
def update_feature(self, enables, disables, gen_cmake):
'''
update feature:
1. Add the default lib and features: hitls_bsl: sal
2. Delete features based on the relationship between features.
'''
libs = self._cfg['libs']
if len(libs) == 0:
if gen_cmake:
raise ValueError("No features are enabled.")
else:
return
libs.setdefault('hitls_bsl', {'c':['sal']})
if 'hitls_bsl' not in libs:
libs['hitls_bsl'] = {'c':['sal']}
elif 'c' not in libs['hitls_bsl']:
libs['hitls_bsl']['c'] = ['sal']
elif 'sal' not in libs['hitls_bsl']['c']:
libs['hitls_bsl']['c'].append('sal')
for lib in libs:
if 'c' in libs[lib]:
libs[lib]['c'] = self._update_enable_feature(libs[lib]['c'], disables)
libs[lib]['c'].sort()
if 'asm' in libs[lib]:
libs[lib]['asm'] = self._update_enable_feature(libs[lib]['asm'], disables)
libs[lib]['asm'].sort()
self._re_sort_lib()
if 'all' in enables:
if 'system' not in self._cfg or not self._cfg['system']:
self._cfg['system'] = self.key_value.get('system').get('default')
if 'bits' not in self._cfg or not self._cfg['bits']:
self._cfg['bits'] = self.key_value.get('bits').get('default')
def save(self, path):
save_json_file(self._cfg, path)
def get_fea_macros(self):
macros = set()
for lib, lib_value in self.libs.items():
lib_upper = lib.upper()
for fea in lib_value.get('c', []):
macros.add("-D%s_%s" % (lib_upper, fea.upper()))
for fea in lib_value.get('asm', []):
fea = fea.split('::')[0]
macros.add("-D%s_%s" % (lib_upper, fea.upper()))
if 'bn' in fea:
macros.add("-D%s_%s_%s" % (lib_upper, 'BN', self.asm_type.upper()))
else:
macros.add("-D%s_%s_%s" % (lib_upper, fea.upper(), self.asm_type.upper()))
if lib_upper not in macros:
macros.add("-D%s" % lib_upper)
if self._cfg['endian'] == 'big':
macros.add("-DHITLS_BIG_ENDIAN")
system = self._cfg.get('system', "")
if system == "linux":
macros.add("-DHITLS_BSL_SAL_LINUX")
elif system == "darwin":
macros.add("-DHITLS_BSL_SAL_DARWIN")
bits = self._cfg.get('bits', 0)
if bits == 32:
macros.add("-DHITLS_THIRTY_TWO_BITS")
elif bits == 64:
macros.add("-DHITLS_SIXTY_FOUR_BITS")
return list(macros)
def _re_get_fea_modules(self, fea, feas_info, asm_type, inc, modules):
"""Obtain the modules on which the current feature and subfeature depend."""
for mod in feas_info[fea].get('modules', []):
modules.setdefault(mod, {})
modules[mod]["asmType"] = asm_type
if inc:
modules[mod]["incSet"] = inc
for child in feas_info[fea].get('children', []):
self._re_get_fea_modules(child, feas_info, asm_type, inc, modules)
def _get_target_modules(self, target, is_lib):
modules = {}
feas_info = self._features.feas_info
obj = self.libs if is_lib else self.executes
for fea in obj[target].get('c', []):
self._re_get_fea_modules(fea, feas_info, 'c', '', modules)
for asm_fea in obj[target].get('asm', []):
fea, inc = self._get_fea_and_inc(asm_fea)
self._re_get_fea_modules(fea, feas_info, self.asm_type, inc, modules)
for mod in modules:
mod_dep_mods = set()
self._features.get_module_deps(mod, [], mod_dep_mods)
modules[mod]['deps'] = list(mod_dep_mods)
if len(modules.keys()) == 0:
raise ValueError("Error: no module is enabled in %s" % target)
return modules
def get_enable_modules(self):
"""
Obtain the modules required for compiling each lib features
and the modules on which the lib feature depends (for obtaining the include directory).
1. Add modules and their dependent modules based on features.
2. Check whether the dependent modules are enabled.
return: {'lib/exe':{"mod1":{"deps":[], "asmType":"", "incSet":""}}}
Module format: top_dir::sub_dir
"""
enable_libs_mods = {}
enable_exes_mods = {}
enable_mods = set()
for lib in self.libs.keys():
enable_libs_mods[lib] = self._get_target_modules(lib, True)
enable_mods.update(enable_libs_mods[lib])
for exe in self.executes.keys():
enable_exes_mods[exe] = self._get_target_modules(exe, False)
enable_mods.update(enable_exes_mods[exe])
for lib in enable_libs_mods.keys():
for mod in enable_libs_mods[lib]:
for dep_mod in enable_libs_mods[lib][mod].get('deps', []):
if dep_mod == "platform::Secure_C":
continue
if dep_mod not in enable_mods:
raise ValueError("Error: '%s' depends on '%s', but '%s' is disabled." % (mod, dep_mod, dep_mod))
for exe in enable_exes_mods.keys():
for mod in enable_exes_mods[exe]:
for dep_mod in enable_exes_mods[exe][mod].get('deps', []):
if dep_mod == "platform::Secure_C":
continue
if dep_mod not in enable_mods:
raise ValueError("Error: '%s' depends on '%s', but '%s' is disabled." % (mod, dep_mod, dep_mod))
return enable_libs_mods, enable_exes_mods
def filter_no_asm_config(self):
self._cfg['asmType'] = 'no_asm'
for lib in self._cfg['libs']:
if 'asm' in self._cfg['libs'][lib]:
self._cfg['libs'][lib]['asm'] = []
def _check_fea_opts_arr(self, opts, fea, enable_feas):
for opt_arr in opts:
has_opt = False
for opt_fea in opt_arr:
if opt_fea in enable_feas:
has_opt = True
break
parent = self._features.feas_info[opt_fea].get('parent', '')
while parent:
if parent in enable_feas:
has_opt = True
break
parent = self._features.feas_info[parent].get('parent', '')
if has_opt:
break
if not has_opt:
raise ValueError("At leaset one fea in %s must be enabled for '%s*'" % (opt_arr, fea))
def _check_opts(self, fea, enable_feas):
if 'opts' not in self._features.feas_info[fea]:
return
opts = self._features.feas_info[fea]['opts']
if not isinstance(opts[0], list):
opts = [opts]
self._check_fea_opts_arr(opts, fea, enable_feas)
def _check_family_opts(self, fea, key, enable_feas):
values = self._features.feas_info[fea].get(key, [])
if not isinstance(values, list):
values = [values]
for value in values:
self._check_opts(value, enable_feas)
self._check_family_opts(value, key, enable_feas)
def check_fea_opts(self):
enable_feas = set()
for _, lib_obj in self.libs.items():
enable_feas.update(lib_obj.get('c', []))
enable_feas.update(lib_obj.get('asm', []))
for fea in enable_feas:
fea = fea.split("::")[0]
self._check_opts(fea, enable_feas)
self._check_family_opts(fea, 'parent', enable_feas)
self._check_family_opts(fea, 'children', enable_feas)
class ConfigResolver:
"""
Resolves configuration based on dimensions (compiler, linker, OS).
Supports _EXTRA, _REMOVE, and _OVERRIDE operations.
"""
@staticmethod
def resolve_flags(common_flags, dimension_flags, dimension_value):
"""
Resolve flags by merging common with dimension-specific flags.
Args:
common_flags: dict - common flags for all dimensions
dimension_flags: dict - dimension-specific flags
dimension_value: str - the current dimension value (e.g., 'gcc', 'ld64', 'darwin')
Returns:
dict - resolved flags
"""
if dimension_value not in dimension_flags:
return ConfigResolver._process_common_operations(common_flags or {})
resolved = {}
specific_config = dimension_flags[dimension_value]
if '_inherit' in specific_config:
parent = specific_config['_inherit']
if parent in dimension_flags:
resolved = ConfigResolver.resolve_flags(
common_flags, dimension_flags, parent
)
if not resolved:
for key, value in (common_flags or {}).items():
resolved[key] = list(value) if isinstance(value, list) else value
for key, value in specific_config.items():
if key.startswith('_'):
continue
if key.endswith('_OVERRIDE'):
base_key = key.replace('_OVERRIDE', '')
resolved[base_key] = list(value) if isinstance(value, list) else value
elif key.endswith('_EXTRA'):
base_key = key.replace('_EXTRA', '')
if base_key not in resolved:
resolved[base_key] = []
if isinstance(value, list):
resolved[base_key].extend(value)
else:
resolved[base_key].append(value)
elif key.endswith('_REMOVE'):
base_key = key.replace('_REMOVE', '')
if base_key in resolved:
remove_items = value if isinstance(value, list) else [value]
resolved[base_key] = [
item for item in resolved[base_key]
if item not in remove_items
]
else:
resolved[key] = list(value) if isinstance(value, list) else value
return resolved
@staticmethod
def _process_common_operations(common_flags):
"""
Process _EXTRA/_REMOVE/_OVERRIDE operations within common flags.
This handles cases where common flags contain operations but there's no
dimension-specific config to apply them against.
Args:
common_flags: dict - common flags that may contain operation suffixes
Returns:
dict - resolved flags with operations applied
"""
if not common_flags:
return {}
resolved = {}
for key, value in common_flags.items():
if not (key.endswith('_EXTRA') or key.endswith('_REMOVE') or key.endswith('_OVERRIDE')):
resolved[key] = list(value) if isinstance(value, list) else value
for key, value in common_flags.items():
if key.endswith('_OVERRIDE'):
base_key = key.replace('_OVERRIDE', '')
resolved[base_key] = list(value) if isinstance(value, list) else value
elif key.endswith('_EXTRA'):
base_key = key.replace('_EXTRA', '')
if base_key not in resolved:
resolved[base_key] = []
if isinstance(value, list):
resolved[base_key].extend(value)
else:
resolved[base_key].append(value)
elif key.endswith('_REMOVE'):
base_key = key.replace('_REMOVE', '')
if base_key in resolved:
remove_items = value if isinstance(value, list) else [value]
resolved[base_key] = [
item for item in resolved[base_key]
if item not in remove_items
]
else:
resolved[key] = value
return resolved
class CompleteOptionParser:
""" Parses all compilation options. """
option_order = [
"CC_DEBUG_FLAGS",
"CC_OPT_LEVEL",
"CC_OVERALL_FLAGS",
"CC_WARN_FLAGS",
"CC_LANGUAGE_FLAGS",
"CC_CDG_FLAGS",
"CC_MD_DEPENDENT_FLAGS",
"CC_OPT_FLAGS",
"CC_SEC_FLAGS",
"CC_DEFINE_FLAGS",
"CC_USER_DEFINE_FLAGS",
]
def __init__(self, file_path):
self._fp = file_path
with open(file_path, 'r') as f:
self._cfg = json.loads(f.read())
self._file_check()
self._option_type_map = {}
for option_type in self._cfg['compileFlag']:
for option in trans2list(self._cfg['compileFlag'][option_type]):
self._option_type_map[option] = option_type
@property
def option_type_map(self):
return self._option_type_map
@property
def type_options_map(self):
return self._cfg['compileFlag']
def _file_check(self):
if 'compileFlag' not in self._cfg or 'linkFlag' not in self._cfg:
raise FileNotFoundError("The format of file %s is incorrect." % self._fp)
for option_type in self._cfg['compileFlag']:
if option_type not in self.option_order:
raise FileNotFoundError("The format of file %s is incorrect." % self._fp)
class CompileConfigParser:
""" Parse the user compilation configuration file with 'common' nested format support.
Uses ConfigResolver to handle dimensional configuration (compiler × linker × platform).
Supports mixed format (nested 'common' + flat user-added sections) in self._cfg.
Keeps original nested format in self._cfg for saving, resolves to flat format for usage.
"""
def __init__(self, all_options: CompleteOptionParser, file_path='',
compiler=None, linker=None, platform=None):
with open(file_path, 'r') as f:
self._cfg = json.loads(f.read())
self._all_options = all_options
self._compiler = compiler or CompilerDetector.detect_compiler_type()
self._linker = linker or LinkerDetector.detect_linker_type()
self._platform = platform or PlatformDetector.get_current_platform()
self._resolved_compile_flags = self._resolve_compile_flags()
self._resolved_link_flags = self._resolve_link_flags()
def _resolve_compile_flags(self):
"""Resolve compileFlag from nested to flat format (without modifying self._cfg)."""
if 'compileFlag' not in self._cfg:
return {}
compile_cfg = self._cfg['compileFlag']
if 'common' not in compile_cfg:
return dict(compile_cfg)
common_flags = compile_cfg.get('common', {})
compiler_configs = {}
flat_sections = {}
for k, v in compile_cfg.items():
if k.startswith('_') or k == 'common':
continue
if isinstance(v, dict) and any(key in v for key in ['CC_FLAGS_ADD', 'CC_FLAGS_DEL']):
flat_sections[k] = v
else:
compiler_configs[k] = v
resolved = ConfigResolver.resolve_flags(
common_flags, compiler_configs, self._compiler
)
flattened = {}
for key, value in resolved.items():
if key.endswith('_EXTRA') or key.endswith('_OVERRIDE'):
base_key = key.replace('_EXTRA', '').replace('_OVERRIDE', '')
if base_key not in flattened:
flattened[base_key] = {'CC_FLAGS_ADD': []}
flags_to_add = value if isinstance(value, list) else [value]
flattened[base_key]['CC_FLAGS_ADD'].extend(flags_to_add)
elif key.endswith('_REMOVE'):
base_key = key.replace('_REMOVE', '')
if base_key not in flattened:
flattened[base_key] = {}
if 'CC_FLAGS_DEL' not in flattened[base_key]:
flattened[base_key]['CC_FLAGS_DEL'] = []
flags_to_del = value if isinstance(value, list) else [value]
flattened[base_key]['CC_FLAGS_DEL'].extend(flags_to_del)
else:
if key not in flattened:
flattened[key] = {'CC_FLAGS_ADD': []}
flags_to_add = value if isinstance(value, list) else [value]
flattened[key]['CC_FLAGS_ADD'].extend(flags_to_add)
for section_name, section_content in flat_sections.items():
if section_name not in flattened:
flattened[section_name] = {}
for op_type, flags in section_content.items():
if op_type not in flattened[section_name]:
flattened[section_name][op_type] = []
flattened[section_name][op_type].extend(flags)
return flattened
def _resolve_link_flags(self):
"""Resolve linkFlag from nested to flat format (without modifying self._cfg)."""
if 'linkFlag' not in self._cfg:
return {'PUBLIC': [], 'SHARED': [], 'EXE': []}
link_cfg = self._cfg['linkFlag']
if 'common' not in link_cfg:
result = {'PUBLIC': [], 'SHARED': [], 'EXE': []}
for key in ['PUBLIC', 'SHARED', 'EXE']:
if key in link_cfg:
result[key] = list(link_cfg[key])
for flag in link_cfg.get('LINK_FLAG_ADD', []):
for category in ['PUBLIC', 'SHARED', 'EXE']:
if flag not in result[category]:
result[category].append(flag)
for flag in link_cfg.get('LINK_FLAG_DEL', []):
for category in ['PUBLIC', 'SHARED', 'EXE']:
if flag in result[category]:
result[category].remove(flag)
return result
common_flags = link_cfg.get('common', {})
linker_configs = {}
flat_categories = {}
for k, v in link_cfg.items():
if k.startswith('_') or k == 'common':
continue
if k in ['PUBLIC', 'SHARED', 'EXE'] and isinstance(v, list):
flat_categories[k] = v
else:
linker_configs[k] = v
resolved = ConfigResolver.resolve_flags(
common_flags, linker_configs, self._linker
)
result = {'PUBLIC': [], 'SHARED': [], 'EXE': []}
for key, value in resolved.items():
if key.endswith('_EXTRA') or key.endswith('_OVERRIDE'):
base_key = key.replace('_EXTRA', '').replace('_OVERRIDE', '')
result[base_key] = value if isinstance(value, list) else [value]
else:
if key in result:
result[key] = value if isinstance(value, list) else [value]
for category, flags in flat_categories.items():
result[category].extend(flags)
return result
@property
def options(self):
return self._resolved_compile_flags
@property
def link_flags(self):
return self._resolved_link_flags
@classmethod
def default_cfg(cls):
config = {
'compileFlag': {},
'linkFlag': {}
}
return config
def save(self, path):
save_json_file(self._cfg, path)
def change_options(self, options, is_add):
"""Add or remove compilation options.
Modifies both self._cfg (for saving) and self._resolved_compile_flags (for usage).
"""
option_op = 'CC_FLAGS_ADD' if is_add else 'CC_FLAGS_DEL'
for option in options:
option_type = 'CC_USER_DEFINE_FLAGS'
if option in self._all_options.option_type_map:
option_type = self._all_options.option_type_map[option]
if option_type not in self._cfg['compileFlag']:
self._cfg['compileFlag'][option_type] = {}
flags = self._cfg['compileFlag'][option_type]
flags[option_op] = list(dict.fromkeys(flags.get(option_op, []) + [option]))
if option_type not in self._resolved_compile_flags:
self._resolved_compile_flags[option_type] = {}
if option_op not in self._resolved_compile_flags[option_type]:
self._resolved_compile_flags[option_type][option_op] = []
self._resolved_compile_flags[option_type][option_op] = flags[option_op].copy()
def change_link_flags(self, flags, is_add):
"""Add or remove link flags from all three categories (PUBLIC, SHARED, EXE).
Modifies both self._cfg (for saving) and self._resolved_link_flags (for usage).
User-added link flags are stored in flat format alongside nested common.
"""
if 'linkFlag' not in self._cfg:
self._cfg['linkFlag'] = {}
for category in ['PUBLIC', 'SHARED', 'EXE']:
if category not in self._cfg['linkFlag']:
self._cfg['linkFlag'][category] = []
for category in ['PUBLIC', 'SHARED', 'EXE']:
if is_add:
for flag in flags:
if flag not in self._cfg['linkFlag'][category]:
self._cfg['linkFlag'][category].append(flag)
if flag not in self._resolved_link_flags[category]:
self._resolved_link_flags[category].append(flag)
else:
for flag in flags:
if flag in self._cfg['linkFlag'][category]:
self._cfg['linkFlag'][category].remove(flag)
if flag in self._resolved_link_flags[category]:
self._resolved_link_flags[category].remove(flag)
def add_debug_options(self):
flags_add = {'CC_FLAGS_ADD': ['-g3', '-gdwarf-2']}
flags_del = {'CC_FLAGS_DEL': ['-O2', '-D_FORTIFY_SOURCE=2']}
self._cfg['compileFlag']['CC_DEBUG_FLAGS'] = flags_add
self._cfg['compileFlag']['CC_OPT_LEVEL'] = flags_del
def filter_hitls_defines(self):
for flag in list(self.link_flags.keys()):
del self.link_flags[flag]
for flag in list(self.options.keys()):
if flag != 'CC_USER_DEFINE_FLAGS' and flag != 'CC_DEFINE_FLAGS':
del self.options[flag]
class CompileParser:
"""
Parse the compile.json file with dimensional format.
json key and value:
compileFlag: compilation options (indexed by compiler)
linkFlag: link option (indexed by linker)
systemDefines: OS-specific defines (indexed by OS)
"""
def __init__(self, all_options: CompleteOptionParser, file_path,
compiler=None, linker=None, platform=None):
self._fp = file_path
self._all_options = all_options
with open(file_path, 'r') as f:
self._cfg = json.loads(f.read())
self._compiler = compiler or CompilerDetector.detect_compiler_type()
self._linker = linker or LinkerDetector.detect_linker_type()
self._platform = platform or PlatformDetector.get_current_platform()
self._resolved_compile_flags = self._resolve_compile_flags()
self._resolved_link_flags = self._resolve_link_flags()
self._resolved_system_defines = self._resolve_system_defines()
self._file_check()
@property
def compiler(self):
return self._compiler
@property
def linker(self):
return self._linker
@property
def platform(self):
return self._platform
@property
def options(self):
"""Returns resolved compile flags."""
return self._resolved_compile_flags
@property
def link_flags(self):
"""Returns resolved link flags."""
return self._resolved_link_flags
def _resolve_compile_flags(self):
"""Resolve compileFlag based on compiler dimension."""
if 'compileFlag' not in self._cfg:
return {}
compile_cfg = self._cfg['compileFlag']
common_flags = compile_cfg.get('common', {})
compiler_configs = {
k: v for k, v in compile_cfg.items()
if not k.startswith('_') and k != 'common'
}
resolved = ConfigResolver.resolve_flags(
common_flags, compiler_configs, self._compiler
)
if 'systemDefines' in self._cfg:
system_defines = self._resolve_system_defines()
for key, value in system_defines.items():
if key in resolved:
if isinstance(resolved[key], list) and isinstance(value, list):
resolved[key].extend(value)
else:
resolved[key] = value
else:
resolved[key] = value
return resolved
def _resolve_link_flags(self):
"""Resolve linkFlag based on linker dimension."""
if 'linkFlag' not in self._cfg:
return {}
link_cfg = self._cfg['linkFlag']
common_flags = link_cfg.get('common', {})
linker_configs = {
k: v for k, v in link_cfg.items()
if not k.startswith('_') and k != 'common'
}
return ConfigResolver.resolve_flags(
common_flags, linker_configs, self._linker
)
def _resolve_system_defines(self):
"""Resolve systemDefines based on OS dimension."""
if 'systemDefines' not in self._cfg:
return {}
system_cfg = self._cfg['systemDefines']
common_defines = system_cfg.get('common', {})
os_configs = {
k: v for k, v in system_cfg.items()
if not k.startswith('_') and k != 'common'
}
return ConfigResolver.resolve_flags(
common_defines, os_configs, self._platform
)
def _file_check(self):
"""Validate configuration file format."""
if 'compileFlag' not in self._cfg:
raise FileNotFoundError("Error compile file: missing 'compileFlag'")
if 'linkFlag' not in self._cfg:
raise FileNotFoundError("Error compile file: missing 'linkFlag'")
def union_options(self, custom_cfg: CompileConfigParser):
options = []
for option_type in CompleteOptionParser.option_order:
options.extend(self.options.get(option_type, []))
if option_type not in custom_cfg.options:
continue
for option in custom_cfg.options[option_type].get('CC_FLAGS_ADD', []):
if option not in options:
options.append(option)
for option in custom_cfg.options[option_type].get('CC_FLAGS_DEL', []):
if option in options:
options.remove(option)
flags = self.link_flags
for category in ['PUBLIC', 'SHARED', 'EXE']:
custom_flags = custom_cfg.link_flags.get(category, [])
for flag in custom_flags:
if flag not in flags[category]:
flags[category].append(flag)
flags['PUBLIC'] = list(dict.fromkeys(flags['PUBLIC']))
flags['EXE'] = list(dict.fromkeys(flags['EXE']))
flags['SHARED'] = list(dict.fromkeys(flags['SHARED']))
return options, flags