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