blob: 50dc0eb3c4d17defc691766b82073eeeaf7d101b [file] [log] [blame]
armvixl0f35e362016-05-10 13:57:58 +01001#!/usr/bin/env python2.7
2
3# Copyright 2016, ARM Limited
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 return 0 if rc == 0 else 1
114
115
116# The multiprocessing map_async function does not allow passing multiple
117# arguments directly, so use a wrapper.
118def ClangFormatWrapper(args):
119 # Run under a try-catch to avoid flooding the output when the script is
120 # interrupted from the keyboard with ctrl+C.
121 try:
122 return ClangFormat(*args)
123 except:
124 sys.exit(1)
125
126
127# Returns the total number of files incorrectly formatted.
128def ClangFormatFiles(files, in_place = False, jobs = 1, progress_prefix = ''):
129 if not util.IsCommandAvailable('clang-format-3.6'):
130 print(
131 printer.COLOUR_RED + \
132 ("`clang-format-3.6` not found. Please ensure it is installed "
133 "and in your PATH.") + \
134 printer.NO_COLOUR)
135 return -1
136
137 pool = multiprocessing.Pool(jobs)
138 # The '.get(9999999)' is workaround to allow killing the test script with
139 # ctrl+C from the shell. This bug is documented at
140 # http://bugs.python.org/issue8296.
141 tasks = [(f, in_place, progress_prefix) for f in files]
142 # Run under a try-catch to avoid flooding the output when the script is
143 # interrupted from the keyboard with ctrl+C.
144 try:
145 results = pool.map_async(ClangFormatWrapper, tasks).get(9999999)
146 pool.close()
147 pool.join()
148 except KeyboardInterrupt:
149 pool.terminate()
150 sys.exit(1)
151 rc = sum(results)
152
153 printer.PrintOverwritableLine(
154 progress_prefix + '%d files are incorrectly formatted.' % rc,
155 type = printer.LINE_TYPE_LINTER)
156 printer.EnsureNewLine()
157 return rc
158
159
160def Find(path, filters = ['*']):
161 files_found = []
162
163 def NameMatchesAnyFilter(name, ff):
164 for f in ff:
165 if fnmatch.fnmatch(name, f):
166 return True
167 return False
168
169 for root, dirs, files in os.walk(path):
170 files_found += [os.path.relpath(os.path.join(root, fn))
171 for fn in files if NameMatchesAnyFilter(fn, filters)]
172 return files_found
173
174
175def GetCppSourceFilesToFormat():
176 sources = []
177 source_dirs = [config.dir_benchmarks,
178 config.dir_examples,
179 config.dir_src_vixl ]
180 for directory in source_dirs:
181 sources += Find(directory, ['*.h', '*.cc'])
182 return sources
183
184
185if __name__ == '__main__':
186 # Parse the arguments.
187 args = BuildOptions()
188 files = args.files
189 if not files:
190 files = GetCppSourceFilesToFormat()
191
192 rc = ClangFormatFiles(files, in_place = args.in_place, jobs = args.jobs)
193 sys.exit(rc)