14Parallel clang-tidy runner
15==========================
17Runs clang-tidy over all files in a compilation database. Requires clang-tidy
18and 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
33Compilation database setup:
34http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
37from __future__ import print_function
60 """Convert a string representation of truth to a bool following LLVM's CLI argument parsing."""
63 if val
in [
"",
"true",
"1"]:
65 elif val
in [
"false",
"0"]:
69 raise argparse.ArgumentTypeError(
70 "'{}' is invalid value for boolean argument! Try 0 or 1.".format(val)
75 """Adjusts the directory until a compilation database is found."""
76 result = os.path.realpath(
"./")
77 while not os.path.isfile(os.path.join(result, path)):
78 parent = os.path.dirname(result)
80 print(
"Error: could not find compilation database.")
89 return os.path.normpath(os.path.join(directory, f))
99 allow_enabling_alpha_checkers,
110 """Gets a command line for clang-tidy."""
111 start = [clang_tidy_binary]
112 if allow_enabling_alpha_checkers:
113 start.append(
"-allow-enabling-analyzer-alpha-checkers")
114 if header_filter
is not None:
115 start.append(
"-header-filter=" + header_filter)
116 if line_filter
is not None:
117 start.append(
"-line-filter=" + line_filter)
118 if use_color
is not None:
120 start.append(
"--use-color")
122 start.append(
"--use-color=false")
124 start.append(
"-checks=" + checks)
125 if tmpdir
is not None:
126 start.append(
"-export-fixes")
129 (handle, name) = tempfile.mkstemp(suffix=
".yaml", dir=tmpdir)
132 for arg
in extra_arg:
133 start.append(
"-extra-arg=%s" % arg)
134 for arg
in extra_arg_before:
135 start.append(
"-extra-arg-before=%s" % arg)
136 start.append(
"-p=" + build_path)
138 start.append(
"-quiet")
140 start.append(
"--config-file=" + config_file_path)
142 start.append(
"-config=" + config)
143 for plugin
in plugins:
144 start.append(
"-load=" + plugin)
145 if warnings_as_errors:
146 start.append(
"--warnings-as-errors=" + warnings_as_errors)
152 """Merge all replacement files in a directory into a single file"""
155 mergekey =
"Diagnostics"
157 for replacefile
in glob.iglob(os.path.join(tmpdir,
"*.yaml")):
158 content = yaml.safe_load(open(replacefile,
"r"))
161 merged.extend(content.get(mergekey, []))
168 output = {
"MainSourceFile":
"", mergekey: merged}
169 with open(mergefile,
"w")
as out:
170 yaml.safe_dump(output, out)
173 open(mergefile,
"w").close()
177 """Get the path for a binary or exit"""
179 if shutil.which(arg):
183 "error: passed binary '{}' was not found or is not executable".format(
188 built_path = os.path.join(build_path,
"bin", name)
189 binary = shutil.which(name)
or shutil.which(built_path)
194 "error: failed to find {} in $PATH or at {}".format(name, built_path)
199 """Calls clang-apply-fixes on a given directory."""
200 invocation = [clang_apply_replacements_binary]
201 invocation.append(
"-ignore-insert-conflict")
203 invocation.append(
"-format")
205 invocation.append(
"-style=" + args.style)
206 invocation.append(tmpdir)
207 subprocess.call(invocation)
210def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, failed_files):
211 """Takes filenames out of queue and runs clang-tidy on them."""
221 args.allow_enabling_alpha_checkers,
223 args.extra_arg_before,
230 args.warnings_as_errors,
233 proc = subprocess.Popen(
234 invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE
236 output, err = proc.communicate()
237 if proc.returncode != 0:
238 if proc.returncode < 0:
239 msg =
"%s: terminated by signal %d\n" % (name, -proc.returncode)
240 err += msg.encode(
"utf-8")
241 failed_files.append(name)
243 sys.stdout.write(
" ".join(invocation) +
"\n" + output.decode(
"utf-8"))
246 sys.stderr.write(err.decode(
"utf-8"))
251 parser = argparse.ArgumentParser(
252 description=
"Runs clang-tidy over all files "
253 "in a compilation database. Requires "
254 "clang-tidy and clang-apply-replacements in "
255 "$PATH or in your build directory."
258 "-allow-enabling-alpha-checkers",
260 help=
"allow alpha checkers from clang-analyzer.",
263 "-clang-tidy-binary", metavar=
"PATH", help=
"path to clang-tidy binary"
266 "-clang-apply-replacements-binary",
268 help=
"path to clang-apply-replacements binary",
273 help=
"checks filter, when not specified, use clang-tidy default",
275 config_group = parser.add_mutually_exclusive_group()
276 config_group.add_argument(
279 help=
"Specifies a configuration in YAML/JSON format: "
280 " -config=\"{Checks: '*', "
281 ' CheckOptions: {x: y}}" '
282 "When the value is empty, clang-tidy will "
283 "attempt to find a file named .clang-tidy for "
284 "each source file in its parent directories.",
286 config_group.add_argument(
289 help=
"Specify the path of .clang-tidy or custom config "
290 "file: e.g. -config-file=/some/path/myTidyConfigFile. "
291 "This option internally works exactly the same way as "
292 "-config option after reading specified config file. "
293 "Use either -config-file or -config, not both.",
298 help=
"regular expression matching the names of the "
299 "headers to output diagnostics from. Diagnostics from "
300 "the main file of each translation unit are always "
306 help=
"List of files with line ranges to filter the warnings.",
311 metavar=
"file_or_directory",
313 help=
"A directory or a yaml file to store suggested fixes in, "
314 "which can be applied with clang-apply-replacements. If the "
315 "parameter is a directory, the fixes of each compilation unit are "
316 "stored in individual yaml files in the directory.",
323 help=
"A directory to store suggested fixes in, which can be applied "
324 "with clang-apply-replacements. The fixes of each compilation unit are "
325 "stored in individual yaml files in the directory.",
331 help=
"number of tidy instances to be run in parallel.",
334 "files", nargs=
"*", default=[
".*"], help=
"files to be processed (regex on path)"
336 parser.add_argument(
"-fix", action=
"store_true", help=
"apply fix-its")
338 "-format", action=
"store_true", help=
"Reformat code after applying fixes"
343 help=
"The style of reformat code after applying fixes",
350 help=
"Use colors in diagnostics, overriding clang-tidy's"
351 " default behavior. This option overrides the 'UseColor"
352 "' option in .clang-tidy file, if any.",
355 "-p", dest=
"build_path", help=
"Path used to read a compile command database."
362 help=
"Additional argument to append to the compiler command line.",
366 dest=
"extra_arg_before",
369 help=
"Additional argument to prepend to the compiler command line.",
372 "-quiet", action=
"store_true", help=
"Run clang-tidy in quiet mode"
379 help=
"Load the specified plugin in clang-tidy.",
382 "-warnings-as-errors",
384 help=
"Upgrades warnings to errors. Same format as '-checks'",
386 args = parser.parse_args()
388 db_path =
"compile_commands.json"
390 if args.build_path
is not None:
391 build_path = args.build_path
396 clang_tidy_binary =
find_binary(args.clang_tidy_binary,
"clang-tidy", build_path)
400 args.clang_apply_replacements_binary,
"clang-apply-replacements", build_path
403 combine_fixes =
False
404 export_fixes_dir =
None
405 delete_fixes_dir =
False
406 if args.export_fixes
is not None:
408 if args.export_fixes.endswith(os.path.sep)
and not os.path.isdir(
411 os.makedirs(args.export_fixes)
413 if not os.path.isdir(args.export_fixes):
416 "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
421 if os.path.isdir(args.export_fixes):
422 export_fixes_dir = args.export_fixes
424 if export_fixes_dir
is None and (args.fix
or combine_fixes):
425 export_fixes_dir = tempfile.mkdtemp()
426 delete_fixes_dir =
True
436 args.allow_enabling_alpha_checkers,
438 args.extra_arg_before,
445 args.warnings_as_errors,
447 invocation.append(
"-list-checks")
448 invocation.append(
"-")
451 with open(os.devnull,
"w")
as dev_null:
452 subprocess.check_call(invocation, stdout=dev_null)
454 subprocess.check_call(invocation)
456 print(
"Unable to run clang-tidy.", file=sys.stderr)
460 database = json.load(open(os.path.join(build_path, db_path)))
462 [
make_absolute(entry[
"file"], entry[
"directory"])
for entry
in database]
467 max_task = multiprocessing.cpu_count()
470 file_name_re = re.compile(
"|".join(args.files))
475 task_queue = queue.Queue(max_task)
478 lock = threading.Lock()
479 for _
in range(max_task):
480 t = threading.Thread(
497 if file_name_re.search(name):
502 if len(failed_files):
505 except KeyboardInterrupt:
508 print(
"\nCtrl-C detected, goodbye.")
510 shutil.rmtree(export_fixes_dir)
514 print(
"Writing fixes to " + args.export_fixes +
" ...")
518 print(
"Error exporting fixes.\n", file=sys.stderr)
519 traceback.print_exc()
523 print(
"Applying fixes ...")
525 apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir)
527 print(
"Error applying fixes.\n", file=sys.stderr)
528 traceback.print_exc()
532 shutil.rmtree(export_fixes_dir)
533 sys.exit(return_code)
536if __name__ ==
"__main__":
def make_absolute(f, directory)
def apply_fixes(args, clang_apply_replacements_binary, tmpdir)
def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, failed_files)
def find_compilation_database(path)
def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, header_filter, allow_enabling_alpha_checkers, extra_arg, extra_arg_before, quiet, config_file_path, config, line_filter, use_color, plugins, warnings_as_errors)
def merge_replacement_files(tmpdir, mergefile)
def find_binary(arg, name, build_path)