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