mirror of
https://github.com/openharmony/third_party_openhitls.git
synced 2026-07-01 10:05:26 -04:00
ecf56f0776
Signed-off-by: Dongjianwei001 <dongjianwei1@huawei.com>
1355 lines
53 KiB
Python
1355 lines
53 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
# This file is part of the openHiTLS project.
|
||
#
|
||
# openHiTLS is licensed under the Mulan PSL v2.
|
||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||
# You may obtain a copy of Mulan PSL v2 at:
|
||
#
|
||
# http://license.coscl.org.cn/MulanPSL2
|
||
#
|
||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||
# See the Mulan PSL v2 for more details.
|
||
|
||
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 # Indicates whether the target file is a lib or exe file.
|
||
|
||
self.parent = parent
|
||
self.children = children
|
||
|
||
self.deps = deps
|
||
self.opts = opts
|
||
|
||
self.impl = impl # Implementation mode
|
||
self.ins_set = ins_set # Instruction 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()
|
||
|
||
# Features and related information.
|
||
self._feas_info = self._get_feas_info()
|
||
# Assembly type supported by the openHiTLS.
|
||
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])
|
||
|
||
# Check that feature names in different target are unique.
|
||
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:
|
||
# A dependency that already exists in a dependency chain is a circular dependency.
|
||
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. """
|
||
# Specifications of keys and values in the 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: # This feature is already in the enable list.
|
||
continue
|
||
|
||
rel = feas_info[fea]
|
||
is_enable = False
|
||
while('parent' in rel):
|
||
parent = rel['parent']
|
||
if parent in enable_feas: # This feature is already in the enable list.
|
||
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()
|
||
# 1. Exist feas in build/feature_config.json
|
||
for _, lib_obj in self._cfg['libs'].items():
|
||
enable_feas.update(lib_obj.get('c', []))
|
||
enable_asm_feas.update(lib_obj.get('asm', []))
|
||
|
||
# 2. Obtains the properties from the input parameter: enable list.
|
||
feas_info = self._features.feas_info
|
||
if 'all' in arg_enable: # all features
|
||
enable_feas.update(set(x for x in feas_info.keys()))
|
||
else:
|
||
for enable in arg_enable:
|
||
if enable in self._features.libs: # features in a lib
|
||
enable_feas.update(set(x for x, y in feas_info.items() if enable == y.get('lib', '')))
|
||
else: # The feature is not lib and needs to be added separately.
|
||
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
|
||
# Clear the assembly features first.
|
||
for lib in self._cfg['libs']:
|
||
if 'asm' in self._cfg['libs'][lib]:
|
||
self._cfg['libs'][lib]['asm'] = []
|
||
# Add assembly features.
|
||
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):
|
||
# Change the key sequence of the 'libs' dictionary. Otherwise, the compilation fails.
|
||
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:
|
||
# Only set defaults if not already configured (silently fill in missing values)
|
||
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])
|
||
|
||
# Check whether the dependent module is enabled.
|
||
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:
|
||
# No dimension-specific config, process common flags for _REMOVE/_EXTRA/_OVERRIDE
|
||
return ConfigResolver._process_common_operations(common_flags or {})
|
||
|
||
resolved = {}
|
||
specific_config = dimension_flags[dimension_value]
|
||
|
||
# Handle inheritance
|
||
if '_inherit' in specific_config:
|
||
parent = specific_config['_inherit']
|
||
if parent in dimension_flags:
|
||
resolved = ConfigResolver.resolve_flags(
|
||
common_flags, dimension_flags, parent
|
||
)
|
||
|
||
# Start with common flags
|
||
if not resolved:
|
||
for key, value in (common_flags or {}).items():
|
||
resolved[key] = list(value) if isinstance(value, list) else value
|
||
|
||
# Process dimension-specific flags
|
||
for key, value in specific_config.items():
|
||
if key.startswith('_'):
|
||
# Skip metadata keys
|
||
continue
|
||
|
||
# Determine operation type
|
||
if key.endswith('_OVERRIDE'):
|
||
# Complete override
|
||
base_key = key.replace('_OVERRIDE', '')
|
||
resolved[base_key] = list(value) if isinstance(value, list) else value
|
||
elif key.endswith('_EXTRA'):
|
||
# Add to existing
|
||
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'):
|
||
# Remove from existing
|
||
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:
|
||
# Direct assignment (default behavior)
|
||
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 = {}
|
||
|
||
# First pass: collect base flags (non-operation keys)
|
||
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
|
||
|
||
# Second pass: apply operations
|
||
for key, value in common_flags.items():
|
||
if key.endswith('_OVERRIDE'):
|
||
# Complete override
|
||
base_key = key.replace('_OVERRIDE', '')
|
||
resolved[base_key] = list(value) if isinstance(value, list) else value
|
||
elif key.endswith('_EXTRA'):
|
||
# Add to existing
|
||
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'):
|
||
# Remove from existing (or mark for later removal if base_key doesn't exist)
|
||
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:
|
||
# Mark for deletion even if base_key doesn't exist yet
|
||
# This allows removal during union_options phase
|
||
resolved[key] = value
|
||
|
||
return resolved
|
||
|
||
|
||
class CompleteOptionParser:
|
||
""" Parses all compilation options. """
|
||
# Sequence in which compilation options are added, including all compilation option types.
|
||
option_order = [
|
||
"CC_DEBUG_FLAGS",
|
||
"CC_OPT_LEVEL", # Optimization Level
|
||
"CC_OVERALL_FLAGS", # Overall Options
|
||
"CC_WARN_FLAGS", # Warning options
|
||
"CC_LANGUAGE_FLAGS", # Language Options
|
||
"CC_CDG_FLAGS", # Code Generation Options
|
||
"CC_MD_DEPENDENT_FLAGS", # Machine-Dependent Options
|
||
"CC_OPT_FLAGS", # Optimization Options
|
||
"CC_SEC_FLAGS", # Secure compilation options
|
||
"CC_DEFINE_FLAGS", # Custom Macro
|
||
"CC_USER_DEFINE_FLAGS", # User-defined compilation options are reserved.
|
||
]
|
||
|
||
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
|
||
|
||
# Detect dimensions for resolving nested format
|
||
self._compiler = compiler or CompilerDetector.detect_compiler_type()
|
||
self._linker = linker or LinkerDetector.detect_linker_type()
|
||
self._platform = platform or PlatformDetector.get_current_platform()
|
||
|
||
# Resolve nested format to flat format (stored separately)
|
||
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']
|
||
|
||
# Check if using nested format
|
||
if 'common' not in compile_cfg:
|
||
# Already flat format, return as-is
|
||
return dict(compile_cfg)
|
||
|
||
common_flags = compile_cfg.get('common', {})
|
||
|
||
# Extract compiler-specific configs and flat sections
|
||
compiler_configs = {}
|
||
flat_sections = {}
|
||
|
||
for k, v in compile_cfg.items():
|
||
if k.startswith('_') or k == 'common':
|
||
continue
|
||
# Check if this is a compiler dimension (contains nested _EXTRA/_REMOVE/_OVERRIDE keys)
|
||
# or a flat section (contains CC_FLAGS_ADD/CC_FLAGS_DEL)
|
||
if isinstance(v, dict) and any(key in v for key in ['CC_FLAGS_ADD', 'CC_FLAGS_DEL']):
|
||
# Flat section (user-added flags)
|
||
flat_sections[k] = v
|
||
else:
|
||
# Compiler-specific config
|
||
compiler_configs[k] = v
|
||
|
||
# Resolve nested format using ConfigResolver
|
||
resolved = ConfigResolver.resolve_flags(
|
||
common_flags, compiler_configs, self._compiler
|
||
)
|
||
|
||
# Convert resolved nested format to flat format
|
||
# All entries are converted to {'CC_FLAGS_ADD': [...]} or {'CC_FLAGS_DEL': [...]} format
|
||
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': []}
|
||
# Extend existing CC_FLAGS_ADD list
|
||
flags_to_add = value if isinstance(value, list) else [value]
|
||
flattened[base_key]['CC_FLAGS_ADD'].extend(flags_to_add)
|
||
elif key.endswith('_REMOVE'):
|
||
# Handle _REMOVE suffix: convert to CC_FLAGS_DEL
|
||
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:
|
||
# Convert direct assignments to CC_FLAGS_ADD format
|
||
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)
|
||
|
||
# Merge flat sections (user-added flags) into the result
|
||
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']
|
||
|
||
# Check if using nested format
|
||
if 'common' not in link_cfg:
|
||
# Already flat format, ensure all categories exist
|
||
result = {'PUBLIC': [], 'SHARED': [], 'EXE': []}
|
||
for key in ['PUBLIC', 'SHARED', 'EXE']:
|
||
if key in link_cfg:
|
||
result[key] = list(link_cfg[key])
|
||
return result
|
||
|
||
common_flags = link_cfg.get('common', {})
|
||
|
||
# Extract linker-specific configs and flat sections
|
||
linker_configs = {}
|
||
flat_categories = {}
|
||
|
||
for k, v in link_cfg.items():
|
||
if k.startswith('_') or k == 'common':
|
||
continue
|
||
# Check if this is a flat category (PUBLIC, SHARED, EXE)
|
||
if k in ['PUBLIC', 'SHARED', 'EXE'] and isinstance(v, list):
|
||
# Flat category (user-added flags)
|
||
flat_categories[k] = v
|
||
else:
|
||
# Linker-specific config
|
||
linker_configs[k] = v
|
||
|
||
# Resolve nested format using ConfigResolver
|
||
resolved = ConfigResolver.resolve_flags(
|
||
common_flags, linker_configs, self._linker
|
||
)
|
||
|
||
# Convert to flat format (PUBLIC, SHARED, EXE)
|
||
result = {'PUBLIC': [], 'SHARED': [], 'EXE': []}
|
||
for key, value in resolved.items():
|
||
# Handle _EXTRA suffix: PUBLIC_EXTRA → PUBLIC, etc.
|
||
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:
|
||
# Direct mapping for PUBLIC, SHARED, EXE
|
||
if key in result:
|
||
result[key] = value if isinstance(value, list) else [value]
|
||
|
||
# Merge flat categories (user-added flags) into the result
|
||
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]
|
||
|
||
# Update original config (flat format alongside nested)
|
||
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]))
|
||
|
||
# Update resolved flags (flat format for usage)
|
||
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.
|
||
"""
|
||
# Ensure linkFlag structure exists in original config
|
||
if 'linkFlag' not in self._cfg:
|
||
self._cfg['linkFlag'] = {}
|
||
|
||
# Ensure all three categories exist in original config
|
||
for category in ['PUBLIC', 'SHARED', 'EXE']:
|
||
if category not in self._cfg['linkFlag']:
|
||
self._cfg['linkFlag'][category] = []
|
||
|
||
# Add or remove flags from all categories in both configs
|
||
for category in ['PUBLIC', 'SHARED', 'EXE']:
|
||
if is_add:
|
||
# Add flags (dedup)
|
||
for flag in flags:
|
||
# Update original config
|
||
if flag not in self._cfg['linkFlag'][category]:
|
||
self._cfg['linkFlag'][category].append(flag)
|
||
# Update resolved flags
|
||
if flag not in self._resolved_link_flags[category]:
|
||
self._resolved_link_flags[category].append(flag)
|
||
else:
|
||
# Remove flags
|
||
for flag in flags:
|
||
# Update original config
|
||
if flag in self._cfg['linkFlag'][category]:
|
||
self._cfg['linkFlag'][category].remove(flag)
|
||
# Update resolved flags
|
||
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())
|
||
|
||
# Detect or use provided dimensions
|
||
self._compiler = compiler or CompilerDetector.detect_compiler_type()
|
||
self._linker = linker or LinkerDetector.detect_linker_type()
|
||
self._platform = platform or PlatformDetector.get_current_platform()
|
||
|
||
# Resolve dimensional configurations
|
||
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', {})
|
||
|
||
# Extract all compiler-specific configs (exclude metadata and 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
|
||
)
|
||
|
||
# Merge with system defines if present
|
||
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', {})
|
||
|
||
# Extract all linker-specific configs
|
||
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', {})
|
||
|
||
# Extract all OS-specific configs
|
||
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
|
||
# Merge custom link flags with all three categories
|
||
for category in ['PUBLIC', 'SHARED', 'EXE']:
|
||
# Add flags from custom config (if category exists)
|
||
custom_flags = custom_cfg.link_flags.get(category, [])
|
||
for flag in custom_flags:
|
||
if flag not in flags[category]:
|
||
flags[category].append(flag)
|
||
|
||
# Deduplicate all flag lists to handle duplicates from any source
|
||
# Preserve order (keep first occurrence)
|
||
flags['PUBLIC'] = list(dict.fromkeys(flags['PUBLIC']))
|
||
flags['EXE'] = list(dict.fromkeys(flags['EXE']))
|
||
flags['SHARED'] = list(dict.fromkeys(flags['SHARED']))
|
||
|
||
return options, flags
|