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