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