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. 15 16# This file provide the detection tool for unconditional dependence of required components on optional components. 17 18import argparse 19import json 20import os 21import re 22 23 24class Analyzer: 25 @classmethod 26 def analysis(cls, gn_path_list, new_line_nums, gn_name, config_path: str, open_components_path, 27 result_json_name: str): 28 if not os.path.exists(config_path): 29 print("error: {} is inaccessible or not found".format(config_path)) 30 return 31 if not os.path.exists(open_components_path): 32 print("error: {} is inaccessible or not found".format(open_components_path)) 33 return 34 if len(gn_path_list) != len(new_line_nums): 35 print("error: The new_line_nums and the gn_path are not in one-to-one correspondence.") 36 return 37 if len(gn_path_list) != len(gn_name): 38 print("error: The gn_path and gn_name are not in one-to-one correspondence.") 39 return 40 required_components = cls.__get_required_components(config_path) 41 open_components, gn_name_list, white_list = cls.__get_open_components(open_components_path) 42 gn_name2component = dict(zip(gn_name_list, open_components)) 43 optional_components = list() 44 for components in open_components: 45 if components not in required_components: 46 optional_components.append(components) 47 result = list() 48 for i, _ in enumerate(gn_path_list): 49 one_result = dict() 50 one_result["file_path"] = gn_path_list[i] 51 if gn_name[i] in gn_name_list and gn_name2component[gn_name[i]] in required_components: 52 one_result["error"] = cls.__judge_deps(gn_path_list[i], new_line_nums[i], open_components, 53 optional_components, white_list) 54 else: 55 one_result["error"] = [] 56 result.append(one_result) 57 with os.fdopen(os.open(result_json_name + ".json", os.O_WRONLY | os.O_CREAT, mode=0o640), "w", 58 encoding='utf-8') as fd: 59 json.dump(result, fd, indent=4, ensure_ascii=False) 60 61 @classmethod 62 def __get_open_components(cls, xml_path): 63 open_components = list() 64 gn_name = list() 65 white_components_list = ["common", "hilog", "ylong_runtime"] 66 with open(xml_path, 'r', encoding='utf-8') as r: 67 xml_info = r.readlines() 68 for line in xml_info: 69 if "path=" in line: 70 one_component = re.findall('path="(.*?)"', line)[0].split('/')[-1] 71 open_components.append(one_component) 72 one_name = re.findall('name="(.*?)"', line)[0] 73 gn_name.append(one_name) 74 return open_components, gn_name, white_components_list 75 76 @classmethod 77 def __deal_config_json(cls, config_json): 78 components = list() 79 for subsystem in config_json['subsystems']: 80 for component in subsystem['components']: 81 if component not in components: 82 components.append(component['component']) 83 return components 84 85 @classmethod 86 def __get_required_components(cls, config_path: str): 87 required_components = list() 88 files = os.listdir(config_path) 89 for file in files: 90 if file.endswith(".json"): 91 with open(os.path.join(config_path, file), 'r', encoding='utf-8') as r: 92 config_json = json.load(r) 93 required_components += cls.__deal_config_json(config_json) 94 return required_components 95 96 @classmethod 97 def __get_line(cls, txt_list, key_words: str): 98 for i, txt in enumerate(txt_list): 99 if key_words in txt: 100 return i + 1 101 return 0 102 103 @classmethod 104 def __judge_deps(cls, gn_path: str, new_line_num: str, open_components_list, optional_components, white_names): 105 deps = list() 106 new_line_num = [int(i) for i in new_line_num.split('_')] 107 with open(gn_path, 'r', encoding='utf-8') as r: 108 gn_lines = [line.strip("\n") for line in r.readlines()] 109 dependent_close = True 110 txt = '' 111 for line in gn_lines: 112 txt += line 113 for component in open_components_list: 114 if dependent_close == True: 115 if component in txt: 116 dependent_close = False 117 scan_line_num = cls.__get_scan_line_num(gn_lines, new_line_num) 118 for i in scan_line_num: 119 if '/' in gn_lines[i - 1]: 120 dep_info = re.findall('(.*?):', gn_lines[i - 1].split("/")[-1])[0] 121 else: 122 dep_info = re.findall('"(.*?):', gn_lines[i - 1])[0] 123 for component in optional_components: 124 if component not in white_names and component == dep_info: 125 deps.append((component, i)) 126 error = list() 127 if dependent_close == True and re.findall('deps =', txt): 128 line = cls.__get_line(gn_lines, 'deps =') 129 error.append( 130 {"line": line, "code": gn_lines[line - 1].strip(), "rule": "depend close component", 131 "detail": "可能依赖闭源部件,请检查deps中的内容"}) 132 for one_dep in deps: 133 error.append( 134 {"line": one_dep[1], "code": gn_lines[one_dep[1] - 1].strip(), "rule": "depend optional component", 135 "detail": "依赖开源部件中的非必选部件{},请检查deps中的内容".format(one_dep[0])}) 136 return error 137 138 @classmethod 139 def __get_scan_line_num(cls, gn_lines, new_line_num): 140 add_line_txt = '' 141 line_num = 0 142 for line in gn_lines: 143 line_num += 1 144 add_line_txt += '@' + str(line_num) + '@' + line 145 in_if_txt = re.findall('if \(.+?\{(.*?)\}', add_line_txt) 146 in_if_line_num = cls.__get_line_num(in_if_txt) 147 in_dep_txt = re.findall('deps = \[(.*?)\]', add_line_txt) + re.findall('deps += \[(.*?)\]', add_line_txt) 148 in_dep_line_num = cls.__get_line_num(in_dep_txt) 149 for line_num, line in enumerate(gn_lines): 150 if ('deps = ' in line or 'deps += ' in line) and ']' in line and (line_num + 1) not in in_dep_line_num: 151 in_dep_line_num.append(line_num + 1) 152 scan_line = list() 153 for num in new_line_num: 154 if num not in in_if_line_num and num in in_dep_line_num: 155 scan_line.append(num) 156 return scan_line 157 158 @classmethod 159 def __get_line_num(cls, txt_line_list): 160 line_num = list() 161 for one_txt in txt_line_list: 162 one_line_list = re.findall('@(.*?)@', one_txt) 163 if one_line_list != ['']: 164 line_num += one_line_list 165 line_num = [int(i) for i in line_num] 166 return line_num 167 168 169def get_args(): 170 parser = argparse.ArgumentParser( 171 description=f"analyze components deps.\n") 172 parser.add_argument("-p", "--components_gn_path_list", required=True, type=str, 173 help="path of pr BUILD.gn") 174 parser.add_argument("-n", "--new_line_nums_list", required=True, type=str, 175 help="eg: 1_2_3,4_5") 176 parser.add_argument("-g", "--gn_name", required=True, type=str, 177 help="gn file corresponding name") 178 parser.add_argument("-c", "--config_path", required=True, type=str, 179 help="path of config_file") 180 parser.add_argument("-o", "--open_component_xml_path", required=True, type=str, 181 help="open component name set") 182 parser.add_argument("-r", "--result_json_name", type=str, default="result", 183 help="name of output_json") 184 return parser.parse_args() 185 186 187if __name__ == '__main__': 188 args = get_args() 189 gn_path_list_name = args.components_gn_path_list.split(',') 190 new_line_nums_list = args.new_line_nums_list.split(',') 191 gn_component_name = args.gn_name.split(',') 192 args_config_path = args.config_path 193 open_components_xml_path = args.open_component_xml_path 194 result_json = args.result_json_name 195 Analyzer.analysis(gn_path_list_name, new_line_nums_list, gn_component_name, args_config_path, open_components_xml_path, 196 result_json)