• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import argparse
19import json
20import multiprocessing
21import os
22import sys
23import subprocess
24import time
25import re
26import tempfile
27
28
29def get_args():
30    parser = argparse.ArgumentParser(
31        description="Runner for clang-tidy for panda project.")
32    parser.add_argument(
33        'panda_dir', help='panda sources directory.')
34    parser.add_argument(
35        'build_dir', help='panda build directory.')
36    parser.add_argument(
37        'fix_dir', help='directory with fixes.')
38    parser.add_argument(
39        'clang_rules_autofix', help='one or several rules for applying fixies')
40    parser.add_argument(
41        '--filename-filter', type=str, action='store', dest='filename_filter',
42        required=False, default="*",
43        help='Regexp for filename with path to it. If missed all source files will be checked.')
44
45    return parser.parse_args()
46
47
48def run_clang_tidy(src_path, panda_dir, build_dir, fix_dir, compile_args, msg, clang_rules_autofix):
49    fname_prefix = os.path.basename(src_path) + '_'
50    fname_fix_patch = tempfile.mkstemp(
51        suffix='.yaml', prefix=fname_prefix, dir=fix_dir)[1]
52
53    cmd = ['clang-tidy-14']
54    cmd += ['--header-filter=.*']
55    cmd += ['-checks=-*,' + clang_rules_autofix]
56    cmd += ['--format-style=file --fix-errors --fix-notes']
57    cmd += ['--config-file=' + os.path.join(panda_dir, '.clang-tidy')]
58    cmd += ['--export-fixes=' + fname_fix_patch + '']
59    cmd += [src_path]
60    cmd += ['--']
61    cmd += ['-ferror-limit=0']
62    cmd += compile_args.split(' ')
63
64    print(msg)
65
66    try:
67        subprocess.check_output(cmd, cwd=build_dir, stderr=subprocess.STDOUT)
68    except subprocess.CalledProcessError as e:
69        # Skip error for some invalid release configurations.
70        if e.stdout:
71            print('Failed: ', ' '.join(cmd))
72            print(e.stdout.decode())
73        else:
74            print("Note: missed output for ", src_path)
75
76        if e.stderr:
77            print(e.stderr.decode())
78
79    if os.path.getsize(fname_fix_patch) == 0:
80        os.remove(fname_fix_patch)
81
82
83def check_file_list(file_list, panda_dir, build_dir, fix_dir, clang_rules_autofix):
84    pool = multiprocessing.Pool(multiprocessing.cpu_count())
85    jobs = []
86    total_count = str(len(file_list))
87    idx = 0
88    for src, args in file_list:
89        idx += 1
90
91        msg = "[%s/%s] Running clang-tidy-rename: %s" % (
92            str(idx), total_count, src)
93        proc = pool.apply_async(func=run_clang_tidy, args=(
94            src, panda_dir, build_dir, fix_dir, args, msg, clang_rules_autofix))
95        jobs.append(proc)
96
97    # Wait for jobs to complete before exiting
98    while(not all([p.ready() for p in jobs])):
99        time.sleep(5)
100
101    # Safely terminate the pool
102    pool.close()
103    pool.join()
104
105
106def need_to_ignore_file(file_path):
107    src_exts = (".c", '.cc', ".cp", ".cxx", ".cpp", ".CPP", ".c++", ".C")
108    if not file_path.endswith(src_exts):
109        return True
110
111    skip_dirs = ['third_party', 'unix', 'windows']
112    for skip_dir in skip_dirs:
113        if skip_dir in file_path:
114            return True
115
116    return False
117
118
119def get_file_list(panda_dir, build_dir, filename_filter):
120    json_cmds_path = os.path.join(build_dir, 'compile_commands.json')
121    cmds_json = []
122    with open(json_cmds_path, 'r') as f:
123        cmds_json = json.load(f)
124
125    if not cmds_json:
126        return []
127
128    regexp = None
129    if filename_filter != '*':
130        regexp = re.compile(filename_filter)
131
132    file_list = []
133    for cmd in cmds_json:
134        file_path = str(os.path.realpath(cmd["file"]))
135        if need_to_ignore_file(file_path):
136            continue
137
138        if file_path.startswith(build_dir):
139            continue
140
141        if regexp is not None and not regexp.search(file_path):
142            continue
143
144        compile_args = cmd["command"]
145        args_pos = compile_args.find(' ')
146        compile_args = compile_args[args_pos:]
147        compile_args = compile_args.replace('\\', '')
148        file_list.append((file_path, compile_args))
149
150    file_list.sort(key=lambda tup: tup[0])
151    return file_list
152
153
154def apply_fixies(fix_dir, panda_dir):
155    print("Running apply fixies ... ")
156
157    # clang-apply-replacements-9 or clang-apply-replacements-14
158    cmd = ['clang-apply-replacements-14']
159    cmd += ['--style=file']
160    cmd += ['--style-config=' + panda_dir]
161    cmd += [fix_dir]
162
163    try:
164        subprocess.check_output(cmd, cwd=fix_dir, stderr=subprocess.STDOUT)
165    except subprocess.CalledProcessError as e:
166        print('Failed: ', ' '.join(cmd))
167        if e.stdout:
168            print(e.stdout.decode())
169        if e.stderr:
170            print(e.stderr.decode())
171        return False
172
173    return True
174
175
176if __name__ == "__main__":
177    arguments = get_args()
178    files_list = []
179
180    arguments.build_dir = str(os.path.abspath(arguments.build_dir))
181    arguments.panda_dir = str(os.path.abspath(arguments.panda_dir))
182    arguments.fix_dir = str(os.path.abspath(arguments.fix_dir))
183
184    arguments.fix_dir = os.path.join(arguments.fix_dir, "renamer_fixes")
185    os.mkdir(arguments.fix_dir)
186
187    if not os.path.exists(os.path.join(arguments.build_dir, 'compile_commands.json')):
188        sys.exit("Error: Missing file `compile_commands.json` in build directory")
189
190    files_list = get_file_list(
191        arguments.panda_dir, arguments.build_dir, arguments.filename_filter)
192
193    if not files_list:
194        sys.exit("Can't be prepaired source list. Please check availble in build `dir compile_commands.json` "
195                 "and correcting of parameter `--filename-filter` if you use it.")
196
197    check_file_list(files_list, arguments.panda_dir, arguments.build_dir,
198                    arguments.fix_dir, arguments.clang_rules_autofix)
199
200    res = apply_fixies(arguments.fix_dir, arguments.panda_dir)
201
202    if res:
203        print("Clang-tidy renamer was applyed successfully! Please review changes")
204    else:
205        print("Clang-tidy renamer has errors, please check it")
206