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)
72 sys.stdout.write(stdout.decode(
"utf-8") +
"\n")
75 sys.stderr.write(stderr.decode(
"utf-8") +
"\n")
77 except Exception
as e:
79 sys.stderr.write(
"Failed: " + str(e) +
": ".join(command) +
"\n")
82 if not (timeout
is None or watchdog
is None):
83 if not watchdog.is_alive():
85 "Terminated by timeout: " +
" ".join(command) +
"\n"
88 task_queue.task_done()
141 parser = argparse.ArgumentParser(
142 description=
"Run clang-tidy against changed files, and "
143 "output diagnostics only for modified "
147 "-clang-tidy-binary",
149 default=
"clang-tidy",
150 help=
"path to clang-tidy binary",
156 help=
"strip the smallest prefix containing P slashes",
162 help=
"custom pattern selecting file paths to check "
163 "(case sensitive, overrides -iregex)",
168 default=
r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)",
169 help=
"custom pattern selecting file paths to check "
170 "(case insensitive, overridden by -regex)",
176 help=
"number of tidy instances to be run in parallel.",
179 "-timeout", type=int, default=
None, help=
"timeout per each file in seconds."
182 "-fix", action=
"store_true", default=
False, help=
"apply suggested fixes"
186 help=
"checks filter, when not specified, use clang-tidy " "default",
192 help=
"Specify the path of .clang-tidy or custom config file",
195 parser.add_argument(
"-use-color", action=
"store_true", help=
"Use colors in output")
197 "-path", dest=
"build_path", help=
"Path used to read a compile command database."
202 metavar=
"FILE_OR_DIRECTORY",
204 help=
"A directory or a yaml file to store suggested fixes in, "
205 "which can be applied with clang-apply-replacements. If the "
206 "parameter is a directory, the fixes of each compilation unit are "
207 "stored in individual yaml files in the directory.",
214 help=
"A directory to store suggested fixes in, which can be applied "
215 "with clang-apply-replacements. The fixes of each compilation unit are "
216 "stored in individual yaml files in the directory.",
223 help=
"Additional argument to append to the compiler " "command line.",
227 dest=
"extra_arg_before",
230 help=
"Additional argument to prepend to the compiler " "command line.",
237 help=
"Arguments to remove from the compiler command line.",
243 help=
"Run clang-tidy in quiet mode",
250 help=
"Load the specified plugin in clang-tidy.",
255 help=
"Allow empty enabled checks.",
259 dest=
"skip_non_compiling",
262 help=
"Only check files in the compilation database",
265 "-warnings-as-errors",
266 help=
"Upgrades clang-tidy warnings to errors. Same format as '-checks'.",
272 help=
"Hide progress",
278 clang_tidy_args.extend(argv[argv.index(
"--") :])
279 argv = argv[: argv.index(
"--")]
281 args = parser.parse_args(argv)
288 for line
in sys.stdin:
289 match = re.search(
r'^\+\+\+\ "?(.*?/){%s}([^ \t\n"]*)' % args.p, line)
291 filename = match.group(2)
295 if args.regex
is not None:
296 if not re.match(
"^%s$" % args.regex, filename):
299 if not re.match(
"^%s$" % args.iregex, filename, re.IGNORECASE):
304 compiling_files
is not None
305 and (Path.cwd() / filename)
not in compiling_files
309 match = re.search(
r"^@@.*\+(\d+)(,(\d+))?", line)
311 start_line = int(match.group(1))
314 line_count = int(match.group(3))
317 end_line = start_line + line_count - 1
318 lines_by_file.setdefault(filename, []).append([start_line, end_line])
320 if not any(lines_by_file):
321 print(
"No relevant changes found.")
324 max_task_count = args.j
325 if max_task_count == 0:
326 max_task_count = multiprocessing.cpu_count()
327 max_task_count = min(len(lines_by_file), max_task_count)
328 if not args.hide_progress:
329 print(f
"Running clang-tidy in {max_task_count} threads...")
331 combine_fixes =
False
332 export_fixes_dir =
None
333 delete_fixes_dir =
False
334 if args.export_fixes
is not None:
336 if args.export_fixes.endswith(os.path.sep)
and not os.path.isdir(
339 os.makedirs(args.export_fixes)
341 if not os.path.isdir(args.export_fixes):
344 "Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
349 if os.path.isdir(args.export_fixes):
350 export_fixes_dir = args.export_fixes
353 export_fixes_dir = tempfile.mkdtemp()
354 delete_fixes_dir =
True
357 task_queue = queue.Queue(max_task_count)
359 lock = threading.Lock()
366 max_task_count, run_tidy, (task_queue, lock, args.timeout, failed_files)
370 common_clang_tidy_args = []
372 common_clang_tidy_args.append(
"-fix")
373 if args.checks !=
"":
374 common_clang_tidy_args.append(
"-checks=" + args.checks)
375 if args.config_file !=
"":
376 common_clang_tidy_args.append(
"-config-file=" + args.config_file)
378 common_clang_tidy_args.append(
"-quiet")
379 if args.build_path
is not None:
380 common_clang_tidy_args.append(
"-p=%s" % args.build_path)
382 common_clang_tidy_args.append(
"--use-color")
383 if args.allow_no_checks:
384 common_clang_tidy_args.append(
"--allow-no-checks")
385 for arg
in args.extra_arg:
386 common_clang_tidy_args.append(
"-extra-arg=%s" % arg)
387 for arg
in args.extra_arg_before:
388 common_clang_tidy_args.append(
"-extra-arg-before=%s" % arg)
389 for arg
in args.removed_arg:
390 common_clang_tidy_args.append(
"-removed-arg=%s" % arg)
391 for plugin
in args.plugins:
392 common_clang_tidy_args.append(
"-load=%s" % plugin)
393 if args.warnings_as_errors:
394 common_clang_tidy_args.append(
"-warnings-as-errors=" + args.warnings_as_errors)
396 for name
in lines_by_file:
397 line_filter_json = json.dumps(
398 [{
"name": name,
"lines": lines_by_file[name]}], separators=(
",",
":")
402 command = [args.clang_tidy_binary]
403 command.append(
"-line-filter=" + line_filter_json)
404 if args.export_fixes
is not None:
407 (handle, tmp_name) = tempfile.mkstemp(suffix=
".yaml", dir=export_fixes_dir)
409 command.append(
"-export-fixes=" + tmp_name)
410 command.extend(common_clang_tidy_args)
412 command.extend(clang_tidy_args)
414 task_queue.put(command)
427 if not args.hide_progress:
428 print(f
"Writing fixes to {args.export_fixes} ...")
432 sys.stderr.write(
"Error exporting fixes.\n")
433 traceback.print_exc()
437 shutil.rmtree(export_fixes_dir)
438 sys.exit(return_code)