• 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 
29 import argparse
30 import multiprocessing
31 import os
32 import re
33 import subprocess
34 import sys
35 import tempfile
36 
37 from threaded_tests import Test, TestQueue
38 import printer
39 import util
40 
41 CLANG_FORMAT_VERSION_MAJOR = 4
42 CLANG_FORMAT_VERSION_MINOR = 0
43 
44 DEFAULT_CLANG_FORMAT = \
45     'clang-format-{}.{}'.format(CLANG_FORMAT_VERSION_MAJOR,
46                                 CLANG_FORMAT_VERSION_MINOR)
47 
48 is_output_redirected = not sys.stdout.isatty()
49 
50 def BuildOptions():
51   parser = argparse.ArgumentParser(
52     description = '''This tool runs `clang-format` on C++ files.
53     If no files are provided on the command-line, all C++ source files are
54     processed, except for the test traces.
55     When available, `colordiff` is automatically used to colour the output.''',
56     # Print default values.
57     formatter_class = argparse.ArgumentDefaultsHelpFormatter)
58   parser.add_argument('files', nargs = '*')
59   parser.add_argument('--clang-format', default=DEFAULT_CLANG_FORMAT,
60                       help='Path to clang-format.')
61   parser.add_argument('--in-place', '-i',
62                       action = 'store_true', default = False,
63                       help = 'Edit files in place.')
64   parser.add_argument('--jobs', '-j', metavar = 'N', type = int, nargs = '?',
65                       default = multiprocessing.cpu_count(),
66                       const = multiprocessing.cpu_count(),
67                       help = '''Runs the tests using N jobs. If the option is set
68                       but no value is provided, the script will use as many jobs
69                       as it thinks useful.''')
70   return parser.parse_args()
71 
72 
73 def ClangFormatIsAvailable(clang_format):
74   if not util.IsCommandAvailable(clang_format):
75     return False
76   cmd = '%s -version' % clang_format
77   rc, version = util.getstatusoutput(cmd)
78   if rc != 0:
79       util.abort("Failed to execute %s: %s" % (cmd, version))
80   m = re.search("^clang-format version (\d)\.(\d)\.\d.*$",
81                 version.decode(), re.M)
82   if not m:
83       util.abort("Failed to get clang-format's version: %s" % version)
84   major, minor = m.groups()
85   return int(major) == CLANG_FORMAT_VERSION_MAJOR and \
86       int(minor) == CLANG_FORMAT_VERSION_MINOR
87 
88 def RunTest(test):
89   filename = test.args['filename']
90   clang_format = test.args['clang_format']
91   in_place = test.args['in_place']
92 
93   rc = 0
94 
95   cmd_format = [clang_format, filename]
96   temp_file, temp_file_name = tempfile.mkstemp(prefix = 'clang_format_')
97   cmd_format_string = '$ ' + ' '.join(cmd_format) + ' > %s' % temp_file_name
98   p_format = subprocess.Popen(cmd_format,
99                               stdout = temp_file, stderr = subprocess.STDOUT)
100 
101   rc += p_format.wait()
102 
103   cmd_diff = ['diff', '--unified', filename, temp_file_name]
104   cmd_diff_string = '$ ' + ' '.join(cmd_diff)
105   p_diff = subprocess.Popen(cmd_diff,
106                             stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
107 
108   if util.IsCommandAvailable('colordiff') and not is_output_redirected:
109     p_colordiff = subprocess.Popen(
110             ['colordiff', '--unified'],
111             stdin = p_diff.stdout,
112             stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
113     out, unused = p_colordiff.communicate()
114   else:
115     out, unused = p_diff.communicate()
116 
117   rc += p_diff.wait()
118 
119   if in_place:
120       cmd_format = [clang_format, '-i', filename]
121       p_format = subprocess.Popen(cmd_format,
122                                   stdout=temp_file, stderr=subprocess.STDOUT)
123 
124   if rc != 0:
125     with Test.n_tests_failed.get_lock(): Test.n_tests_failed.value += 1
126   else:
127     with Test.n_tests_passed.get_lock(): Test.n_tests_passed.value += 1
128 
129   printer.__print_lock__.acquire()
130 
131   printer.UpdateProgress(test.shared.start_time,
132                          Test.n_tests_passed.value,
133                          Test.n_tests_failed.value,
134                          test.shared.n_tests,
135                          Test.n_tests_skipped.value,
136                          test.shared.n_known_failures,
137                          test.name,
138                          prevent_next_overwrite = rc != 0,
139                          has_lock = True,
140                          prefix = test.shared.progress_prefix)
141 
142   if rc != 0:
143     printer.Print('Incorrectly formatted file: ' + filename + '\n' + \
144                   cmd_format_string + '\n' + \
145                   cmd_diff_string + '\n' + \
146                   out, has_lock = True)
147   printer.__print_lock__.release()
148 
149   os.remove(temp_file_name)
150 
151 # Returns the total number of files incorrectly formatted.
152 def ClangFormatFiles(files, clang_format, in_place = False, jobs = 1,
153                      progress_prefix = ''):
154   if not ClangFormatIsAvailable(clang_format):
155     error_message = "`{}` version {}.{} not found. Please ensure it " \
156                     "is installed, in your PATH and the correct version." \
157                     .format(clang_format,
158                             CLANG_FORMAT_VERSION_MAJOR,
159                             CLANG_FORMAT_VERSION_MINOR)
160     print(printer.COLOUR_RED + error_message + printer.NO_COLOUR)
161     return -1
162 
163   queue = TestQueue(prefix = progress_prefix)
164   for f in files:
165     queue.AddTest(f, filename = f, clang_format = clang_format, in_place = in_place)
166 
167   rc = queue.Run(jobs, True, RunTest)
168 
169   printer.PrintOverwritableLine(
170       progress_prefix + '%d files are incorrectly formatted.' % rc,
171       type = printer.LINE_TYPE_LINTER)
172   printer.EnsureNewLine()
173 
174   return rc
175 
176 if __name__ == '__main__':
177   # Parse the arguments.
178   args = BuildOptions()
179   files = args.files or util.get_source_files()
180 
181   rc = ClangFormatFiles(files, clang_format = args.clang_format,
182                         in_place = args.in_place, jobs = args.jobs)
183 
184   sys.exit(rc)
185