• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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