1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2023 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. 15import logging 16import os 17import json 18import argparse 19import stat 20 21 22def get_args(): 23 parser = argparse.ArgumentParser(add_help=True) 24 parser.add_argument( 25 "-p", 26 "--project_path", 27 default=r"", 28 type=str, 29 help="root path of project. default: ./", 30 ) 31 args = parser.parse_args() 32 return args 33 34 35def dict_to_json(output_path: str, syscaps_dict: dict): 36 """ 37 output diff product syscaps json to output path 38 :param output_path: 39 :param syscaps_dict: 40 :return: 41 """ 42 print("start generate syscap json...") 43 flags = os.O_WRONLY | os.O_CREAT 44 modes = stat.S_IWUSR | stat.S_IRUSR 45 for product_name, syscaps_list in syscaps_dict.items(): 46 filename = os.path.join(output_path, f'{product_name}.json') 47 with os.fdopen(os.open(filename, flags, modes), 'w') as f: 48 json.dump({'SysCaps': syscaps_list}, f) 49 print("end...") 50 51 52def check_syscap(syscap_str: str): 53 syscap = syscap_str.split(' = ') 54 if syscap[-1].lower() == 'false': 55 return False 56 else: 57 return syscap[0] 58 59 60def bundle_syscap_list_handler(bundle_syscap_list: list, component_syscaps_list: list): 61 """ 62 check syscap 63 :param bundle_syscap_list: 64 :param component_syscaps_list: 65 :return: 66 """ 67 for component_syscap in component_syscaps_list: 68 component_syscap = check_syscap(component_syscap) 69 if component_syscap: 70 bundle_syscap_list.append(component_syscap) 71 return bundle_syscap_list 72 73 74def read_json_file(bundle_json_path: str): 75 bundle_syscap_list = list() 76 error_list = dict() 77 try: 78 with open(bundle_json_path, 'r', encoding='utf-8') as f: 79 bundle_data = json.load(f) 80 component_data = bundle_data.get("component") 81 component_syscaps_list = component_data.get("syscap") 82 if component_syscaps_list: 83 bundle_syscap_list = bundle_syscap_list_handler(bundle_syscap_list, component_syscaps_list) 84 except FileNotFoundError as e: 85 error_list[bundle_json_path] = str(e) 86 except Exception as e: 87 error_list[bundle_json_path] = str(e) 88 return bundle_syscap_list, error_list 89 90 91def get_all_components_path(components_file_path: str): 92 try: 93 with open(components_file_path, 'r', encoding='utf-8') as f: 94 path_dict = json.load(f) 95 return path_dict 96 except FileNotFoundError: 97 logging.error(r"PATH ERROR") 98 return {} 99 100 101def path_component_to_bundle(path: str) -> str: 102 bundle_json_path = os.path.join(path, 'bundle.json') 103 return bundle_json_path 104 105 106def handle_bundle_json_file(component_path_dict: dict): 107 """ 108 from product required part bundle.json to all products parts list 109 :param component_path_dict: 110 :return: all products parts list 111 """ 112 print("start collect syscap path...") 113 syscap_dict = dict() 114 errors_list = list() 115 for product_name, path_list in component_path_dict.items(): 116 bundles_list = list() 117 for path in path_list: 118 bundle_json_path = path_component_to_bundle(path) 119 bundle_syscap_list, error_list = read_json_file(bundle_json_path) 120 bundles_list.extend(bundle_syscap_list) 121 errors_list.extend(error_list) 122 syscap_dict.update({product_name: bundles_list}) 123 return syscap_dict, errors_list 124 125 126def format_component_path(component_path: str): 127 sep_list = ['\\', '/'] 128 sep_list.remove(os.sep) 129 component_path = component_path.replace(sep_list[0], os.sep) 130 return component_path 131 132 133def traversal_path(parts_path_info: dict, project_path: str, product_define_dict): 134 component_path_dict = dict() 135 for product_name, component_name_list in product_define_dict.items(): 136 component_paths = list() 137 for component_name in component_name_list: 138 component_relpath = parts_path_info.get(component_name) 139 if component_relpath: 140 component_path = os.path.join(project_path, component_relpath) 141 component_path = format_component_path(component_path) 142 component_paths.append(component_path) 143 else: 144 logging.error(f'can\'t find component_name : {component_name}') 145 component_path_dict.update({product_name: component_paths}) 146 return component_path_dict 147 148 149def collect_all_product_component_syscap_dict(parts_path_info: dict, project_path: str, product_define_dict): 150 """ 151 get all syscap to dict 152 :param parts_path_info: 153 :param project_path: 154 :param product_define_dict: 155 :return: 156 """ 157 if parts_path_info: 158 print("start collect component path...") 159 component_path_dict = traversal_path(parts_path_info, project_path, product_define_dict) 160 syscap_dict, errors_list = handle_bundle_json_file(component_path_dict) 161 return syscap_dict, errors_list 162 else: 163 return 0, 0 164 165 166def get_subsystem_info(subsystem_config_file, source_root_dir): 167 """ 168 get subsystem name and subsystem path from oh/build/subsystem_config.json 169 :param subsystem_config_file: subsystem_config_file path 170 :param source_root_dir: oh project path 171 :return: subsystem name and subsystem path 172 """ 173 subsystem_configs = scan(subsystem_config_file, source_root_dir) 174 _all_components_path = [] 175 for _, value in subsystem_configs.get('subsystem').items(): 176 for i in value.get('build_files'): 177 _all_components_path.append(i) 178 return subsystem_configs.get('subsystem') 179 180 181def _check_path_prefix(paths): 182 allow_path_prefix = ['vendor', 'device'] 183 result = list( 184 filter(lambda x: x is False, 185 map(lambda p: p.split('/')[0] in allow_path_prefix, paths))) 186 return len(result) <= 1 187 188 189def traversal_files(subsystem_path, _files): 190 for item in os.scandir(subsystem_path): 191 if is_symlik(item.path): 192 continue 193 elif item.is_file() and item.name == 'ohos.build': 194 _files.append(item.path) 195 elif item.is_file() and item.name == 'bundle.json': 196 _files.append(item.path) 197 elif item.is_dir(): 198 traversal_files(item, _files) 199 return _files 200 201 202def get_file_type(file_path): 203 if os.path.islink(file_path): 204 return 'symlink' 205 elif os.path.isfile(file_path): 206 return 'file' 207 elif os.path.isdir(file_path): 208 return 'directory' 209 else: 210 return 'unknown' 211 212 213def is_symlik(file_path): 214 file_type = get_file_type(file_path) 215 if file_type == 'symlink': 216 link_target = os.readlink(file_path) 217 return link_target != file_type 218 return False 219 220 221def _scan_build_file(subsystem_path): 222 _files = [] 223 _bundle_files = [] 224 try: 225 _files = traversal_files(subsystem_path, _files) 226 except FileNotFoundError: 227 print(f"read file {subsystem_path} failed.") 228 return _files 229 230 231def scan(subsystem_config_file, source_root_dir): 232 subsystem_infos = _read_config(subsystem_config_file) 233 _default_subsystem = {"build": "build"} 234 subsystem_infos.update(_default_subsystem) 235 no_src_subsystem = {} 236 _build_configs = {} 237 for key, val in subsystem_infos.items(): 238 _all_build_config_files = [] 239 if not isinstance(val, list): 240 val = [val] 241 else: 242 if not _check_path_prefix(val): 243 raise Exception("subsystem '{}' path configuration is incorrect.".format(key), "2013") 244 _info = {'path': val} 245 for _path in val: 246 _subsystem_path = os.path.join(source_root_dir, _path) 247 _build_config_files = _scan_build_file(_subsystem_path) 248 _all_build_config_files.extend(_build_config_files) 249 if _all_build_config_files: 250 _info['build_files'] = _all_build_config_files 251 _build_configs[key] = _info 252 else: 253 no_src_subsystem[key] = val 254 255 scan_result = { 256 'source_path': source_root_dir, 257 'subsystem': _build_configs, 258 } 259 print('subsystem config scan completed') 260 return scan_result 261 262 263def _read_config(subsystem_config_file): 264 if not os.path.exists(subsystem_config_file): 265 raise Exception("config file '{}' doesn't exist.".format(subsystem_config_file), "2013") 266 subsystem_config = _read_json_file(subsystem_config_file) 267 if subsystem_config is None: 268 raise Exception("read file '{}' failed.".format(subsystem_config_file), "2013") 269 270 subsystem_info = {} 271 for key, val in subsystem_config.items(): 272 if 'path' not in val: 273 raise Exception("subsystem '{}' not config path.".format(key), "2013") 274 subsystem_info[key] = val.get('path') 275 return subsystem_info 276 277 278def read_build_file(ohos_build_file): 279 if not os.path.exists(ohos_build_file): 280 raise Exception("config file '{}' doesn't exist.".format(ohos_build_file), "2014") 281 subsystem_config = _read_json_file(ohos_build_file) 282 if not subsystem_config: 283 raise Exception("read file '{}' failed.".format(ohos_build_file), "2014") 284 return subsystem_config 285 286 287class BundlePartObj(object): 288 def __init__(self, bundle_config_file): 289 self._build_config_file = bundle_config_file 290 self._loading_config() 291 292 def _loading_config(self): 293 if not os.path.exists(self._build_config_file): 294 raise Exception("file '{}' doesn't exist.".format( 295 self._build_config_file), "2011") 296 self.bundle_info = _read_json_file(self._build_config_file) 297 if not self.bundle_info: 298 raise Exception("read file '{}' failed.".format( 299 self._build_config_file), "2011") 300 301 def to_ohos_build(self): 302 _component_info = self.bundle_info.get('component') 303 _subsystem_name = _component_info.get('subsystem') 304 _part_name = _component_info.get('name') 305 _bundle_build = _component_info.get('build') 306 _ohos_build_info = dict() 307 _ohos_build_info['subsystem'] = _subsystem_name 308 _part_info = {} 309 module_list = [] 310 if _component_info.get('build').__contains__('sub_component'): 311 _part_info['module_list'] = _component_info.get('build').get( 312 'sub_component') 313 elif _component_info.get('build').__contains__('modules'): 314 _part_info['module_list'] = _component_info.get( 315 'build').get('modules') 316 elif _component_info.get('build').__contains__('group_type'): 317 _module_groups = _component_info.get('build').get('group_type') 318 for _group_type, _module_list in _module_groups.items(): 319 _key = '{}:{}'.format(_subsystem_name, _part_name) 320 _part_info['module_list'] = module_list 321 if 'inner_kits' in _bundle_build: 322 _part_info['inner_kits'] = _bundle_build.get('inner_kits') 323 elif 'inner_api' in _bundle_build: 324 _part_info['inner_kits'] = _bundle_build.get('inner_api') 325 if 'features' in _component_info: 326 _part_info['feature_list'] = _component_info.get('features') 327 if 'syscap' in _component_info: 328 _part_info['system_capabilities'] = _component_info.get('syscap') 329 if 'hisysevent_config' in _component_info: 330 _part_info['hisysevent_config'] = _component_info.get( 331 'hisysevent_config') 332 _part_info['part_deps'] = _component_info.get('deps', {}) 333 _part_info['part_deps']['build_config_file'] = self._build_config_file 334 _ohos_build_info['parts'] = {_part_name: _part_info} 335 return _ohos_build_info 336 337 338class LoadBuildConfig(object): 339 """load build config file and parse configuration info.""" 340 341 def __init__(self, source_root_dir, subsystem_build_info, subsystem_name): 342 self._source_root_dir = source_root_dir 343 self._build_info = subsystem_build_info 344 self._is_load = False 345 self._parts_variants = {} 346 self._part_list = {} 347 self._part_targets_label = {} 348 self._subsystem_name = subsystem_name 349 self._parts_info_dict = {} 350 self._phony_targets = {} 351 self._parts_path_dict = {} 352 self._part_hisysevent_config = {} 353 self._parts_module_list = {} 354 self._parts_deps = {} 355 356 def _merge_build_config(self): 357 _build_files = self._build_info.get('build_files') 358 is_thirdparty_subsystem = False 359 if _build_files[0].startswith(self._source_root_dir + 'third_party'): 360 is_thirdparty_subsystem = True 361 subsystem_name = None 362 parts_info = {} 363 parts_path_dict = {} 364 for _build_file in _build_files: 365 if _build_file.endswith('bundle.json'): 366 bundle_part_obj = BundlePartObj(_build_file) 367 _parts_config = bundle_part_obj.to_ohos_build() 368 else: 369 _parts_config = read_build_file(_build_file) 370 _subsystem_name = _parts_config.get('subsystem') 371 if not is_thirdparty_subsystem and subsystem_name and _subsystem_name != subsystem_name: 372 raise Exception( 373 "subsystem name config incorrect in '{}'.".format( 374 _build_file), "2014") 375 subsystem_name = _subsystem_name 376 _curr_parts_info = _parts_config.get('parts') 377 for _pname in _curr_parts_info.keys(): 378 parts_path_dict[_pname] = os.path.relpath( 379 os.path.dirname(_build_file), self._source_root_dir) 380 parts_info.update(_curr_parts_info) 381 subsystem_config = dict() 382 subsystem_config['subsystem'] = subsystem_name 383 subsystem_config['parts'] = parts_info 384 return subsystem_config, parts_path_dict 385 386 def parse(self): 387 """parse part info from build config file.""" 388 if self._is_load: 389 return 390 subsystem_config, parts_path_dict = self._merge_build_config() 391 parts_config = subsystem_config.get('parts') 392 self._parts_module_list.update(parts_config) 393 self._parts_path_dict = parts_path_dict 394 self._is_load = True 395 396 def parts_path_info(self): 397 """parts to path info.""" 398 self.parse() 399 return self._parts_path_dict 400 401 def parts_info_filter(self, save_part): 402 if save_part is None: 403 raise Exception 404 self._parts_info_dict = { 405 key: value for key, value in self._parts_info_dict.items() if key in save_part} 406 407 408def get_parts_info(source_root_dir, subsystem_info, build_xts=False): 409 """ 410 get parts path info from subsystem info 411 :param source_root_dir: oh project path 412 :param subsystem_info: 413 :param build_xts: 414 :return: parts path info 415 """ 416 _phony_target = {} 417 _parts_path_info = {} 418 _parts_hisysevent_config = {} 419 _parts_modules_info = {} 420 _parts_deps = {} 421 for subsystem_name, build_config_info in subsystem_info.items(): 422 if not len(build_config_info.get("build_files")): 423 continue 424 build_loader = LoadBuildConfig(source_root_dir, build_config_info, subsystem_name) 425 if subsystem_name == 'xts' and build_xts is False: 426 xts_device_attest_name = ['device_attest_lite', 'device_attest'] 427 build_loader.parse() 428 build_loader.parts_info_filter(xts_device_attest_name) 429 _parts_path_info.update(build_loader.parts_path_info()) 430 return _parts_path_info 431 432 433def _read_json_file(input_file): 434 if not os.path.exists(input_file): 435 print("file '{}' doesn't exist.".format(input_file)) 436 return {} 437 try: 438 with open(input_file, 'r') as input_f: 439 data = json.load(input_f) 440 return data 441 except json.decoder.JSONDecodeError: 442 print("The file '{}' format is incorrect.".format(input_file)) 443 raise 444 except Exception: 445 print("read file '{}' failed.".format(input_file)) 446 raise 447 448 449def get_product_define_path(source_root_dir): 450 return os.path.join(source_root_dir, 'productdefine', 'common', 'inherit') 451 452 453def components_list_handler(product_file_json): 454 components_list = list() 455 for subsystems in product_file_json.get('subsystems'): 456 for components in subsystems.get('components'): 457 components_list.append(components.get('component')) 458 459 return components_list 460 461 462def product_component_handler(product_file, product_file_path): 463 all_components_dict = dict() 464 components_list = list() 465 try: 466 with open(product_file_path, 'r', encoding='utf-8') as f: 467 product_file_json = json.load(f) 468 components_list = components_list_handler(product_file_json) 469 except FileNotFoundError: 470 print(f"read file {product_file_path} failed.") 471 all_components_dict.update({product_file.split('.')[0]: components_list}) 472 return all_components_dict 473 474 475def collect_all_product_component(product_file_dict: dict): 476 all_components_dict = dict() 477 for product_file, product_file_path in product_file_dict.items(): 478 product_components_dict = product_component_handler(product_file, product_file_path) 479 all_components_dict.update(product_components_dict) 480 return all_components_dict 481 482 483def get_product_define_dict(source_root_dir): 484 product_define_path = get_product_define_path(source_root_dir) 485 product_file_dict = dict() 486 for file in os.scandir(product_define_path): 487 if file.name.split('.')[-1] == 'json': 488 product_file_dict.update({file.name: os.path.join(product_define_path, file.name)}) 489 product_define_dict = collect_all_product_component(product_file_dict) 490 return product_define_dict 491 492 493def output_path_handler(project_path): 494 output_path = os.path.join(project_path, 'interface', 'sdk-js', 'api', 'device-define-common') 495 folder = os.path.exists(output_path) 496 # 多线程创建文件夹问题 497 if not folder: 498 os.makedirs(output_path, exist_ok=True) 499 return output_path 500 501 502def project_path_handler(project_path): 503 if not project_path: 504 project_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 505 return project_path 506 507 508def main(): 509 logging.basicConfig(level=logging.INFO) 510 args = get_args() 511 project_path = args.project_path 512 project_path = project_path_handler(project_path) 513 output_path = output_path_handler(project_path) 514 subsystem_config_file = os.path.join(project_path, 'build', 'subsystem_config.json') 515 product_define_dict = get_product_define_dict(project_path) 516 _subsystem_info = get_subsystem_info(subsystem_config_file, project_path) 517 _parts_path_info = get_parts_info(project_path, _subsystem_info) 518 syscap_dict, errors_list = collect_all_product_component_syscap_dict(_parts_path_info, project_path, 519 product_define_dict) 520 if syscap_dict: 521 dict_to_json(output_path, syscap_dict) 522 523 524if __name__ == "__main__": 525 main() 526