• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2021 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from dataclasses import dataclass
17import os
18import sys
19import argparse
20
21sys.path.append(
22    os.path.dirname(os.path.dirname(os.path.dirname(
23        os.path.abspath(__file__)))))
24from hb_internal.common.config import Config
25from hb_internal.common.utils import read_json_file
26from hb_internal.common.utils import dump_json_file
27from hb_internal.preloader.parse_lite_subsystems_config import parse_lite_subsystem_config
28from hb_internal.preloader.parse_vendor_product_config import get_vendor_parts_list
29
30
31def _get_base_parts(base_config_dir, os_level):
32    system_base_config_file = os.path.join(base_config_dir,
33                                           '{}_system.json'.format(os_level))
34    if not os.path.exists(system_base_config_file):
35        raise Exception("product configuration '{}' doesn't exist.".format(
36            system_base_config_file))
37    return read_json_file(system_base_config_file)
38
39
40def _get_inherit_parts(inherit, source_root_dir):
41    inherit_parts = {}
42    for _config in inherit:
43        _file = os.path.join(source_root_dir, _config)
44        _info = read_json_file(_file)
45        parts = _info.get('parts')
46        if parts:
47            inherit_parts.update(parts)
48        else:
49            inherit_parts.update(get_vendor_parts_list(_info))
50    return inherit_parts
51
52
53def _get_sys_relate_parts(system_component_info, _parts, source_root_dir):
54    _info = read_json_file(os.path.join(source_root_dir, system_component_info))
55    ret = {}
56    parts = _info.get('parts')
57    if not parts:
58        parts = get_vendor_parts_list(_info)
59    for part, featrue in parts.items():
60        if not _parts.get(part):
61            ret[part] = featrue
62    return ret
63
64
65def _get_device_info(device_name, config_dir):
66    device_config_file = os.path.join(config_dir,
67                                      '{}.json'.format(device_name))
68    device_info = read_json_file(device_config_file)
69    if device_info and device_info.get('device_name') != device_name:
70        raise Exception("device name configuration incorrect in '{}'".format(
71            device_config_file))
72    return device_info
73
74
75def _output_platforms_config(target_os, target_cpu, toolchain_label,
76                             parts_config_file, output_file):
77    config = {
78        'target_os': target_os,
79        "target_cpu": target_cpu,
80        "toolchain": toolchain_label,
81        "parts_config": parts_config_file
82    }
83
84    platform_config = {'version': 2, 'platforms': {'phone': config}}
85    dump_json_file(output_file, platform_config)
86
87
88def _output_gnargs_prop(all_features, output_file):
89    features_list = _part_features_to_list(all_features)
90    with open(output_file, 'w') as fobj:
91        fobj.write('\n'.join(features_list))
92
93
94def _get_org_subsystem_info(subsystem_config_file, os_level, config_dirs):
95    subsystem_info = {}
96    if os_level == "standard":
97        subsystem_info = read_json_file(subsystem_config_file)
98    elif os_level == "mini" or os_level == "small":
99        ohos_build_output_dir = os.path.join(config_dirs.preloader_output_dir,
100                                             '{}_system'.format(os_level))
101        subsystem_info = parse_lite_subsystem_config(
102            config_dirs.lite_components_dir, ohos_build_output_dir,
103            config_dirs.source_root_dir, subsystem_config_file)
104    return subsystem_info
105
106
107def _merge_subsystem_config(product, device, config_dirs, os_level,
108                            output_file):
109    subsystem_info = _get_org_subsystem_info(config_dirs.subsystem_config_json,
110                                            os_level, config_dirs)
111    if subsystem_info:
112        subsystem_info.update(product.get_product_specific_subsystem())
113        subsystem_info.update(device.get_device_specific_subsystem())
114    dump_json_file(output_file, subsystem_info)
115
116
117def _output_parts_features(all_parts, output_file):
118    all_features = {}
119    part_feature_map = {}
120    for _part_name, vals in all_parts.items():
121        _features = vals.get('features')
122        if _features:
123            all_features.update(_features)
124        if _features:
125            part_feature_map[_part_name.split(':')[1]] = list(_features.keys())
126    parts_feature_info = {
127        "features": all_features,
128        "part_to_feature": part_feature_map
129    }
130    dump_json_file(output_file, parts_feature_info)
131    return all_features
132
133
134def _output_parts_syscap(all_parts, output_file):
135    all_syscap = {}
136    part_syscap_map = {}
137    for _part_name, vals in all_parts.items():
138        _syscap = vals.get('syscap')
139        if _syscap:
140            all_syscap.update(_syscap)
141            part_syscap_map[_part_name.split(':')[1]] = _syscap
142    parts_syscap_info = {
143        "syscap": all_syscap,
144        "part_to_syscap": part_syscap_map
145    }
146    dump_json_file(output_file, parts_syscap_info)
147
148
149def _output_exclusion_modules_json(all_parts, output_file):
150    exclusions = {}
151    for _part_name, vals in all_parts.items():
152        _exclusions = vals.get('exclusions')
153        if _exclusions:
154            pair = dict()
155            pair[_part_name] = _exclusions
156            exclusions.update(pair)
157    dump_json_file(output_file, exclusions)
158
159
160def _part_features_to_list(all_part_features):
161    attr_list = []
162    for key, val in all_part_features.items():
163        _item = ''
164        if isinstance(val, bool):
165            _item = f'{key}={str(val).lower()}'
166        elif isinstance(val, int):
167            _item = f'{key}={val}'
168        elif isinstance(val, str):
169            _item = f'{key}="{val}"'
170        else:
171            raise Exception("part feature '{key}:{val}' type not support.")
172        attr_list.append(_item)
173    return attr_list
174
175
176def _output_build_vars(build_vars, build_prop, build_config_json):
177    build_vars_list = []
178    for k, v in build_vars.items():
179        build_vars_list.append('{}={}'.format(k, v))
180    with open(build_prop, 'w') as fobj:
181        fobj.write('\n'.join(build_vars_list))
182    dump_json_file(build_config_json, build_vars)
183
184
185def _output_parts_json(all_parts, output_file):
186    parts_info = {"parts": sorted(list(all_parts.keys()))}
187    dump_json_file(output_file, parts_info)
188
189def _output_parts_config_json(all_parts, output_file):
190    parts_config = {}
191    for part in all_parts:
192        part = part.replace(":", "_")
193        part = part.replace("-", "_")
194        part = part.replace(".", "_")
195        part = part.replace("/", "_")
196        parts_config[part] = True
197    dump_json_file(output_file, parts_config)
198
199class MyProduct():
200
201    def __init__(self, product_name, config_dirs, config_json):
202        self._name = product_name
203        self._dirs = config_dirs
204        self._device = None
205        self._config = {}
206        self._build_vars = {}
207        self._parts = {}
208        self._syscap_info = {}
209        self._parsed = False
210        self._config_file = config_json
211
212    def parse_config(self):
213        self._do_parse()
214        return self._parts, self._build_vars
215
216    def get_device(self):
217        self._do_parse()
218        return self._device
219
220    def get_product_specific_subsystem(self):
221        info = {}
222        self._do_parse()
223        subsystem_name = 'product_{}'.format(self._name)
224        if self._get_product_build_path():
225            info[subsystem_name] = {
226                'name': subsystem_name,
227                'path': self._get_product_build_path()
228            }
229        return info
230
231    def _get_product_build_path(self):
232        return self._config.get('product_build_path')
233
234    def _remove_excluded_components(self):
235        items_to_remove = []
236        for part, val in self._parts.items():
237            if "exclude" in val and val["exclude"] == "true":
238                items_to_remove.append(part)
239        for item in items_to_remove:
240            del self._parts[item]
241
242    def _do_parse(self):
243        if self._parsed is False:
244            self._config = read_json_file(self._config_file)
245
246            version = self._config.get('version', '3.0')
247            product_name = self._config.get('product_name')
248            if product_name == None:
249                product_name = ""
250            os_level = self._config.get('type')
251            if os_level == None:
252                os_level = ""
253            api_version = self._config.get('api_version')
254            if api_version == None:
255                api_version = 0
256            manufacturer_id = self._config.get('manufacturer_id')
257            if manufacturer_id == None:
258                manufacturer_id = 0
259            self._syscap_info = {'product':product_name, 'api_version':api_version,
260                'system_type':os_level, 'manufacturer_id':manufacturer_id}
261            if version == "1.0":
262                self._parse_config_v1()
263            else:
264                self._parse_config_v2p(self._config, version)
265            self._remove_excluded_components()
266            self._parsed = True
267
268    def _get_product_specific_parts(self):
269        part_name = 'product_{}'.format(self._name)
270        subsystem_name = part_name
271        info = {}
272        info['{}:{}'.format(subsystem_name, part_name)] = {}
273        return info
274
275    def _parse_config_v2(self, config):
276        all_parts = {}
277
278        os_level = config.get("type", "standard")
279        device_name = config.get('product_device')
280        current_product_parts = config.get("parts")
281        if current_product_parts:
282            all_parts.update(current_product_parts)
283
284        build_vars = {}
285        build_vars['os_level'] = os_level
286        build_vars['product_name'] = config.get('product_name')
287        if device_name:
288            build_vars['device_name'] = device_name
289        else:
290            build_vars['device_name'] = ''
291        build_vars['product_company'] = config.get('product_company')
292        if 'support_jsapi' in config:
293            build_vars['support_jsapi'] = config.get('support_jsapi')
294        if 'build_seccomp' in config:
295            build_vars['build_seccomp'] = config.get('build_seccomp')
296        if 'chipprod_config_path' in config:
297            chipprod_config_path = os.path.join(self._dirs.source_root_dir, config.get('chipprod_config_path'))
298            if os.path.exists(chipprod_config_path):
299                build_vars['chipprod_config_path'] = chipprod_config_path
300        self._build_vars = build_vars
301        self._parts.update(all_parts)
302
303        device_name = config.get('product_device')
304        if device_name:
305            self._device = MyDevice(device_name, self._dirs)
306
307    # Generate build_info needed for V3 configuration
308    def _make_device_info(self, config):
309        # NOTE:
310        # Product_name, device_company are necessary for
311        # config.json, DON NOT use .get to replace []
312        device_info = {
313            'device_name': config['board'],
314            'device_company': config['device_company']
315        }
316        if config.get('target_os'):
317            device_info['target_os'] = config.get('target_os')
318        else:
319            device_info['target_os'] = 'ohos'
320
321        if config.get('target_cpu'):
322            device_info['target_cpu'] = config['target_cpu']
323        else:
324            # Target cpu is used to set default toolchain for standard system.
325            print(
326                "The target_cpu needs to be specified, default target_cpu=arm")
327            device_info['target_cpu'] = 'arm'
328        if config.get('kernel_version'):
329            device_info['kernel_version'] = config.get('kernel_version')
330        if config.get('device_build_path'):
331            device_info['device_build_path'] = config.get('device_build_path')
332        else:
333            device_build_path = os.path.join(self._dirs.device_dir,
334                                             config['device_company'],
335                                             config['board'])
336            if not os.path.exists(device_build_path):
337                device_build_path = os.path.join(self._dirs.device_dir,
338                                                 'board',
339                                                 config['device_company'],
340                                                 config['board'])
341            device_info['device_build_path'] = device_build_path
342
343        return device_info
344
345    def _parse_config_v3(self, config):
346        all_parts = {}
347        all_parts.update(get_vendor_parts_list(config))
348        all_parts.update(self._get_product_specific_parts())
349
350        device_name = config.get('board')
351        if device_name:
352            device_info = self._make_device_info(config)
353            self._device = MyDevice(device_name, self._dirs, device_info)
354            all_parts.update(self._device.get_device_specific_parts())
355
356        build_vars = {}
357        build_vars['os_level'] = config.get('type', 'mini')
358        build_vars['product_name'] = config.get('product_name')
359        build_vars['device_name'] = config.get('board')
360        if config.get('product_company'):
361            build_vars['product_company'] = config.get('product_company')
362        elif not self._is_built_in_product(self._config_file):
363            relpath = os.path.relpath(self._config_file, self._dirs.vendor_dir)
364            build_vars['product_company'] = relpath.split('/')[0]
365        else:
366            build_vars['product_company'] = config.get('device_company')
367        if 'enable_ramdisk' in config:
368            build_vars['enable_ramdisk'] = config.get('enable_ramdisk')
369        if 'build_selinux' in config:
370            build_vars['build_selinux'] = config.get('build_selinux')
371        if 'build_seccomp' in config:
372            build_vars['build_seccomp'] = config.get('build_seccomp')
373        if 'support_jsapi' in config:
374            build_vars['support_jsapi'] = config.get('support_jsapi')
375        if 'chipprod_config_path' in config:
376            chipprod_config_path = os.path.join(self._dirs.source_root_dir, config.get('chipprod_config_path'))
377            if os.path.exists(chipprod_config_path):
378                build_vars['chipprod_config_path'] = chipprod_config_path
379        self._build_vars = build_vars
380        self._parts.update(all_parts)
381
382        if not self._is_built_in_product(self._config_file) and not hasattr(
383                config, 'product_build_path'):
384            config['product_build_path'] = os.path.relpath(
385                os.path.dirname(self._config_file), self._dirs.source_root_dir)
386
387    def _is_built_in_product(self, config_file):
388        if os.path.dirname(config_file) == self._dirs.built_in_product_dir:
389            return True
390        else:
391            return False
392
393    def _sanitize(self, config):
394        if config and self._name != config.get('product_name'):
395            raise Exception(
396                "product name configuration incorrect for '{}'".format(
397                    self._name))
398
399    # parse v2 and plus
400    def _parse_config_v2p(self, config, version):
401        self._sanitize(config)
402
403        # 1. inherit parts information from base config
404        if version == "2.0":
405            os_level = config.get("type", "standard")
406        else:
407            os_level = config.get("type", "mini")
408        # 2. product config based on default minimum system
409        based_on_mininum_system = config.get('based_on_mininum_system')
410        if based_on_mininum_system == "true":
411            self._parts = _get_base_parts(self._dirs.built_in_base_dir, os_level)
412        # 3. inherit parts information from inherit config
413        inherit = config.get('inherit')
414        if inherit:
415            self._parts.update(
416                _get_inherit_parts(inherit, self._dirs.source_root_dir))
417
418        # 4. chipset products relate system parts config
419        sys_info_path = config.get('system_component')
420        if sys_info_path:
421            sys_parts = _get_sys_relate_parts(sys_info_path, self._parts, self._dirs.source_root_dir)
422            self._parts.update(sys_parts)
423
424        # 5. get parts information from product config
425        if version == '2.0':
426            self._parse_config_v2(config)
427        else:
428            self._parse_config_v3(config)
429
430    def _parse_config_v1(self):
431        self._parts = {}
432        self._build_vars = {"os_level": 'large'}
433
434
435class MyDevice():
436
437    def __init__(self, device_name, config_dirs, device_info=None):
438        self._name = device_name
439        self._dirs = config_dirs
440        if device_info is None:
441            self._device_info = _get_device_info(
442                self._name, self._dirs.built_in_device_dir)
443        else:
444            self._device_info = device_info
445
446    def get_device_info(self):
447        return self._device_info
448
449    def get_device_specific_parts(self):
450        info = {}
451        if self._device_info:
452            device_build_path = self._device_info.get('device_build_path')
453            if device_build_path:
454                subsystem_name = 'device_{}'.format(self._name)
455                part_name = subsystem_name
456                info['{}:{}'.format(subsystem_name, part_name)] = {}
457        return info
458
459    def get_device_specific_subsystem(self):
460        info = {}
461        subsystem_name = 'device_{}'.format(self._name)
462        if self._get_device_build_path():
463            info[subsystem_name] = {
464                'name': subsystem_name,
465                'path': self._get_device_build_path()
466            }
467        return info
468
469    def _get_device_build_path(self):
470        if self._device_info:
471            return self._device_info.get('device_build_path')
472        else:
473            return None
474
475
476@dataclass
477class Dirs:
478
479    def __init__(self, config):
480        self.__post_init__(config)
481
482    def __post_init__(self, config):
483        self.source_root_dir = config.root_path
484        self.built_in_product_dir = config.built_in_product_path
485        self.productdefine_dir = os.path.join(self.source_root_dir,
486                                              'productdefine/common')
487        self.built_in_device_dir = config.built_in_device_path
488        self.built_in_base_dir = os.path.join(self.productdefine_dir, 'base')
489
490        # Configs of vendor specified products are stored in
491        # ${vendor_dir} directory.
492        self.vendor_dir = config.vendor_path
493        # Configs of device specified products are stored in
494        # ${device_dir} directory.
495        self.device_dir = os.path.join(config.root_path, 'device')
496
497        self.subsystem_config_json = os.path.join(config.root_path,
498                                                  config.subsystem_config_json)
499        self.lite_components_dir = os.path.join(config.root_path,
500                                                'build/lite/components')
501
502        self.preloader_output_dir = os.path.join(config.root_path,
503                                                 'out/preloader',
504                                                 config.product)
505
506
507@dataclass
508class Outputs:
509
510    def __init__(self, output_dir):
511        self.__post_init__(output_dir)
512
513    def __post_init__(self, output_dir):
514        self.build_prop = os.path.join(output_dir, 'build.prop')
515        self.build_config_json = os.path.join(output_dir, 'build_config.json')
516        self.parts_json = os.path.join(output_dir, 'parts.json')
517        self.parts_config_json = os.path.join(output_dir, 'parts_config.json')
518        self.build_gnargs_prop = os.path.join(output_dir, 'build_gnargs.prop')
519        self.features_json = os.path.join(output_dir, 'features.json')
520        self.syscap_json = os.path.join(output_dir, 'syscap.json')
521        self.exclusion_modules_json = os.path.join(output_dir,
522                                                   'exclusion_modules.json')
523        self.subsystem_config_json = os.path.join(output_dir,
524                                                  'subsystem_config.json')
525        self.platforms_build = os.path.join(output_dir, 'platforms.build')
526        self.systemcapability_json = os.path.join(output_dir, 'SystemCapability.json')
527
528
529class Preloader():
530
531    @property
532    def target_cpu(self):
533        return self._target_cpu
534
535    @target_cpu.setter
536    def target_cpu(self, value):
537        self._target_cpu = value
538
539    @property
540    def compile_config(self):
541        return self._compile_config
542
543    @target_cpu.setter
544    def compile_config(self, value):
545        self._compile_config = value
546
547    def __init__(self, config):
548        # All kinds of directories and subsystem_config_json
549        if isinstance(config, Config):
550            self._dirs = Dirs(config)
551            self._target_cpu = config.target_cpu
552            self._compile_config = config.compile_config
553
554            # Product & Device
555            self._product = MyProduct(config.product, self._dirs,
556                                    config.product_json)
557            self._device = self._product.get_device()
558
559            # All kinds of output files
560            os.makedirs(self._dirs.preloader_output_dir, exist_ok=True)
561            self._outputs = Outputs(self._dirs.preloader_output_dir)
562
563        if isinstance(config, argparse.Namespace):
564            self._dirs = Dirs(config)
565            self._target_cpu = config.target_cpu
566            self._compile_config = None
567
568            # Product & Device
569            self._product = MyProduct(config.product, self._dirs,
570                                    config.product_json)
571            self._device = self._product.get_device()
572
573            # All kinds of output files
574            os.makedirs(self._dirs.preloader_output_dir, exist_ok=True)
575            self._outputs = Outputs(self._dirs.preloader_output_dir)
576
577
578    def run(self, *args):
579        all_parts, build_vars = self._product.parse_config()
580        if self._device:
581            device_info = self._device.get_device_info()
582            if device_info:
583                if self._target_cpu:
584                    device_info["target_cpu"] = self._target_cpu
585                if self._compile_config:
586                    device_info[self._compile_config] = True
587                build_vars.update(device_info)
588
589        dump_json_file(self._outputs.systemcapability_json, self._product._syscap_info)
590        # save parts to parts_json
591        _output_parts_json(all_parts, self._outputs.parts_json)
592        _output_parts_config_json(all_parts, self._outputs.parts_config_json)
593
594        # save features to features_json
595        all_features = _output_parts_features(all_parts,
596                                              self._outputs.features_json)
597
598       # save syscap to syscap_json
599        _output_parts_syscap(all_parts, self._outputs.syscap_json)
600
601        # Save gn args to build_gnargs_prop
602        _output_gnargs_prop(all_features, self._outputs.build_gnargs_prop)
603
604        # save exclusion modules to exclusion_modules_json
605        _output_exclusion_modules_json(all_parts,
606                                       self._outputs.exclusion_modules_json)
607
608        # generate toolchain
609        os_level = build_vars.get('os_level')
610        target_os = build_vars.get('target_os')
611        target_cpu = build_vars.get('target_cpu')
612        if os_level == 'mini' or os_level == 'small':
613            toolchain_label = ""
614        else:
615            toolchain_label = '//build/toolchain/{0}:{0}_clang_{1}'.format(
616                target_os, target_cpu)
617
618        # add toolchain label
619        build_vars['product_toolchain_label'] = toolchain_label
620
621        # output platform config
622        parts_config_file = os.path.relpath(self._outputs.parts_json,
623                                            self._dirs.preloader_output_dir)
624        _output_platforms_config(target_os, target_cpu, toolchain_label,
625                                 parts_config_file,
626                                 self._outputs.platforms_build)
627
628        # output build info to file
629        _output_build_vars(build_vars, self._outputs.build_prop,
630                           self._outputs.build_config_json)
631
632        # output subsystem info to file
633        _merge_subsystem_config(self._product, self._device, self._dirs,
634                                os_level, self._outputs.subsystem_config_json)
635
636def main(argv):
637    parser = argparse.ArgumentParser()
638    parser.add_argument('--product', required=True)
639    parser.add_argument('--root-path', required=True)
640    parser.add_argument('--built-in-product-path', required=True)
641    parser.add_argument('--built-in-device-path', required=True)
642    parser.add_argument('--product-json', required=True)
643    parser.add_argument('--vendor-path', required=True)
644    parser.add_argument('--lite-components-dir', required=True)
645    parser.add_argument('--preloader-output-dir', required=True)
646    parser.add_argument('--device-dir', required=True)
647    parser.add_argument('--target-cpu', required=True)
648    parser.add_argument('--subsystem-config-file',
649                        dest='subsystem_config_json',
650                        required=True)
651
652    args = parser.parse_args(argv)
653    preloader = Preloader(args)
654    preloader.run()
655    return 0
656
657if __name__ == '__main__':
658    sys.exit(main(sys.argv[1:]))
659