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