clang-tools 3.8.0

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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 import argparse
38 import json
39 import multiprocessing
40 import os
41 import Queue
42 import re
43 import shutil
44 import subprocess
45 import sys
46 import tempfile
47 import threading
48
49
50 def find_compilation_database(path):
51 """Adjusts the directory until a compilation database is found."""
52 result = './'
53 while not os.path.isfile(os.path.join(result, path)):
54 if os.path.realpath(result) == '/':
55 print 'Error: could not find compilation database.'
56 sys.exit(1)
57 result += '../'
58 return os.path.realpath(result)
59
60
61 def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
62 header_filter):
63 """Gets a command line for clang-tidy."""
64 start = [clang_tidy_binary]
65 if header_filter is not None:
66 start.append('-header-filter=' + header_filter)
67 else:
68 # Show warnings in all in-project headers by default.
69 start.append('-header-filter=^' + build_path + '/.*')
70 if checks:
71 start.append('-checks=' + checks)
72 if tmpdir is not None:
73 start.append('-export-fixes')
74 # Get a temporary file. We immediately close the handle so clang-tidy can
75 # overwrite it.
76 (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
77 os.close(handle)
78 start.append(name)
79 start.append('-p=' + build_path)
80 start.append(f)
81 return start
82
83
84 def apply_fixes(args, tmpdir):
85 """Calls clang-apply-fixes on a given directory. Deletes the dir when done."""
86 invocation = [args.clang_apply_replacements_binary]
87 if args.format:
88 invocation.append('-format')
89 invocation.append(tmpdir)
90 subprocess.call(invocation)
91 shutil.rmtree(tmpdir)
92
93
94 def run_tidy(args, tmpdir, build_path, queue):
95 """Takes filenames out of queue and runs clang-tidy on them."""
96 while True:
97 name = queue.get()
98 invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks,
99 tmpdir, build_path, args.header_filter)
100 sys.stdout.write(' '.join(invocation) + '\n')
101 subprocess.call(invocation)
102 queue.task_done()
103
104
105 def main():
106 parser = argparse.ArgumentParser(description='Runs clang-tidy over all files '
107 'in a compilation database. Requires '
108 'clang-tidy and clang-apply-replacements in '
109 '$PATH.')
110 parser.add_argument('-clang-tidy-binary', metavar='PATH',
111 default='clang-tidy',
112 help='path to clang-tidy binary')
113 parser.add_argument('-clang-apply-replacements-binary', metavar='PATH',
114 default='clang-apply-replacements',
115 help='path to clang-apply-replacements binary')
116 parser.add_argument('-checks', default=None,
117 help='checks filter, when not specified, use clang-tidy '
118 'default')
119 parser.add_argument('-header-filter', default=None,
120 help='regular expression matching the names of the '
121 'headers to output diagnostics from. Diagnostics from '
122 'the main file of each translation unit are always '
123 'displayed.')
124 parser.add_argument('-j', type=int, default=0,
125 help='number of tidy instances to be run in parallel.')
126 parser.add_argument('files', nargs='*', default=['.*'],
127 help='files to be processed (regex on path)')
128 parser.add_argument('-fix', action='store_true', help='apply fix-its')
129 parser.add_argument('-format', action='store_true', help='Reformat code '
130 'after applying fixes')
131 parser.add_argument('-p', dest='build_path',
132 help='Path used to read a compile command database.')
133 args = parser.parse_args()
134
135 db_path = 'compile_commands.json'
136
137 if args.build_path is not None:
138 build_path = args.build_path
139 else:
140 # Find our database
141 build_path = find_compilation_database(db_path)
142
143 try:
144 invocation = [args.clang_tidy_binary, '-list-checks']
145 invocation.append('-p=' + build_path)
146 if args.checks:
147 invocation.append('-checks=' + args.checks)
148 invocation.append('-')
149 print subprocess.check_output(invocation)
150 except:
151 print >>sys.stderr, "Unable to run clang-tidy."
152 sys.exit(1)
153
154 # Load the database and extract all files.
155 database = json.load(open(os.path.join(build_path, db_path)))
156 files = [entry['file'] for entry in database]
157
158 max_task = args.j
159 if max_task == 0:
160 max_task = multiprocessing.cpu_count()
161
162 tmpdir = None
163 if args.fix:
164 tmpdir = tempfile.mkdtemp()
165
166 # Build up a big regexy filter from all command line arguments.
167 file_name_re = re.compile('(' + ')|('.join(args.files) + ')')
168
169 try:
170 # Spin up a bunch of tidy-launching threads.
171 queue = Queue.Queue(max_task)
172 for _ in range(max_task):
173 t = threading.Thread(target=run_tidy,
174 args=(args, tmpdir, build_path, queue))
175 t.daemon = True
176 t.start()
177
178 # Fill the queue with files.
179 for name in files:
180 if file_name_re.search(name):
181 queue.put(name)
182
183 # Wait for all threads to be done.
184 queue.join()
185
186 except KeyboardInterrupt:
187 # This is a sad hack. Unfortunately subprocess goes
188 # bonkers with ctrl-c and we start forking merrily.
189 print '\nCtrl-C detected, goodbye.'
190 if args.fix:
191 shutil.rmtree(tmpdir)
192 os.kill(0, 9)
193
194 if args.fix:
195 print 'Applying fixes ...'
196 apply_fixes(args, tmpdir)
197
198 if __name__ == '__main__':
199 main()
run-clang-tidy.find_compilation_database
def find_compilation_database
Definition: run-clang-tidy.py:50
run-clang-tidy.run_tidy
def run_tidy
Definition: run-clang-tidy.py:94
run-clang-tidy.apply_fixes
def apply_fixes
Definition: run-clang-tidy.py:84
run-clang-tidy.main
def main
Definition: run-clang-tidy.py:105
run-clang-tidy.get_tidy_invocation
def get_tidy_invocation
Definition: run-clang-tidy.py:62