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 __get_open_components(cls, xml_path): 27 open_components = list() 28 gn_name = list() 29 white_components_list = ["common", "hilog", "ylong_runtime"] 30 with open(xml_path, 'r', encoding='utf-8') as r: 31 xml_info = r.readlines() 32 for line in xml_info: 33 if "path=" in line: 34 one_component = re.findall('path="(.*?)"', line)[0].split('/')[-1] 35 open_components.append(one_component) 36 one_name = re.findall('name="(.*?)"', line)[0] 37 gn_name.append(one_name) 38 return open_components, gn_name, white_components_list 39 40 @classmethod 41 def __deal_config_json(cls, config_json): 42 components = list() 43 for subsystem in config_json['subsystems']: 44 for component in subsystem['components']: 45 if component not in components: 46 components.append(component['component']) 47 return components 48 49 @classmethod 50 def __get_required_components(cls, config_path: str): 51 required_components = list() 52 files = os.listdir(config_path) 53 for file in files: 54 if file.endswith(".json"): 55 with open(os.path.join(config_path, file), 'r', encoding='utf-8') as r: 56 config_json = json.load(r) 57 required_components += cls.__deal_config_json(config_json) 58 return required_components 59 60 @classmethod 61 def __get_line(cls, txt_list, key_words: str): 62 for i, txt in enumerate(txt_list): 63 if key_words in txt: 64 return i + 1 65 return 0 66 67 @classmethod 68 def __judge_deps(cls, gn_path: str, new_line_num: str, open_components_list, optional_components, white_names): 69 deps = list() 70 new_line_num = [int(i) for i in new_line_num.split('_')] 71 with open(gn_path, 'r', encoding='utf-8') as r: 72 gn_lines = [line.strip("\n") for line in r.readlines()] 73 dependent_close = True 74 txt = '' 75 for line in gn_lines: 76 txt += line 77 for component in open_components_list: 78 if dependent_close == True: 79 if component in txt: 80 dependent_close = False 81 scan_line_num = cls.__get_scan_line_num(gn_lines, new_line_num) 82 for i in scan_line_num: 83 if '/' in gn_lines[i - 1]: 84 dep_info = re.findall('(.*?):', gn_lines[i - 1].split("/")[-1])[0] 85 else: 86 dep_info = re.findall('"(.*?):', gn_lines[i - 1])[0] 87 for component in optional_components: 88 if component not in white_names and component == dep_info: 89 deps.append((component, i)) 90 error = list() 91 if dependent_close == True and re.findall('deps =', txt): 92 line = cls.__get_line(gn_lines, 'deps =') 93 error.append( 94 {"line": line, "code": gn_lines[line - 1].strip(), "rule": "depend close component", 95 "detail": "可能依赖闭源部件,请检查deps中的内容"}) 96 for one_dep in deps: 97 error.append( 98 {"line": one_dep[1], "code": gn_lines[one_dep[1] - 1].strip(), "rule": "depend optional component", 99 "detail": "依赖开源部件中的非必选部件{},请检查deps中的内容".format(one_dep[0])}) 100 return error 101 102 @classmethod 103 def __get_scan_line_num(cls, gn_lines, new_line_num): 104 add_line_txt = '' 105 line_num = 0 106 for line in gn_lines: 107 line_num += 1 108 add_line_txt += '@' + str(line_num) + '@' + line 109 in_if_txt = re.findall('if \(.+?\{(.*?)\}', add_line_txt) 110 in_if_line_num = cls.__get_line_num(in_if_txt) 111 in_dep_txt = re.findall('deps = \[(.*?)\]', add_line_txt) + re.findall('deps += \[(.*?)\]', add_line_txt) 112 in_dep_line_num = cls.__get_line_num(in_dep_txt) 113 for line_num, line in enumerate(gn_lines): 114 if ('deps = ' in line or 'deps += ' in line) and ']' in line and (line_num + 1) not in in_dep_line_num: 115 in_dep_line_num.append(line_num + 1) 116 scan_line = list() 117 for num in new_line_num: 118 if num not in in_if_line_num and num in in_dep_line_num: 119 scan_line.append(num) 120 return scan_line 121 122 @classmethod 123 def __get_line_num(cls, txt_line_list): 124 line_num = list() 125 for one_txt in txt_line_list: 126 one_line_list = re.findall('@(.*?)@', one_txt) 127 if one_line_list != ['']: 128 line_num += one_line_list 129 line_num = [int(i) for i in line_num] 130 return line_num 131 132 @classmethod 133 def analysis(cls, gn_path_list, new_line_nums, gn_name, config_path: str, open_components_path, 134 result_json_name: str): 135 if not os.path.exists(config_path): 136 print("error: {} is inaccessible or not found".format(config_path)) 137 return 138 if not os.path.exists(open_components_path): 139 print("error: {} is inaccessible or not found".format(open_components_path)) 140 return 141 if len(gn_path_list) != len(new_line_nums): 142 print("error: The new_line_nums and the gn_path are not in one-to-one correspondence.") 143 return 144 if len(gn_path_list) != len(gn_name): 145 print("error: The gn_path and gn_name are not in one-to-one correspondence.") 146 return 147 required_components = cls.__get_required_components(config_path) 148 open_components, gn_name_list, white_list = cls.__get_open_components(open_components_path) 149 gn_name2component = dict(zip(gn_name_list, open_components)) 150 optional_components = list() 151 for components in open_components: 152 if components not in required_components: 153 optional_components.append(components) 154 result = list() 155 for i, _ in enumerate(gn_path_list): 156 one_result = dict() 157 one_result["file_path"] = gn_path_list[i] 158 if gn_name[i] in gn_name_list and gn_name2component[gn_name[i]] in required_components: 159 one_result["error"] = cls.__judge_deps(gn_path_list[i], new_line_nums[i], open_components, 160 optional_components, white_list) 161 else: 162 one_result["error"] = [] 163 result.append(one_result) 164 with os.fdopen(os.open(result_json_name + ".json", os.O_WRONLY | os.O_CREAT, mode=0o640), "w", 165 encoding='utf-8') as fd: 166 json.dump(result, fd, indent=4, ensure_ascii=False) 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 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, config_path, open_components_xml_path, 196 result_json) 197