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 json 18import re 19import argparse 20from prettytable import PrettyTable, ALL 21 22 23def get_args(): 24 parser = argparse.ArgumentParser(add_help=True) 25 parser.add_argument( 26 "-p", 27 "--project_path", 28 default=r"./", 29 type=str, 30 help="root path of project. default: ./", 31 ) 32 parser.add_argument( 33 "-t", 34 "--check_target", 35 type=str, 36 choices=["component_codec", "component_sdk", "sdk_codec"], 37 required=True, 38 help="the target to be compared", 39 ) 40 parser.add_argument( 41 "-b", 42 "--bundles", 43 nargs="*", 44 type=str, 45 help="option take effect only when the check_target is component_codec. allow multiple json file. " 46 "default: all bundle.json file", 47 ) 48 args = parser.parse_args() 49 return args 50 51 52def list_to_multiline(target_list: list): 53 return str(target_list).lstrip("[").rstrip("]").replace(", ", "\n") 54 55 56def convert_set_to_sorted_list(target_set) -> list: 57 return sorted(list(target_set)) 58 59 60def add_dict_as_table_row(f_table: PrettyTable, d_dict: dict, out_converter=list_to_multiline) -> None: 61 s_keys = convert_set_to_sorted_list(d_dict.keys()) 62 for i, k in enumerate(s_keys): 63 f_table.add_row([i + 1, k, out_converter(convert_set_to_sorted_list(d_dict.get(k)))]) 64 65 66def bundle_syscap_post_handler(syscap: str) -> str: 67 return syscap.split('=')[0].strip() 68 69 70def read_value_from_json(filepath: str, 71 key_hierarchy: tuple, 72 result_dict: dict, 73 post_handler=None 74 ) -> None: 75 if os.path.exists(filepath) is False: 76 print('error: file "{}" not exist.'.format(filepath)) 77 return 78 if not os.path.isfile(filepath): 79 print('error: "{}" is not a file.') 80 return 81 with open(filepath, 'r', encoding='utf-8') as f: 82 data = json.load(f) 83 for key in key_hierarchy: 84 try: 85 data = data[key] 86 except KeyError: 87 return 88 finally: 89 pass 90 data = [post_handler(x) for x in data if len(x) != 0 and not x.isspace()] 91 if len(data) != 0: 92 result_dict[filepath] = data 93 94 95def collect_syscap_from_codec(filepath: str, pattern: str = r'{"(.*)"') -> tuple: 96 """ 97 从syscap_define.h收集syscap 98 :param filepath: 文件的路径 99 :param pattern: 匹配syscap的规则 100 :return: syscap_define.h中声明的syscap集合 101 """ 102 array_syscap_set = set() 103 array_syscap_dict = dict() 104 ptrn = re.compile(pattern) 105 with open(filepath, "r") as f: 106 content = f.read() 107 array_syscap_set.update(re.findall(ptrn, content)) 108 array_syscap_dict[filepath] = list() 109 for v in array_syscap_set: 110 array_syscap_dict.get(filepath).append(v) 111 return array_syscap_set, array_syscap_dict 112 113 114def collect_syscap_from_component(project_path: str, 115 black_dirs: tuple, 116 key_heirarchy: tuple, 117 bundles: list = None) -> tuple: 118 """ 119 从部件的bundle.json中收集syscap 120 :param project_path: 项目根路径 121 :param black_dirs: 根路径下的一级目录的黑名单,不扫描这些bundle.json下面的内容 122 :param key_heirarchy: bundle.json中syscap所在的key的元组 123 :param bundles: 指定的bundle.json 124 :return: syscap_set,文件名和其syscap list组成的dict 125 """ 126 result_dict = dict() 127 if bundles is None: 128 search_dir_list = [ 129 os.path.join(project_path, x) 130 for x in os.listdir(project_path) 131 if os.path.isdir(os.path.join(project_path, x)) and x not in black_dirs 132 ] 133 for folder in search_dir_list: 134 output = os.popen("find {} -name bundle.json".format(folder)) 135 for line in output: 136 line = line.strip() 137 read_value_from_json(line, key_heirarchy, result_dict, post_handler=bundle_syscap_post_handler) 138 output.close() 139 else: 140 for bundle in bundles: 141 read_value_from_json(bundle, key_heirarchy, result_dict, post_handler=bundle_syscap_post_handler) 142 result_set = set() 143 for v in result_dict.values(): 144 result_set.update(v) 145 return result_set, result_dict 146 147 148def sdk_syscap_post_handler(syscap: str) -> str: 149 return syscap.strip().lstrip("*").lstrip().lstrip("*@syscap").strip() 150 151 152def collect_syscap_from_sdk(ts_path: str, 153 pattern: str = r"\* *@syscap +((?i)SystemCapability\..*)", 154 post_handler=None 155 ) -> tuple: 156 """ 157 从sdk下的*.d.ts中收集syscap 158 :param ts_path: ts文件的所在路径 159 :param pattern: 匹配syscap的pattern 160 :param post_handler: 对匹配到的syscap进行后置处理 161 :return: syscap_set,和由文件名、syscap list组成的dict 162 """ 163 ts_list = list() 164 for item in os.listdir(ts_path): 165 file_path = os.path.join(ts_path, item) 166 if file_path.endswith(".d.ts") and os.path.isfile(file_path): 167 ts_list.append(file_path) 168 syscap_dict = dict() 169 ptrn = re.compile(pattern) 170 for ts in ts_list: 171 with open(ts, 'r') as f: 172 content = f.read() 173 sub_syscap_list = re.findall(ptrn, content) 174 sub_syscap_set = set() 175 for syscap in sub_syscap_list: 176 sub_syscap_set.add(post_handler(syscap)) 177 syscap_dict[ts] = sub_syscap_set 178 syscap_set = set() 179 for v in syscap_dict.values(): 180 syscap_set.update(v) 181 return syscap_set, syscap_dict 182 183 184def find_files_containes_value(value_set: set, file_values_dict: dict) -> dict: 185 """ 186 查看包含指定值的文件 187 :param value_set: 指定值 188 :param file_values_dict: 文件和值组成的dict 189 :return: 值和文件组成的dict 190 """ 191 value_files_dict = dict() 192 for v in value_set: 193 filename_set = set() 194 for file in file_values_dict.keys(): 195 if v in file_values_dict[file]: 196 filename_set.add(file) 197 if 0 != len(filename_set): 198 value_files_dict[v] = filename_set 199 return value_files_dict 200 201 202def mutual_diff(a_set: set, b_set: set) -> tuple: 203 return a_set.difference(b_set), b_set.difference(a_set) 204 205 206def print_inconsistent(diff_set: set, a_name: str, b_name: str, table: PrettyTable, 207 value_file_dict: dict) -> None: 208 table.clear() 209 if len(diff_set) != 0: 210 table.field_names = ["index", "SysCap only in {}".format(a_name), "Files"] 211 add_dict_as_table_row(table, value_file_dict) 212 elif len(diff_set) == 0: 213 table.field_names = ["All SysCap in {} have been Covered by {}".format(a_name, b_name)] 214 print(table) 215 print() 216 217 218def print_consistent(a_diff_b_set: set, b_diff_a_set: set, table: PrettyTable, a_name: str, b_name: str) -> bool: 219 if len(a_diff_b_set) == 0 and len(b_diff_a_set) == 0: 220 table.field_names = ["{} and {} are Consistent".format(a_name, b_name)] 221 print(table) 222 print() 223 return True 224 return False 225 226 227def print_check_result(a_diff_b_set: set, 228 b_diff_a_set: set, 229 a_name: str, 230 b_name: str, 231 a_value_file_dict: dict, 232 b_value_file_dict: dict, 233 ) -> None: 234 f_table = PrettyTable() 235 f_table.hrules = ALL 236 consistent_flag = print_consistent(a_diff_b_set, b_diff_a_set, f_table, a_name, b_name) 237 if not consistent_flag: 238 print_inconsistent(a_diff_b_set, a_name, b_name, f_table, a_value_file_dict) 239 print_inconsistent(b_diff_a_set, b_name, a_name, f_table, b_value_file_dict) 240 241 242def check_component_and_codec(project_path, syscap_define_path: str, component_black_dirs: tuple, 243 bundle_key_heirarchy: tuple, bundles=None): 244 component_syscap_set, component_syscap_dict = collect_syscap_from_component( 245 project_path, black_dirs=component_black_dirs, key_heirarchy=bundle_key_heirarchy, bundles=bundles 246 ) 247 array_syscap_set, array_syscap_dict = collect_syscap_from_codec(syscap_define_path) 248 component_diff_array, array_diff_component = mutual_diff(component_syscap_set, array_syscap_set) 249 value_component_dict = find_files_containes_value( 250 component_diff_array, component_syscap_dict 251 ) 252 value_h_dict = find_files_containes_value(array_diff_component, array_syscap_dict) 253 print_check_result(component_diff_array, array_diff_component, a_name="Component", b_name="Codec", 254 a_value_file_dict=value_component_dict, b_value_file_dict=value_h_dict) 255 256 257def check_component_and_sdk(project_path, component_black_dirs: tuple, component_key_heirarchy: tuple, sdk_path: str): 258 component_syscap_set, component_syscap_dict = collect_syscap_from_component( 259 project_path, black_dirs=component_black_dirs, key_heirarchy=component_key_heirarchy 260 ) 261 ts_syscap_set, ts_syscap_dict = collect_syscap_from_sdk(ts_path=sdk_path, 262 post_handler=sdk_syscap_post_handler) 263 component_diff_ts, ts_diff_component = mutual_diff(component_syscap_set, ts_syscap_set) 264 value_ts_dict = find_files_containes_value(ts_diff_component, ts_syscap_dict) 265 value_component_dict = find_files_containes_value( 266 component_diff_ts, component_syscap_dict 267 ) 268 print_check_result(component_diff_ts, ts_diff_component, a_name="Component", b_name="SDK", 269 a_value_file_dict=value_component_dict, b_value_file_dict=value_ts_dict) 270 271 272def check_sdk_and_codec(syscap_define_path: str, sdk_path: str) -> None: 273 ts_syscap_set, ts_syscap_dict = collect_syscap_from_sdk(ts_path=sdk_path, 274 post_handler=sdk_syscap_post_handler) 275 array_syscap_set, array_syscap_dict = collect_syscap_from_codec(syscap_define_path) 276 ts_diff_array, array_diff_ts = mutual_diff(ts_syscap_set, array_syscap_set) 277 value_ts_dict = find_files_containes_value(ts_diff_array, ts_syscap_dict) 278 value_h_dict = find_files_containes_value(array_diff_ts, array_syscap_dict) 279 print_check_result(ts_diff_array, array_diff_ts, a_name="SDK", b_name="Codec", a_value_file_dict=value_ts_dict, 280 b_value_file_dict=value_h_dict) 281 282 283def main(): 284 args = get_args() 285 project_path = args.project_path 286 check_target = args.check_target 287 bundles = args.bundles 288 syscap_define_path = os.path.join(project_path, "developtools", "syscap_codec", "include", "syscap_define.h") 289 ts_path = os.path.join(project_path, "interface", "sdk-js", "api") 290 component_black_dirs = ("out",) 291 bundle_syscap_heirarchy = ("component", "syscap") 292 if (bundles is not None or (bundles is not None and len(bundles) != 0)) and check_target != "component_codec": 293 print("error: --bundles could only be used with -t component_codec") 294 return 295 if "component_sdk" == check_target: 296 check_component_and_sdk(project_path, component_black_dirs, bundle_syscap_heirarchy, sdk_path=ts_path) 297 elif "sdk_codec" == check_target: 298 check_sdk_and_codec(syscap_define_path=syscap_define_path, sdk_path=ts_path) 299 elif "component_codec" == check_target: 300 if bundles is not None and len(bundles) == 0: 301 print(r"error: '--bundles' parameter is specified, but has no value") 302 else: 303 check_component_and_codec(project_path, syscap_define_path, component_black_dirs=component_black_dirs, 304 bundle_key_heirarchy=bundle_syscap_heirarchy, bundles=bundles) 305 306 307if __name__ == "__main__": 308 main() 309 310