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=
"Regular expression matching the names of the "
307 "source files from compilation database to output "
313 help=
"List of files with line ranges to filter the warnings.",
318 metavar=
"file_or_directory",
320 help=
"A directory or a yaml file to store suggested fixes in, "
321 "which can be applied with clang-apply-replacements. If the "
322 "parameter is a directory, the fixes of each compilation unit are "
323 "stored in individual yaml files in the directory.",
330 help=
"A directory to store suggested fixes in, which can be applied "
331 "with clang-apply-replacements. The fixes of each compilation unit are "
332 "stored in individual yaml files in the directory.",
338 help=
"number of tidy instances to be run in parallel.",
341 "files", nargs=
"*", default=[
".*"], help=
"files to be processed (regex on path)"
343 parser.add_argument(
"-fix", action=
"store_true", help=
"apply fix-its")
345 "-format", action=
"store_true", help=
"Reformat code after applying fixes"
350 help=
"The style of reformat code after applying fixes",
357 help=
"Use colors in diagnostics, overriding clang-tidy's"
358 " default behavior. This option overrides the 'UseColor"
359 "' option in .clang-tidy file, if any.",
362 "-p", dest=
"build_path", help=
"Path used to read a compile command database."
369 help=
"Additional argument to append to the compiler command line.",
373 dest=
"extra_arg_before",
376 help=
"Additional argument to prepend to the compiler command line.",
379 "-quiet", action=
"store_true", help=
"Run clang-tidy in quiet mode"
386 help=
"Load the specified plugin in clang-tidy.",
389 "-warnings-as-errors",
391 help=
"Upgrades warnings to errors. Same format as '-checks'",
393 args = parser.parse_args()
395 db_path =
"compile_commands.json"
397 if args.build_path
is not None:
398 build_path = args.build_path
403 clang_tidy_binary =
find_binary(args.clang_tidy_binary,
"clang-tidy", build_path)
407 args.clang_apply_replacements_binary,
"clang-apply-replacements", build_path
410 combine_fixes =
False
411 export_fixes_dir =
None
412 delete_fixes_dir =
False
413 if args.export_fixes
is not None:
415 if args.export_fixes.endswith(os.path.sep)
and not os.path.isdir(
418 os.makedirs(args.export_fixes)
420 if not os.path.isdir(args.export_fixes):
423 "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
428 if os.path.isdir(args.export_fixes):
429 export_fixes_dir = args.export_fixes
431 if export_fixes_dir
is None and (args.fix
or combine_fixes):
432 export_fixes_dir = tempfile.mkdtemp()
433 delete_fixes_dir =
True
443 args.allow_enabling_alpha_checkers,
445 args.extra_arg_before,
452 args.warnings_as_errors,
454 invocation.append(
"-list-checks")
455 invocation.append(
"-")
458 with open(os.devnull,
"w")
as dev_null:
459 subprocess.check_call(invocation, stdout=dev_null)
461 subprocess.check_call(invocation)
463 print(
"Unable to run clang-tidy.", file=sys.stderr)
467 database = json.load(open(os.path.join(build_path, db_path)))
469 [
make_absolute(entry[
"file"], entry[
"directory"])
for entry
in database]
473 if args.source_filter:
475 source_filter_re = re.compile(args.source_filter)
478 "Error: unable to compile regex from arg -source-filter:",
481 traceback.print_exc()
483 files = {f
for f
in files
if source_filter_re.match(f)}
487 max_task = multiprocessing.cpu_count()
490 file_name_re = re.compile(
"|".join(args.files))
495 task_queue = queue.Queue(max_task)
498 lock = threading.Lock()
499 for _
in range(max_task):
500 t = threading.Thread(
517 if file_name_re.search(name):
522 if len(failed_files):
525 except KeyboardInterrupt:
528 print(
"\nCtrl-C detected, goodbye.")
530 shutil.rmtree(export_fixes_dir)
534 print(
"Writing fixes to " + args.export_fixes +
" ...")
538 print(
"Error exporting fixes.\n", file=sys.stderr)
539 traceback.print_exc()
543 print(
"Applying fixes ...")
545 apply_fixes(args, clang_apply_replacements_binary, export_fixes_dir)
547 print(
"Error applying fixes.\n", file=sys.stderr)
548 traceback.print_exc()
552 shutil.rmtree(export_fixes_dir)
553 sys.exit(return_code)
556if __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)