47def run_tidy(task_queue, lock, timeout, failed_files):
50 command = task_queue.get()
52 proc = subprocess.Popen(
53 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
56 if timeout
is not None:
57 watchdog = threading.Timer(timeout, proc.kill)
60 stdout, stderr = proc.communicate()
61 if proc.returncode != 0:
62 if proc.returncode < 0:
63 msg =
"Terminated by signal %d : %s\n" % (
67 stderr += msg.encode(
"utf-8")
68 failed_files.append(command)
71 sys.stdout.write(stdout.decode(
"utf-8") +
"\n")
74 sys.stderr.write(stderr.decode(
"utf-8") +
"\n")
76 except Exception
as e:
78 sys.stderr.write(
"Failed: " + str(e) +
": ".join(command) +
"\n")
81 if not (timeout
is None or watchdog
is None):
82 if not watchdog.is_alive():
84 "Terminated by timeout: " +
" ".join(command) +
"\n"
87 task_queue.task_done()
140 parser = argparse.ArgumentParser(
141 description=
"Run clang-tidy against changed files, and "
142 "output diagnostics only for modified "
146 "-clang-tidy-binary",
148 default=
"clang-tidy",
149 help=
"path to clang-tidy binary",
155 help=
"strip the smallest prefix containing P slashes",
161 help=
"custom pattern selecting file paths to check "
162 "(case sensitive, overrides -iregex)",
167 default=
r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)",
168 help=
"custom pattern selecting file paths to check "
169 "(case insensitive, overridden by -regex)",
175 help=
"number of tidy instances to be run in parallel.",
178 "-timeout", type=int, default=
None, help=
"timeout per each file in seconds."
181 "-fix", action=
"store_true", default=
False, help=
"apply suggested fixes"
185 help=
"checks filter, when not specified, use clang-tidy " "default",
191 help=
"Specify the path of .clang-tidy or custom config file",
194 parser.add_argument(
"-use-color", action=
"store_true", help=
"Use colors in output")
196 "-path", dest=
"build_path", help=
"Path used to read a compile command database."
201 metavar=
"FILE_OR_DIRECTORY",
203 help=
"A directory or a yaml file to store suggested fixes in, "
204 "which can be applied with clang-apply-replacements. If the "
205 "parameter is a directory, the fixes of each compilation unit are "
206 "stored in individual yaml files in the directory.",
213 help=
"A directory to store suggested fixes in, which can be applied "
214 "with clang-apply-replacements. The fixes of each compilation unit are "
215 "stored in individual yaml files in the directory.",
222 help=
"Additional argument to append to the compiler " "command line.",
226 dest=
"extra_arg_before",
229 help=
"Additional argument to prepend to the compiler " "command line.",
235 help=
"Run clang-tidy in quiet mode",
242 help=
"Load the specified plugin in clang-tidy.",
247 help=
"Allow empty enabled checks.",
251 dest=
"skip_non_compiling",
254 help=
"Only check files in the compilation database",
257 "-warnings-as-errors",
258 help=
"Upgrades clang-tidy warnings to errors. Same format as '-checks'.",
264 help=
"Hide progress",
270 clang_tidy_args.extend(argv[argv.index(
"--") :])
271 argv = argv[: argv.index(
"--")]
273 args = parser.parse_args(argv)
280 for line
in sys.stdin:
281 match = re.search(
r'^\+\+\+\ "?(.*?/){%s}([^ \t\n"]*)' % args.p, line)
283 filename = match.group(2)
287 if args.regex
is not None:
288 if not re.match(
"^%s$" % args.regex, filename):
291 if not re.match(
"^%s$" % args.iregex, filename, re.IGNORECASE):
296 compiling_files
is not None
297 and (Path.cwd() / filename)
not in compiling_files
301 match = re.search(
r"^@@.*\+(\d+)(,(\d+))?", line)
303 start_line = int(match.group(1))
306 line_count = int(match.group(3))
309 end_line = start_line + line_count - 1
310 lines_by_file.setdefault(filename, []).append([start_line, end_line])
312 if not any(lines_by_file):
313 print(
"No relevant changes found.")
316 max_task_count = args.j
317 if max_task_count == 0:
318 max_task_count = multiprocessing.cpu_count()
319 max_task_count = min(len(lines_by_file), max_task_count)
320 if not args.hide_progress:
321 print(f
"Running clang-tidy in {max_task_count} threads...")
323 combine_fixes =
False
324 export_fixes_dir =
None
325 delete_fixes_dir =
False
326 if args.export_fixes
is not None:
328 if args.export_fixes.endswith(os.path.sep)
and not os.path.isdir(
331 os.makedirs(args.export_fixes)
333 if not os.path.isdir(args.export_fixes):
336 "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
341 if os.path.isdir(args.export_fixes):
342 export_fixes_dir = args.export_fixes
345 export_fixes_dir = tempfile.mkdtemp()
346 delete_fixes_dir =
True
349 task_queue = queue.Queue(max_task_count)
351 lock = threading.Lock()
358 max_task_count, run_tidy, (task_queue, lock, args.timeout, failed_files)
362 common_clang_tidy_args = []
364 common_clang_tidy_args.append(
"-fix")
365 if args.checks !=
"":
366 common_clang_tidy_args.append(
"-checks=" + args.checks)
367 if args.config_file !=
"":
368 common_clang_tidy_args.append(
"-config-file=" + args.config_file)
370 common_clang_tidy_args.append(
"-quiet")
371 if args.build_path
is not None:
372 common_clang_tidy_args.append(
"-p=%s" % args.build_path)
374 common_clang_tidy_args.append(
"--use-color")
375 if args.allow_no_checks:
376 common_clang_tidy_args.append(
"--allow-no-checks")
377 for arg
in args.extra_arg:
378 common_clang_tidy_args.append(
"-extra-arg=%s" % arg)
379 for arg
in args.extra_arg_before:
380 common_clang_tidy_args.append(
"-extra-arg-before=%s" % arg)
381 for plugin
in args.plugins:
382 common_clang_tidy_args.append(
"-load=%s" % plugin)
383 if args.warnings_as_errors:
384 common_clang_tidy_args.append(
"-warnings-as-errors=" + args.warnings_as_errors)
386 for name
in lines_by_file:
387 line_filter_json = json.dumps(
388 [{
"name": name,
"lines": lines_by_file[name]}], separators=(
",",
":")
392 command = [args.clang_tidy_binary]
393 command.append(
"-line-filter=" + line_filter_json)
394 if args.export_fixes
is not None:
397 (handle, tmp_name) = tempfile.mkstemp(suffix=
".yaml", dir=export_fixes_dir)
399 command.append(
"-export-fixes=" + tmp_name)
400 command.extend(common_clang_tidy_args)
402 command.extend(clang_tidy_args)
404 task_queue.put(command)
417 if not args.hide_progress:
418 print(f
"Writing fixes to {args.export_fixes} ...")
422 sys.stderr.write(
"Error exporting fixes.\n")
423 traceback.print_exc()
427 shutil.rmtree(export_fixes_dir)
428 sys.exit(return_code)