12 ClangTidy Diff Checker 13 ====================== 15 This script reads input from a unified diff, runs clang-tidy on all changed 16 files and outputs clang-tidy warnings in changed lines only. This is useful to 17 detect clang-tidy regressions in the lines touched by a specific patch. 18 Example usage for git/svn users: 20 git diff -U0 HEAD^ | clang-tidy-diff.py -p1 21 svn diff --diff-cmd=diff -x-U0 | \ 22 clang-tidy-diff.py -fix -checks=-*,modernize-use-override 29 import multiprocessing
44 is_py2 = sys.version[0] ==
'2' 55 command = task_queue.get()
57 proc = subprocess.Popen(command,
58 stdout=subprocess.PIPE,
59 stderr=subprocess.PIPE)
61 if timeout
is not None:
62 watchdog = threading.Timer(timeout, proc.kill)
65 stdout, stderr = proc.communicate()
68 sys.stdout.write(stdout.decode(
'utf-8') +
'\n')
71 sys.stderr.write(stderr.decode(
'utf-8') +
'\n')
73 except Exception
as e:
75 sys.stderr.write(
'Failed: ' + str(e) +
': '.
join(command) +
'\n')
78 if (
not timeout
is None)
and (
not watchdog
is None):
79 if not watchdog.is_alive():
80 sys.stderr.write(
'Terminated by timeout: ' +
81 ' '.
join(command) +
'\n')
83 task_queue.task_done()
87 for _
in range(max_tasks):
88 t = threading.Thread(target=tidy_caller, args=(task_queue, lock, timeout))
93 """Merge all replacement files in a directory into a single file""" 96 mergekey =
"Diagnostics" 98 for replacefile
in glob.iglob(os.path.join(tmpdir,
'*.yaml')):
99 content = yaml.safe_load(open(replacefile,
'r')) 102 merged.extend(content.get(mergekey, []))
109 output = {
'MainSourceFile':
'', mergekey: merged }
110 with open(mergefile,
'w')
as out:
111 yaml.safe_dump(output, out)
114 open(mergefile,
'w').close()
118 parser = argparse.ArgumentParser(description=
119 'Run clang-tidy against changed files, and ' 120 'output diagnostics only for modified ' 122 parser.add_argument(
'-clang-tidy-binary', metavar=
'PATH',
123 default=
'clang-tidy',
124 help=
'path to clang-tidy binary')
125 parser.add_argument(
'-p', metavar=
'NUM', default=0,
126 help=
'strip the smallest prefix containing P slashes')
127 parser.add_argument(
'-regex', metavar=
'PATTERN', default=
None,
128 help=
'custom pattern selecting file paths to check ' 129 '(case sensitive, overrides -iregex)')
130 parser.add_argument(
'-iregex', metavar=
'PATTERN', default=
131 r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)',
132 help=
'custom pattern selecting file paths to check ' 133 '(case insensitive, overridden by -regex)')
134 parser.add_argument(
'-j', type=int, default=1,
135 help=
'number of tidy instances to be run in parallel.')
136 parser.add_argument(
'-timeout', type=int, default=
None,
137 help=
'timeout per each file in seconds.')
138 parser.add_argument(
'-fix', action=
'store_true', default=
False,
139 help=
'apply suggested fixes')
140 parser.add_argument(
'-checks',
141 help=
'checks filter, when not specified, use clang-tidy ' 144 parser.add_argument(
'-path', dest=
'build_path',
145 help=
'Path used to read a compile command database.')
147 parser.add_argument(
'-export-fixes', metavar=
'FILE', dest=
'export_fixes',
148 help=
'Create a yaml file to store suggested fixes in, ' 149 'which can be applied with clang-apply-replacements.')
150 parser.add_argument(
'-extra-arg', dest=
'extra_arg',
151 action=
'append', default=[],
152 help=
'Additional argument to append to the compiler ' 154 parser.add_argument(
'-extra-arg-before', dest=
'extra_arg_before',
155 action=
'append', default=[],
156 help=
'Additional argument to prepend to the compiler ' 158 parser.add_argument(
'-quiet', action=
'store_true', default=
False,
159 help=
'Run clang-tidy in quiet mode')
163 clang_tidy_args.extend(argv[argv.index(
'--'):])
164 argv = argv[:argv.index(
'--')]
166 args = parser.parse_args(argv)
171 for line
in sys.stdin:
172 match = re.search(
'^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line)
174 filename = match.group(2)
178 if args.regex
is not None:
179 if not re.match(
'^%s$' % args.regex, filename):
182 if not re.match(
'^%s$' % args.iregex, filename, re.IGNORECASE):
185 match = re.search(
'^@@.*\+(\d+)(,(\d+))?', line)
187 start_line = int(match.group(1))
190 line_count = int(match.group(3))
193 end_line = start_line + line_count - 1
194 lines_by_file.setdefault(filename, []).append([start_line, end_line])
196 if not any(lines_by_file):
197 print(
"No relevant changes found.")
200 max_task_count = args.j
201 if max_task_count == 0:
202 max_task_count = multiprocessing.cpu_count()
203 max_task_count = min(len(lines_by_file), max_task_count)
206 if yaml
and args.export_fixes:
207 tmpdir = tempfile.mkdtemp()
210 task_queue = queue.Queue(max_task_count)
212 lock = threading.Lock()
215 start_workers(max_task_count, run_tidy, task_queue, lock, args.timeout)
218 common_clang_tidy_args = []
220 common_clang_tidy_args.append(
'-fix')
221 if args.checks !=
'':
222 common_clang_tidy_args.append(
'-checks=' + args.checks)
224 common_clang_tidy_args.append(
'-quiet')
225 if args.build_path
is not None:
226 common_clang_tidy_args.append(
'-p=%s' % args.build_path)
227 for arg
in args.extra_arg:
228 common_clang_tidy_args.append(
'-extra-arg=%s' % arg)
229 for arg
in args.extra_arg_before:
230 common_clang_tidy_args.append(
'-extra-arg-before=%s' % arg)
232 for name
in lines_by_file:
233 line_filter_json = json.dumps(
234 [{
"name": name,
"lines": lines_by_file[name]}],
235 separators=(
',',
':'))
238 command = [args.clang_tidy_binary]
239 command.append(
'-line-filter=' + line_filter_json)
240 if yaml
and args.export_fixes:
243 (handle, tmp_name) = tempfile.mkstemp(suffix=
'.yaml', dir=tmpdir)
245 command.append(
'-export-fixes=' + tmp_name)
246 command.extend(common_clang_tidy_args)
248 command.extend(clang_tidy_args)
250 task_queue.put(command)
255 if yaml
and args.export_fixes:
256 print(
'Writing fixes to ' + args.export_fixes +
' ...')
260 sys.stderr.write(
'Error exporting fixes.\n')
261 traceback.print_exc()
264 shutil.rmtree(tmpdir)
267 if __name__ ==
'__main__':
def merge_replacement_files(tmpdir, mergefile)
def start_workers(max_tasks, tidy_caller, task_queue, lock, timeout)
def run_tidy(task_queue, lock, timeout)
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)