Merge lp:~widelands-dev/widelands/clang-tidy into lp:widelands
- clang-tidy
- Merge into trunk
Proposed by
GunChleoc
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 8481 | ||||
Proposed branch: | lp:~widelands-dev/widelands/clang-tidy | ||||
Merge into: | lp:widelands | ||||
Diff against target: |
346 lines (+342/-0) 1 file modified
utils/run-clang-tidy.py (+342/-0) |
||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/clang-tidy | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Widelands Developers | Pending | ||
Review via email: mp+333407@code.launchpad.net |
Commit message
Added a utils script to run clang-tidy.
Description of the change
I downloaded a script to run clang-tidy and added some documentation to it on how I run it.
To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote : | # |
Revision history for this message
GunChleoc (gunchleoc) wrote : | # |
I want to start playing with this, so I'm merging it.
@bunnybot merge
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'utils/run-clang-tidy.py' |
2 | --- utils/run-clang-tidy.py 1970-01-01 00:00:00 +0000 |
3 | +++ utils/run-clang-tidy.py 2017-11-08 20:04:18 +0000 |
4 | @@ -0,0 +1,342 @@ |
5 | +#!/usr/bin/env python |
6 | +# |
7 | +#===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# |
8 | +# |
9 | +# The LLVM Compiler Infrastructure |
10 | +# |
11 | +# This file is distributed under the University of Illinois Open Source |
12 | +# License. See LICENSE.TXT for details. |
13 | +# |
14 | +#===------------------------------------------------------------------------===# |
15 | +# FIXME: Integrate with clang-tidy-diff.py |
16 | +# |
17 | +#===------------------------------------------------------------------------===# |
18 | +# |
19 | +# Downloaded from https://github.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/tool/run-clang-tidy.py |
20 | +# Version 38b98640c3820151aa4aba16c5b7797ec8c8b3e7 |
21 | +# |
22 | +#===------------------------------------------------------------------------===# |
23 | +# HOW TO RUN THIS TOOL |
24 | +# |
25 | +# 1. Install the needed libraries. On Ubuntu, this is: |
26 | +# |
27 | +# sudo apt-get install clang clang-tidy python-yaml |
28 | +# |
29 | +# 2. Run compile.sh to create the build directory. You can abort this pretty quick. |
30 | +# |
31 | +# 3. Run the following commands: |
32 | +# |
33 | +# cd build |
34 | +# cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. |
35 | +# |
36 | +# This will give you a file 'build/compile_commands.json' |
37 | +# |
38 | +# 4. In 'build/compile_commands.json', do replace-all with an empty string |
39 | +# for the following switches that would clutter the result with warnings: |
40 | +# |
41 | +# -Wlogical-op |
42 | +# -Wsync-nand |
43 | +# -Wtrampolines |
44 | +# -fext-numeric-literals |
45 | +# |
46 | +# 5. Run the tool from the 'build' directory with: |
47 | +# |
48 | +# python ../utils/run-clang-tidy.py -checks=*,-llvm-*,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-pro-type-const-cast,-readability-named-parameter > ../clang-tidy.log |
49 | +# |
50 | +# Results will then be in 'clang-tidy.log' |
51 | +# |
52 | +# 6. You can pick which warnings you want with the checks parameter. |
53 | +# |
54 | +# 7. Documentation is available at: |
55 | +# |
56 | +# https://www.kdab.com/clang-tidy-part-1-modernize-source-code-using-c11c14/ |
57 | +# |
58 | +# http://clang.llvm.org/extra/clang-tidy/ |
59 | +# |
60 | +#===------------------------------------------------------------------------===# |
61 | +# |
62 | + |
63 | +""" |
64 | +Parallel clang-tidy runner |
65 | +========================== |
66 | + |
67 | +Runs clang-tidy over all files in a compilation database. Requires clang-tidy |
68 | +and clang-apply-replacements in $PATH. |
69 | + |
70 | +Example invocations. |
71 | +- Run clang-tidy on all files in the current working directory with a default |
72 | + set of checks and show warnings in the cpp files and all project headers. |
73 | + run-clang-tidy.py $PWD |
74 | + |
75 | +- Fix all header guards. |
76 | + run-clang-tidy.py -fix -checks=-*,llvm-header-guard |
77 | + |
78 | +- Fix all header guards included from clang-tidy and header guards |
79 | + for clang-tidy headers. |
80 | + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ |
81 | + -header-filter=extra/clang-tidy |
82 | + |
83 | +Compilation database setup: |
84 | +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html |
85 | +""" |
86 | + |
87 | +from __future__ import print_function |
88 | + |
89 | +import argparse |
90 | +import glob |
91 | +import json |
92 | +import multiprocessing |
93 | +import os |
94 | +import re |
95 | +import shutil |
96 | +import subprocess |
97 | +import sys |
98 | +import tempfile |
99 | +import threading |
100 | +import traceback |
101 | +import yaml |
102 | + |
103 | +is_py2 = sys.version[0] == '2' |
104 | + |
105 | +if is_py2: |
106 | + import Queue as queue |
107 | +else: |
108 | + import queue as queue |
109 | + |
110 | +def find_compilation_database(path): |
111 | + """Adjusts the directory until a compilation database is found.""" |
112 | + result = './' |
113 | + while not os.path.isfile(os.path.join(result, path)): |
114 | + if os.path.realpath(result) == '/': |
115 | + print('Error: could not find compilation database.') |
116 | + sys.exit(1) |
117 | + result += '../' |
118 | + return os.path.realpath(result) |
119 | + |
120 | + |
121 | +def make_absolute(f, directory): |
122 | + if os.path.isabs(f): |
123 | + return f |
124 | + return os.path.normpath(os.path.join(directory, f)) |
125 | + |
126 | + |
127 | +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, |
128 | + header_filter, extra_arg, extra_arg_before, quiet): |
129 | + """Gets a command line for clang-tidy.""" |
130 | + start = [clang_tidy_binary] |
131 | + if header_filter is not None: |
132 | + start.append('-header-filter=' + header_filter) |
133 | + else: |
134 | + # Show warnings in all in-project headers by default. |
135 | + start.append('-header-filter=^' + build_path + '/.*') |
136 | + if checks: |
137 | + start.append('-checks=' + checks) |
138 | + if tmpdir is not None: |
139 | + start.append('-export-fixes') |
140 | + # Get a temporary file. We immediately close the handle so clang-tidy can |
141 | + # overwrite it. |
142 | + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) |
143 | + os.close(handle) |
144 | + start.append(name) |
145 | + for arg in extra_arg: |
146 | + start.append('-extra-arg=%s' % arg) |
147 | + for arg in extra_arg_before: |
148 | + start.append('-extra-arg-before=%s' % arg) |
149 | + start.append('-p=' + build_path) |
150 | + if quiet: |
151 | + start.append('-quiet') |
152 | + start.append(f) |
153 | + return start |
154 | + |
155 | + |
156 | +def merge_replacement_files(tmpdir, mergefile): |
157 | + """Merge all replacement files in a directory into a single file""" |
158 | + # The fixes suggested by clang-tidy >= 4.0.0 are given under |
159 | + # the top level key 'Diagnostics' in the output yaml files |
160 | + mergekey="Diagnostics" |
161 | + merged=[] |
162 | + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): |
163 | + content = yaml.safe_load(open(replacefile, 'r')) |
164 | + if not content: |
165 | + continue # Skip empty files. |
166 | + merged.extend(content.get(mergekey, [])) |
167 | + |
168 | + if merged: |
169 | + # MainSourceFile: The key is required by the definition inside |
170 | + # include/clang/Tooling/ReplacementsYaml.h, but the value |
171 | + # is actually never used inside clang-apply-replacements, |
172 | + # so we set it to '' here. |
173 | + output = { 'MainSourceFile': '', mergekey: merged } |
174 | + with open(mergefile, 'w') as out: |
175 | + yaml.safe_dump(output, out) |
176 | + else: |
177 | + # Empty the file: |
178 | + open(mergefile, 'w').close() |
179 | + |
180 | + |
181 | +def check_clang_apply_replacements_binary(args): |
182 | + """Checks if invoking supplied clang-apply-replacements binary works.""" |
183 | + try: |
184 | + subprocess.check_call([args.clang_apply_replacements_binary, '--version']) |
185 | + except: |
186 | + print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' |
187 | + 'binary correctly specified?', file=sys.stderr) |
188 | + traceback.print_exc() |
189 | + sys.exit(1) |
190 | + |
191 | + |
192 | +def apply_fixes(args, tmpdir): |
193 | + """Calls clang-apply-fixes on a given directory.""" |
194 | + invocation = [args.clang_apply_replacements_binary] |
195 | + if args.format: |
196 | + invocation.append('-format') |
197 | + if args.style: |
198 | + invocation.append('-style=' + args.style) |
199 | + invocation.append(tmpdir) |
200 | + subprocess.call(invocation) |
201 | + |
202 | + |
203 | +def run_tidy(args, tmpdir, build_path, queue): |
204 | + """Takes filenames out of queue and runs clang-tidy on them.""" |
205 | + while True: |
206 | + name = queue.get() |
207 | + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, |
208 | + tmpdir, build_path, args.header_filter, |
209 | + args.extra_arg, args.extra_arg_before, |
210 | + args.quiet) |
211 | + sys.stdout.write(' '.join(invocation) + '\n') |
212 | + subprocess.call(invocation) |
213 | + queue.task_done() |
214 | + |
215 | + |
216 | +def main(): |
217 | + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' |
218 | + 'in a compilation database. Requires ' |
219 | + 'clang-tidy and clang-apply-replacements in ' |
220 | + '$PATH.') |
221 | + parser.add_argument('-clang-tidy-binary', metavar='PATH', |
222 | + default='clang-tidy', |
223 | + help='path to clang-tidy binary') |
224 | + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', |
225 | + default='clang-apply-replacements', |
226 | + help='path to clang-apply-replacements binary') |
227 | + parser.add_argument('-checks', default=None, |
228 | + help='checks filter, when not specified, use clang-tidy ' |
229 | + 'default') |
230 | + parser.add_argument('-header-filter', default=None, |
231 | + help='regular expression matching the names of the ' |
232 | + 'headers to output diagnostics from. Diagnostics from ' |
233 | + 'the main file of each translation unit are always ' |
234 | + 'displayed.') |
235 | + parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes', |
236 | + help='Create a yaml file to store suggested fixes in, ' |
237 | + 'which can be applied with clang-apply-replacements.') |
238 | + parser.add_argument('-j', type=int, default=0, |
239 | + help='number of tidy instances to be run in parallel.') |
240 | + parser.add_argument('files', nargs='*', default=['.*'], |
241 | + help='files to be processed (regex on path)') |
242 | + parser.add_argument('-fix', action='store_true', help='apply fix-its') |
243 | + parser.add_argument('-format', action='store_true', help='Reformat code ' |
244 | + 'after applying fixes') |
245 | + parser.add_argument('-style', default='file', help='The style of reformat ' |
246 | + 'code after applying fixes') |
247 | + parser.add_argument('-p', dest='build_path', |
248 | + help='Path used to read a compile command database.') |
249 | + parser.add_argument('-extra-arg', dest='extra_arg', |
250 | + action='append', default=[], |
251 | + help='Additional argument to append to the compiler ' |
252 | + 'command line.') |
253 | + parser.add_argument('-extra-arg-before', dest='extra_arg_before', |
254 | + action='append', default=[], |
255 | + help='Additional argument to prepend to the compiler ' |
256 | + 'command line.') |
257 | + parser.add_argument('-quiet', action='store_true', |
258 | + help='Run clang-tidy in quiet mode') |
259 | + args = parser.parse_args() |
260 | + |
261 | + db_path = 'compile_commands.json' |
262 | + |
263 | + if args.build_path is not None: |
264 | + build_path = args.build_path |
265 | + else: |
266 | + # Find our database |
267 | + build_path = find_compilation_database(db_path) |
268 | + |
269 | + try: |
270 | + invocation = [args.clang_tidy_binary, '-list-checks'] |
271 | + invocation.append('-p=' + build_path) |
272 | + if args.checks: |
273 | + invocation.append('-checks=' + args.checks) |
274 | + invocation.append('-') |
275 | + print(subprocess.check_output(invocation)) |
276 | + except: |
277 | + print("Unable to run clang-tidy.", file=sys.stderr) |
278 | + sys.exit(1) |
279 | + |
280 | + # Load the database and extract all files. |
281 | + database = json.load(open(os.path.join(build_path, db_path))) |
282 | + files = [make_absolute(entry['file'], entry['directory']) |
283 | + for entry in database] |
284 | + |
285 | + max_task = args.j |
286 | + if max_task == 0: |
287 | + max_task = multiprocessing.cpu_count() |
288 | + |
289 | + tmpdir = None |
290 | + if args.fix or args.export_fixes: |
291 | + check_clang_apply_replacements_binary(args) |
292 | + tmpdir = tempfile.mkdtemp() |
293 | + |
294 | + # Build up a big regexy filter from all command line arguments. |
295 | + file_name_re = re.compile('|'.join(args.files)) |
296 | + |
297 | + try: |
298 | + # Spin up a bunch of tidy-launching threads. |
299 | + task_queue = queue.Queue(max_task) |
300 | + for _ in range(max_task): |
301 | + t = threading.Thread(target=run_tidy, |
302 | + args=(args, tmpdir, build_path, task_queue)) |
303 | + t.daemon = True |
304 | + t.start() |
305 | + |
306 | + # Fill the queue with files. |
307 | + for name in files: |
308 | + if file_name_re.search(name): |
309 | + task_queue.put(name) |
310 | + |
311 | + # Wait for all threads to be done. |
312 | + task_queue.join() |
313 | + |
314 | + except KeyboardInterrupt: |
315 | + # This is a sad hack. Unfortunately subprocess goes |
316 | + # bonkers with ctrl-c and we start forking merrily. |
317 | + print('\nCtrl-C detected, goodbye.') |
318 | + if tmpdir: |
319 | + shutil.rmtree(tmpdir) |
320 | + os.kill(0, 9) |
321 | + |
322 | + return_code = 0 |
323 | + if args.export_fixes: |
324 | + print('Writing fixes to ' + args.export_fixes + ' ...') |
325 | + try: |
326 | + merge_replacement_files(tmpdir, args.export_fixes) |
327 | + except: |
328 | + print('Error exporting fixes.\n', file=sys.stderr) |
329 | + traceback.print_exc() |
330 | + return_code=1 |
331 | + |
332 | + if args.fix: |
333 | + print('Applying fixes ...') |
334 | + try: |
335 | + apply_fixes(args, tmpdir) |
336 | + except: |
337 | + print('Error applying fixes.\n', file=sys.stderr) |
338 | + traceback.print_exc() |
339 | + return_code=1 |
340 | + |
341 | + if tmpdir: |
342 | + shutil.rmtree(tmpdir) |
343 | + sys.exit(return_code) |
344 | + |
345 | +if __name__ == '__main__': |
346 | + main() |
Continuous integration builds have changed state:
Travis build 2783. State: passed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 299303165. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ clang_tidy- 2594.
Appveyor build 2594. State: success. Details: https:/