14 Parallel clang-tidy runner
15 ==========================
17 Runs clang-tidy over all files in a compilation database. Requires clang-tidy
18 and clang-apply-replacements in $PATH.
21 - Run clang-tidy on all files in the current working directory with a default
22 set of checks and show warnings in the cpp files and all project headers.
23 run-clang-tidy.py $PWD
25 - Fix all header guards.
26 run-clang-tidy.py -fix -checks=-*,llvm-header-guard
28 - Fix all header guards included from clang-tidy and header guards
29 for clang-tidy headers.
30 run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \
31 -header-filter=extra/clang-tidy
33 Compilation database setup:
34 http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
37 from __future__
import print_function
42 import multiprocessing
57 is_py2 = sys.version[0] ==
'2'
66 """Adjusts the directory until a compilation database is found."""
68 while not os.path.isfile(os.path.join(result, path)):
69 if os.path.realpath(result) ==
'/':
70 print(
'Error: could not find compilation database.')
73 return os.path.realpath(result)
79 return os.path.normpath(os.path.join(directory, f))
83 header_filter, allow_enabling_alpha_checkers,
84 extra_arg, extra_arg_before, quiet, config):
85 """Gets a command line for clang-tidy."""
86 start = [clang_tidy_binary]
87 if allow_enabling_alpha_checkers
is not None:
88 start.append(
'-allow-enabling-analyzer-alpha-checkers')
89 if header_filter
is not None:
90 start.append(
'-header-filter=' + header_filter)
92 start.append(
'-checks=' + checks)
93 if tmpdir
is not None:
94 start.append(
'-export-fixes')
97 (handle, name) = tempfile.mkstemp(suffix=
'.yaml', dir=tmpdir)
100 for arg
in extra_arg:
101 start.append(
'-extra-arg=%s' % arg)
102 for arg
in extra_arg_before:
103 start.append(
'-extra-arg-before=%s' % arg)
104 start.append(
'-p=' + build_path)
106 start.append(
'-quiet')
108 start.append(
'-config=' + config)
114 """Merge all replacement files in a directory into a single file"""
117 mergekey =
"Diagnostics"
119 for replacefile
in glob.iglob(os.path.join(tmpdir,
'*.yaml')):
120 content = yaml.safe_load(open(replacefile,
'r'))
123 merged.extend(content.get(mergekey, []))
130 output = {
'MainSourceFile':
'', mergekey: merged}
131 with open(mergefile,
'w')
as out:
132 yaml.safe_dump(output, out)
135 open(mergefile,
'w').close()
139 """Checks if invoking supplied clang-apply-replacements binary works."""
141 subprocess.check_call([args.clang_apply_replacements_binary,
'--version'])
143 print(
'Unable to run clang-apply-replacements. Is clang-apply-replacements '
144 'binary correctly specified?', file=sys.stderr)
145 traceback.print_exc()
150 """Calls clang-apply-fixes on a given directory."""
151 invocation = [args.clang_apply_replacements_binary]
153 invocation.append(
'-format')
155 invocation.append(
'-style=' + args.style)
156 invocation.append(tmpdir)
157 subprocess.call(invocation)
160 def run_tidy(args, tmpdir, build_path, queue, lock, failed_files):
161 """Takes filenames out of queue and runs clang-tidy on them."""
165 tmpdir, build_path, args.header_filter,
166 args.allow_enabling_alpha_checkers,
167 args.extra_arg, args.extra_arg_before,
168 args.quiet, args.config)
170 proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
171 output, err = proc.communicate()
172 if proc.returncode != 0:
173 failed_files.append(name)
175 sys.stdout.write(
' '.
join(invocation) +
'\n' + output.decode(
'utf-8'))
178 sys.stderr.write(err.decode(
'utf-8'))
183 parser = argparse.ArgumentParser(description=
'Runs clang-tidy over all files '
184 'in a compilation database. Requires '
185 'clang-tidy and clang-apply-replacements in '
187 parser.add_argument(
'-allow-enabling-alpha-checkers',
188 action=
'store_true', help=
'allow alpha checkers from '
190 parser.add_argument(
'-clang-tidy-binary', metavar=
'PATH',
191 default=
'clang-tidy',
192 help=
'path to clang-tidy binary')
193 parser.add_argument(
'-clang-apply-replacements-binary', metavar=
'PATH',
194 default=
'clang-apply-replacements',
195 help=
'path to clang-apply-replacements binary')
196 parser.add_argument(
'-checks', default=
None,
197 help=
'checks filter, when not specified, use clang-tidy '
199 parser.add_argument(
'-config', default=
None,
200 help=
'Specifies a configuration in YAML/JSON format: '
201 ' -config="{Checks: \'*\', '
202 ' CheckOptions: [{key: x, '
204 'When the value is empty, clang-tidy will '
205 'attempt to find a file named .clang-tidy for '
206 'each source file in its parent directories.')
207 parser.add_argument(
'-header-filter', default=
None,
208 help=
'regular expression matching the names of the '
209 'headers to output diagnostics from. Diagnostics from '
210 'the main file of each translation unit are always '
213 parser.add_argument(
'-export-fixes', metavar=
'filename', dest=
'export_fixes',
214 help=
'Create a yaml file to store suggested fixes in, '
215 'which can be applied with clang-apply-replacements.')
216 parser.add_argument(
'-j', type=int, default=0,
217 help=
'number of tidy instances to be run in parallel.')
218 parser.add_argument(
'files', nargs=
'*', default=[
'.*'],
219 help=
'files to be processed (regex on path)')
220 parser.add_argument(
'-fix', action=
'store_true', help=
'apply fix-its')
221 parser.add_argument(
'-format', action=
'store_true', help=
'Reformat code '
222 'after applying fixes')
223 parser.add_argument(
'-style', default=
'file', help=
'The style of reformat '
224 'code after applying fixes')
225 parser.add_argument(
'-p', dest=
'build_path',
226 help=
'Path used to read a compile command database.')
227 parser.add_argument(
'-extra-arg', dest=
'extra_arg',
228 action=
'append', default=[],
229 help=
'Additional argument to append to the compiler '
231 parser.add_argument(
'-extra-arg-before', dest=
'extra_arg_before',
232 action=
'append', default=[],
233 help=
'Additional argument to prepend to the compiler '
235 parser.add_argument(
'-quiet', action=
'store_true',
236 help=
'Run clang-tidy in quiet mode')
237 args = parser.parse_args()
239 db_path =
'compile_commands.json'
241 if args.build_path
is not None:
242 build_path = args.build_path
248 invocation = [args.clang_tidy_binary,
'-list-checks']
249 if args.allow_enabling_alpha_checkers:
250 invocation.append(
'-allow-enabling-analyzer-alpha-checkers')
251 invocation.append(
'-p=' + build_path)
253 invocation.append(
'-checks=' + args.checks)
254 invocation.append(
'-')
257 with open(os.devnull,
'w')
as dev_null:
258 subprocess.check_call(invocation, stdout=dev_null)
260 subprocess.check_call(invocation)
262 print(
"Unable to run clang-tidy.", file=sys.stderr)
266 database = json.load(open(os.path.join(build_path, db_path)))
268 for entry
in database]
272 max_task = multiprocessing.cpu_count()
275 if args.fix
or (yaml
and args.export_fixes):
277 tmpdir = tempfile.mkdtemp()
280 file_name_re = re.compile(
'|'.
join(args.files))
285 task_queue = queue.Queue(max_task)
288 lock = threading.Lock()
289 for _
in range(max_task):
290 t = threading.Thread(target=run_tidy,
291 args=(args, tmpdir, build_path, task_queue, lock, failed_files))
297 if file_name_re.search(name):
302 if len(failed_files):
305 except KeyboardInterrupt:
308 print(
'\nCtrl-C detected, goodbye.')
310 shutil.rmtree(tmpdir)
313 if yaml
and args.export_fixes:
314 print(
'Writing fixes to ' + args.export_fixes +
' ...')
318 print(
'Error exporting fixes.\n', file=sys.stderr)
319 traceback.print_exc()
323 print(
'Applying fixes ...')
327 print(
'Error applying fixes.\n', file=sys.stderr)
328 traceback.print_exc()
332 shutil.rmtree(tmpdir)
333 sys.exit(return_code)
336 if __name__ ==
'__main__':