blob: a9c53c9c5562a1cd54a92f7f0cb147ac4f39b97f [file] [log] [blame]
snickolls-arm2decd2c2024-01-17 11:24:49 +00001#!/usr/bin/env python3
armvixl0f35e362016-05-10 13:57:58 +01002
Alexandre Ramesb78f1392016-07-01 14:22:22 +01003# Copyright 2016, VIXL authors
armvixl0f35e362016-05-10 13:57:58 +01004# 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
armvixl0f35e362016-05-10 13:57:58 +010030import multiprocessing
31import os
Pierre Langlois44096c42018-05-23 23:15:25 +010032import re
armvixl0f35e362016-05-10 13:57:58 +010033import subprocess
34import sys
35import tempfile
mmc28a1a2c1d32024-02-01 16:43:49 +000036import shutil
armvixl0f35e362016-05-10 13:57:58 +010037
Anthony Barbier89eefef2019-07-05 11:15:13 +010038from threaded_tests import Test, TestQueue
armvixl0f35e362016-05-10 13:57:58 +010039import printer
40import util
41
mmc28a1a2c1d32024-02-01 16:43:49 +000042CLANG_TOOL_SUPPORTED_VERSIONS = range(11, 16)
Pierre Langlois44096c42018-05-23 23:15:25 +010043
mmc28a1a2c1d32024-02-01 16:43:49 +000044DEFAULT_CLANG_FORMAT = 'clang-format'
45
46CLANG_TOOL_VERSION_MATCH = r"(clang-format|LLVM) version ([\d]+)\.[\d]+\.[\d]+.*$"
armvixl0f35e362016-05-10 13:57:58 +010047
48is_output_redirected = not sys.stdout.isatty()
49
armvixl0f35e362016-05-10 13:57:58 +010050def BuildOptions():
51 parser = argparse.ArgumentParser(
52 description = '''This tool runs `clang-format` on C++ files.
Jacob Bramleyf89cb482019-06-28 15:53:18 +010053 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.''',
armvixl0f35e362016-05-10 13:57:58 +010056 # Print default values.
57 formatter_class = argparse.ArgumentDefaultsHelpFormatter)
58 parser.add_argument('files', nargs = '*')
Pierre Langlois44096c42018-05-23 23:15:25 +010059 parser.add_argument('--clang-format', default=DEFAULT_CLANG_FORMAT,
60 help='Path to clang-format.')
armvixl0f35e362016-05-10 13:57:58 +010061 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
mmc28a1a2c1d32024-02-01 16:43:49 +000072def is_supported(tool):
73 if not shutil.which(tool):
Pierre Langlois44096c42018-05-23 23:15:25 +010074 return False
mmc28a1a2c1d32024-02-01 16:43:49 +000075
76 cmd = '%s -version' % tool
77
78 try:
79 rc, version = util.getstatusoutput(cmd)
80 except OSError:
81 return False
82
Anthony Barbier89eefef2019-07-05 11:15:13 +010083 if rc != 0:
84 util.abort("Failed to execute %s: %s" % (cmd, version))
mmc28a1a2c1d32024-02-01 16:43:49 +000085 m = re.search(CLANG_TOOL_VERSION_MATCH, version, re.MULTILINE)
Anthony Barbier89eefef2019-07-05 11:15:13 +010086 if not m:
mmc28a1a2c1d32024-02-01 16:43:49 +000087 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
Pierre Langlois44096c42018-05-23 23:15:25 +0100103
Anthony Barbier89eefef2019-07-05 11:15:13 +0100104def RunTest(test):
105 filename = test.args['filename']
106 clang_format = test.args['clang_format']
107 in_place = test.args['in_place']
Pierre Langlois44096c42018-05-23 23:15:25 +0100108
armvixl0f35e362016-05-10 13:57:58 +0100109 rc = 0
armvixl0f35e362016-05-10 13:57:58 +0100110
Pierre Langlois44096c42018-05-23 23:15:25 +0100111 cmd_format = [clang_format, filename]
armvixl0f35e362016-05-10 13:57:58 +0100112 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)
mmc28a1a2c1d32024-02-01 16:43:49 +0000121
armvixl0f35e362016-05-10 13:57:58 +0100122 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()
mmc28aef2f4d12024-02-09 11:34:04 +0000131 rc += p_colordiff.returncode
armvixl0f35e362016-05-10 13:57:58 +0100132 else:
133 out, unused = p_diff.communicate()
mmc28aef2f4d12024-02-09 11:34:04 +0000134 rc += p_diff.returncode
armvixl0f35e362016-05-10 13:57:58 +0100135
armvixl0f35e362016-05-10 13:57:58 +0100136
137 if in_place:
Pierre Langlois44096c42018-05-23 23:15:25 +0100138 cmd_format = [clang_format, '-i', filename]
mmc28a1a2c1d32024-02-01 16:43:49 +0000139 subprocess.run(cmd_format, stdout=temp_file, stderr=subprocess.STDOUT)
armvixl0f35e362016-05-10 13:57:58 +0100140
141 if rc != 0:
Anthony Barbier89eefef2019-07-05 11:15:13 +0100142 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:
armvixl0f35e362016-05-10 13:57:58 +0100160 printer.Print('Incorrectly formatted file: ' + filename + '\n' + \
161 cmd_format_string + '\n' + \
162 cmd_diff_string + '\n' + \
mmc28a1a2c1d32024-02-01 16:43:49 +0000163 out.decode(), has_lock = True)
164
Anthony Barbier89eefef2019-07-05 11:15:13 +0100165 printer.__print_lock__.release()
armvixl0f35e362016-05-10 13:57:58 +0100166
Jacob Bramleybe4b7522016-10-19 14:27:52 +0100167 os.remove(temp_file_name)
168
armvixl0f35e362016-05-10 13:57:58 +0100169# Returns the total number of files incorrectly formatted.
Pierre Langlois44096c42018-05-23 23:15:25 +0100170def ClangFormatFiles(files, clang_format, in_place = False, jobs = 1,
171 progress_prefix = ''):
mmc28a1a2c1d32024-02-01 16:43:49 +0000172
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."
Pierre Langlois44096c42018-05-23 23:15:25 +0100178 print(printer.COLOUR_RED + error_message + printer.NO_COLOUR)
armvixl0f35e362016-05-10 13:57:58 +0100179 return -1
180
Anthony Barbier89eefef2019-07-05 11:15:13 +0100181 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)
armvixl0f35e362016-05-10 13:57:58 +0100186
187 printer.PrintOverwritableLine(
188 progress_prefix + '%d files are incorrectly formatted.' % rc,
189 type = printer.LINE_TYPE_LINTER)
190 printer.EnsureNewLine()
armvixl0f35e362016-05-10 13:57:58 +0100191
Anthony Barbier89eefef2019-07-05 11:15:13 +0100192 return rc
armvixl0f35e362016-05-10 13:57:58 +0100193
armvixl0f35e362016-05-10 13:57:58 +0100194if __name__ == '__main__':
195 # Parse the arguments.
196 args = BuildOptions()
mmc28a1a2c1d32024-02-01 16:43:49 +0000197 files = args.files or util.get_source_files(exclude_dirs=['.*', '*/traces/*', '*/aarch32/*'])
armvixl0f35e362016-05-10 13:57:58 +0100198
Pierre Langlois44096c42018-05-23 23:15:25 +0100199 rc = ClangFormatFiles(files, clang_format = args.clang_format,
200 in_place = args.in_place, jobs = args.jobs)
201
armvixl0f35e362016-05-10 13:57:58 +0100202 sys.exit(rc)