1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4#
5# Copyright (c) 2023 Huawei Device Co., Ltd.
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18import os
19
20from services.interface.load_interface import LoadInterface
21from containers.status import throw_exception
22from exceptions.ohos_exception import OHOSException
23from util.loader import platforms_loader  # noqa: E402
24from util.loader import generate_targets_gn  # noqa: E402
25from util.loader import load_ohos_build  # noqa: E402
26from util.loader import subsystem_scan  # noqa: E402
27from util.loader import subsystem_info  # noqa: E402
28from scripts.util.file_utils import read_json_file, write_json_file, write_file  # noqa: E402, E501
29from util.log_util import LogUtil
30
31
32class OHOSLoader(LoadInterface):
33
34    def __init__(self):
35        super().__init__()
36        self.source_root_dir = ""
37        self.gn_root_out_dir = ""
38        self.os_level = ""
39        self.target_cpu = ""
40        self.target_os = ""
41        self.config_output_relpath = ""
42        self.config_output_dir = ""
43        self.target_arch = ""
44        self.subsystem_config_file = ""
45        self.subsystem_config_overlay_file = ""
46        self.platforms_config_file = ""
47        self.exclusion_modules_config_file = ""
48        self.example_subsystem_file = ""
49        self.build_example = ""
50        self.scalable_build = ""
51        self.build_platform_name = ""
52        self.build_xts = ""
53        self.ignore_api_check = ""
54        self.load_test_config = ""
55        self.subsystem_configs = ""
56        self._subsystem_info = ""
57        self.skip_partlist_check = ""
58
59    def __post_init__(self):
60        self.source_root_dir = self.config.root_path + '/'
61        self.gn_root_out_dir = self.config.out_path if not self.config.out_path.startswith(
62            '/') else os.path.relpath(self.config.out_path, self.config.root_path)
63        self.os_level = self.config.os_level if self.config.os_level else "standard"
64        self.target_cpu = self.config.target_cpu if self.config.target_cpu else "arm"
65        self.target_os = self.config.target_os if self.config.target_os else "ohos"
66        self.config_output_relpath = os.path.join(
67            self.gn_root_out_dir, 'build_configs')
68        self.config_output_dir = os.path.join(
69            self.source_root_dir, self.config_output_relpath)
70        self.target_arch = '{}_{}'.format(self.target_os, self.target_cpu)
71        self.subsystem_config_file = os.path.join(
72            self.config.root_path, 'out/preloader', self.config.product, 'subsystem_config.json')
73        self.platforms_config_file = os.path.join(
74            self.config.root_path, 'out/preloader', self.config.product, 'platforms.build')
75        self.exclusion_modules_config_file = os.path.join(
76            self.config.root_path, 'out/preloader', self.config.product, 'exclusion_modules.json')
77        self.example_subsystem_file = os.path.join(
78            self.config.root_path, 'build', 'subsystem_config_example.json')
79
80        compile_standard_allow_file = os.path.join(
81            self.config.root_path, 'out/preloader', self.config.product, 'compile_standard_whitelist.json')
82        compile_standard_allow_info = read_json_file(compile_standard_allow_file)
83        bundle_subsystem_allow_list = compile_standard_allow_info.get("bundle_subsystem_error", [])
84
85        # check config args
86        self._check_args()
87
88        self.build_example = self.args_dict.get('build_example')
89        if not self.build_example:
90            self.example_subsystem_file = ""
91        self.scalable_build = self.args_dict.get('scalable_build')
92        self.build_platform_name = self.args_dict.get('build_platform_name')
93        self.build_xts = self.args_dict.get('build_xts')
94        self.ignore_api_check = self.args_dict.get('ignore_api_check')
95        self.load_test_config = self.args_dict.get('load_test_config')
96        self.skip_partlist_check = self.args_dict.get('skip_partlist_check')
97
98        self._subsystem_info = subsystem_info.get_subsystem_info(
99            self.subsystem_config_file,
100            self.example_subsystem_file,
101            self.source_root_dir,
102            self.config_output_relpath,
103            self.os_level)
104        overrided_components = self._override_components()
105
106        self._platforms_info = platforms_loader.get_platforms_info(
107            self.platforms_config_file,
108            self.source_root_dir,
109            self.gn_root_out_dir,
110            self.target_arch,
111            self.config_output_relpath,
112            self.scalable_build)
113        self.variant_toolchains = self._platforms_info.get(
114            'variant_toolchain_info').get('platform_toolchain')
115        self._all_platforms = self.variant_toolchains.keys()
116        self.build_platforms = self._get_build_platforms()
117        self.parts_config_info = load_ohos_build.get_parts_info(
118            self.source_root_dir,
119            self.config_output_relpath,
120            self._subsystem_info,
121            self.variant_toolchains,
122            self.target_arch,
123            self.ignore_api_check,
124            self.exclusion_modules_config_file,
125            self.load_test_config,
126            overrided_components,
127            bundle_subsystem_allow_list,
128            self.skip_partlist_check,
129            self.build_xts)
130        self.parts_targets = self.parts_config_info.get('parts_targets')
131        self.phony_targets = self.parts_config_info.get('phony_target')
132        self.parts_info = self.parts_config_info.get('parts_info')
133        self.target_platform_parts = self._get_platforms_all_parts()
134        self.target_platform_stubs = self._get_platforms_all_stubs()
135        self.required_parts_targets_list = self._get_required_build_parts_list()
136        self.required_phony_targets = self._get_required_phony_targets()
137        self.required_parts_targets = self._get_required_build_targets()
138
139# check method
140
141    '''Description: Check the parameters passed in config. If the parameters are not
142                    specified or the file content pointed to by the parameters does not
143                    exist, an exception will be thrown directly.
144    @parameter:none
145    @return :none
146    '''
147    @throw_exception
148    def _check_args(self):
149        LogUtil.hb_info("Checking all build args...")
150        # check subsystem_config_file
151        if not read_json_file(self.subsystem_config_file):
152            self.subsystem_config_file = os.path.join(
153                self.source_root_dir, 'build/subsystem_config.json')
154        if not read_json_file(self.subsystem_config_file):
155            raise OHOSException("Cannot get the content from platform config file, \
156                            please check whether the corresponding file('out/preloader/{}/subsystem_config.json' or \
157                            'build/subsystem_config.json') is written correctly.".format(self.config.product), "2001")
158
159        # check gn_root_out_dir
160        if not self.gn_root_out_dir:
161            raise OHOSException("Args gn_root_out_dir is required.", "2002")
162        if not os.path.realpath(self.gn_root_out_dir).startswith(self.source_root_dir):
163            raise OHOSException("Args gn_root_out_dir is incorrect.", "2003")
164
165        # check platform config file
166        if not read_json_file(self.platforms_config_file):
167            raise OHOSException("Cannot get the content from platform config file, \
168                            please check whether the corresponding file('out/preloader/${product_name}/platforms.build') \
169                            is written correctly.".format(self.config.product), "2004")
170
171        # check example subsystem file
172        if not read_json_file(self.example_subsystem_file):
173            raise OHOSException("Cannot get the content from example subsystem file, please check whether \
174                                the corresponding file ('build/subsystem_config_example.json') exists.", "2005")
175
176    @throw_exception
177    def _check_product_part_feature(self):
178        LogUtil.hb_info("Checking all product features...")
179        product_preloader_dir = os.path.dirname(self.platforms_config_file)
180        _preloader_feature_file = os.path.join(product_preloader_dir,
181                                               'features.json')
182        _preloader_feature_info = read_json_file(_preloader_feature_file)
183        part_to_feature = _preloader_feature_info.get('part_to_feature')
184        _feature_whitelist_file = os.path.join(
185            self.source_root_dir, "out/products_ext", "component_feature_whitelist.json"
186        )
187        if not os.path.exists(_feature_whitelist_file):
188            _feature_whitelist_file = os.path.join(
189                self.source_root_dir, "build/", "component_feature_whitelist.json"
190            )
191        _feature_whitelist_info = read_json_file(_feature_whitelist_file)
192        _feature_whitelist_list = []
193        if _feature_whitelist_info:
194            _feature_whitelist_list = list(_feature_whitelist_info.keys())
195        for key, vals in part_to_feature.items():
196            part = self.parts_info.get(key)
197            if part is None:
198                continue
199            _p_info = part[0]
200            def_feature_list = _p_info.get('feature_list')
201            if vals and not def_feature_list:
202                message = "The product use a feature vals='{}', but that is not defined " \
203                      "in this part bundle.json file, part_name='{}'".format(vals, key)
204                if key not in _feature_whitelist_list:
205                    raise OHOSException(message, "2006")
206                LogUtil.hb_warning(message)
207                continue
208            for _f_name in vals:
209                if _f_name not in def_feature_list:
210                    raise OHOSException(
211                        "The product use a feature that is not supported"
212                        " by this part, part_name='{}', feature='{}'".format(
213                            key, _f_name), "2006")
214
215    @throw_exception
216    def _check_parts_config_info(self):
217        LogUtil.hb_info("Checking parts config...")
218        if not ('parts_info' in self.parts_config_info
219                and 'subsystem_parts' in self.parts_config_info
220                and 'parts_variants' in self.parts_config_info
221                and 'parts_kits_info' in self.parts_config_info
222                and 'parts_inner_kits_info' in self.parts_config_info
223                and 'parts_targets' in self.parts_config_info):
224            raise OHOSException(
225                "Loading ohos.build information is incorrect.", "2007")
226
227# generate method
228
229    '''Description: Generate SystemCapability.json & syscap.json & syscap.para, dir:[
230        (//out/preloader/${product_name}/system/etc/SystemCapability.json),
231        (//out/preloader/${product_name}/system/etc/syscap.json),
232        (//out/preloader/${product_name}/system/etc/param/syscap.para)]
233    @parameter:none
234    @return :none
235    '''
236    @throw_exception
237    def _generate_syscap_files(self):
238        pre_syscap_info_path = os.path.dirname(self.platforms_config_file)
239        system_path = os.path.join(self.source_root_dir, os.path.join(
240            os.path.dirname(self.platforms_config_file), "system/"))
241        syscap_product_dict = read_json_file(
242            os.path.join(pre_syscap_info_path, "syscap.json"))
243        syscap_info_list = self.parts_config_info.get('syscap_info')
244        target_syscap_with_part_name_list = []
245        target_syscap_list = []
246        target_syscap_for_init_list = []
247        all_syscap_list = []
248        for syscap in syscap_info_list:
249            if syscap['component'] not in self.required_parts_targets_list:
250                continue
251            if 'syscap' not in syscap or syscap['syscap'] is None \
252                    or len(syscap['syscap']) == 0 or syscap['syscap'] == [""]:
253                continue
254            for syscap_string in syscap['syscap']:
255                all_syscap_list.append(syscap_string.split('=')[0].strip())
256
257        for key, value in syscap_product_dict['part_to_syscap'].items():
258            part = self.parts_info.get(key)
259            if part is None:
260                continue
261            for syscap in value:
262                if syscap not in all_syscap_list:
263                    raise OHOSException(
264                        "In config.json of part [{}],the syscap[{}] is incorrect, \
265                        please check the syscap name".format(key, syscap), "2008")
266
267        for syscap in syscap_info_list:
268            remove_list = []
269            if syscap['component'] not in self.required_parts_targets_list:
270                continue
271            if 'syscap' not in syscap or syscap['syscap'] is None \
272                    or len(syscap['syscap']) == 0 or syscap['syscap'] == [""]:
273                continue
274            for syscap_string in syscap['syscap']:
275                if syscap_string.startswith("SystemCapability.") is True:
276                    target_syscap_init_str = "const."
277                    syscap_name = syscap_string.split('=')[0].strip()
278                    all_syscap_product = syscap_product_dict['syscap']
279                    if syscap_name in all_syscap_product and not all_syscap_product[syscap_name]:
280                        remove_list.append(syscap_string)
281                        continue
282                    elif syscap_name in all_syscap_product and all_syscap_product[syscap_name]:
283                        target_syscap_init_str += syscap_name + '=true\n'
284                    else:
285                        if syscap_string.endswith('true'):
286                            target_syscap_init_str += syscap_name + '=true\n'
287                        elif syscap_string.endswith('false'):
288                            remove_list.append(syscap_string)
289                            continue
290                        else:
291                            target_syscap_init_str += syscap_string + "=true\n"
292                    if target_syscap_init_str not in target_syscap_for_init_list:
293                        target_syscap_for_init_list.append(
294                            target_syscap_init_str)
295                else:
296                    raise OHOSException("""In bundle.json of part [{}], The syscap string [{}] is incorrect,
297                    need start with \"SystemCapability.\"""".format(syscap['component'], syscap_string), "2009")
298
299            for remove_str in remove_list:
300                syscap['syscap'].remove(remove_str)
301            for i in range(len(syscap['syscap'])):
302                if syscap['syscap'][i].endswith('true') or syscap['syscap'][i].endswith('false'):
303                    syscap['syscap'][i] = syscap['syscap'][i].split('=')[
304                        0].strip()
305
306            syscap['syscap'].sort()
307            target_syscap_with_part_name_list.append(syscap)
308            target_syscap_list.extend(syscap['syscap'])
309
310        # Generate SystemCapability.json & syscap.json & syscap.para
311        target_syscap_list.sort()
312        syscap_info_dict = read_json_file(os.path.join(
313            pre_syscap_info_path, "SystemCapability.json"))
314        syscap_info_dict.update({'syscap': {'os': target_syscap_list}})
315        system_etc_path = os.path.join(system_path, "etc/")
316        if not os.path.exists(system_path):
317            os.mkdir(system_path)
318        if not os.path.exists(system_etc_path):
319            os.mkdir(system_etc_path)
320        syscap_info_json = os.path.join(
321            system_etc_path, "SystemCapability.json")
322        write_json_file(syscap_info_json, syscap_info_dict)
323        LogUtil.hb_info(
324            "generate syscap info file to '{}'".format(syscap_info_json), mode=self.config.log_mode)
325        target_syscap_with_part_name_list.sort(
326            key=lambda syscap: syscap['component'])
327        syscap_info_with_part_name_file = os.path.join(
328            system_etc_path, "syscap.json")
329        write_json_file(syscap_info_with_part_name_file, {
330            'components': target_syscap_with_part_name_list})
331        LogUtil.hb_info("generate syscap info with part name list to '{}'".format(
332            syscap_info_with_part_name_file), mode=self.config.log_mode)
333        if not os.path.exists(os.path.join(system_etc_path, "param/")):
334            os.mkdir(os.path.join(system_etc_path, "param/"))
335        target_syscap_for_init_file = os.path.join(
336            system_etc_path, "param/syscap.para")
337        with open(target_syscap_for_init_file, "w") as file:
338            file.writelines(sorted(target_syscap_for_init_list))
339        LogUtil.hb_info("generate target syscap for init list to '{}'".format(
340            target_syscap_for_init_file), mode=self.config.log_mode)
341
342# get method
343    @throw_exception
344    def _get_build_platforms(self) -> list:
345        build_platforms = []
346        if self.build_platform_name == 'all':
347            build_platforms = self._all_platforms
348        elif self.build_platform_name in self._all_platforms:
349            build_platforms = [self.build_platform_name]
350        else:
351            raise OHOSException(
352                "The target_platform is incorrect, only allows [{}].".format(
353                    ', '.join(self._all_platforms)), "2010")
354        return build_platforms
355
356    '''Description: output infos for testfwk into a json file. \
357        (/out/${product_name}/build_configs/infos_for_testfwk.json)
358    @parameter:none
359    @return :none
360    '''
361
362    def _generate_infos_for_testfwk(self):
363        infos_for_testfwk_file = os.path.join(self.config_output_dir,
364                                              "infos_for_testfwk.json")
365        parts_info = self.parts_config_info.get('parts_info')
366        parts_info_dict = {}
367        for _part_name, _parts in parts_info.items():
368            for _info in _parts:
369                parts_info_dict[_info.get('part_name')] = _info
370        _output_infos = {}
371        for _platform, _parts in self.target_platform_parts.items():
372            result = self._output_infos_by_platform(_parts, parts_info_dict)
373            _output_infos[_platform] = result
374        write_json_file(infos_for_testfwk_file,
375                        _output_infos, check_changes=True)
376        LogUtil.hb_info("generate infos for testfwk to '{}'".format(
377            infos_for_testfwk_file), mode=self.config.log_mode)
378
379    '''Description: output all target platform parts into a json file \
380        (/out/${product_name}/build_configs/target_platforms_parts.json)
381    @parameter:none
382    @return :none
383    '''
384
385    def _generate_target_platform_parts(self):
386        target_platform_parts_file = os.path.join(self.config_output_dir,
387                                                  "target_platforms_parts.json")
388        write_json_file(target_platform_parts_file,
389                        self.target_platform_parts,
390                        check_changes=True)
391        LogUtil.hb_info("generate target platform parts to '{}'".format(
392            target_platform_parts_file), mode=self.config.log_mode)
393
394    '''Description: Generate parts differences in different platforms, using phone as base. \
395        (/out/${product_name}/build_configs/parts_different_info.json)
396    @parameter: none
397    @return :none
398    '''
399
400    def _generate_part_different_info(self):
401        parts_different_info = self._get_parts_by_platform()
402        parts_different_info_file = os.path.join(self.config_output_dir,
403                                                 "parts_different_info.json")
404        write_json_file(parts_different_info_file,
405                        parts_different_info,
406                        check_changes=True)
407        LogUtil.hb_info("generate part different info to '{}'".format(
408            parts_different_info_file), mode=self.config.log_mode)
409
410    '''Description: output platforms list into a gni file. \
411        (/out/${product_name}/build_configs/platforms_list.gni)
412    @parameter: none
413    @return: none
414    '''
415
416    def _generate_platforms_list(self):
417        platforms_list_gni_file = os.path.join(self.config_output_dir,
418                                               "platforms_list.gni")
419        _platforms = set(self.build_platforms)
420        _gni_file_content = ['target_platform_list = [', '  "{}"'.format('",\n  "'.join(_platforms)), ']',
421                             'kits_platform_list = [', '  "{}",'.format('",\n  "'.join(_platforms))]
422        if 'phone' not in self.build_platforms:
423            _gni_file_content.append('  "phone"')
424        _gni_file_content.append(']')
425        write_file(platforms_list_gni_file, '\n'.join(_gni_file_content))
426        LogUtil.hb_info("generate platforms list to '{}'".format(
427            platforms_list_gni_file), mode=self.config.log_mode)
428
429    '''Description: output auto install part into a json file. \
430        (/out/${product_name}/build_configs/auto_install_parts.json)
431    @parameter: none
432    @return: none
433    '''
434
435    def _generate_auto_install_part(self):
436        parts_path_info = self.parts_config_info.get("parts_path_info")
437        auto_install_part_list = []
438        for part, path in parts_path_info.items():
439            if str(path).startswith("drivers/interface") or \
440                    str(path).startswith("third_party"):
441                auto_install_part_list.append(part)
442        auto_install_list_file = os.path.join(
443            self.config_output_dir, "auto_install_parts.json")
444        write_json_file(auto_install_list_file, auto_install_part_list)
445        LogUtil.hb_info("generate auto install part to '{}'".format(
446            auto_install_list_file), mode=self.config.log_mode)
447
448    '''Description: output src flag into a json file. \
449        (/out/${product_name}/build_configs/parts_src_flag.json)
450    @parameter: none
451    @return :none
452    '''
453
454    def _generate_src_flag(self):
455        parts_src_flag_file = os.path.join(self.config_output_dir,
456                                           "parts_src_flag.json")
457        write_json_file(parts_src_flag_file,
458                        self._get_parts_src_list(),
459                        check_changes=True)
460        LogUtil.hb_info(
461            "generated parts src flag to '{}/subsystem_info/parts_src_flag.json'".format(
462                self.config_output_dir), mode=self.config.log_mode)
463
464    '''Description: output build target list into a json file.\
465        (/out/${product_name}/build_configs/required_parts_targets_list.json)
466    @parameter: none
467    @return :none
468    '''
469
470    def _generate_required_parts_targets_list(self):
471        build_targets_list_file = os.path.join(self.config_output_dir,
472                                               "required_parts_targets_list.json")
473        write_json_file(build_targets_list_file,
474                        list(self.required_parts_targets.values()))
475        LogUtil.hb_info("generate build targets list file to '{}'".format(
476            build_targets_list_file), mode=self.config.log_mode)
477
478    '''Description: output build target info into a json file. \
479        (/out/${product_name}/build_configs/required_parts_targets.json)
480    @parameter: none
481    @return: none
482    '''
483
484    def _generate_required_parts_targets(self):
485        build_targets_info_file = os.path.join(self.config_output_dir,
486                                               "required_parts_targets.json")
487        write_json_file(build_targets_info_file, self.required_parts_targets)
488        LogUtil.hb_info("generate required parts targets to '{}'".format(
489            build_targets_info_file), mode=self.config.log_mode)
490
491    '''Description: output platforms part by src into a json file. \
492        (/out/${product_name}/build_configs/platforms_parts_by_src.json)
493    @parameter: none
494    @return :none
495    '''
496
497    def _generate_platforms_part_by_src(self):
498        platforms_parts_by_src = self._get_platforms_parts()
499        platforms_parts_by_src_file = os.path.join(self.source_root_dir,
500                                                   self.config_output_relpath,
501                                                   "platforms_parts_by_src.json")
502        write_json_file(platforms_parts_by_src_file,
503                        platforms_parts_by_src,
504                        check_changes=True)
505        LogUtil.hb_info("generated platforms parts by src to '{}'".format(
506            platforms_parts_by_src_file), mode=self.config.log_mode)
507
508    '''Description: output system configs info into 4 files:[
509        (/out/${product_name}/build_configs/subsystem_info/parts_list.gni),
510        (/out/${product_name}/build_configs/subsystem_info/inner_kits_list.gni),
511        (/out/${product_name}/build_configs/subsystem_info/system_kits_list.gni),
512        (/out/${product_name}/build_configs/subsystem_info/parts_test_list.gni),
513        (/out/${product_name}/build_configs/subsystem_info/BUILD.gn)]
514    @parameter: none
515    @return :none
516    '''
517
518    def _generate_target_gn(self):
519        generate_targets_gn.gen_targets_gn(self.required_parts_targets,
520                                           self.config_output_dir)
521
522    '''Description: output phony targets build file. \
523        (/out/${product_name}/build_configs/phony_target/BUILD.gn)
524    @parameter: none
525    @return :none
526    '''
527
528    def _generate_phony_targets_build_file(self):
529        generate_targets_gn.gen_phony_targets(self.required_phony_targets,
530                                              self.config_output_dir)
531
532    '''Description: output system configs info into 2 files:[
533        (/out/${product_name}/build_configs/subsystem_info/${platform}-stub/BUILG.gn),
534        (/out/${product_name}/build_configs/subsystem_info/${platform}-stub/zframework_stub_exists.gni)]
535    @parameter: none
536    @return :none
537    '''
538
539    def _generate_stub_targets(self):
540        generate_targets_gn.gen_stub_targets(
541            self.parts_config_info.get('parts_kits_info'),
542            self.target_platform_stubs,
543            self.config_output_dir)
544
545    '''Description: output system capabilities into a json file. \
546        (/out/${product_name}/build_configs/${platform}_system_capabilities.json)
547    @parameter: none
548    @return :none
549    '''
550
551    def _generate_system_capabilities(self):
552        for platform in self.build_platforms:
553            platform_parts = self.target_platform_parts.get(platform)
554            platform_capabilities = []
555            for _, origin in platform_parts.items():
556                # parts_info.get() might be None if the part is a binary package
557                all_parts_variants = self.parts_info.get(origin)
558                if all_parts_variants is None:
559                    continue
560                part = all_parts_variants[0]
561                if part.get('system_capabilities'):
562                    entry = part.get('system_capabilities')
563                    if len(entry) > 0:
564                        platform_capabilities.extend(entry)
565            platform_part_json_file = os.path.join(
566                self.config_output_dir, "{0}_system_capabilities.json".format(platform))
567            write_json_file(platform_part_json_file,
568                            sorted(platform_capabilities),
569                            check_changes=True)
570            LogUtil.hb_info(
571                "generated system capabilities to '{}/{}_system_capabilities.json'".format(
572                    self.config_output_dir, platform), mode=self.config.log_mode)
573
574    '''Description: output system configs info into three json files:[
575        (/out/${product_name}/build_configs/subsystem_info/subsystem_build_config.json),
576        (/out/${product_name}/build_configs/subsystem_info/src_subsystem_info.json),
577        (/out/${product_name}/build_configs/subsystem_info/no_src_subsystem_info.json)]
578    @parameter: none
579    @return :none
580    '''
581
582    def _generate_subsystem_configs(self):
583
584        # The function has been implemented in module util/loader/subsystem_info.py
585        LogUtil.hb_info(
586            "generated subsystem build config to '{}/subsystem_info/subsystem_build_config.json'".format(
587                self.config_output_dir), mode=self.config.log_mode)
588        LogUtil.hb_info(
589            "generated src subsystem info to '{}/subsystem_info/src_subsystem_info.json'".format(
590                self.config_output_dir), mode=self.config.log_mode)
591        LogUtil.hb_info(
592            "generated no src subsystem info to '{}/subsystem_info/no_src_subsystem_info.json'".format(
593                self.config_output_dir), mode=self.config.log_mode)
594
595    def _get_parts_by_platform(self) -> dict:
596        parts_info = {}
597        if 'phone' in self.target_platform_parts:
598            phone_parts_list = self.target_platform_parts.get('phone').keys()
599        else:
600            phone_parts_list = []
601        for _platform, _parts_info in self.target_platform_parts.items():
602            base_parts_list = []
603            curr_parts_list = []
604            for _real_name, _original_name in _parts_info.items():
605                if _real_name in phone_parts_list:
606                    base_parts_list.append(_real_name)
607                elif _original_name in phone_parts_list:
608                    base_parts_list.append(_real_name)
609                else:
610                    curr_parts_list.append(_real_name)
611            result_data = {
612                "base_parts_list": base_parts_list,
613                "curr_parts_list": curr_parts_list
614            }
615            parts_info[_platform] = result_data
616        return parts_info
617
618    def _get_platforms_all_parts(self) -> dict:
619        _dist_parts_variants = self._load_component_dist()
620        target_platform_parts = {}
621        all_parts = self._platforms_info.get('all_parts')
622        parts_variants = self.parts_config_info.get('parts_variants')
623        for _platform, _parts in all_parts.items():
624            if _platform not in self.build_platforms:
625                continue
626            part_name_info = {}
627            for part_def in _parts:
628                real_name, original_name = self._get_real_part_name(
629                    part_def, _platform, parts_variants)
630                if real_name is None:
631                    # find this from component_dist
632                    real_name, original_name = self._get_real_part_name(
633                        part_def, _platform, _dist_parts_variants)
634                if real_name is None:
635                    continue
636                part_name_info[real_name] = original_name
637            target_platform_parts[_platform] = part_name_info
638        return target_platform_parts
639
640    def _get_platforms_all_stubs(self) -> dict:
641        _dist_parts_variants = self._load_component_dist()
642        platform_stubs = {}
643        all_stubs = self._platforms_info.get('all_stubs')
644        parts_variants = self.parts_config_info.get('parts_variants')
645        for _platform, _part_names in all_stubs.items():
646            if _platform not in self.build_platforms:
647                continue
648            stub_parts_from_src = []
649            stub_parts_from_dist = []
650            for part_name in _part_names:
651                real_name, original_name = self._get_real_part_name(
652                    part_name, _platform, parts_variants)
653                # real_name=None means part_name doesn't exist in source tree,
654                # use binary in component_dist then.
655                if real_name is None:
656                    # find this from component_dist
657                    real_name, original_name = self._get_real_part_name(
658                        part_name, _platform, _dist_parts_variants)
659                    if real_name is None:
660                        continue
661                    else:
662                        stub_sources = os.path.join(
663                            self.source_root_dir,
664                            "component_dist/{}-{}/api_stubs/{}/stubs_sources_list.txt"  # noqa: E501
665                            .format(self.target_os, self.target_cpu, real_name))
666                        stub_parts_from_dist.append(
667                            '"{}"'.format(stub_sources))
668                else:
669                    stub_parts_from_src.append(real_name)
670            platform_stubs[_platform] = {
671                "src": stub_parts_from_src,
672                "dist": stub_parts_from_dist,
673            }
674        return platform_stubs
675
676    def _get_platforms_parts(self) -> dict:
677        platforms_parts = {}
678        src_parts_targets = self.parts_targets
679        src_all_parts = src_parts_targets.keys()
680        for _platform, _all_parts in self.target_platform_parts.items():
681            src_parts_list = []
682            no_src_parts_list = []
683            for _part in _all_parts.keys():
684                if _part in src_all_parts:
685                    src_parts_list.append(_part)
686                else:
687                    no_src_parts_list.append(_part)
688            _data = {
689                'src_parts': src_parts_list,
690                'no_src_parts': no_src_parts_list
691            }
692            platforms_parts[_platform] = _data
693        return platforms_parts
694
695    def _get_parts_src_list(self) -> list:
696        parts_name_map = {}
697        for _list in self.parts_info.values():
698            for _info in _list:
699                parts_name_map[_info.get('part_name')] = _info.get(
700                    'origin_part_name')
701        _src_set = set()
702        for _name in self.required_parts_targets.keys():
703            _origin_name = parts_name_map.get(_name)
704            if _origin_name is None:
705                continue
706            _src_set.add(_origin_name)
707        return list(_src_set)
708
709    def _get_required_build_targets(self) -> dict:
710        required_build_targets = {}
711        for _p_name, _info in self.parts_targets.items():
712            if _p_name not in self.required_parts_targets_list:
713                continue
714            required_build_targets[_p_name] = _info
715        return required_build_targets
716
717    def _get_required_phony_targets(self) -> dict:
718        required_build_targets = {}
719        for _p_name, _info in self.phony_targets.items():
720            if _p_name not in self.required_parts_targets_list:
721                continue
722            required_build_targets[_p_name] = _info
723        return required_build_targets
724
725    def _get_required_build_parts_list(self) -> list:
726        parts_set = set()
727        for _parts_list in self.target_platform_parts.values():
728            parts_set.update(_parts_list)
729        return list(parts_set)
730
731# util method
732
733    def _load_component_dist(self) -> dict:
734        _parts_variants_info = {}
735        _dir = "component_dist/{}-{}/packages_to_install".format(
736            self.target_os, self.target_cpu)
737        _file_name = "dist_parts_info.json"
738        _dist_parts_info_file = os.path.join(
739            self.source_root_dir, _dir, _file_name)
740        if not os.path.exists(_dist_parts_info_file):
741            # If the file does not exist, do nothing and return
742            return _parts_variants_info
743        _parts_info = read_json_file(_dist_parts_info_file)
744        if _parts_info is None:
745            raise Exception("read file '{}' failed.".format(
746                _dist_parts_info_file))
747        for _part_info in _parts_info:
748            origin_part_name = _part_info.get('origin_part_name')
749            if origin_part_name in _parts_variants_info:
750                variants = _parts_variants_info.get(origin_part_name)
751            else:
752                variants = []
753            _variant_name = _part_info.get('variant_name')
754            variants.append(_variant_name)
755            _parts_variants_info[origin_part_name] = variants
756        return _parts_variants_info
757
758    def _get_real_part_name(self, original_part_name: str, current_platform: str, parts_variants: dict):
759        part_info = parts_variants.get(original_part_name)
760        if part_info is None:
761            return None, None
762        if current_platform in part_info and current_platform != 'phone':
763            real_name = '{}_{}'.format(original_part_name, current_platform)
764        else:
765            real_name = original_part_name
766        return real_name, original_part_name
767
768    '''Description: called by _out_infos_for_testfwk, output information by platform
769    @parameter:none
770    @return :none
771    '''
772
773    def _output_infos_by_platform(self, part_name_infos: dict, parts_info_dict: dict):
774        required_parts = {}
775        subsystem_infos = {}
776        for part_name, origin_part_name in part_name_infos.items():
777            part_info = parts_info_dict.get(part_name)
778            if part_info is None:
779                continue
780            if origin_part_name != part_info.get('origin_part_name'):
781                raise Exception("part configuration is incorrect.")
782            required_parts[origin_part_name] = part_info
783            _subsystem_name = part_info.get('subsystem_name')
784            if _subsystem_name in subsystem_infos:
785                p_list = subsystem_infos.get(_subsystem_name)
786            else:
787                p_list = []
788            p_list.append(origin_part_name)
789            subsystem_infos[_subsystem_name] = p_list
790        result = {}
791        result['subsystem_infos'] = subsystem_infos
792        result['part_infos'] = required_parts
793        return result
794
795    def _execute_loader_args_display(self):
796        LogUtil.hb_info('Loading configuration file...')
797        args = []
798        args.append('platforms_config_file="{}"'.format(
799            self.platforms_config_file))
800        args.append('subsystem_config_file="{}"'.format(
801            self.subsystem_config_file))
802        args.append('example_subsystem_file="{}"'.format(
803            self.example_subsystem_file))
804        args.append('exclusion_modules_config_file="{}"'.format(
805            self.exclusion_modules_config_file))
806        args.append('source_root_dir="{}"'.format(self.source_root_dir))
807        args.append('gn_root_out_dir="{}"'.format(self.gn_root_out_dir))
808        args.append('build_platform_name={}'.format(self.build_platform_name))
809        args.append('build_xts={}'.format(self.build_xts))
810        args.append('load_test_config={}'.format(self.load_test_config))
811        args.append('target_os={}'.format(self.target_os))
812        args.append('target_cpu={}'.format(self.target_cpu))
813        args.append('os_level={}'.format(self.os_level))
814        args.append('ignore_api_check={}'.format(self.ignore_api_check))
815        args.append('scalable_build={}'.format(self.scalable_build))
816        args.append('skip_partlist_check={}'.format(self.skip_partlist_check))
817        LogUtil.write_log(self.config.log_path,
818                          'loader args:{}'.format(args), 'info')
819
820    def _override_components(self):
821        '''Description: Check whether there are components that need to be replaced, and if so,
822            replace the component configuration file bundle.json in subsystem_info and update
823            the component list generated by the preloader.
824        @parameter:none
825        @return :overrided_components
826        '''
827        parts_file = self.platforms_config_file.replace(
828            "platforms.build", "parts.json")
829        all_parts = read_json_file(parts_file)
830        if "parts" not in all_parts:
831            LogUtil.hb_warning("{} does not contain parts!".format(parts_file))
832            return {}
833        overrided = False
834        overrided_components = {}
835        all_parts = all_parts["parts"]
836        component_override_map = {}
837        all_component_override_map = {}
838        for subsystem_name, build_config_info in self._subsystem_info.items():
839            if "build_files" not in build_config_info:
840                continue
841
842            # scan all bundle.json or ohos.build files with named groups
843            for build_file in build_config_info["build_files"]:
844
845                # ohos.build does not support overrided components
846                if not build_file.endswith('bundle.json'):
847                    continue
848
849                # Only device or vendor components can do named groups extensions
850                if (not build_file.startswith(self.source_root_dir + 'device/')) \
851                        and (not build_file.startswith(self.source_root_dir + 'vendor/')):
852                    continue
853
854                # "subsystem", "name" and "override" is required
855                component = read_json_file(build_file).get("component")
856
857                if (not component) or (not all(key in component for key in ("subsystem", "name", "override"))):
858                    continue
859
860                full_part_name = f"{component.get('subsystem')}:{component.get('name')}"
861                if full_part_name not in all_parts:
862                    LogUtil.hb_warning("{} was not configured for this product: {}".format(
863                        build_file, full_part_name))
864                    continue
865
866                if self._override_one_component(self._subsystem_info, component, build_file, all_parts, overrided_components, component_override_map):
867                    overrided = True
868
869                if overrided:
870                    # Update parts.json and parts_config.json generated by preloader
871                    write_json_file(parts_file, {"parts": all_parts})
872                    parts_file = self.platforms_config_file.replace(
873                        "platforms.build", "parts_config.json")
874                    self._output_parts_config_json(all_parts, parts_file)
875                    all_component_override_map.update(component_override_map)
876        write_json_file(
877            f"{self.config_output_dir}/component_override_map.json", all_component_override_map)
878        return overrided_components
879
880    def _override_one_component(self, subsystem_info: dict, component: dict, build_file: str, all_parts: dict, overrided_components: dict, component_override_map: dict):
881        '''Description: Perform a replacement of a single component and return the component list update result.
882        @parameter:subsystem_info, component, build_file, all_parts, overrided_components
883        @return :True or False(Whether replacement has been performed)
884        '''
885        splits = component["override"].split(":")
886        if len(splits) != 2:
887            LogUtil.hb_warning(
888                "{} override value is invalid format. Skip override process".format(build_file))
889            return False
890        overrided_subsystem = splits[0]
891        overrided_component = splits[1]
892        if overrided_subsystem not in subsystem_info:
893            LogUtil.hb_warning(
894                "{} override invalid subsystem. Skip override process".format(build_file))
895            return False
896
897        founded_bundle = ""
898
899        for bundle in subsystem_info[overrided_subsystem]["build_files"]:
900            if not bundle.endswith('bundle.json'):
901                continue
902
903            bundle_obj = read_json_file(bundle)
904
905            if bundle_obj.get("component", {}).get("name") == overrided_component:
906                founded_bundle = bundle
907                break
908
909        if founded_bundle:
910            origin_component = read_json_file(build_file).get('component')
911            LogUtil.hb_warning(
912                f"You are trying to override \"{component['override']}\" with \"{origin_component.get('subsystem')}:{origin_component.get('name')}\". \nPlease ensure that the modules in \"{component['override']}\" only rely on the interfaces of other components through \"external_deps\"")
913
914            # replace bundle.json in subsystem_info's build_files
915            subsystem_info[overrided_subsystem]["build_files"].remove(
916                founded_bundle)
917
918            # Update parts.json generated by preloader, which means that new added components will not be installed
919            # Ensure that the overrided components will be installed
920            full_partname = f"{overrided_subsystem}:{overrided_component}"
921            if full_partname in all_parts:
922                all_parts.remove(full_partname)
923
924            overrided_components[f"{component['subsystem']}:{component['name']}"] = {
925                'subsystem': overrided_subsystem,
926                'partName': overrided_component
927            }
928            component_override_map[overrided_component] = component["name"]
929            return True
930        LogUtil.hb_warning(
931            "{}:{} is not configured in product, \new add component will be installed!".format(
932                overrided_subsystem, overrided_component))
933        return False
934
935    def _output_parts_config_json(self, all_parts: dict, output_file: dict):
936        '''Description: Update the parts list file generated by preloader
937        @parameter: all_parts, output_file
938        @return :none
939        '''
940        parts_config = {}
941        for part in all_parts:
942            part = part.replace(":", "_")
943            part = part.replace("-", "_")
944            part = part.replace(".", "_")
945            part = part.replace("/", "_")
946            parts_config[part] = True
947        write_json_file(output_file, parts_config)
948