• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2.7
2
3# Copyright 2016, VIXL authors
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9#   * Redistributions of source code must retain the above copyright notice,
10#     this list of conditions and the following disclaimer.
11#   * Redistributions in binary form must reproduce the above copyright notice,
12#     this list of conditions and the following disclaimer in the documentation
13#     and/or other materials provided with the distribution.
14#   * Neither the name of ARM Limited nor the names of its contributors may be
15#     used to endorse or promote products derived from this software without
16#     specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import argparse
30import fnmatch
31import multiprocessing
32import os
33import signal
34import subprocess
35import sys
36import tempfile
37
38import config
39import git
40import printer
41import util
42
43
44is_output_redirected = not sys.stdout.isatty()
45
46# Catch SIGINT to gracefully exit when ctrl+C is pressed.
47def sigint_handler(signal, frame):
48  sys.exit(1)
49signal.signal(signal.SIGINT, sigint_handler)
50
51def BuildOptions():
52  parser = argparse.ArgumentParser(
53    description = '''This tool runs `clang-format` on C++ files.
54    If no files are provided on the command-line, all C++ source files in `src`,
55    `sample`, and `benchmarks` are processed.
56    When available, `colordiff` is automatically used to clour the output.''',
57    # Print default values.
58    formatter_class = argparse.ArgumentDefaultsHelpFormatter)
59  parser.add_argument('files', nargs = '*')
60  parser.add_argument('--in-place', '-i',
61                      action = 'store_true', default = False,
62                      help = 'Edit files in place.')
63  parser.add_argument('--jobs', '-j', metavar = 'N', type = int, nargs = '?',
64                      default = multiprocessing.cpu_count(),
65                      const = multiprocessing.cpu_count(),
66                      help = '''Runs the tests using N jobs. If the option is set
67                      but no value is provided, the script will use as many jobs
68                      as it thinks useful.''')
69  return parser.parse_args()
70
71
72# Returns 0 if the file is correctly formatted, or 1 otherwise.
73def ClangFormat(filename, in_place = False, progress_prefix = ''):
74  rc = 0
75  printer.PrintOverwritableLine('Processing %s' % filename,
76                                type = printer.LINE_TYPE_LINTER)
77
78  cmd_format = ['clang-format-3.6', filename]
79  temp_file, temp_file_name = tempfile.mkstemp(prefix = 'clang_format_')
80  cmd_format_string = '$ ' + ' '.join(cmd_format) + ' > %s' % temp_file_name
81  p_format = subprocess.Popen(cmd_format,
82                              stdout = temp_file, stderr = subprocess.STDOUT)
83
84  rc += p_format.wait()
85
86  cmd_diff = ['diff', '--unified', filename, temp_file_name]
87  cmd_diff_string = '$ ' + ' '.join(cmd_diff)
88  p_diff = subprocess.Popen(cmd_diff,
89                            stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
90
91  if util.IsCommandAvailable('colordiff') and not is_output_redirected:
92    p_colordiff = subprocess.Popen(
93            ['colordiff', '--unified'],
94            stdin = p_diff.stdout,
95            stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
96    out, unused = p_colordiff.communicate()
97  else:
98    out, unused = p_diff.communicate()
99
100  rc += p_diff.wait()
101
102  if in_place:
103      cmd_format = ['clang-format-3.6', '-i', filename]
104      p_format = subprocess.Popen(cmd_format,
105                                  stdout=temp_file, stderr=subprocess.STDOUT)
106
107  if rc != 0:
108    printer.Print('Incorrectly formatted file: ' + filename + '\n' + \
109                  cmd_format_string + '\n' + \
110                  cmd_diff_string + '\n' + \
111                  out)
112
113  os.remove(temp_file_name)
114
115  return 0 if rc == 0 else 1
116
117
118# The multiprocessing map_async function does not allow passing multiple
119# arguments directly, so use a wrapper.
120def ClangFormatWrapper(args):
121  # Run under a try-catch  to avoid flooding the output when the script is
122  # interrupted from the keyboard with ctrl+C.
123  try:
124    return ClangFormat(*args)
125  except:
126    sys.exit(1)
127
128
129# Returns the total number of files incorrectly formatted.
130def ClangFormatFiles(files, in_place = False, jobs = 1, progress_prefix = ''):
131  if not util.IsCommandAvailable('clang-format-3.6'):
132    print(
133      printer.COLOUR_RED + \
134      ("`clang-format-3.6` not found. Please ensure it is installed "
135       "and in your PATH.") + \
136      printer.NO_COLOUR)
137    return -1
138
139  pool = multiprocessing.Pool(jobs)
140  # The '.get(9999999)' is workaround to allow killing the test script with
141  # ctrl+C from the shell. This bug is documented at
142  # http://bugs.python.org/issue8296.
143  tasks = [(f, in_place, progress_prefix) for f in files]
144  # Run under a try-catch  to avoid flooding the output when the script is
145  # interrupted from the keyboard with ctrl+C.
146  try:
147    results = pool.map_async(ClangFormatWrapper, tasks).get(9999999)
148    pool.close()
149    pool.join()
150  except KeyboardInterrupt:
151    pool.terminate()
152    sys.exit(1)
153  rc = sum(results)
154
155  printer.PrintOverwritableLine(
156      progress_prefix + '%d files are incorrectly formatted.' % rc,
157      type = printer.LINE_TYPE_LINTER)
158  printer.EnsureNewLine()
159  return rc
160
161
162def Find(path, filters = ['*'], excluded_dir = ""):
163  files_found = []
164
165  def NameMatchesAnyFilter(name, ff):
166    for f in ff:
167      if fnmatch.fnmatch(name, f):
168        return True
169    return False
170
171  for root, dirs, files in os.walk(path):
172    files_found += [
173        os.path.join(root, fn)
174        for fn in files
175        # Include files which names match "filters".
176        # Exclude files for which the base directory is "excluded_dir".
177        if NameMatchesAnyFilter(os.path.relpath(fn), filters) and \
178            not os.path.dirname(os.path.join(root, fn)).endswith(excluded_dir)
179    ]
180  return files_found
181
182
183def GetCppSourceFilesToFormat():
184  sources = []
185  source_dirs = [config.dir_aarch32_benchmarks,
186                 config.dir_aarch32_examples,
187                 config.dir_aarch64_benchmarks,
188                 config.dir_aarch64_examples,
189                 config.dir_tests,
190                 config.dir_src_vixl ]
191  for directory in source_dirs:
192    sources += Find(directory, ['*.h', '*.cc'], 'traces')
193  return sources
194
195
196if __name__ == '__main__':
197  # Parse the arguments.
198  args = BuildOptions()
199  files = args.files
200  if not files:
201    files = GetCppSourceFilesToFormat()
202
203  rc = ClangFormatFiles(files, in_place = args.in_place, jobs = args.jobs)
204  sys.exit(rc)
205