• 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 stat
18import json
19import argparse
20import re
21import pandas as pd
22from bundle_check.bundle_check_common import BundleCheckTools
23from bundle_check.warning_info import BCWarnInfo
24
25
26class OhosInfo:
27    g_root_path = BundleCheckTools.get_root_path()
28    g_ohos_version = BundleCheckTools.get_ohos_version(g_root_path)
29
30
31def check_all_bundle_json(path:str) -> list:
32    '''
33    @func: 检查指定目录下所有 bundle.json 的文件规范。
34    '''
35
36    if os.path.isabs(path):
37        target_path = path
38    else:
39        target_path = os.path.join(OhosInfo.g_root_path, os.path.normpath(path))
40
41    cur_path = os.getcwd()
42    os.chdir(target_path)
43
44    all_bundle = get_all_bundle_json()
45    all_error = []
46
47    for bundle_json_path in all_bundle:
48        bundle_path = bundle_json_path.strip()
49        bundle = BundleJson(bundle_path)
50        bundle_error = bundle.check()
51
52        subsystem_name = bundle.subsystem_name
53        component_name = bundle.component_name
54        if len(subsystem_name) == 0:
55            subsystem_name = "Unknow"
56        if len(component_name) == 0:
57            component_name = "Unknow"
58
59        if not bundle_error:
60            continue
61        for item in bundle_error:
62            item['rule'] = BCWarnInfo.CHECK_RULE_2_1
63            item['path'] = bundle_path
64            item['component'] = component_name
65            item['subsystem'] = subsystem_name
66        all_error.extend(bundle_error)
67    count = len(all_error)
68
69    print('-------------------------------')
70    print('Bundle.json check successfully!')
71    print('There are {} issues in total'.format(count))
72    print('-------------------------------')
73    os.chdir(cur_path)
74    return all_error
75
76
77def get_all_bundle_json(path:str = '.') -> list:
78    '''
79    @func: 获取所有源码工程中所有 bundle.json 文件。
80    '''
81    exclude_list = [
82        r'"./out/*"',
83        r'"./.repo/*"'
84    ]
85    cmd = "find {} -name {}".format(path, "bundle.json")
86    for i in exclude_list:
87        cmd += " ! -path {}".format(i)
88    bundle_josn_list = os.popen(cmd).readlines()
89    return bundle_josn_list
90
91
92class BundlesCheck:
93    '''导出全量检查的结果。'''
94
95    @staticmethod
96    def to_json(all_errors:dict,
97                output_path:str = '.',
98                output_name:str = 'all_bundle_error.json'):
99        '''@func: 导出所有错误到 json 格式文件中。'''
100        all_errors = check_all_bundle_json(OhosInfo.g_root_path)
101        all_error_json = json.dumps(all_errors,
102                                    indent=4,
103                                    ensure_ascii=False,
104                                    separators=(', ', ': '))
105        out_path = os.path.normpath(output_path) + '/' + output_name
106
107        flags = os.O_WRONLY | os.O_CREAT
108        modes = stat.S_IWUSR | stat.S_IRUSR
109        with os.fdopen(os.open(out_path, flags, modes), 'w') as file:
110            file.write(all_error_json)
111        print("Please check " + out_path)
112
113    @staticmethod
114    def to_df(path:str = None) -> pd.DataFrame:
115        '''将所有错误的 dict 数据类型转为 pd.DataFrame 类型。'''
116        if path is None:
117            path = OhosInfo.g_root_path
118        else:
119            path = os.path.join(OhosInfo.g_root_path, path)
120        all_errors = check_all_bundle_json(path)
121        columns = ['子系统', '部件', '文件', '违反规则', '详细', '说明']
122        errors_list = []
123        for item in all_errors:
124            error_temp = [
125                item['subsystem'],
126                item['component'],
127                item['path'],
128                item['rule'],
129                "line" + str(item['line']) + ": " + item['contents'],
130                item['description']
131            ]
132            errors_list.append(error_temp)
133        ret = pd.DataFrame(errors_list, columns=columns)
134        return ret
135
136    @staticmethod
137    def to_excel(output_path:str = '.',
138                 output_name:str = 'all_bundle_error.xlsx'):
139        '''
140        @func: 导出所有错误到 excel 格式文件中。
141        '''
142        err_df = BundlesCheck.to_df()
143        outpath = os.path.normpath(output_path) + '/' + output_name
144        err_df.to_excel(outpath, index=None)
145        print('Please check ' + outpath)
146
147
148class BundleJson(object):
149    '''以 bundle.josn 路径来初始化的对象,包含关于该 bundle.josn 的一些属性和操作。
150    @var:
151      - ``__all_errors`` : 表示该文件的所有错误列表。
152      - ``__json`` : 表示将该 josn 文件转为 dict 类型后的内容。
153      - ``__lines`` : 表示将该 josn 文件转为 list 类型后的内容。
154
155    @method:
156      - ``component_name()`` : 返回该 bundle.json 所在部件名。
157      - ``subsystem_name()`` : 返回该 bundle.json 所在子系统名。
158      - ``readlines()`` : 返回该 bundle.json 以每一行内容为元素的 list。
159      - ``get_line_number(s)`` : 返回 s 字符串在该 bundle.josn 中的行号,未知则返回 0。
160      - ``check()`` : 静态检查该 bundle.json,返回错误告警 list。
161    '''
162
163    def __init__(self, path:str) -> None:
164        self.__all_errors = [] # 该文件的所有错误列表
165        self.__json  = {} # 将该 josn 文件转为字典类型内容
166        self.__lines = [] # 将该 josn 文件转为列表类型内容
167        with open(path, 'r') as file:
168            try:
169                self.__json = json.load(file)
170            except json.decoder.JSONDecodeError as error:
171                raise ValueError("'" + path + "'" + " is not a json file.")
172        with open(path, 'r') as file:
173            self.__lines = file.readlines()
174
175    @property
176    def component_name(self) -> str:
177        return self.__json.get('component').get('name')
178
179    @property
180    def subsystem_name(self) -> str: # 目前存在为空的情况
181        return self.__json.get('component').get('subsystem')
182
183    def readlines(self) -> list:
184        return self.__lines
185
186    def get_line_number(self, string) -> int:
187        '''
188        @func: 获取指定字符串所在行号。
189        '''
190        line_num = 0
191        for line in self.__lines:
192            line_num += 1
193            if string in line:
194                return line_num
195        return 0
196
197    def check(self) -> list:
198        '''
199        @func: 检查该 bundle.json 规范。
200        @note: 去除检查 version 字段。
201        '''
202        err_name = self.check_name()
203        err_segment = self.check_segment()
204        err_component = self.check_component()
205        if err_name:
206            self.__all_errors.append(err_name)
207        if err_segment:
208            self.__all_errors.extend(err_segment)
209        if err_component:
210            self.__all_errors.extend(err_component)
211
212        return self.__all_errors
213
214    # name
215    def check_name(self) -> dict:
216        bundle_error = dict(line=0, contents='"name"')
217
218        if 'name' not in self.__json:
219            bundle_error["description"] = BCWarnInfo.NAME_NO_FIELD
220            return bundle_error
221
222        name = self.__json['name']
223        bundle_error["line"] = self.get_line_number('"name"')
224        if not name: # 为空
225            bundle_error["description"] = BCWarnInfo.NAME_EMPTY
226            return bundle_error
227
228        bundle_error["description"] = BCWarnInfo.NAME_FORMAT_ERROR + \
229                BCWarnInfo.COMPONENT_NAME_FROMAT + \
230                BCWarnInfo.COMPONENT_NAME_FROMAT_LEN
231        match = BundleCheckTools.match_bundle_full_name(name)
232        if not match:
233            return bundle_error
234        match = BundleCheckTools.match_unix_like_name(name.split('/')[1])
235        if not match:
236            return bundle_error
237
238        return dict()
239
240    # version
241    def check_version(self) -> dict:
242        bundle_error = dict(line=0, contents='version')
243
244        if 'version' not in self.__json:
245            bundle_error["description"] = BCWarnInfo.VERSION_NO_FIELD
246            return bundle_error
247
248        bundle_error["line"] = self.get_line_number('"version": ')
249        if len(self.__json['version']) < 3: # example 3.1
250            bundle_error["description"] = BCWarnInfo.VERSION_ERROR
251            return bundle_error
252
253        if self.__json['version'] != OhosInfo.g_ohos_version:
254            bundle_error['description'] = BCWarnInfo.VERSION_ERROR + \
255                ' current ohos version is: ' + OhosInfo.g_ohos_version
256            return bundle_error
257        return dict()
258
259    # segment
260    def check_segment(self) -> list:
261        bundle_error_segment = []
262        bundle_error = dict(line=0, contents='"segment"')
263
264        if 'segment' not in self.__json:
265            bundle_error["description"] = BCWarnInfo.SEGMENT_NO_FIELD
266            bundle_error_segment.append(bundle_error)
267            return bundle_error_segment
268
269        bundle_error["line"] = self.get_line_number('"segment":')
270        if 'destPath' not in self.__json['segment']:
271            bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_NO_FIELD
272            bundle_error_segment.append(bundle_error)
273            return bundle_error_segment
274
275        path = self.__json['segment']['destPath']
276        bundle_error["line"] = self.get_line_number('"destPath":')
277        bundle_error["contents"] = '"segment:destPath"'
278        if not path:
279            bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_EMPTY
280            bundle_error_segment.append(bundle_error)
281            return bundle_error_segment
282
283        if type(path) != str:
284            bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_UNIQUE
285            bundle_error_segment.append(bundle_error)
286            return bundle_error_segment
287
288        if os.path.isabs(path):
289            bundle_error["description"] = BCWarnInfo.SEGMENT_DESTPATH_ABS
290            bundle_error_segment.append(bundle_error)
291            return bundle_error_segment
292
293        return bundle_error_segment
294
295    # component
296    def check_component(self) -> list:
297        bundle_error_component = []
298
299        if 'component' not in self.__json:
300            bundle_error = dict(line=0, contents='"component"',
301                                description=BCWarnInfo.COMPONENT_NO_FIELD)
302            bundle_error_component.append(bundle_error)
303            return bundle_error_component
304
305        component = self.__json.get('component')
306        component_line = self.get_line_number('"component":')
307        self._check_component_name(component, component_line, bundle_error_component)
308        self._check_component_subsystem(component, component_line, bundle_error_component)
309        self._check_component_syscap(component, bundle_error_component)
310        self._check_component_ast(component, component_line, bundle_error_component)
311        self._check_component_rom(component, component_line, bundle_error_component)
312        self._check_component_ram(component, component_line, bundle_error_component)
313        self._check_component_deps(component, component_line, bundle_error_component)
314
315        return bundle_error_component
316
317        # component name
318    def _check_component_name(self, component, component_line, bundle_error_component):
319        if 'name' not in component:
320            bundle_error = dict(line=component_line,
321                                contents='"component"',
322                                description=BCWarnInfo.COMPONENT_NAME_NO_FIELD)
323            bundle_error_component.append(bundle_error)
324        else:
325            bundle_error = dict(line=component_line + 1,
326                                contents='"component:name"')  # 同名 "name" 暂用 "component" 行号+1
327            if not component['name']:
328                bundle_error["description"] = BCWarnInfo.COMPONENT_NAME_EMPTY
329                bundle_error_component.append(bundle_error)
330            elif 'name' in self.__json and '/' in self.__json['name']:
331                if component['name'] != self.__json['name'].split('/')[1]:
332                    bundle_error["description"] = BCWarnInfo.COMPONENT_NAME_VERACITY
333                    bundle_error_component.append(bundle_error)
334
335        # component subsystem
336    def _check_component_subsystem(self, component, component_line,
337                                   bundle_error_component):
338        if 'subsystem' not in component:
339            bundle_error = dict(line=component_line,
340                                contents="component",
341                                description=BCWarnInfo.COMPONENT_SUBSYSTEM_NO_FIELD)
342            bundle_error_component.append(bundle_error)
343        else:
344            bundle_error = dict(line=self.get_line_number('"subsystem":'),
345                                contents='"component:subsystem"')
346            if not component['subsystem']:
347                bundle_error["description"] = BCWarnInfo.COMPONENT_SUBSYSTEM_EMPTY
348                bundle_error_component.append(bundle_error)
349            elif not BundleCheckTools.is_all_lower(component['subsystem']):
350                bundle_error["description"] = BCWarnInfo.COMPONENT_SUBSYSTEM_LOWCASE
351                bundle_error_component.append(bundle_error)
352
353        # component syscap 可选且可以为空
354    def _check_component_syscap(self, component, bundle_error_component):
355        if 'syscap' not in component:
356            pass
357        elif component['syscap']:
358            bundle_error = dict(line=self.get_line_number('"syscap":'),
359                                contents='"component:syscap"')
360            err = [] # 收集所有告警
361            for i in component['syscap']:
362                # syscap string empty
363                if not i:
364                    err.append(BCWarnInfo.COMPONENT_SYSCAP_STRING_EMPTY)
365                    continue
366                match = re.match(r'^SystemCapability(\.[A-Z][a-zA-Z]{1,63}){2,6}$', i)
367                if not match:
368                    err.append(BCWarnInfo.COMPONENT_SYSCAP_STRING_FORMAT_ERROR)
369            errs = list(set(err)) # 去重告警
370            if errs:
371                bundle_error["description"] = str(errs)
372                bundle_error_component.append(bundle_error)
373
374        # component adapted_system_type
375    def _check_component_ast(self, component, component_line, bundle_error_component):
376        if 'adapted_system_type' not in component:
377            bundle_error = dict(line=component_line, contents='"component"',
378                                description=BCWarnInfo.COMPONENT_AST_NO_FIELD)
379            bundle_error_component.append(bundle_error)
380            return
381
382        bundle_error = dict(line=self.get_line_number('"adapted_system_type":'),
383                            contents='"component:adapted_system_type"')
384        ast = component["adapted_system_type"]
385        if not ast:
386            bundle_error["description"] = BCWarnInfo.COMPONENT_AST_EMPTY
387            bundle_error_component.append(bundle_error)
388            return
389
390        type_set = tuple(set(ast))
391        if len(ast) > 3 or len(type_set) != len(ast):
392            bundle_error["description"] = BCWarnInfo.COMPONENT_AST_NO_REP
393            bundle_error_component.append(bundle_error)
394            return
395
396        all_type_list = ["mini", "small", "standard"]
397        # 不符合要求的 type
398        error_type = [i for i in ast if i not in all_type_list]
399        if error_type:
400            bundle_error["description"] = BCWarnInfo.COMPONENT_AST_NO_REP
401            bundle_error_component.append(bundle_error)
402        return
403
404        # component rom
405    def _check_component_rom(self, component, component_line, bundle_error_component):
406        if 'rom' not in component:
407            bundle_error = dict(line=component_line, contents='"component:rom"',
408                                description=BCWarnInfo.COMPONENT_ROM_NO_FIELD)
409            bundle_error_component.append(bundle_error)
410        elif not component["rom"]:
411            bundle_error = dict(line=self.get_line_number('"rom":'),
412                                contents='"component:rom"',
413                                description=BCWarnInfo.COMPONENT_ROM_EMPTY)
414            bundle_error_component.append(bundle_error)
415        else:
416            bundle_error = dict(line=self.get_line_number('"rom":'),
417                                contents='"component:rom"')
418            num, unit = BundleCheckTools.split_by_unit(component["rom"])
419            if num < 0:
420                bundle_error["description"] = BCWarnInfo.COMPONENT_ROM_SIZE_ERROR # 非数值或小于0
421                bundle_error_component.append(bundle_error)
422            elif unit:
423                unit_list = ["KB", "KByte", "MByte", "MB"]
424                if unit not in unit_list:
425                    bundle_error["description"] = BCWarnInfo.COMPONENT_ROM_UNIT_ERROR # 单位有误
426                    bundle_error_component.append(bundle_error)
427
428        # component ram
429    def _check_component_ram(self, component, component_line, bundle_error_component):
430        if 'ram' not in component:
431            bundle_error = dict(line=component_line, contents='"component:ram"',
432                                description=BCWarnInfo.COMPONENT_RAM_NO_FIELD)
433            bundle_error_component.append(bundle_error)
434        elif not component["ram"]:
435            bundle_error = dict(line=self.get_line_number('"ram":'),
436                                contents='"component:ram"',
437                                description=BCWarnInfo.COMPONENT_RAM_EMPTY)
438            bundle_error_component.append(bundle_error)
439        else:
440            bundle_error = dict(line=self.get_line_number('"ram":'),
441                                contents='"component:ram"')
442            num, unit = BundleCheckTools.split_by_unit(component["ram"])
443            if num <= 0:
444                bundle_error["description"] = BCWarnInfo.COMPONENT_RAM_SIZE_ERROR # 非数值或小于0
445                bundle_error_component.append(bundle_error)
446            elif unit:
447                unit_list = ["KB", "KByte", "MByte", "MB"]
448                if unit not in unit_list:
449                    bundle_error["description"] = BCWarnInfo.COMPONENT_RAM_UNIT_ERROR # 单位有误
450                    bundle_error_component.append(bundle_error)
451
452        # component deps
453    def _check_component_deps(self, component, component_line, bundle_error_component):
454        if 'deps' not in component:
455            bundle_error = dict(line=component_line, contents='"component:deps"',
456                                description=BCWarnInfo.COMPONENT_DEPS_NO_FIELD)
457            bundle_error_component.append(bundle_error)
458        else:
459            pass
460
461
462def parse_args():
463    parser = argparse.ArgumentParser()
464    # exclusive output format
465    ex_format = parser.add_mutually_exclusive_group()
466    ex_format.add_argument("--xlsx", help="output format: xls(default).",
467                           action="store_true")
468    ex_format.add_argument("--json", help="output format: json.",
469                           action="store_true")
470    # exclusive input
471    ex_input = parser.add_mutually_exclusive_group()
472    ex_input.add_argument("-P", "--project", help="project root path.", type=str)
473    ex_input.add_argument("-p", "--path", help="bundle.json path list.", nargs='+')
474    # output path
475    parser.add_argument("-o", "--output", help="ouput path.")
476    args = parser.parse_args()
477
478    export_path = '.'
479    if args.output:
480        export_path = args.output
481
482    if args.project:
483        if not BundleCheckTools.is_project(args.project):
484            print("'" + args.project + "' is not a oopeharmony project.")
485            exit(1)
486
487        if args.json:
488            BundlesCheck.to_json(export_path)
489        else:
490            BundlesCheck.to_excel(export_path)
491    elif args.path:
492        bundle_list_error = {}
493        for bundle_json_path in args.path:
494            bundle = BundleJson(bundle_json_path)
495            error_field = bundle.check()
496            if error_field:
497                bundle_list_error[bundle_json_path] = \
498                    dict([(BCWarnInfo.CHECK_RULE_2_1, error_field)])
499        # temp
500        test_json = json.dumps(bundle_list_error,
501                               indent=4, separators=(', ', ': '),
502                               ensure_ascii=False)
503        print(test_json)
504    else:
505        print("use '-h' get help.")
506
507
508if __name__ == '__main__':
509    parse_args()
510