1bae44755Sopenharmony_ci#!/usr/bin/env python3
2bae44755Sopenharmony_ci# -*- coding: utf-8 -*-
3bae44755Sopenharmony_ci# Copyright (c) 2022 Huawei Device Co., Ltd.
4bae44755Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
5bae44755Sopenharmony_ci# you may not use this file except in compliance with the License.
6bae44755Sopenharmony_ci# You may obtain a copy of the License at
7bae44755Sopenharmony_ci#
8bae44755Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
9bae44755Sopenharmony_ci#
10bae44755Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
11bae44755Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
12bae44755Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bae44755Sopenharmony_ci# See the License for the specific language governing permissions and
14bae44755Sopenharmony_ci# limitations under the License.
15bae44755Sopenharmony_ci
16bae44755Sopenharmony_ciimport os
17bae44755Sopenharmony_ciimport json
18bae44755Sopenharmony_ciimport re
19bae44755Sopenharmony_ciimport argparse
20bae44755Sopenharmony_cifrom prettytable import PrettyTable, ALL
21bae44755Sopenharmony_ci
22bae44755Sopenharmony_ci
23bae44755Sopenharmony_cidef get_args():
24bae44755Sopenharmony_ci    parser = argparse.ArgumentParser(add_help=True)
25bae44755Sopenharmony_ci    parser.add_argument(
26bae44755Sopenharmony_ci        "-p",
27bae44755Sopenharmony_ci        "--project_path",
28bae44755Sopenharmony_ci        default=r"./",
29bae44755Sopenharmony_ci        type=str,
30bae44755Sopenharmony_ci        help="root path of project. default: ./",
31bae44755Sopenharmony_ci    )
32bae44755Sopenharmony_ci    parser.add_argument(
33bae44755Sopenharmony_ci        "-t",
34bae44755Sopenharmony_ci        "--check_target",
35bae44755Sopenharmony_ci        type=str,
36bae44755Sopenharmony_ci        choices=["component_codec", "component_sdk", "sdk_codec"],
37bae44755Sopenharmony_ci        required=True,
38bae44755Sopenharmony_ci        help="the target to be compared",
39bae44755Sopenharmony_ci    )
40bae44755Sopenharmony_ci    parser.add_argument(
41bae44755Sopenharmony_ci        "-b",
42bae44755Sopenharmony_ci        "--bundles",
43bae44755Sopenharmony_ci        nargs="*",
44bae44755Sopenharmony_ci        type=str,
45bae44755Sopenharmony_ci        help="option take effect only when the check_target is component_codec. allow multiple json file. "
46bae44755Sopenharmony_ci             "default: all bundle.json file",
47bae44755Sopenharmony_ci    )
48bae44755Sopenharmony_ci    args = parser.parse_args()
49bae44755Sopenharmony_ci    return args
50bae44755Sopenharmony_ci
51bae44755Sopenharmony_ci
52bae44755Sopenharmony_cidef list_to_multiline(target_list: list):
53bae44755Sopenharmony_ci    return str(target_list).lstrip("[").rstrip("]").replace(", ", "\n")
54bae44755Sopenharmony_ci
55bae44755Sopenharmony_ci
56bae44755Sopenharmony_cidef convert_set_to_sorted_list(target_set) -> list:
57bae44755Sopenharmony_ci    return sorted(list(target_set))
58bae44755Sopenharmony_ci
59bae44755Sopenharmony_ci
60bae44755Sopenharmony_cidef add_dict_as_table_row(f_table: PrettyTable, d_dict: dict, out_converter=list_to_multiline) -> None:
61bae44755Sopenharmony_ci    s_keys = convert_set_to_sorted_list(d_dict.keys())
62bae44755Sopenharmony_ci    for i, k in enumerate(s_keys):
63bae44755Sopenharmony_ci        f_table.add_row([i + 1, k, out_converter(convert_set_to_sorted_list(d_dict.get(k)))])
64bae44755Sopenharmony_ci
65bae44755Sopenharmony_ci
66bae44755Sopenharmony_cidef bundle_syscap_post_handler(syscap: str) -> str:
67bae44755Sopenharmony_ci    return syscap.split('=')[0].strip()
68bae44755Sopenharmony_ci
69bae44755Sopenharmony_ci
70bae44755Sopenharmony_cidef read_value_from_json(filepath: str,
71bae44755Sopenharmony_ci                         key_hierarchy: tuple,
72bae44755Sopenharmony_ci                         result_dict: dict,
73bae44755Sopenharmony_ci                         post_handler=None
74bae44755Sopenharmony_ci                         ) -> None:
75bae44755Sopenharmony_ci    if os.path.exists(filepath) is False:
76bae44755Sopenharmony_ci        print('error: file "{}" not exist.'.format(filepath))
77bae44755Sopenharmony_ci        return
78bae44755Sopenharmony_ci    if not os.path.isfile(filepath):
79bae44755Sopenharmony_ci        print('error: "{}" is not a file.')
80bae44755Sopenharmony_ci        return
81bae44755Sopenharmony_ci    with open(filepath, 'r', encoding='utf-8') as f:
82bae44755Sopenharmony_ci        data = json.load(f)
83bae44755Sopenharmony_ci        for key in key_hierarchy:
84bae44755Sopenharmony_ci            try:
85bae44755Sopenharmony_ci                data = data[key]
86bae44755Sopenharmony_ci            except KeyError:
87bae44755Sopenharmony_ci                return
88bae44755Sopenharmony_ci            finally:
89bae44755Sopenharmony_ci                pass
90bae44755Sopenharmony_ci    data = [post_handler(x) for x in data if len(x) != 0 and not x.isspace()]
91bae44755Sopenharmony_ci    if len(data) != 0:
92bae44755Sopenharmony_ci        result_dict[filepath] = data
93bae44755Sopenharmony_ci
94bae44755Sopenharmony_ci
95bae44755Sopenharmony_cidef collect_syscap_from_codec(filepath: str, pattern: str = r'{"(.*)"') -> tuple:
96bae44755Sopenharmony_ci    """
97bae44755Sopenharmony_ci    从syscap_define.h收集syscap
98bae44755Sopenharmony_ci    :param filepath: 文件的路径
99bae44755Sopenharmony_ci    :param pattern: 匹配syscap的规则
100bae44755Sopenharmony_ci    :return: syscap_define.h中声明的syscap集合
101bae44755Sopenharmony_ci    """
102bae44755Sopenharmony_ci    array_syscap_set = set()
103bae44755Sopenharmony_ci    array_syscap_dict = dict()
104bae44755Sopenharmony_ci    ptrn = re.compile(pattern)
105bae44755Sopenharmony_ci    with open(filepath, "r") as f:
106bae44755Sopenharmony_ci        content = f.read()
107bae44755Sopenharmony_ci        array_syscap_set.update(re.findall(ptrn, content))
108bae44755Sopenharmony_ci    array_syscap_dict[filepath] = list()
109bae44755Sopenharmony_ci    for v in array_syscap_set:
110bae44755Sopenharmony_ci        array_syscap_dict.get(filepath).append(v)
111bae44755Sopenharmony_ci    return array_syscap_set, array_syscap_dict
112bae44755Sopenharmony_ci
113bae44755Sopenharmony_ci
114bae44755Sopenharmony_cidef collect_syscap_from_component(project_path: str,
115bae44755Sopenharmony_ci                                  black_dirs: tuple,
116bae44755Sopenharmony_ci                                  key_heirarchy: tuple,
117bae44755Sopenharmony_ci                                  bundles: list = None) -> tuple:
118bae44755Sopenharmony_ci    """
119bae44755Sopenharmony_ci    从部件的bundle.json中收集syscap
120bae44755Sopenharmony_ci    :param project_path: 项目根路径
121bae44755Sopenharmony_ci    :param black_dirs: 根路径下的一级目录的黑名单,不扫描这些bundle.json下面的内容
122bae44755Sopenharmony_ci    :param key_heirarchy: bundle.json中syscap所在的key的元组
123bae44755Sopenharmony_ci    :param bundles: 指定的bundle.json
124bae44755Sopenharmony_ci    :return: syscap_set,文件名和其syscap list组成的dict
125bae44755Sopenharmony_ci    """
126bae44755Sopenharmony_ci    result_dict = dict()
127bae44755Sopenharmony_ci    if bundles is None:
128bae44755Sopenharmony_ci        search_dir_list = [
129bae44755Sopenharmony_ci            os.path.join(project_path, x)
130bae44755Sopenharmony_ci            for x in os.listdir(project_path)
131bae44755Sopenharmony_ci            if os.path.isdir(os.path.join(project_path, x)) and x not in black_dirs
132bae44755Sopenharmony_ci        ]
133bae44755Sopenharmony_ci        for folder in search_dir_list:
134bae44755Sopenharmony_ci            output = os.popen("find {} -name bundle.json".format(folder))
135bae44755Sopenharmony_ci            for line in output:
136bae44755Sopenharmony_ci                line = line.strip()
137bae44755Sopenharmony_ci                read_value_from_json(line, key_heirarchy, result_dict, post_handler=bundle_syscap_post_handler)
138bae44755Sopenharmony_ci            output.close()
139bae44755Sopenharmony_ci    else:
140bae44755Sopenharmony_ci        for bundle in bundles:
141bae44755Sopenharmony_ci            read_value_from_json(bundle, key_heirarchy, result_dict, post_handler=bundle_syscap_post_handler)
142bae44755Sopenharmony_ci    result_set = set()
143bae44755Sopenharmony_ci    for v in result_dict.values():
144bae44755Sopenharmony_ci        result_set.update(v)
145bae44755Sopenharmony_ci    return result_set, result_dict
146bae44755Sopenharmony_ci
147bae44755Sopenharmony_ci
148bae44755Sopenharmony_cidef sdk_syscap_post_handler(syscap: str) -> str:
149bae44755Sopenharmony_ci    return syscap.strip().lstrip("*").lstrip().lstrip("*@syscap").strip()
150bae44755Sopenharmony_ci
151bae44755Sopenharmony_ci
152bae44755Sopenharmony_cidef collect_syscap_from_sdk(ts_path: str,
153bae44755Sopenharmony_ci                            pattern: str = r"\* *@syscap +((?i)SystemCapability\..*)",
154bae44755Sopenharmony_ci                            post_handler=None
155bae44755Sopenharmony_ci                            ) -> tuple:
156bae44755Sopenharmony_ci    """
157bae44755Sopenharmony_ci    从sdk下的*.d.ts中收集syscap
158bae44755Sopenharmony_ci    :param ts_path: ts文件的所在路径
159bae44755Sopenharmony_ci    :param pattern: 匹配syscap的pattern
160bae44755Sopenharmony_ci    :param post_handler: 对匹配到的syscap进行后置处理
161bae44755Sopenharmony_ci    :return: syscap_set,和由文件名、syscap list组成的dict
162bae44755Sopenharmony_ci    """
163bae44755Sopenharmony_ci    ts_list = list()
164bae44755Sopenharmony_ci    for item in os.listdir(ts_path):
165bae44755Sopenharmony_ci        file_path = os.path.join(ts_path, item)
166bae44755Sopenharmony_ci        if file_path.endswith(".d.ts") and os.path.isfile(file_path):
167bae44755Sopenharmony_ci            ts_list.append(file_path)
168bae44755Sopenharmony_ci    syscap_dict = dict()
169bae44755Sopenharmony_ci    ptrn = re.compile(pattern)
170bae44755Sopenharmony_ci    for ts in ts_list:
171bae44755Sopenharmony_ci        with open(ts, 'r') as f:
172bae44755Sopenharmony_ci            content = f.read()
173bae44755Sopenharmony_ci            sub_syscap_list = re.findall(ptrn, content)
174bae44755Sopenharmony_ci            sub_syscap_set = set()
175bae44755Sopenharmony_ci            for syscap in sub_syscap_list:
176bae44755Sopenharmony_ci                sub_syscap_set.add(post_handler(syscap))
177bae44755Sopenharmony_ci            syscap_dict[ts] = sub_syscap_set
178bae44755Sopenharmony_ci    syscap_set = set()
179bae44755Sopenharmony_ci    for v in syscap_dict.values():
180bae44755Sopenharmony_ci        syscap_set.update(v)
181bae44755Sopenharmony_ci    return syscap_set, syscap_dict
182bae44755Sopenharmony_ci
183bae44755Sopenharmony_ci
184bae44755Sopenharmony_cidef find_files_containes_value(value_set: set, file_values_dict: dict) -> dict:
185bae44755Sopenharmony_ci    """
186bae44755Sopenharmony_ci    查看包含指定值的文件
187bae44755Sopenharmony_ci    :param value_set: 指定值
188bae44755Sopenharmony_ci    :param file_values_dict: 文件和值组成的dict
189bae44755Sopenharmony_ci    :return: 值和文件组成的dict
190bae44755Sopenharmony_ci    """
191bae44755Sopenharmony_ci    value_files_dict = dict()
192bae44755Sopenharmony_ci    for v in value_set:
193bae44755Sopenharmony_ci        filename_set = set()
194bae44755Sopenharmony_ci        for file in file_values_dict.keys():
195bae44755Sopenharmony_ci            if v in file_values_dict[file]:
196bae44755Sopenharmony_ci                filename_set.add(file)
197bae44755Sopenharmony_ci        if 0 != len(filename_set):
198bae44755Sopenharmony_ci            value_files_dict[v] = filename_set
199bae44755Sopenharmony_ci    return value_files_dict
200bae44755Sopenharmony_ci
201bae44755Sopenharmony_ci
202bae44755Sopenharmony_cidef mutual_diff(a_set: set, b_set: set) -> tuple:
203bae44755Sopenharmony_ci    return a_set.difference(b_set), b_set.difference(a_set)
204bae44755Sopenharmony_ci
205bae44755Sopenharmony_ci
206bae44755Sopenharmony_cidef print_inconsistent(diff_set: set, a_name: str, b_name: str, table: PrettyTable,
207bae44755Sopenharmony_ci                       value_file_dict: dict) -> None:
208bae44755Sopenharmony_ci    table.clear()
209bae44755Sopenharmony_ci    if len(diff_set) != 0:
210bae44755Sopenharmony_ci        table.field_names = ["index", "SysCap only in {}".format(a_name), "Files"]
211bae44755Sopenharmony_ci        add_dict_as_table_row(table, value_file_dict)
212bae44755Sopenharmony_ci    elif len(diff_set) == 0:
213bae44755Sopenharmony_ci        table.field_names = ["All SysCap in {} have been Covered by {}".format(a_name, b_name)]
214bae44755Sopenharmony_ci    print(table)
215bae44755Sopenharmony_ci    print()
216bae44755Sopenharmony_ci
217bae44755Sopenharmony_ci
218bae44755Sopenharmony_cidef print_consistent(a_diff_b_set: set, b_diff_a_set: set, table: PrettyTable, a_name: str, b_name: str) -> bool:
219bae44755Sopenharmony_ci    if len(a_diff_b_set) == 0 and len(b_diff_a_set) == 0:
220bae44755Sopenharmony_ci        table.field_names = ["{} and {} are Consistent".format(a_name, b_name)]
221bae44755Sopenharmony_ci        print(table)
222bae44755Sopenharmony_ci        print()
223bae44755Sopenharmony_ci        return True
224bae44755Sopenharmony_ci    return False
225bae44755Sopenharmony_ci
226bae44755Sopenharmony_ci
227bae44755Sopenharmony_cidef print_check_result(a_diff_b_set: set,
228bae44755Sopenharmony_ci                       b_diff_a_set: set,
229bae44755Sopenharmony_ci                       a_name: str,
230bae44755Sopenharmony_ci                       b_name: str,
231bae44755Sopenharmony_ci                       a_value_file_dict: dict,
232bae44755Sopenharmony_ci                       b_value_file_dict: dict,
233bae44755Sopenharmony_ci                       ) -> None:
234bae44755Sopenharmony_ci    f_table = PrettyTable()
235bae44755Sopenharmony_ci    f_table.hrules = ALL
236bae44755Sopenharmony_ci    consistent_flag = print_consistent(a_diff_b_set, b_diff_a_set, f_table, a_name, b_name)
237bae44755Sopenharmony_ci    if not consistent_flag:
238bae44755Sopenharmony_ci        print_inconsistent(a_diff_b_set, a_name, b_name, f_table, a_value_file_dict)
239bae44755Sopenharmony_ci        print_inconsistent(b_diff_a_set, b_name, a_name, f_table, b_value_file_dict)
240bae44755Sopenharmony_ci
241bae44755Sopenharmony_ci
242bae44755Sopenharmony_cidef check_component_and_codec(project_path, syscap_define_path: str, component_black_dirs: tuple,
243bae44755Sopenharmony_ci                              bundle_key_heirarchy: tuple, bundles=None):
244bae44755Sopenharmony_ci    component_syscap_set, component_syscap_dict = collect_syscap_from_component(
245bae44755Sopenharmony_ci        project_path, black_dirs=component_black_dirs, key_heirarchy=bundle_key_heirarchy, bundles=bundles
246bae44755Sopenharmony_ci    )
247bae44755Sopenharmony_ci    array_syscap_set, array_syscap_dict = collect_syscap_from_codec(syscap_define_path)
248bae44755Sopenharmony_ci    component_diff_array, array_diff_component = mutual_diff(component_syscap_set, array_syscap_set)
249bae44755Sopenharmony_ci    value_component_dict = find_files_containes_value(
250bae44755Sopenharmony_ci        component_diff_array, component_syscap_dict
251bae44755Sopenharmony_ci    )
252bae44755Sopenharmony_ci    value_h_dict = find_files_containes_value(array_diff_component, array_syscap_dict)
253bae44755Sopenharmony_ci    print_check_result(component_diff_array, array_diff_component, a_name="Component", b_name="Codec",
254bae44755Sopenharmony_ci                       a_value_file_dict=value_component_dict, b_value_file_dict=value_h_dict)
255bae44755Sopenharmony_ci
256bae44755Sopenharmony_ci
257bae44755Sopenharmony_cidef check_component_and_sdk(project_path, component_black_dirs: tuple, component_key_heirarchy: tuple, sdk_path: str):
258bae44755Sopenharmony_ci    component_syscap_set, component_syscap_dict = collect_syscap_from_component(
259bae44755Sopenharmony_ci        project_path, black_dirs=component_black_dirs, key_heirarchy=component_key_heirarchy
260bae44755Sopenharmony_ci    )
261bae44755Sopenharmony_ci    ts_syscap_set, ts_syscap_dict = collect_syscap_from_sdk(ts_path=sdk_path,
262bae44755Sopenharmony_ci                                                            post_handler=sdk_syscap_post_handler)
263bae44755Sopenharmony_ci    component_diff_ts, ts_diff_component = mutual_diff(component_syscap_set, ts_syscap_set)
264bae44755Sopenharmony_ci    value_ts_dict = find_files_containes_value(ts_diff_component, ts_syscap_dict)
265bae44755Sopenharmony_ci    value_component_dict = find_files_containes_value(
266bae44755Sopenharmony_ci        component_diff_ts, component_syscap_dict
267bae44755Sopenharmony_ci    )
268bae44755Sopenharmony_ci    print_check_result(component_diff_ts, ts_diff_component, a_name="Component", b_name="SDK",
269bae44755Sopenharmony_ci                       a_value_file_dict=value_component_dict, b_value_file_dict=value_ts_dict)
270bae44755Sopenharmony_ci
271bae44755Sopenharmony_ci
272bae44755Sopenharmony_cidef check_sdk_and_codec(syscap_define_path: str, sdk_path: str) -> None:
273bae44755Sopenharmony_ci    ts_syscap_set, ts_syscap_dict = collect_syscap_from_sdk(ts_path=sdk_path,
274bae44755Sopenharmony_ci                                                            post_handler=sdk_syscap_post_handler)
275bae44755Sopenharmony_ci    array_syscap_set, array_syscap_dict = collect_syscap_from_codec(syscap_define_path)
276bae44755Sopenharmony_ci    ts_diff_array, array_diff_ts = mutual_diff(ts_syscap_set, array_syscap_set)
277bae44755Sopenharmony_ci    value_ts_dict = find_files_containes_value(ts_diff_array, ts_syscap_dict)
278bae44755Sopenharmony_ci    value_h_dict = find_files_containes_value(array_diff_ts, array_syscap_dict)
279bae44755Sopenharmony_ci    print_check_result(ts_diff_array, array_diff_ts, a_name="SDK", b_name="Codec", a_value_file_dict=value_ts_dict,
280bae44755Sopenharmony_ci                       b_value_file_dict=value_h_dict)
281bae44755Sopenharmony_ci
282bae44755Sopenharmony_ci
283bae44755Sopenharmony_cidef main():
284bae44755Sopenharmony_ci    args = get_args()
285bae44755Sopenharmony_ci    project_path = args.project_path
286bae44755Sopenharmony_ci    check_target = args.check_target
287bae44755Sopenharmony_ci    bundles = args.bundles
288bae44755Sopenharmony_ci    syscap_define_path = os.path.join(project_path, "developtools", "syscap_codec", "include", "syscap_define.h")
289bae44755Sopenharmony_ci    ts_path = os.path.join(project_path, "interface", "sdk-js", "api")
290bae44755Sopenharmony_ci    component_black_dirs = ("out",)
291bae44755Sopenharmony_ci    bundle_syscap_heirarchy = ("component", "syscap")
292bae44755Sopenharmony_ci    if (bundles is not None or (bundles is not None and len(bundles) != 0)) and check_target != "component_codec":
293bae44755Sopenharmony_ci        print("error: --bundles could only be used with -t component_codec")
294bae44755Sopenharmony_ci        return
295bae44755Sopenharmony_ci    if "component_sdk" == check_target:
296bae44755Sopenharmony_ci        check_component_and_sdk(project_path, component_black_dirs, bundle_syscap_heirarchy, sdk_path=ts_path)
297bae44755Sopenharmony_ci    elif "sdk_codec" == check_target:
298bae44755Sopenharmony_ci        check_sdk_and_codec(syscap_define_path=syscap_define_path, sdk_path=ts_path)
299bae44755Sopenharmony_ci    elif "component_codec" == check_target:
300bae44755Sopenharmony_ci        if bundles is not None and len(bundles) == 0:
301bae44755Sopenharmony_ci            print(r"error: '--bundles' parameter is specified, but has no value")
302bae44755Sopenharmony_ci        else:
303bae44755Sopenharmony_ci            check_component_and_codec(project_path, syscap_define_path, component_black_dirs=component_black_dirs,
304bae44755Sopenharmony_ci                                      bundle_key_heirarchy=bundle_syscap_heirarchy, bundles=bundles)
305bae44755Sopenharmony_ci
306bae44755Sopenharmony_ci
307bae44755Sopenharmony_ciif __name__ == "__main__":
308bae44755Sopenharmony_ci    main()
309bae44755Sopenharmony_ci
310