1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2024 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import os 19import re 20import json 21from enum import Enum 22 23HOME = os.path.dirname(os.path.dirname(os.path.dirname( 24 os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) 25 26 27class ChangeFileEntity: 28 def __init__(self, name, path): 29 self.name = name 30 self.path = path 31 self.add = [] 32 self.modified = [] 33 self.delete = [] 34 self._already_match_utils = False 35 36 def addAddPaths(self, add_list): 37 self.add += list(map(lambda x: os.path.join(self.path, x), add_list)) 38 self.add.sort() 39 40 def addModifiedPaths(self, modified_list): 41 self.modified += list(map(lambda x: os.path.join(self.path, x), modified_list)) 42 self.modified.sort() 43 44 def addRenamePathsto(self, rename_list): 45 for list in rename_list: 46 self.add += [os.path.join(self.path, list[1])] 47 self.delete += [os.path.join(self.path, list[0])] 48 self.add.sort() 49 self.delete.sort() 50 51 def addDeletePaths(self, delete_list): 52 self.delete += list(map(lambda x: os.path.join(self.path, x), delete_list)) 53 self.delete.sort() 54 55 def isEmpty(self): 56 if self.add: 57 return False 58 if self.modified: 59 return False 60 if self.delete: 61 return False 62 return True 63 64 def get_already_match_utils(self): 65 return self._already_match_utils 66 67 def set_already_match_utils(self, already_match_utils): 68 self._already_match_utils = already_match_utils 69 70 def __str__(self): 71 add_str = '\n '.join(self.add) if self.add else 'None' 72 modified_str = '\n '.join(self.modified) if self.modified else 'None' 73 delete_str = '\n '.join(self.delete) if self.delete else 'None' 74 75 return (f"ChangeFileEntity(\n" 76 f" name: {self.name},\n" 77 f" path: {self.path},\n" 78 f" add: [\n {add_str}\n ],\n" 79 f" modified: [\n {modified_str}\n ],\n" 80 f" delete: [\n {delete_str}\n ]\n" 81 f")") 82 83 84class MatchConfig: 85 config_path = os.path.join(HOME, "test/xts/tools/config") 86 MACTH_CONFIG_PATH = os.path.join(config_path, "ci_match_config.json") 87 exception_path = {} 88 all_com_path = {} 89 skip_judge_build_path = {} 90 temple_list = [] 91 acts_All_template_ex_list = [] 92 xts_path_list = [] 93 94 INTERFACE_BUNDLE_NAME_PATH = os.path.join(config_path, "ci_api_part_name.json") 95 interface_js_data = {} 96 interface_c_data = {} 97 driver_interface = {} 98 99 WHITE_LIST_PATH = os.path.join(config_path, "ci_target_white_list.json") 100 white_list_repo = {} 101 102 # 逃生通道 103 ESCAPE_PATH = os.path.join(config_path, "ci_escape.json") 104 escape_list = [] 105 106 # 不参与编译测试套配置 107 uncompile_suite = {} 108 109 @classmethod 110 def initialization(cls): 111 if cls.exception_path == {}: 112 print("MatchConfig 开始初始化") 113 if not os.path.exists(cls.MACTH_CONFIG_PATH): 114 print(f"{cls.MACTH_CONFIG_PATH} 不存在,读取配置文件异常") 115 with open(cls.MACTH_CONFIG_PATH, 'r') as file: 116 rules_data = json.load(file) 117 cls.exception_path = rules_data['exception_path'] 118 cls.all_com_path = rules_data['all_com_path'] 119 cls.skip_judge_build_path = rules_data['skip_judge_build_path'] 120 cls.temple_list = rules_data['temple_list'] 121 cls.acts_All_template_ex_list = rules_data['acts_All_template_ex'] 122 cls.xts_path_list = rules_data['xts_path_list'] 123 cls.interface_path_list = rules_data['interface_path_list'] 124 print("MatchConfig 已完成初始化") 125 126 @classmethod 127 def interface_initialization(cls): 128 129 if cls.interface_js_data == {}: 130 print("INTERFACE_BUNDLE_NAME 开始初始化") 131 if not os.path.exists(cls.INTERFACE_BUNDLE_NAME_PATH): 132 print(f"{cls.INTERFACE_BUNDLE_NAME_PATH} 不存在,读取配置文件异常\n") 133 with open(cls.INTERFACE_BUNDLE_NAME_PATH, 'r') as file: 134 interface_data = json.load(file) 135 cls.interface_js_data = interface_data['sdk-js'] 136 cls.interface_c_data = interface_data['sdk_c'] 137 cls.driver_interface = interface_data['driver_interface'] 138 139 print("INTERFACE_BUNDLE_NAME 已完成初始化") 140 141 @classmethod 142 def get_interface_json_js_data(cls): 143 if cls.interface_js_data == {}: 144 cls.interface_initialization() 145 return cls.interface_js_data 146 147 @classmethod 148 def get_interface_json_c_data(cls): 149 if cls.interface_c_data == {}: 150 cls.interface_initialization() 151 return cls.interface_c_data 152 153 @classmethod 154 def get_interface_json_driver_interface_data(cls): 155 if cls.driver_interface == {}: 156 cls.interface_initialization() 157 return cls.driver_interface 158 159 @classmethod 160 def get_interface_path_list(cls): 161 if cls.interface_path_list == []: 162 cls.initialization() 163 return cls.interface_path_list 164 165 @classmethod 166 def get_exception_path(cls): 167 if cls.exception_path == {}: 168 cls.initialization() 169 return cls.exception_path 170 171 @classmethod 172 def get_all_com_path(cls): 173 if cls.all_com_path == {}: 174 cls.initialization() 175 return cls.all_com_path 176 177 @classmethod 178 def get_skip_judge_build_path(cls): 179 if cls.skip_judge_build_path == {}: 180 cls.initialization() 181 return cls.skip_judge_build_path 182 183 @classmethod 184 def get_temple_list(cls): 185 if cls.temple_list == []: 186 cls.initialization() 187 return cls.temple_list 188 189 @classmethod 190 def get_acts_All_template_ex_list(cls): 191 if cls.acts_All_template_ex_list == []: 192 cls.initialization() 193 return cls.acts_All_template_ex_list 194 195 @classmethod 196 def get_xts_path_list(cls): 197 if cls.xts_path_list == []: 198 cls.initialization() 199 return cls.xts_path_list 200 201 @classmethod 202 def initialization_white_list(cls): 203 if cls.white_list_repo == {}: 204 print("白名单开始初始化") 205 if not os.path.exists(cls.WHITE_LIST_PATH): 206 print(f"{cls.WHITE_LIST_PATH} 不存在,读取配置文件异常") 207 with open(cls.WHITE_LIST_PATH, 'r') as file: 208 white_file = json.load(file) 209 white_repos = white_file["repo_list"] 210 for white_repo in white_repos: 211 cls.white_list_repo[white_repo["path"]] = white_repo 212 print("白名单已完成初始化") 213 214 @classmethod 215 def get_white_list_repo(cls): 216 if cls.white_list_repo == {}: 217 cls.initialization_white_list() 218 return cls.white_list_repo 219 220 @classmethod 221 def initialization_escape_list(cls): 222 if cls.escape_list == []: 223 print("逃生仓列表 开始初始化") 224 if not os.path.exists(cls.ESCAPE_PATH): 225 print(f"{cls.ESCAPE_PATH} 不存在,无逃生仓") 226 return 227 with open(cls.ESCAPE_PATH, 'r') as file: 228 escape_map = json.load(file) 229 cls.escape_list = escape_map.keys() 230 print("逃生仓列表 已完成初始化") 231 232 @classmethod 233 def get_escape_list(cls): 234 if cls.escape_list == []: 235 cls.initialization_escape_list() 236 return cls.escape_list 237 238 @classmethod 239 def get_uncompile_suite_list(cls, xts_root_dir): 240 xts_suite = PathUtils.get_root_target(xts_root_dir) 241 if xts_suite in cls.uncompile_suite: 242 return cls.uncompile_suite[xts_suite] 243 else: 244 xts_name = os.path.basename(xts_root_dir) 245 UNCOMPILE_PATH = os.path.join(HOME, "test", "xts", xts_name, "ci_uncompile_suite.json") 246 if not os.path.exists(UNCOMPILE_PATH): 247 print(f"{UNCOMPILE_PATH} 不存在,读取不参与编译测试套异常") 248 return [] 249 with open(UNCOMPILE_PATH, 'r') as file: 250 cls.uncompile_suite[xts_suite] = json.load(file) 251 return cls.uncompile_suite[xts_suite] 252 253class XTSTargetUtils: 254 255 @staticmethod 256 def get_current_Build(xts_root_dir, current_dir): 257 while PathUtils.is_parent_path(xts_root_dir, current_dir): 258 # 当前目录是否包含需跳过的keywords 259 if PathUtils.isMatchRules(current_dir, MatchConfig.get_skip_judge_build_path()): 260 current_dir = os.path.dirname(current_dir) 261 continue 262 # 检查当前目录下是否存在BUILD.gn文件 263 build_gn_path = os.path.join(current_dir, 'BUILD.gn') 264 if os.path.exists(build_gn_path): 265 return build_gn_path 266 # 如果没有找到,向上一层目录移动 267 current_dir = os.path.dirname(current_dir) 268 # xts仓最外层均有BUILD.gn文件 269 return current_dir 270 271 # 路径获取target 272 @staticmethod 273 def getTargetfromPath(xts_root_dir, path) -> list: 274 if path == xts_root_dir: 275 root_target = PathUtils.get_all_build_target(xts_root_dir) 276 return root_target 277 build_file = XTSTargetUtils.get_current_Build(xts_root_dir, path) 278 targets = XTSTargetUtils.getTargetFromBuild(build_file) 279 if targets == None: 280 return XTSTargetUtils.getTargetfromPath(xts_root_dir, os.path.dirname(os.path.dirname(build_file))) 281 return targets 282 283 @staticmethod 284 def getTargetFromBuild(build_File) -> list: 285 pattern = re.compile(r'(\b(?:' + '|'.join( 286 re.escape(word) for word in MatchConfig.get_temple_list()) + r')\b)\("([^"]*)"\)') 287 with open(build_File, 'r', encoding='utf-8') as file: 288 content = file.read() 289 matches = pattern.findall(content) 290 targets = [match[1] for match in matches] 291 relative_path = os.path.relpath(os.path.dirname(build_File), HOME) 292 if len(targets) > 1: 293 deps = XTSTargetUtils.getDepsinBuild(content) 294 # 编译本gn中未被依赖的目标 295 targets = [item for item in targets if item not in deps] 296 return [f"{relative_path}:{item}" for item in targets] 297 298 @staticmethod 299 def getDepsinBuild(build): 300 # 定义正则表达式模式来匹配deps数组 301 pattern = re.compile(r'deps\s*=\s*\[\s*(?P<deps>.*?)\s*\]', re.DOTALL) 302 # pattern = r'\s*deps\s*=\s*<deps>' 303 # 搜索文本中的匹配项 304 matches = pattern.findall(build) 305 all_deps = [] 306 307 for match in matches: 308 # 分割字符串并去除双引号和空格 309 deps_list = [dep.strip('\n').strip().strip('"').lstrip(':') for dep in match.split(',')] 310 all_deps.extend(deps_list) 311 312 return all_deps 313 314 @staticmethod 315 def getPathsByBundle(bundle, test_home) -> list: 316 matching_files = [] 317 # 遍历根目录及其子目录 318 for root, dirs, files in os.walk(test_home): 319 if PathUtils.isMatchRules(root, MatchConfig.get_exception_path()): 320 continue 321 for file in files: 322 if file == 'BUILD.gn': 323 file_path = os.path.join(root, file) 324 # 读取文件内容 325 with open(file_path, 'r', encoding='utf-8') as f: 326 content = f.read() 327 # 检查是否包含bundle 328 for bundle_ in bundle: 329 part_name = f'part_name = "{bundle_}"' 330 if part_name in content: 331 matching_files.append(root) 332 continue 333 return matching_files 334 335 336class PathUtils: 337 338 # 路径列表简化 339 @staticmethod 340 def removeSubandDumpPath(path_list: list) -> list: 341 # 排序,确保父目录在子目录之前,减少运算 342 path_list.sort() 343 # 存储最小集 344 minimal_paths_set = set() 345 # 记录已存在的父目录的全部未添加编译的子目录 346 parent_dirs = {} 347 348 for path in path_list: 349 # 检查当前路径或其父路径是否已经在最小集中 350 isinclude = False 351 for m_path in minimal_paths_set: 352 if PathUtils.is_parent_path(m_path, path): 353 isinclude = True 354 break 355 # 添加逻辑 356 if not isinclude: 357 PathUtils.addPathClean(path, minimal_paths_set, parent_dirs) 358 359 return list(minimal_paths_set) 360 361 @staticmethod 362 def addPathClean(path, minimal_paths_set, parent_dirs): 363 # 检查当前路径的首层父目录是否在最小集中 364 parent_path = os.path.dirname(path) 365 if parent_path in parent_dirs: 366 # 在-原list修改 367 subdirs = parent_dirs[parent_path] 368 else: 369 # 不在-记录父目录及本目录 370 subdirs = [os.path.join(parent_path, d) for d in os.listdir(parent_path) if 371 os.path.isdir(os.path.join(parent_path, d))] 372 parent_dirs[parent_path] = subdirs 373 subdirs.remove(path) 374 minimal_paths_set.add(path) 375 # 检查是否替换为添加其直接父目录 376 if len(subdirs) == 0: 377 del parent_dirs[parent_path] 378 # minimal_paths_sets删除parent_path子目录 379 for d in os.listdir(parent_path): 380 p = os.path.join(parent_path, d) 381 if os.path.isdir(p) and p in minimal_paths_set: 382 minimal_paths_set.remove(os.path.join(parent_path, d)) 383 PathUtils.addPathClean(parent_path, minimal_paths_set, parent_dirs) 384 385 @staticmethod 386 def get_current_exist(root_path, path) -> str: 387 current_dir = path 388 while PathUtils.is_parent_path(root_path, current_dir): 389 if os.path.exists(current_dir): 390 return current_dir 391 current_dir = os.path.dirname(current_dir) 392 # 根目录必然存在 393 return root_path 394 395 @staticmethod 396 def is_parent_path(parent_path, child_path): 397 # 获取公共路径 398 common_path = os.path.commonpath([parent_path, child_path]) 399 return common_path == parent_path 400 401 @staticmethod 402 def get_all_build_target(xts_root_dir): 403 if xts_root_dir.endswith("acts"): 404 return MatchConfig.get_acts_All_template_ex_list() 405 return [PathUtils.get_root_target(xts_root_dir)] 406 407 @staticmethod 408 def get_root_target(xts_root_dir): 409 xts_suite = os.path.basename(xts_root_dir) 410 # relative_path = os.path.relpath(xts_root_dir, HOME) 411 target = f"xts_{xts_suite}" 412 return target 413 414 @staticmethod 415 def isMatchRules(file, rules): 416 string_rules = rules["string_rules"] 417 re_rules = rules["re_rules"] 418 for rule in string_rules: 419 if rule in file: 420 return True 421 for rule in re_rules: 422 if re.compile(rule).search(file): 423 return True 424 return False 425 426 @staticmethod 427 def isTargetContains(targetFiles, file) -> bool: 428 for f in targetFiles: 429 if PathUtils.is_parent_path(f, file): 430 return True 431 return False 432