1#!/usr/bin/env python3 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 36import shutil 37 38from threaded_tests import Test, TestQueue 39import printer 40import util 41 42CLANG_TOOL_SUPPORTED_VERSIONS = range(11, 16) 43 44DEFAULT_CLANG_FORMAT = 'clang-format' 45 46CLANG_TOOL_VERSION_MATCH = r"(clang-format|LLVM) version ([\d]+)\.[\d]+\.[\d]+.*$" 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 72def is_supported(tool): 73 if not shutil.which(tool): 74 return False 75 76 cmd = '%s -version' % tool 77 78 try: 79 rc, version = util.getstatusoutput(cmd) 80 except OSError: 81 return False 82 83 if rc != 0: 84 util.abort("Failed to execute %s: %s" % (cmd, version)) 85 m = re.search(CLANG_TOOL_VERSION_MATCH, version, re.MULTILINE) 86 if not m: 87 util.abort("Failed to get clang tool version: %s" % version) 88 _, major = m.groups() 89 90 if int(major) in CLANG_TOOL_SUPPORTED_VERSIONS: 91 return True 92 93 return False 94 95def detect_clang_tool(tool): 96 supported_tools = [tool] + [tool + '-' + str(ver) for ver in CLANG_TOOL_SUPPORTED_VERSIONS] 97 for tool in supported_tools: 98 if is_supported(tool): 99 return tool 100 101 return None 102 103 104def RunTest(test): 105 filename = test.args['filename'] 106 clang_format = test.args['clang_format'] 107 in_place = test.args['in_place'] 108 109 rc = 0 110 111 cmd_format = [clang_format, filename] 112 temp_file, temp_file_name = tempfile.mkstemp(prefix = 'clang_format_') 113 cmd_format_string = '$ ' + ' '.join(cmd_format) + ' > %s' % temp_file_name 114 p_format = subprocess.Popen(cmd_format, 115 stdout = temp_file, stderr = subprocess.STDOUT) 116 117 rc += p_format.wait() 118 119 cmd_diff = ['diff', '--unified', filename, temp_file_name] 120 cmd_diff_string = '$ ' + ' '.join(cmd_diff) 121 122 p_diff = subprocess.Popen(cmd_diff, 123 stdout = subprocess.PIPE, stderr = subprocess.STDOUT) 124 125 if util.IsCommandAvailable('colordiff') and not is_output_redirected: 126 p_colordiff = subprocess.Popen( 127 ['colordiff', '--unified'], 128 stdin = p_diff.stdout, 129 stdout = subprocess.PIPE, stderr = subprocess.STDOUT) 130 out, unused = p_colordiff.communicate() 131 rc += p_colordiff.returncode 132 else: 133 out, unused = p_diff.communicate() 134 rc += p_diff.returncode 135 136 137 if in_place: 138 cmd_format = [clang_format, '-i', filename] 139 subprocess.run(cmd_format, stdout=temp_file, stderr=subprocess.STDOUT) 140 141 if rc != 0: 142 with Test.n_tests_failed.get_lock(): Test.n_tests_failed.value += 1 143 else: 144 with Test.n_tests_passed.get_lock(): Test.n_tests_passed.value += 1 145 146 printer.__print_lock__.acquire() 147 148 printer.UpdateProgress(test.shared.start_time, 149 Test.n_tests_passed.value, 150 Test.n_tests_failed.value, 151 test.shared.n_tests, 152 Test.n_tests_skipped.value, 153 test.shared.n_known_failures, 154 test.name, 155 prevent_next_overwrite = rc != 0, 156 has_lock = True, 157 prefix = test.shared.progress_prefix) 158 159 if rc != 0: 160 printer.Print('Incorrectly formatted file: ' + filename + '\n' + \ 161 cmd_format_string + '\n' + \ 162 cmd_diff_string + '\n' + \ 163 out.decode(), has_lock = True) 164 165 printer.__print_lock__.release() 166 167 os.remove(temp_file_name) 168 169# Returns the total number of files incorrectly formatted. 170def ClangFormatFiles(files, clang_format, in_place = False, jobs = 1, 171 progress_prefix = ''): 172 173 clang_format = detect_clang_tool("clang-format") 174 175 if not clang_format: 176 error_message = "clang-format not found. Please ensure it " \ 177 "is installed, in your PATH and the correct version." 178 print(printer.COLOUR_RED + error_message + printer.NO_COLOUR) 179 return -1 180 181 queue = TestQueue(prefix = progress_prefix) 182 for f in files: 183 queue.AddTest(f, filename = f, clang_format = clang_format, in_place = in_place) 184 185 rc = queue.Run(jobs, True, RunTest) 186 187 printer.PrintOverwritableLine( 188 progress_prefix + '%d files are incorrectly formatted.' % rc, 189 type = printer.LINE_TYPE_LINTER) 190 printer.EnsureNewLine() 191 192 return rc 193 194if __name__ == '__main__': 195 # Parse the arguments. 196 args = BuildOptions() 197 files = args.files or util.get_source_files(exclude_dirs=['.*', '*/traces/*', '*/aarch32/*']) 198 199 rc = ClangFormatFiles(files, clang_format = args.clang_format, 200 in_place = args.in_place, jobs = args.jobs) 201 202 sys.exit(rc) 203