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