1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (c) 2024 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 argparse
17import json
18import os
19import re
20import glob
21import os.path
22import stat
23
24
25class Analyzer:
26    @classmethod
27    def get_components_from_inherit_attr(cls, components, inherit, project):
28        for json_name in inherit:
29            with open(project + os.sep + json_name, 'r', encoding='utf-8') as r:
30                inherit_file = json.load(r)
31            for subsystem in inherit_file['subsystems']:
32                for component in subsystem['components']:
33                    component['subsystem'] = subsystem['subsystem']
34                    components.append(component)
35
36    @classmethod
37    def check(cls, include):
38        if include is not None and './' in include.group():
39            return True
40        return False
41
42    @classmethod
43    def scan_files(cls, components, proj_path):
44        results = []
45        for component in components:
46            if not component.__contains__('scan_path') or component['scan_path'].strip() == '':
47                continue
48            files = glob.glob(os.path.join(component['scan_path'], '**', '*.c'), recursive=True)
49            files.extend(glob.glob(os.path.join(component['scan_path'], '**', '*.cpp'), recursive=True))
50            files.extend(glob.glob(os.path.join(component['scan_path'], '**', '*.cc'), recursive=True))
51            files.extend(glob.glob(os.path.join(component['scan_path'], '**', '*.h'), recursive=True))
52            for file in files:
53                try:
54                    cls.scan_each_file(component, file, proj_path, results)
55                except UnicodeDecodeError as e:
56                    print("scan file {} with unicode decode error: {}".format(file, e))
57        return results
58
59    @classmethod
60    def scan_each_file(cls, component, file, project_path, results):
61        with open(file, 'r', encoding='ISO-8859-1') as f:
62            line_list = f.readlines()
63            line_num = 0
64            for line in line_list:
65                include = re.match(r'#include\s+"([^">]+)"', line)
66                line_num = line_num + 1
67                if cls.check(include):
68                    result = {'line_num': line_num, 'file_path': file.replace(project_path, "/"),
69                              'include_cmd': include.group(), 'component': component['component'],
70                              'subsystem': component['subsystem']}
71                    results.append(result)
72
73    @classmethod
74    def analysis(cls, config: str, project_path: str, components_info: str, output_path: str):
75        if not os.path.exists(config):
76            print("error: {} is inaccessible or not found".format(config))
77            return
78        if not os.path.exists(project_path):
79            print("error: {} is inaccessible or not found".format(project_path))
80            return
81        if not os.path.exists(components_info):
82            print("error: {} is inaccessible or not found".format(components_info))
83            return
84        components = cls.__get_components(config, project_path)
85        cls.__get_need_scan_path(components, project_path, components_info)
86        print("scan:")
87        print([item['scan_path'] for item in components], project_path)
88        result = cls.scan_files(components, project_path)
89        flags = os.O_WRONLY | os.O_CREAT
90        modes = stat.S_IWUSR | stat.S_IRUSR
91        with os.fdopen(os.open(output_path, flags, modes), 'w') as f:
92            for ele in result:
93                items = ele['subsystem'], ele['component'], ele['file_path'], str(ele['line_num']), ele['include_cmd']
94                f.write(f'{" ".join(items)}\n')
95
96    @classmethod
97    def __get_need_scan_path(cls, components, project, components_info_path):
98        path_info = dict()
99        with open(components_info_path, 'r', encoding='utf-8') as r:
100            xml_info = r.readlines()
101        for line in xml_info:
102            if "path=" in line:
103                path = re.findall('path="(.*?)"', line)[0]
104                component = path.split('/')[-1]
105                path_info[component] = path
106        item_list = list(path_info.keys())
107        for component in components:
108            if component['component'] in path_info.keys():
109                component['scan_path'] = project + '/' + path_info[component['component']]
110                if (component['component'] in item_list):
111                    item_list.remove(component['component'])
112            else:
113                component['scan_path'] = ''
114        print("no scan component :" + " ".join(item_list))
115
116    @classmethod
117    def __get_components(cls, config: str, project: str):
118        components = list()
119        with open(config, 'r', encoding='utf-8') as r:
120            config_json = json.load(r)
121        if "inherit" in config_json.keys():
122            inherit = config_json['inherit']
123            cls.get_components_from_inherit_attr(components, inherit, project)
124        for subsystem in config_json['subsystems']:
125            for component in subsystem['components']:
126                if component not in components:
127                    component['subsystem'] = subsystem['subsystem']
128                    components.append(component)
129        return components
130
131
132
133def get_args():
134    parser = argparse.ArgumentParser(
135        description=f"common_template.\n")
136    parser.add_argument("-c", "--config_path", required=True, type=str,
137                        help="path of config_file", default="")
138    parser.add_argument("-p", "--project_path", type=str, required=False,
139                        help="root path of openharmony. eg: -p ~/openharmony", default="./")
140    parser.add_argument("-x", "--xml_path", type=str, required=True,
141                        help="path of ohos.xml", default="")
142    parser.add_argument("-o", "--output_path", required=False, type=str,
143        default="include_relative_path.list", help="name of output_json")
144    return parser.parse_args()
145
146if __name__ == '__main__':
147    args = get_args()
148    local_config_path = args.config_path
149    local_project_path = args.project_path
150    local_xml_path = args.xml_path
151    local_output_path = args.output_path
152    Analyzer.analysis(local_config_path, local_project_path, local_xml_path, local_output_path)
153