Merge lp:~widelands-dev/widelands/clang-tidy into lp:widelands

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
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 :

Continuous integration builds have changed state:

Travis build 2783. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/299303165.
Appveyor build 2594. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_clang_tidy-2594.

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()

Subscribers

People subscribed via source and target branches

to status/vote changes: