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