clang-tools  4.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 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 
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()
def find_compilation_database
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)