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