1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2022 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 16import os 17import stat 18import json 19import argparse 20import re 21import pandas as pd 22from bundle_check.bundle_check_common import BundleCheckTools 23from bundle_check.warning_info import BCWarnInfo 24 25 26class OhosInfo: 27 g_root_path = BundleCheckTools.get_root_path() 28 g_ohos_version = BundleCheckTools.get_ohos_version(g_root_path) 29 30 31def check_all_bundle_json(path: str) -> list: 32 ''' 33 @func: 检查指定目录下所有 bundle.json 的文件规范。 34 ''' 35 36 if os.path.isabs(path): 37 target_path = path 38 else: 39 target_path = os.path.join(OhosInfo.g_root_path, os.path.normpath(path)) 40 41 cur_path = os.getcwd() 42 os.chdir(target_path) 43 44 all_bundle = get_all_bundle_json() 45 all_error = [] 46 47 for bundle_json_path in all_bundle: 48 bundle_path = bundle_json_path.strip() 49 bundle = BundleJson(bundle_path) 50 bundle_error = bundle.check() 51 52 subsystem_name = bundle.subsystem_name 53 component_name = bundle.component_name 54 if len(subsystem_name) == 0: 55 subsystem_name = "Unknow" 56 if len(component_name) == 0: 57 component_name = "Unknow" 58 59 if not bundle_error: 60 continue 61 for item in bundle_error: 62 item['rule'] = BCWarnInfo.CHECK_RULE_2_1 63 item['path'] = bundle_path 64 item['component'] = component_name 65 item['subsystem'] = subsystem_name 66 all_error.extend(bundle_error) 67 count = len(all_error) 68 69 print('-------------------------------') 70 print('Bundle.json check successfully!') 71 print('There are {} issues in total'.format(count)) 72 print('-------------------------------') 73 os.chdir(cur_path) 74 return all_error 75 76 77def get_all_bundle_json(path: str = '.') -> list: 78 ''' 79 @func: 获取所有源码工程中所有 bundle.json 文件。 80 ''' 81 exclude_list = [ 82 r'"./out/*"', 83 r'"./.repo/*"' 84 ] 85 cmd = "find {} -name {}".format(path, "bundle.json") 86 for i in exclude_list: 87 cmd += " ! -path {}".format(i) 88 with os.popen(cmd) as input_cmd: 89 bundle_josn_list = input_cmd.readlines() 90 return bundle_josn_list 91 92 93class BundlesCheck: 94 '''导出全量检查的结果。''' 95 96 @staticmethod 97 def to_json(all_errors: dict, 98 output_path: str = '.', 99 output_name: str = 'all_bundle_error.json'): 100 '''@func: 导出所有错误到 json 格式文件中。''' 101 all_errors = check_all_bundle_json(OhosInfo.g_root_path) 102 all_error_json = json.dumps(all_errors, 103 indent=4, 104 ensure_ascii=False, 105 separators=(', ', ': ')) 106 out_path = os.path.normpath(output_path) + '/' + output_name 107 108 flags = os.O_WRONLY | os.O_CREAT 109 modes = stat.S_IWUSR | stat.S_IRUSR 110 with os.fdopen(os.open(out_path, flags, modes), 'w') as file: 111 file.write(all_error_json) 112 print("Please check " + out_path) 113 114 @staticmethod 115 def to_df(path: str = None) -> pd.DataFrame: 116 '''将所有错误的 dict 数据类型转为 pd.DataFrame 类型。''' 117 if path is None: 118 path = OhosInfo.g_root_path 119 else: 120 path = os.path.join(OhosInfo.g_root_path, path) 121 all_errors = check_all_bundle_json(path) 122 columns = ['子系统', '部件', '文件', '违反规则', '详细', '说明'] 123 errors_list = [] 124 for item in all_errors: 125 error_temp = [ 126 item['subsystem'], 127 item['component'], 128 item['path'], 129 item['rule'], 130 "line" + str(item['line']) + ": " + item['contents'], 131 item['description'] 132 ] 133 errors_list.append(error_temp) 134 ret = pd.DataFrame(errors_list, columns=columns) 135 return ret 136 137 @staticmethod 138 def to_excel(output_path: str = '.', 139 output_name: str = 'all_bundle_error.xlsx'): 140 ''' 141 @func: 导出所有错误到 excel 格式文件中。 142 ''' 143 err_df = BundlesCheck.to_df() 144 outpath = os.path.normpath(output_path) + '/' + output_name 145 err_df.to_excel(outpath, index=None) 146 print('Please check ' + outpath) 147 148 149class BundleJson(object): 150 '''以 bundle.josn 路径来初始化的对象,包含关于该 bundle.josn 的一些属性和操作。 151 @var: 152 - ``__all_errors`` : 表示该文件的所有错误列表。 153 - ``__json`` : 表示将该 josn 文件转为 dict 类型后的内容。 154 - ``__lines`` : 表示将该 josn 文件转为 list 类型后的内容。 155 156 @method: 157 - ``component_name()`` : 返回该 bundle.json 所在部件名。 158 - ``subsystem_name()`` : 返回该 bundle.json 所在子系统名。 159 - ``readlines()`` : 返回该 bundle.json 以每一行内容为元素的 list。 160 - ``get_line_number(s)`` : 返回 s 字符串在该 bundle.josn 中的行号,未知则返回 0。 161 - ``check()`` : 静态检查该 bundle.json,返回错误告警 list。 162 ''' 163 164 def __init__(self, path: str) -> None: 165 self.__all_errors = [] # 该文件的所有错误列表 166 self.__json = {} # 将该 josn 文件转为字典类型内容 167 self.__lines = [] # 将该 josn 文件转为列表类型内容 168 with open(path, 'r') as file: 169 try: 170 self.__json = json.load(file) 171 except json.decoder.JSONDecodeError as error: 172 raise ValueError("'" + path + "'" + " is not a json file.") 173 with open(path, 'r') as file: 174 self.__lines = file.readlines() 175 176 @property 177 def component_name(self) -> str: 178 return self.__json.get('component').get('name') 179 180 @property 181 def subsystem_name(self) -> str: # 目前存在为空的情况 182 return self.__json.get('component').get('subsystem') 183 184 def readlines(self) -> list: 185 return self.__lines 186 187 def get_line_number(self, string) -> int: 188 ''' 189 @func: 获取指定字符串所在行号。 190 ''' 191 line_num = 0 192 for line in self.__lines: 193 line_num += 1 194 if string in line: 195 return line_num 196 return 0 197 198 def check(self) -> list: 199 ''' 200 @func: 检查该 bundle.json 规范。 201 @note: 去除检查 version 字段。 202 ''' 203 err_name = self.check_name() 204 err_segment = self.check_segment() 205 err_component = self.check_component() 206 if err_name: 207 self.__all_errors.append(err_name) 208 if err_segment: 209 self.__all_errors.extend(err_segment) 210 if err_component: 211 self.__all_errors.extend(err_component) 212 213 return self.__all_errors 214 215 # name 216 def check_name(self) -> dict: 217 bundle_error = dict(line=0, contents='"name"') 218 219 if 'name' not in self.__json: 220 bundle_error["description"] = BCWarnInfo.NAME_NO_FIELD 221 return bundle_error 222 223 name = self.__json['name'] 224 bundle_error["line"] = self.get_line_number('"name"') 225 if not name: # 为空 226 bundle_error["description"] = BCWarnInfo.NAME_EMPTY 227 return bundle_error 228 229 bundle_error["description"] = BCWarnInfo.NAME_FORMAT_ERROR + \ 230 BCWarnInfo.COMPONENT_NAME_FROMAT + \ 231 BCWarnInfo.COMPONENT_NAME_FROMAT_LEN 232 match = BundleCheckTools.match_bundle_full_name(name) 233 if not match: 234 return bundle_error 235 match = BundleCheckTools.match_unix_like_name(name.split('/')[1]) 236 if not match: 237 return bundle_error 238 239 return dict() 240 241 # version 242 def check_version(self) -> dict: 243 bundle_error = dict(line=0, contents='version') 244 245 if 'version' not in self.__json: 246 bundle_error["description"] = BCWarnInfo.VERSION_NO_FIELD 247 return bundle_error 248 249 bundle_error["line"] = self.get_line_number('"version": ') 250 if len(self.__json['version']) < 3: # example 3.1 251 bundle_error["description"] = BCWarnInfo.VERSION_ERROR 252 return bundle_error 253 254 if self.__json['version'] != OhosInfo.g_ohos_version: 255 bundle_error['description'] = BCWarnInfo.VERSION_ERROR + \ 256 ' current ohos version is: ' + OhosInfo.g_ohos_version 257 return bundle_error 258 return dict() 259 260 # segment 261 def check_segment(self) -> list: 262 bundle_error_segment = [] 263 bundle_error = dict(line=0, contents='"segment"') 264 265 if 'segment' not in self.__json: 266 bundle_error["description"] = BCWarnInfo.SEGMENT_NO_FIELD 267 bundle_error_segment.append(bundle_error) 268 return bundle_error_segment 269 270 bundle_error["line"] = self.get_line_number('"segment":') 271 if 'destPath' not in self.__json['segment']: 272 bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_NO_FIELD 273 bundle_error_segment.append(bundle_error) 274 return bundle_error_segment 275 276 path = self.__json['segment']['destPath'] 277 bundle_error["line"] = self.get_line_number('"destPath":') 278 bundle_error["contents"] = '"segment:destPath"' 279 if not path: 280 bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_EMPTY 281 bundle_error_segment.append(bundle_error) 282 return bundle_error_segment 283 284 if isinstance(path, str): 285 bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_UNIQUE 286 bundle_error_segment.append(bundle_error) 287 return bundle_error_segment 288 289 if os.path.isabs(path): 290 bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_ABS 291 bundle_error_segment.append(bundle_error) 292 return bundle_error_segment 293 294 return bundle_error_segment 295 296 # component 297 def check_component(self) -> list: 298 bundle_error_component = [] 299 300 if 'component' not in self.__json: 301 bundle_error = dict(line=0, contents='"component"', 302 description=BCWarnInfo.COMPONENT_NO_FIELD) 303 bundle_error_component.append(bundle_error) 304 return bundle_error_component 305 306 component = self.__json.get('component') 307 component_line = self.get_line_number('"component":') 308 self._check_component_name(component, component_line, bundle_error_component) 309 self._check_component_subsystem(component, component_line, bundle_error_component) 310 self._check_component_syscap(component, bundle_error_component) 311 self._check_component_ast(component, component_line, bundle_error_component) 312 self._check_component_rom(component, component_line, bundle_error_component) 313 self._check_component_ram(component, component_line, bundle_error_component) 314 self._check_component_deps(component, component_line, bundle_error_component) 315 316 return bundle_error_component 317 318 # component name 319 320 def _check_component_name(self, component: dict, component_line: int, bundle_error_component: list): 321 if 'name' not in component: 322 bundle_error = dict(line=component_line, 323 contents='"component"', 324 description=BCWarnInfo.COMPONENT_NAME_NO_FIELD) 325 bundle_error_component.append(bundle_error) 326 else: 327 bundle_error = dict(line=component_line + 1, 328 contents='"component:name"') # 同名 "name" 暂用 "component" 行号+1 329 if not component['name']: 330 bundle_error["description"] = BCWarnInfo.COMPONENT_NAME_EMPTY 331 bundle_error_component.append(bundle_error) 332 elif 'name' in self.__json and '/' in self.__json['name']: 333 if component['name'] != self.__json['name'].split('/')[1]: 334 bundle_error["description"] = BCWarnInfo.COMPONENT_NAME_VERACITY 335 bundle_error_component.append(bundle_error) 336 337 # component subsystem 338 339 def _check_component_subsystem(self, component: dict, component_line: int, 340 bundle_error_component: list): 341 if 'subsystem' not in component: 342 bundle_error = dict(line=component_line, 343 contents="component", 344 description=BCWarnInfo.COMPONENT_SUBSYSTEM_NO_FIELD) 345 bundle_error_component.append(bundle_error) 346 else: 347 bundle_error = dict(line=self.get_line_number('"subsystem":'), 348 contents='"component:subsystem"') 349 if not component['subsystem']: 350 bundle_error["description"] = BCWarnInfo.COMPONENT_SUBSYSTEM_EMPTY 351 bundle_error_component.append(bundle_error) 352 elif not BundleCheckTools.is_all_lower(component['subsystem']): 353 bundle_error["description"] = BCWarnInfo.COMPONENT_SUBSYSTEM_LOWCASE 354 bundle_error_component.append(bundle_error) 355 356 # component syscap 可选且可以为空 357 358 def _check_component_syscap(self, component: dict, bundle_error_component: list): 359 if 'syscap' not in component: 360 pass 361 elif component['syscap']: 362 bundle_error = dict(line=self.get_line_number('"syscap":'), 363 contents='"component:syscap"') 364 err = [] # 收集所有告警 365 for i in component['syscap']: 366 # syscap string empty 367 if not i: 368 err.append(BCWarnInfo.COMPONENT_SYSCAP_STRING_EMPTY) 369 continue 370 match = re.match(r'^SystemCapability(\.[A-Z][a-zA-Z]{1,63}){2,6}$', i) 371 if not match: 372 err.append(BCWarnInfo.COMPONENT_SYSCAP_STRING_FORMAT_ERROR) 373 errs = list(set(err)) # 去重告警 374 if errs: 375 bundle_error["description"] = str(errs) 376 bundle_error_component.append(bundle_error) 377 378 # component adapted_system_type 379 380 def _check_component_ast(self, component: dict, component_line: int, bundle_error_component: list): 381 if 'adapted_system_type' not in component: 382 bundle_error = dict(line=component_line, contents='"component"', 383 description=BCWarnInfo.COMPONENT_AST_NO_FIELD) 384 bundle_error_component.append(bundle_error) 385 return 386 387 bundle_error = dict(line=self.get_line_number('"adapted_system_type":'), 388 contents='"component:adapted_system_type"') 389 ast = component["adapted_system_type"] 390 if not ast: 391 bundle_error["description"] = BCWarnInfo.COMPONENT_AST_EMPTY 392 bundle_error_component.append(bundle_error) 393 return 394 395 type_set = tuple(set(ast)) 396 if len(ast) > 3 or len(type_set) != len(ast): 397 bundle_error["description"] = BCWarnInfo.COMPONENT_AST_NO_REP 398 bundle_error_component.append(bundle_error) 399 return 400 401 all_type_list = ["mini", "small", "standard"] 402 # 不符合要求的 type 403 error_type = [i for i in ast if i not in all_type_list] 404 if error_type: 405 bundle_error["description"] = BCWarnInfo.COMPONENT_AST_NO_REP 406 bundle_error_component.append(bundle_error) 407 return 408 409 # component rom 410 411 def _check_component_rom(self, component: dict, component_line: int, bundle_error_component: list): 412 if 'rom' not in component: 413 bundle_error = dict(line=component_line, contents='"component:rom"', 414 description=BCWarnInfo.COMPONENT_ROM_NO_FIELD) 415 bundle_error_component.append(bundle_error) 416 elif not component["rom"]: 417 bundle_error = dict(line=self.get_line_number('"rom":'), 418 contents='"component:rom"', 419 description=BCWarnInfo.COMPONENT_ROM_EMPTY) 420 bundle_error_component.append(bundle_error) 421 else: 422 bundle_error = dict(line=self.get_line_number('"rom":'), 423 contents='"component:rom"') 424 num, unit = BundleCheckTools.split_by_unit(component["rom"]) 425 if num < 0: 426 bundle_error["description"] = BCWarnInfo.COMPONENT_ROM_SIZE_ERROR # 非数值或小于0 427 bundle_error_component.append(bundle_error) 428 elif unit: 429 unit_list = ["KB", "KByte", "MByte", "MB"] 430 if unit not in unit_list: 431 bundle_error["description"] = BCWarnInfo.COMPONENT_ROM_UNIT_ERROR # 单位有误 432 bundle_error_component.append(bundle_error) 433 434 # component ram 435 436 def _check_component_ram(self, component: dict, component_line: int, bundle_error_component: list): 437 if 'ram' not in component: 438 bundle_error = dict(line=component_line, contents='"component:ram"', 439 description=BCWarnInfo.COMPONENT_RAM_NO_FIELD) 440 bundle_error_component.append(bundle_error) 441 elif not component["ram"]: 442 bundle_error = dict(line=self.get_line_number('"ram":'), 443 contents='"component:ram"', 444 description=BCWarnInfo.COMPONENT_RAM_EMPTY) 445 bundle_error_component.append(bundle_error) 446 else: 447 bundle_error = dict(line=self.get_line_number('"ram":'), 448 contents='"component:ram"') 449 num, unit = BundleCheckTools.split_by_unit(component["ram"]) 450 if num <= 0: 451 bundle_error["description"] = BCWarnInfo.COMPONENT_RAM_SIZE_ERROR # 非数值或小于0 452 bundle_error_component.append(bundle_error) 453 elif unit: 454 unit_list = ["KB", "KByte", "MByte", "MB"] 455 if unit not in unit_list: 456 bundle_error["description"] = BCWarnInfo.COMPONENT_RAM_UNIT_ERROR # 单位有误 457 bundle_error_component.append(bundle_error) 458 459 # component deps 460 461 def _check_component_deps(self, component: dict, component_line: int, bundle_error_component: list): 462 if 'deps' not in component: 463 bundle_error = dict(line=component_line, contents='"component:deps"', 464 description=BCWarnInfo.COMPONENT_DEPS_NO_FIELD) 465 bundle_error_component.append(bundle_error) 466 else: 467 pass 468 469 470def parse_args(): 471 parser = argparse.ArgumentParser() 472 # exclusive output format 473 ex_format = parser.add_mutually_exclusive_group() 474 ex_format.add_argument("--xlsx", help="output format: xls(default).", 475 action="store_true") 476 ex_format.add_argument("--json", help="output format: json.", 477 action="store_true") 478 # exclusive input 479 ex_input = parser.add_mutually_exclusive_group() 480 ex_input.add_argument("-P", "--project", help="project root path.", type=str) 481 ex_input.add_argument("-p", "--path", help="bundle.json path list.", nargs='+') 482 # output path 483 parser.add_argument("-o", "--output", help="ouput path.") 484 args = parser.parse_args() 485 486 export_path = '.' 487 if args.output: 488 export_path = args.output 489 490 if args.project: 491 if not BundleCheckTools.is_project(args.project): 492 print("'" + args.project + "' is not a oopeharmony project.") 493 exit(1) 494 495 if args.json: 496 BundlesCheck.to_json(export_path) 497 else: 498 BundlesCheck.to_excel(export_path) 499 elif args.path: 500 bundle_list_error = {} 501 for bundle_json_path in args.path: 502 bundle = BundleJson(bundle_json_path) 503 error_field = bundle.check() 504 if error_field: 505 bundle_list_error[bundle_json_path] = \ 506 dict([(BCWarnInfo.CHECK_RULE_2_1, error_field)]) 507 # temp 508 test_json = json.dumps(bundle_list_error, 509 indent=4, separators=(', ', ': '), 510 ensure_ascii=False) 511 print(test_json) 512 else: 513 print("use '-h' get help.") 514 515 516if __name__ == '__main__': 517 parse_args() 518