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 multiprocessing 31import os 32import re 33import subprocess 34import sys 35import tempfile 36 37from threaded_tests import Test, TestQueue 38import printer 39import util 40 41CLANG_FORMAT_VERSION_MAJOR = 3 42CLANG_FORMAT_VERSION_MINOR = 8 43 44DEFAULT_CLANG_FORMAT = \ 45 'clang-format-{}.{}'.format(CLANG_FORMAT_VERSION_MAJOR, 46 CLANG_FORMAT_VERSION_MINOR) 47 48is_output_redirected = not sys.stdout.isatty() 49 50def 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 73def 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 88def 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. 152def 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 176if __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