• 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
18
19from hb_internal.common.utils import read_json_file
20from hb_internal.common.utils import dump_json_file
21from hb_internal.preloader.parse_lite_subsystems_config import parse_lite_subsystem_config
22from hb_internal.preloader.parse_vendor_product_config import get_vendor_parts_list
23
24
25def _get_base_parts(base_config_dir, os_level):
26    system_base_config_file = os.path.join(base_config_dir,
27                                           '{}_system.json'.format(os_level))
28    if not os.path.exists(system_base_config_file):
29        raise Exception("product configuration '{}' doesn't exist.".format(
30            system_base_config_file))
31    return read_json_file(system_base_config_file)
32
33
34def _get_inherit_parts(inherit, source_root_dir):
35    inherit_parts = {}
36    for _config in inherit:
37        _file = os.path.join(source_root_dir, _config)
38        _info = read_json_file(_file)
39        parts = _info.get('parts')
40        if parts:
41            inherit_parts.update(parts)
42    return inherit_parts
43
44
45def _get_device_info(device_name, config_dir):
46    device_config_file = os.path.join(config_dir,
47                                      '{}.json'.format(device_name))
48    device_info = read_json_file(device_config_file)
49    if device_info and device_info.get('device_name') != device_name:
50        raise Exception("device name configuration incorrect in '{}'".format(
51            device_config_file))
52    return device_info
53
54
55def _output_platforms_config(target_os, target_cpu, toolchain_label,
56                             parts_config_file, output_file):
57    config = {
58        'target_os': target_os,
59        "target_cpu": target_cpu,
60        "toolchain": toolchain_label,
61        "parts_config": parts_config_file
62    }
63
64    platform_config = {'version': 2, 'platforms': {'phone': config}}
65    dump_json_file(output_file, platform_config)
66
67
68def _output_gnargs_prop(all_features, output_file):
69    features_list = _part_features_to_list(all_features)
70    with open(output_file, 'w') as fobj:
71        fobj.write('\n'.join(features_list))
72
73
74def _get_org_subsytem_info(subsystem_config_file, os_level, config_dirs):
75    subsystem_info = {}
76    if os_level == "standard":
77        subsystem_info = read_json_file(subsystem_config_file)
78    elif os_level == "mini" or os_level == "small":
79        ohos_build_output_dir = os.path.join(config_dirs.preloader_output_dir,
80                                             '{}_system'.format(os_level))
81        subsystem_info = parse_lite_subsystem_config(
82            config_dirs.lite_components_dir, ohos_build_output_dir,
83            config_dirs.source_root_dir, subsystem_config_file)
84    return subsystem_info
85
86
87def _merge_subsystem_config(product, device, config_dirs, os_level,
88                            output_file):
89    subsystem_info = _get_org_subsytem_info(config_dirs.subsystem_config_json,
90                                            os_level, config_dirs)
91    if subsystem_info:
92        subsystem_info.update(product.get_product_specific_subsystem())
93        subsystem_info.update(device.get_device_specific_subsystem())
94    dump_json_file(output_file, subsystem_info)
95
96
97def _output_parts_features(all_parts, output_file):
98    all_features = {}
99    part_feature_map = {}
100    for _part_name, vals in all_parts.items():
101        _features = vals.get('features')
102        if _features:
103            all_features.update(_features)
104        if _features:
105            part_feature_map[_part_name.split(':')[1]] = list(_features.keys())
106    parts_feature_info = {
107        "features": all_features,
108        "part_to_feature": part_feature_map
109    }
110    dump_json_file(output_file, parts_feature_info)
111    return all_features
112
113
114def _part_features_to_list(all_part_features):
115    attr_list = []
116    for key, val in all_part_features.items():
117        _item = ''
118        if isinstance(val, bool):
119            _item = f'{key}={str(val).lower()}'
120        elif isinstance(val, int):
121            _item = f'{key}={val}'
122        elif isinstance(val, str):
123            _item = f'{key}="{val}"'
124        else:
125            raise Exception("part feature '{key}:{val}' type not support.")
126        attr_list.append(_item)
127    return attr_list
128
129
130def _output_build_vars(build_vars, build_prop, build_config_json):
131    build_vars_list = []
132    for k, v in build_vars.items():
133        build_vars_list.append('{}={}'.format(k, v))
134    with open(build_prop, 'w') as fobj:
135        fobj.write('\n'.join(build_vars_list))
136    dump_json_file(build_config_json, build_vars)
137
138
139def _output_parts_json(all_parts, output_file):
140    parts_info = {"parts": sorted(list(all_parts.keys()))}
141    dump_json_file(output_file, parts_info)
142
143def _output_parts_config_json(all_parts, output_file):
144    parts_config = {}
145    for part in all_parts:
146        part = part.replace(":", "_")
147        part = part.replace("-", "_")
148        part = part.replace(".", "_")
149        parts_config[part] = True
150    dump_json_file(output_file, parts_config)
151
152class MyProduct():
153
154    def __init__(self, product_name, config_dirs, config_json):
155        self._name = product_name
156        self._dirs = config_dirs
157        self._device = None
158        self._config = {}
159        self._build_vars = {}
160        self._parts = {}
161        self._syscap_info = {}
162        self._parsed = False
163        self._config_file = config_json
164
165    def parse_config(self):
166        self._do_parse()
167        return self._parts, self._build_vars
168
169    def get_device(self):
170        self._do_parse()
171        return self._device
172
173    def get_product_specific_subsystem(self):
174        info = {}
175        self._do_parse()
176        subsystem_name = 'product_{}'.format(self._name)
177        if self._get_product_build_path():
178            info[subsystem_name] = {
179                'name': subsystem_name,
180                'path': self._get_product_build_path()
181            }
182        return info
183
184    def _get_product_build_path(self):
185        return self._config.get('product_build_path')
186
187    def _do_parse(self):
188        if self._parsed is False:
189            self._config = read_json_file(self._config_file)
190
191            version = self._config.get('version', '3.0')
192            product_name = self._config.get('product_name')
193            if product_name == None:
194                product_name = ""
195            os_level = self._config.get('type')
196            if os_level == None:
197                os_level = ""
198            api_version = self._config.get('api_version')
199            if api_version == None:
200                api_version = 0
201            manufacturer_id = self._config.get('manufacturer_id')
202            if manufacturer_id == None:
203                manufacturer_id = 0
204            self._syscap_info = {'product':product_name, 'api_version':api_version,
205                'system_type':os_level, 'manufacturer_id':manufacturer_id}
206            if version == "1.0":
207                self._parse_config_v1()
208            else:
209                self._parse_config_v2p(self._config, version)
210            self._parsed = True
211
212    def _get_product_specific_parts(self):
213        part_name = 'product_{}'.format(self._name)
214        subsystem_name = part_name
215        info = {}
216        info['{}:{}'.format(subsystem_name, part_name)] = {}
217        return info
218
219    def _parse_config_v2(self, config):
220        all_parts = {}
221
222        os_level = config.get("type", "standard")
223        device_name = config.get('product_device')
224        current_product_parts = config.get("parts")
225        if current_product_parts:
226            all_parts.update(current_product_parts)
227
228        build_vars = {}
229        build_vars['os_level'] = os_level
230        build_vars['product_name'] = config.get('product_name')
231        if device_name:
232            build_vars['device_name'] = device_name
233        else:
234            build_vars['device_name'] = ''
235        build_vars['product_company'] = config.get('product_company')
236        if 'support_jsapi' in config:
237            build_vars['support_jsapi'] = config.get('support_jsapi')
238
239        self._build_vars = build_vars
240        self._parts.update(all_parts)
241
242        device_name = config.get('product_device')
243        if device_name:
244            self._device = MyDevice(device_name, self._dirs)
245
246    # Generate build_info needed for V3 configuration
247    def _make_device_info(self, config):
248        # NOTE:
249        # Product_name, device_company are necessary for
250        # config.json, DON NOT use .get to replace []
251        device_info = {
252            'device_name': config['board'],
253            'device_company': config['device_company']
254        }
255        if config.get('target_os'):
256            device_info['target_os'] = config.get('target_os')
257        else:
258            device_info['target_os'] = 'ohos'
259
260        if config.get('target_cpu'):
261            device_info['target_cpu'] = config['target_cpu']
262        else:
263            # Target cpu is used to set default toolchain for standard system.
264            print(
265                "The target_cpu needs to be specified, default target_cpu=arm")
266            device_info['target_cpu'] = 'arm'
267        if config.get('kernel_version'):
268            device_info['kernel_version'] = config.get('kernel_version')
269        if config.get('device_build_path'):
270            device_info['device_build_path'] = config.get('device_build_path')
271        else:
272            device_build_path = os.path.join(self._dirs.device_dir,
273                                             config['device_company'],
274                                             config['board'])
275            if not os.path.exists(device_build_path):
276                device_build_path = os.path.join(self._dirs.device_dir,
277                                                 'board',
278                                                 config['device_company'],
279                                                 config['board'])
280            device_info['device_build_path'] = device_build_path
281
282        return device_info
283
284    def _parse_config_v3(self, config):
285        all_parts = {}
286        all_parts.update(get_vendor_parts_list(config))
287        all_parts.update(self._get_product_specific_parts())
288
289        device_name = config.get('board')
290        if device_name:
291            device_info = self._make_device_info(config)
292            self._device = MyDevice(device_name, self._dirs, device_info)
293            all_parts.update(self._device.get_device_specific_parts())
294
295        build_vars = {}
296        build_vars['os_level'] = config.get('type', 'mini')
297        build_vars['product_name'] = config.get('product_name')
298        build_vars['device_name'] = config.get('board')
299        if config.get('product_company'):
300            build_vars['product_company'] = config.get('product_company')
301        elif not self._is_built_in_product(self._config_file):
302            relpath = os.path.relpath(self._config_file, self._dirs.vendor_dir)
303            build_vars['product_company'] = relpath.split('/')[0]
304        else:
305            build_vars['product_company'] = config.get('device_company')
306        if 'enable_ramdisk' in config:
307            build_vars['enable_ramdisk'] = config.get('enable_ramdisk')
308        if 'support_jsapi' in config:
309            build_vars['support_jsapi'] = config.get('support_jsapi')
310
311        self._build_vars = build_vars
312        self._parts.update(all_parts)
313
314        if not self._is_built_in_product(self._config_file) and not hasattr(
315                config, 'product_build_path'):
316            config['product_build_path'] = os.path.relpath(
317                os.path.dirname(self._config_file), self._dirs.source_root_dir)
318
319    def _is_built_in_product(self, config_file):
320        if os.path.dirname(config_file) == self._dirs.built_in_product_dir:
321            return True
322        else:
323            return False
324
325    def _sanitize(self, config):
326        if config and self._name != config.get('product_name'):
327            raise Exception(
328                "product name configuration incorrect for '{}'".format(
329                    self._name))
330
331    # parse v2 and plus
332    def _parse_config_v2p(self, config, version):
333        self._sanitize(config)
334
335        # 1. inherit parts infomation from base config
336        if version == "2.0":
337            os_level = config.get("type", "standard")
338        else:
339            os_level = config.get("type", "mini")
340        self._parts = _get_base_parts(self._dirs.built_in_base_dir, os_level)
341        # 2. inherit parts information from inherit config
342        inherit = config.get('inherit')
343        if inherit:
344            self._parts.update(
345                _get_inherit_parts(inherit, self._dirs.source_root_dir))
346
347        # 3. get parts information from product config
348        if version == '2.0':
349            self._parse_config_v2(config)
350        else:
351            self._parse_config_v3(config)
352
353    def _parse_config_v1(self):
354        self._parts = {}
355        self._build_vars = {"os_level": 'large'}
356
357
358class MyDevice():
359
360    def __init__(self, device_name, config_dirs, device_info=None):
361        self._name = device_name
362        self._dirs = config_dirs
363        if device_info is None:
364            self._device_info = _get_device_info(
365                self._name, self._dirs.built_in_device_dir)
366        else:
367            self._device_info = device_info
368
369    def get_device_info(self):
370        return self._device_info
371
372    def get_device_specific_parts(self):
373        info = {}
374        if self._device_info:
375            device_build_path = self._device_info.get('device_build_path')
376            if device_build_path:
377                subsystem_name = 'device_{}'.format(self._name)
378                part_name = subsystem_name
379                info['{}:{}'.format(subsystem_name, part_name)] = {}
380        return info
381
382    def get_device_specific_subsystem(self):
383        info = {}
384        subsystem_name = 'device_{}'.format(self._name)
385        if self._get_device_build_path():
386            info[subsystem_name] = {
387                'name': subsystem_name,
388                'path': self._get_device_build_path()
389            }
390        return info
391
392    def _get_device_build_path(self):
393        if self._device_info:
394            return self._device_info.get('device_build_path')
395        else:
396            return None
397
398
399@dataclass
400class Dirs:
401
402    def __init__(self, config):
403        self.__post_init__(config)
404
405    def __post_init__(self, config):
406        self.source_root_dir = config.root_path
407        self.built_in_product_dir = config.built_in_product_path
408        self.productdefine_dir = os.path.join(self.source_root_dir,
409                                              'productdefine/common')
410        self.built_in_device_dir = config.built_in_device_path
411        self.built_in_base_dir = os.path.join(self.productdefine_dir, 'base')
412
413        # Configs of vendor specified products are stored in
414        # ${vendor_dir} directory.
415        self.vendor_dir = config.vendor_path
416        # Configs of device specified products are stored in
417        # ${device_dir} directory.
418        self.device_dir = os.path.join(config.root_path, 'device')
419
420        self.subsystem_config_json = os.path.join(
421            config.root_path, 'build/subsystem_config.json')
422        self.lite_components_dir = os.path.join(config.root_path,
423                                                'build/lite/components')
424
425        self.preloader_output_dir = os.path.join(config.root_path,
426                                                 'out/preloader',
427                                                 config.product)
428
429
430@dataclass
431class Outputs:
432
433    def __init__(self, output_dir):
434        self.__post_init__(output_dir)
435
436    def __post_init__(self, output_dir):
437        self.build_prop = os.path.join(output_dir, 'build.prop')
438        self.build_config_json = os.path.join(output_dir, 'build_config.json')
439        self.parts_json = os.path.join(output_dir, 'parts.json')
440        self.parts_config_json = os.path.join(output_dir, 'parts_config.json')
441        self.build_gnargs_prop = os.path.join(output_dir, 'build_gnargs.prop')
442        self.features_json = os.path.join(output_dir, 'features.json')
443        self.subsystem_config_json = os.path.join(output_dir,
444                                                  'subsystem_config.json')
445        self.platforms_build = os.path.join(output_dir, 'platforms.build')
446        self.systemcapability_json = os.path.join(output_dir, 'SystemCapability.json')
447
448
449class Preloader():
450
451    def __init__(self, config):
452        # All kinds of directories and subsystem_config_json
453        self._dirs = Dirs(config)
454
455        # Product & Device
456        self._product = MyProduct(config.product, self._dirs,
457                                  config.product_json)
458        self._device = self._product.get_device()
459
460        # All kinds of output files
461        os.makedirs(self._dirs.preloader_output_dir, exist_ok=True)
462        self._outputs = Outputs(self._dirs.preloader_output_dir)
463
464    def run(self, *args):
465        all_parts, build_vars = self._product.parse_config()
466        if self._device:
467            device_info = self._device.get_device_info()
468            if device_info:
469                build_vars.update(device_info)
470
471        dump_json_file(self._outputs.systemcapability_json, self._product._syscap_info)
472        # save parts to parts_json
473        _output_parts_json(all_parts, self._outputs.parts_json)
474        _output_parts_config_json(all_parts, self._outputs.parts_config_json)
475
476        # save features to features_json
477        all_features = _output_parts_features(all_parts,
478                                              self._outputs.features_json)
479
480        # Save gn args to build_gnargs_prop
481        _output_gnargs_prop(all_features, self._outputs.build_gnargs_prop)
482
483        # generate toolchain
484        os_level = build_vars.get('os_level')
485        target_os = build_vars.get('target_os')
486        target_cpu = build_vars.get('target_cpu')
487        if os_level == 'mini' or os_level == 'small':
488            toolchain_label = ""
489        else:
490            toolchain_label = '//build/toolchain/{0}:{0}_clang_{1}'.format(
491                target_os, target_cpu)
492
493        # add toolchain label
494        build_vars['product_toolchain_label'] = toolchain_label
495
496        # output platform config
497        parts_config_file = os.path.relpath(self._outputs.parts_json,
498                                            self._dirs.preloader_output_dir)
499        _output_platforms_config(target_os, target_cpu, toolchain_label,
500                                 parts_config_file,
501                                 self._outputs.platforms_build)
502
503        # output build info to file
504        _output_build_vars(build_vars, self._outputs.build_prop,
505                           self._outputs.build_config_json)
506
507        # output subsystem info to file
508        _merge_subsystem_config(self._product, self._device, self._dirs,
509                                os_level, self._outputs.subsystem_config_json)
510