• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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