clang-tools 6.0.0

run-clang-tidy.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 #===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===#
4 #
5 # The LLVM Compiler Infrastructure
6 #
7 # This file is distributed under the University of Illinois Open Source
8 # License. See LICENSE.TXT for details.
9 #
10 #===------------------------------------------------------------------------===#
11 # FIXME: Integrate with clang-tidy-diff.py
12
13 """
14 Parallel clang-tidy runner
15 ==========================
16
17 Runs clang-tidy over all files in a compilation database. Requires clang-tidy
18 and clang-apply-replacements in $PATH.
19
20 Example invocations.
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
24
25 - Fix all header guards.
26 run-clang-tidy.py -fix -checks=-*,llvm-header-guard
27
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
32
33 Compilation database setup:
34 http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
35 """
36
37 from __future__ import print_function
38
39 import argparse
40 import glob
41 import json
42 import multiprocessing
43 import os
44 import re
45 import shutil
46 import subprocess
47 import sys
48 import tempfile
49 import threading
50 import traceback
51 import yaml
52
53 is_py2 = sys.version[0] == '2'
54
55 if is_py2:
56 import Queue as queue
57 else:
58 import queue as queue
59
60 def find_compilation_database(path):
61 """Adjusts the directory until a compilation database is found."""
62 result = './'
63 while not os.path.isfile(os.path.join(result, path)):
64 if os.path.realpath(result) == '/':
65 print('Error: could not find compilation database.')
66 sys.exit(1)
67 result += '../'
68 return os.path.realpath(result)
69
70
71 def make_absolute(f, directory):
72 if os.path.isabs(f):
73 return f
74 return os.path.normpath(os.path.join(directory, f))
75
76
77 def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
78 header_filter, extra_arg, extra_arg_before, quiet):
79 """Gets a command line for clang-tidy."""
80 start = [clang_tidy_binary]
81 if header_filter is not None:
82 start.append('-header-filter=' + header_filter)
83 else:
84 # Show warnings in all in-project headers by default.
85 start.append('-header-filter=^' + build_path + '/.*')
86 if checks:
87 start.append('-checks=' + checks)
88 if tmpdir is not None:
89 start.append('-export-fixes')
90 # Get a temporary file. We immediately close the handle so clang-tidy can
91 # overwrite it.
92 (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
93 os.close(handle)
94 start.append(name)
95 for arg in extra_arg:
96 start.append('-extra-arg=%s' % arg)
97 for arg in extra_arg_before:
98 start.append('-extra-arg-before=%s' % arg)
99 start.append('-p=' + build_path)
100 if quiet:
101 start.append('-quiet')
102 start.append(f)
103 return start
104
105
106 def merge_replacement_files(tmpdir, mergefile):
107 """Merge all replacement files in a directory into a single file"""
108 # The fixes suggested by clang-tidy >= 4.0.0 are given under
109 # the top level key 'Diagnostics' in the output yaml files
110 mergekey="Diagnostics"
111 merged=[]
112 for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')):
113 content = yaml.safe_load(open(replacefile, 'r'))
114 if not content:
115 continue # Skip empty files.
116 merged.extend(content.get(mergekey, []))
117
118 if merged:
119 # MainSourceFile: The key is required by the definition inside
120 # include/clang/Tooling/ReplacementsYaml.h, but the value
121 # is actually never used inside clang-apply-replacements,
122 # so we set it to '' here.
123 output = { 'MainSourceFile': '', mergekey: merged }
124 with open(mergefile, 'w') as out:
125 yaml.safe_dump(output, out)
126 else:
127 # Empty the file:
128 open(mergefile, 'w').close()
129
130
131 def check_clang_apply_replacements_binary(args):
132 """Checks if invoking supplied clang-apply-replacements binary works."""
133 try:
134 subprocess.check_call([args.clang_apply_replacements_binary, '--version'])
135 except:
136 print('Unable to run clang-apply-replacements. Is clang-apply-replacements '
137 'binary correctly specified?', file=sys.stderr)
138 traceback.print_exc()
139 sys.exit(1)
140
141
142 def apply_fixes(args, tmpdir):
143 """Calls clang-apply-fixes on a given directory."""
144 invocation = [args.clang_apply_replacements_binary]
145 if args.format:
146 invocation.append('-format')
147 if args.style:
148 invocation.append('-style=' + args.style)
149 invocation.append(tmpdir)
150 subprocess.call(invocation)
151
152
153 def run_tidy(args, tmpdir, build_path, queue):
154 """Takes filenames out of queue and runs clang-tidy on them."""
155 while True:
156 name = queue.get()
157 invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks,
158 tmpdir, build_path, args.header_filter,
159 args.extra_arg, args.extra_arg_before,
160 args.quiet)
161 sys.stdout.write(' '.join(invocation) + '\n')
162 subprocess.call(invocation)
163 queue.task_done()
164
165
166 def main():
167 parser = argparse.ArgumentParser(description='Runs clang-tidy over all files '
168 'in a compilation database. Requires '
169 'clang-tidy and clang-apply-replacements in '
170 '$PATH.')
171 parser.add_argument('-clang-tidy-binary', metavar='PATH',
172 default='clang-tidy',
173 help='path to clang-tidy binary')
174 parser.add_argument('-clang-apply-replacements-binary', metavar='PATH',
175 default='clang-apply-replacements',
176 help='path to clang-apply-replacements binary')
177 parser.add_argument('-checks', default=None,
178 help='checks filter, when not specified, use clang-tidy '
179 'default')
180 parser.add_argument('-header-filter', default=None,
181 help='regular expression matching the names of the '
182 'headers to output diagnostics from. Diagnostics from '
183 'the main file of each translation unit are always '
184 'displayed.')
185 parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes',
186 help='Create a yaml file to store suggested fixes in, '
187 'which can be applied with clang-apply-replacements.')
188 parser.add_argument('-j', type=int, default=0,
189 help='number of tidy instances to be run in parallel.')
190 parser.add_argument('files', nargs='*', default=['.*'],
191 help='files to be processed (regex on path)')
192 parser.add_argument('-fix', action='store_true', help='apply fix-its')
193 parser.add_argument('-format', action='store_true', help='Reformat code '
194 'after applying fixes')
195 parser.add_argument('-style', default='file', help='The style of reformat '
196 'code after applying fixes')
197 parser.add_argument('-p', dest='build_path',
198 help='Path used to read a compile command database.')
199 parser.add_argument('-extra-arg', dest='extra_arg',
200 action='append', default=[],
201 help='Additional argument to append to the compiler '
202 'command line.')
203 parser.add_argument('-extra-arg-before', dest='extra_arg_before',
204 action='append', default=[],
205 help='Additional argument to prepend to the compiler '
206 'command line.')
207 parser.add_argument('-quiet', action='store_true',
208 help='Run clang-tidy in quiet mode')
209 args = parser.parse_args()
210
211 db_path = 'compile_commands.json'
212
213 if args.build_path is not None:
214 build_path = args.build_path
215 else:
216 # Find our database
217 build_path = find_compilation_database(db_path)
218
219 try:
220 invocation = [args.clang_tidy_binary, '-list-checks']
221 invocation.append('-p=' + build_path)
222 if args.checks:
223 invocation.append('-checks=' + args.checks)
224 invocation.append('-')
225 subprocess.check_call(invocation)
226 except:
227 print("Unable to run clang-tidy.", file=sys.stderr)
228 sys.exit(1)
229
230 # Load the database and extract all files.
231 database = json.load(open(os.path.join(build_path, db_path)))
232 files = [make_absolute(entry['file'], entry['directory'])
233 for entry in database]
234
235 max_task = args.j
236 if max_task == 0:
237 max_task = multiprocessing.cpu_count()
238
239 tmpdir = None
240 if args.fix or args.export_fixes:
241 check_clang_apply_replacements_binary(args)
242 tmpdir = tempfile.mkdtemp()
243
244 # Build up a big regexy filter from all command line arguments.
245 file_name_re = re.compile('|'.join(args.files))
246
247 try:
248 # Spin up a bunch of tidy-launching threads.
249 task_queue = queue.Queue(max_task)
250 for _ in range(max_task):
251 t = threading.Thread(target=run_tidy,
252 args=(args, tmpdir, build_path, task_queue))
253 t.daemon = True
254 t.start()
255
256 # Fill the queue with files.
257 for name in files:
258 if file_name_re.search(name):
259 task_queue.put(name)
260
261 # Wait for all threads to be done.
262 task_queue.join()
263
264 except KeyboardInterrupt:
265 # This is a sad hack. Unfortunately subprocess goes
266 # bonkers with ctrl-c and we start forking merrily.
267 print('\nCtrl-C detected, goodbye.')
268 if tmpdir:
269 shutil.rmtree(tmpdir)
270 os.kill(0, 9)
271
272 return_code = 0
273 if args.export_fixes:
274 print('Writing fixes to ' + args.export_fixes + ' ...')
275 try:
276 merge_replacement_files(tmpdir, args.export_fixes)
277 except:
278 print('Error exporting fixes.\n', file=sys.stderr)
279 traceback.print_exc()
280 return_code=1
281
282 if args.fix:
283 print('Applying fixes ...')
284 try:
285 apply_fixes(args, tmpdir)
286 except:
287 print('Error applying fixes.\n', file=sys.stderr)
288 traceback.print_exc()
289 return_code=1
290
291 if tmpdir:
292 shutil.rmtree(tmpdir)
293 sys.exit(return_code)
294
295 if __name__ == '__main__':
296 main()
run-clang-tidy.make_absolute
def make_absolute(f, directory)
Definition: run-clang-tidy.py:71
run-clang-tidy.get_tidy_invocation
def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, header_filter, extra_arg, extra_arg_before, quiet)
Definition: run-clang-tidy.py:78
run-clang-tidy.find_compilation_database
def find_compilation_database(path)
Definition: run-clang-tidy.py:60
run-clang-tidy.check_clang_apply_replacements_binary
def check_clang_apply_replacements_binary(args)
Definition: run-clang-tidy.py:131
run-clang-tidy.main
def main()
Definition: run-clang-tidy.py:166
run-clang-tidy.merge_replacement_files
def merge_replacement_files(tmpdir, mergefile)
Definition: run-clang-tidy.py:106
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:82
run-clang-tidy.apply_fixes
def apply_fixes(args, tmpdir)
Definition: run-clang-tidy.py:142
run-clang-tidy.run_tidy
def run_tidy(args, tmpdir, build_path, queue)
Definition: run-clang-tidy.py:153