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 sys 18 19import pandas as pd 20from bundle_check.get_subsystem_with_component import \ 21 get_subsystem_components_modified 22from gn_check.gn_common_tools import GnCommon 23 24 25class CheckGn(object): 26 """GN检查类 27 """ 28 COLUMNS_NAME_FOR_PART = ['文件', '定位', '违反规则', '错误说明'] 29 COLUMNS_NAME_FOR_ALL = ['子系统', '部件', '文件', '定位', '违反规则', '错误说明'] 30 SCRIPT_PATH = 'build/tools/component_tools/static_check/gn_check' 31 TARGET_NAME = ('ohos_shared_library', 32 'ohos_static_library', 'ohos_executable', 33 'ohos_source_set', 34 'ohos_copy', 35 'ohos_group', 36 'ohos_prebuilt_executable', 37 'ohos_prebuilt_shared_library', 38 'ohos_prebuilt_static_library', 39 'ohos_prebuilt_etc') 40 41 def __init__(self, ohos_root: str, black_dir: tuple = tuple(), check_path='') -> None: 42 """GN检查类的初始化,定义常用变量及初始化 43 44 Args: 45 ohos_root (str): ohos源码的路径,可以是绝对路径,也可以是相对路径 46 black_dir (tuple): 不检查的目录 47 """ 48 49 self.ohos_root = ohos_root 50 self.check_path = check_path 51 self.black_dir = black_dir 52 53 if check_path == '' or check_path is None: 54 self.abs_check_path = self.ohos_root 55 else: 56 self.abs_check_path = os.path.join(self.ohos_root, check_path) 57 self.all_gn_files = GnCommon.find_files( 58 self.abs_check_path, black_dirs=black_dir) 59 self.subsystem_info = get_subsystem_components_modified(ohos_root) 60 61 def get_all_gn_data(self) -> dict: 62 """获取BUILD.gn中所有的target代码段,并返回一个字典 63 64 Returns: 65 dict: key是文件名(包含路径),values是target列表 66 """ 67 68 target_pattern = r"^( *)(" 69 for target in self.TARGET_NAME: 70 target_pattern += target 71 if target != self.TARGET_NAME[-1]: 72 target_pattern += r'|' 73 target_pattern += r")[\s|\S]*?\n\1}$" 74 all_gn_data = dict() 75 76 for gn_file in self.all_gn_files: 77 if not gn_file.endswith('.gn'): 78 continue 79 with open(gn_file, errors='ignore') as file: 80 targets_ret = GnCommon.find_paragraph_iter( 81 target_pattern, file.read()) 82 target = list() # 每个文件中的target 83 for target_ret in targets_ret: 84 target.append(target_ret.group()) 85 if len(target) == 0: 86 continue 87 all_gn_data.update({gn_file[len(self.ohos_root) + 1:]: target}) 88 return all_gn_data 89 90 def get_all_abs_path(self) -> list: 91 """通过正则表达式匹配出所有BUILD.gn中的绝对路径,并返回一个列表 92 93 Returns: 94 list: list中的元素是字典,每个字典中是一条绝对路径的信息 95 """ 96 abs_path_pattern = r'"\/(\/[^\/\n]+){1,63}"' 97 ret_list = list() 98 99 all_info = GnCommon.grep_one( 100 abs_path_pattern, self.abs_check_path, excludes=self.black_dir, grep_parameter='Porn') 101 if all_info is None: 102 return list() 103 row_info = all_info.split('\n') 104 for item in row_info: 105 abs_info = item.split(':') 106 path = abs_info[0][len(self.ohos_root) + 1:] 107 line_number = abs_info[1] 108 content = abs_info[2].strip('"') 109 ret_list.append( 110 {'path': path, 'line_number': line_number, 'content': content}) 111 return ret_list 112 113 def check_have_product_name(self) -> pd.DataFrame: 114 """检查BUILD.gn中是否存product_name和device_name 115 返回包含这两个字段的dataframe信息 116 117 不包含子系统部件信息版本 118 119 Returns: 120 pd.DataFrame: 数据的组织方式为[ 121 '文件', '定位', '违反规则', '错误说明'] 122 """ 123 pattern = 'product_name|device_name' 124 issue = '存在 product_name 或 device_name' 125 rules = '规则4.1 部件编译脚本中禁止使用产品名称变量' 126 bad_targets_to_excel = list() 127 all_info = GnCommon.grep_one( 128 pattern, self.abs_check_path, excludes=self.black_dir) 129 if all_info is None: 130 return pd.DataFrame() 131 product_name_data = all_info.split('\n') 132 for line in product_name_data: 133 info = line.split(':') 134 if info[2].find("==") == -1: 135 continue 136 file_name = info[0][len(self.abs_check_path) + 1:] 137 bad_targets_to_excel.append([file_name, 'line:{}:{}'.format( 138 info[1], info[2].strip()), rules, issue]) 139 bad_targets_to_excel = pd.DataFrame( 140 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_PART) 141 142 return bad_targets_to_excel 143 144 def check_have_product_name_all(self) -> pd.DataFrame: 145 """检查BUILD.gn中是否存product_name和device_name 146 返回包含这两个字段的dataframe信息 147 148 包含子系统部件信息版本 149 150 Returns: 151 pd.DataFrame: 数据的组织方式为[ 152 '子系统', '部件', '文件', '定位', '违反规则', '错误说明'] 153 """ 154 pattern = 'product_name|device_name' 155 issue = '存在 product_name 或 device_name' 156 rules = '规则4.1 部件编译脚本中禁止使用产品名称变量' 157 bad_targets_to_excel = list() 158 all_info = GnCommon.grep_one( 159 pattern, self.abs_check_path, excludes=self.black_dir) 160 if all_info is None: 161 return pd.DataFrame() 162 product_name_data = all_info.split('\n') 163 164 for line in product_name_data: 165 info = line.split(':') 166 if info[2].find("==") == -1: 167 continue 168 file_name = info[0][len(self.ohos_root) + 1:] 169 170 subsys_comp = list() 171 for path, content in self.subsystem_info.items(): 172 if file_name.startswith(path): 173 subsys_comp.append(content['subsystem']) 174 subsys_comp.append(content['component']) 175 break 176 if subsys_comp: 177 bad_targets_to_excel.append([subsys_comp[0], subsys_comp[1], file_name, 'line:{}:{}'.format( 178 info[1], info[2].strip()), rules, issue]) 179 else: 180 bad_targets_to_excel.append(['null', 'null', file_name, 'line:{}:{}'.format( 181 info[1], info[2].strip()), rules, issue]) 182 bad_targets_to_excel = pd.DataFrame( 183 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_ALL) 184 185 return bad_targets_to_excel 186 187 def check_pn_sn(self) -> pd.DataFrame: 188 """检查BUILD.gn中target是否包含subsystem_name和part_name字段, 189 返回不包含这两个字段的dataframe信息 190 191 不包含子系统部件信息版本 192 193 Returns: 194 pd.DataFrame: 数据的组织方式为[ 195 '文件', '定位', '违反规则', '错误说明'] 196 """ 197 rules = '规则3.2 部件编译目标必须指定部件和子系统名' 198 bad_targets_to_excel = list() 199 all_gn_data = self.get_all_gn_data() 200 201 for key, values in all_gn_data.items(): 202 bad_target_to_excel = list() 203 if len(values) == 0: 204 continue 205 for target in values: 206 flags = [False, False] 207 if target.find('subsystem_name') == -1: 208 flags[0] = True 209 if target.find('part_name') == -1: 210 flags[1] = True 211 if any(flags): 212 content = target.split()[0] 213 grep_info = GnCommon.grep_one(content, os.path.join( 214 self.ohos_root, key), grep_parameter='n') 215 row_number_info = grep_info.split(':')[0] 216 issue = '不存在 ' 217 issue += 'subsystem_name' if flags[0] else '' 218 issue += ',' if all(flags) else '' 219 issue += 'part_name' if flags[1] else '' 220 pos = 'line {}:{}'.format(row_number_info, content) 221 bad_target_to_excel.append( 222 [key[len(self.check_path) + 1:], pos, rules, issue]) 223 if not bad_target_to_excel: 224 continue 225 for target_item in bad_target_to_excel: 226 bad_targets_to_excel.append(target_item) 227 228 bad_targets_to_excel = pd.DataFrame( 229 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_PART) 230 231 return bad_targets_to_excel 232 233 def check_pn_sn_all(self) -> pd.DataFrame: 234 """检查BUILD.gn中target是否包含subsystem_name和part_name字段, 235 返回不包含这两个字段的dataframe信息 236 237 包含子系统部件信息版本 238 239 Returns: 240 pd.DataFrame: 数据的组织方式为[ 241 '子系统', '部件', '文件', '定位', '违反规则', '错误说明'] 242 """ 243 rules = '规则3.2 部件编译目标必须指定部件和子系统名' 244 bad_targets_to_excel = list() 245 all_gn_data = self.get_all_gn_data() 246 247 for key, values in all_gn_data.items(): 248 bad_target_to_excel = list() 249 if len(values) == 0: 250 continue 251 for target in values: 252 flags = [False, False] 253 if target.find('subsystem_name') == -1: 254 flags[0] = True 255 if target.find('part_name') == -1: 256 flags[1] = True 257 if any(flags): 258 content = target.split('\n')[0].strip() 259 grep_info = GnCommon.grep_one(content, os.path.join( 260 self.ohos_root, key), grep_parameter='n') 261 row_number_info = grep_info.split(':')[0] 262 issue = '不存在 ' 263 issue += 'subsystem_name' if flags[0] else '' 264 issue += ',' if all(flags) else '' 265 issue += 'part_name' if flags[1] else '' 266 pos = 'line {}:{}'.format(row_number_info, content) 267 bad_target_to_excel.append( 268 ['null', 'null', key, pos, rules, issue]) 269 if not bad_target_to_excel: 270 continue 271 for path, content in self.subsystem_info.items(): 272 for index in range(len(bad_target_to_excel)): 273 bad_target_to_excel[index][:2] = [content['subsystem'], content['component']] if key.startswith( 274 path) else bad_target_to_excel[index][:2] 275 276 for target_item in bad_target_to_excel: 277 bad_targets_to_excel.append(target_item) 278 279 bad_targets_to_excel = pd.DataFrame( 280 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_ALL) 281 282 return bad_targets_to_excel 283 284 def check_abs_path(self) -> pd.DataFrame: 285 """检查绝对路径,返回标准信息 286 287 不包含子系统部件信息版本 288 289 Returns: 290 pd.DataFrame: 数据的组织方式为[ 291 '文件', '定位', '违反规则', '错误说明'] 292 """ 293 rules = '规则3.1 部件编译脚本中只允许引用本部件路径,禁止引用其他部件的绝对或相对路径' 294 issue = '引用使用了绝对路径' 295 bad_targets_to_excel = list() 296 abs_path = self.get_all_abs_path() 297 298 for item in abs_path: 299 if item['content'].startswith('//third_party'): 300 continue 301 if item['content'].startswith('//build'): 302 continue 303 if item['content'].startswith('//prebuilts'): 304 continue 305 if item['content'].startswith('//out'): 306 continue 307 bad_targets_to_excel.append([item['path'][len( 308 self.check_path) + 1:], 'line {}:{}'.format(item['line_number'], item['content']), rules, issue]) 309 bad_targets_to_excel = pd.DataFrame( 310 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_PART) 311 312 return bad_targets_to_excel 313 314 def check_abs_path_all(self) -> pd.DataFrame: 315 """检查绝对路径,返回标准信息 316 317 包含子系统部件信息版本 318 319 Returns: 320 pd.DataFrame: 数据的组织方式为[ 321 '子系统', '部件', '文件', '定位', '违反规则', '错误说明'] 322 """ 323 rules = '规则3.1 部件编译脚本中只允许引用本部件路径,禁止引用其他部件的绝对或相对路径' 324 issue = '引用使用了绝对路径' 325 bad_targets_to_excel = list() 326 abs_path = self.get_all_abs_path() 327 328 for item in abs_path: 329 if item['content'].startswith('//third_party'): 330 continue 331 if item['content'].startswith('//build'): 332 continue 333 if item['content'].startswith('//prebuilts'): 334 continue 335 if item['content'].startswith('//out'): 336 continue 337 subsys_comp = list() 338 for path, content in self.subsystem_info.items(): 339 if item['path'].startswith(path): 340 subsys_comp.append(content['subsystem']) 341 subsys_comp.append(content['component']) 342 break 343 if subsys_comp: 344 bad_targets_to_excel.append([subsys_comp[0], subsys_comp[1], item['path'], 'line {}:{}'.format( 345 item['line_number'], item['content']), rules, issue]) 346 else: 347 bad_targets_to_excel.append(['null', 'null', item['path'], 'line {}:{}'.format( 348 item['line_number'], item['content']), rules, issue]) 349 bad_targets_to_excel = pd.DataFrame( 350 bad_targets_to_excel, columns=self.COLUMNS_NAME_FOR_ALL) 351 352 return bad_targets_to_excel 353 354 def output(self): 355 if self.check_path == '' or self.check_path is None: 356 product_name_info = self.check_have_product_name_all() 357 part_name_subsystem_name_info = self.check_pn_sn_all() 358 abs_path_info = self.check_abs_path_all() 359 else: 360 product_name_info = self.check_have_product_name() 361 part_name_subsystem_name_info = self.check_pn_sn() 362 abs_path_info = self.check_abs_path() 363 364 out = pd.concat( 365 [product_name_info, part_name_subsystem_name_info, abs_path_info]) 366 for black_dir in self.black_dir: 367 out = out[~out['文件'].astype(str).str.startswith(black_dir)] 368 369 print('-------------------------------') 370 print('BUILD.gn check successfully!') 371 print('There are {} issues in total'.format(out.shape[0])) 372 print('-------------------------------') 373 374 return out 375