blob: f67799b2d51132296830d05d49a5f5285c25f221 [file] [log] [blame]
snickolls-arm2decd2c2024-01-17 11:24:49 +00001#!/usr/bin/env python3
armvixl4a102ba2014-07-14 09:02:40 +01002
Alexandre Ramesb78f1392016-07-01 14:22:22 +01003# Copyright 2015, VIXL authors
armvixl4a102ba2014-07-14 09:02:40 +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
Alexandre Rames61e54cf2016-07-08 14:32:36 +010030import fnmatch
Alexandre Ramesb2746622016-07-11 16:12:39 +010031import hashlib
armvixl4a102ba2014-07-14 09:02:40 +010032import multiprocessing
Alexandre Ramesb2746622016-07-11 16:12:39 +010033import os
34import pickle
armvixl4a102ba2014-07-14 09:02:40 +010035import re
armvixldb644342015-07-21 11:37:10 +010036import signal
armvixl4a102ba2014-07-14 09:02:40 +010037import subprocess
38import sys
39
armvixldb644342015-07-21 11:37:10 +010040import config
armvixl4a102ba2014-07-14 09:02:40 +010041import printer
42import util
43
44
armvixldb644342015-07-21 11:37:10 +010045# Catch SIGINT to gracefully exit when ctrl+C is pressed.
46def sigint_handler(signal, frame):
47 sys.exit(1)
48signal.signal(signal.SIGINT, sigint_handler)
armvixl4a102ba2014-07-14 09:02:40 +010049
50def BuildOptions():
armvixl0f35e362016-05-10 13:57:58 +010051 parser = argparse.ArgumentParser(
armvixl4a102ba2014-07-14 09:02:40 +010052 description =
armvixl0f35e362016-05-10 13:57:58 +010053 '''This tool lints C++ files and produces a summary of the errors found.
Jacob Bramleyf89cb482019-06-28 15:53:18 +010054 If no files are provided on the command-line, all C++ source files are
55 processed, except for the test traces.
Alexandre Ramesb2746622016-07-11 16:12:39 +010056 Results are cached to speed up the process.
57 ''',
armvixl4a102ba2014-07-14 09:02:40 +010058 # Print default values.
59 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
armvixl0f35e362016-05-10 13:57:58 +010060 parser.add_argument('files', nargs = '*')
61 parser.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
62 default=multiprocessing.cpu_count(),
63 const=multiprocessing.cpu_count(),
armvixl4a102ba2014-07-14 09:02:40 +010064 help='''Runs the tests using N jobs. If the option is set
65 but no value is provided, the script will use as many jobs
66 as it thinks useful.''')
Alexandre Ramesb2746622016-07-11 16:12:39 +010067 parser.add_argument('--no-cache',
68 action='store_true', default=False,
69 help='Do not use cached lint results.')
armvixl0f35e362016-05-10 13:57:58 +010070 return parser.parse_args()
armvixl4a102ba2014-07-14 09:02:40 +010071
72
73
Alexandre Ramesb2746622016-07-11 16:12:39 +010074# Returns a tuple (filename, number of lint errors).
armvixldb644342015-07-21 11:37:10 +010075def Lint(filename, progress_prefix = ''):
76 command = ['cpplint.py', filename]
armvixl4a102ba2014-07-14 09:02:40 +010077 process = subprocess.Popen(command,
78 stdout=subprocess.PIPE,
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010079 stderr=subprocess.STDOUT)
armvixl4a102ba2014-07-14 09:02:40 +010080
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010081 outerr, _ = process.communicate()
armvixl4a102ba2014-07-14 09:02:40 +010082
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010083 if process.returncode == 0:
84 printer.PrintOverwritableLine(
85 progress_prefix + "Done processing %s" % filename,
86 type = printer.LINE_TYPE_LINTER)
87 return (filename, 0)
armvixl4a102ba2014-07-14 09:02:40 +010088
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010089 if progress_prefix:
90 outerr = re.sub('^', progress_prefix, outerr, flags=re.MULTILINE)
91 printer.Print(outerr)
armvixl4a102ba2014-07-14 09:02:40 +010092
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010093 # Find the number of errors in this file.
mmc28aff82b332025-02-06 16:16:15 +000094 res = re.search(r'Total errors found: (\d+)', outerr)
Jacob Bramley4c550932020-09-10 10:46:06 +010095 if res:
96 n_errors_str = res.string[res.start(1):res.end(1)]
97 n_errors = int(n_errors_str)
98 else:
99 print("Couldn't parse cpplint.py output.")
100 n_errors = -1
armvixl4a102ba2014-07-14 09:02:40 +0100101
Alexandre Ramesbf499cc2016-07-11 16:21:34 +0100102 return (filename, n_errors)
armvixl4a102ba2014-07-14 09:02:40 +0100103
104
105# The multiprocessing map_async function does not allow passing multiple
106# arguments directly, so use a wrapper.
107def LintWrapper(args):
armvixldb644342015-07-21 11:37:10 +0100108 # Run under a try-catch to avoid flooding the output when the script is
109 # interrupted from the keyboard with ctrl+C.
110 try:
111 return Lint(*args)
112 except:
113 sys.exit(1)
armvixl4a102ba2014-07-14 09:02:40 +0100114
115
Alexandre Ramesb2746622016-07-11 16:12:39 +0100116def ShouldLint(filename, cached_results):
117 filename = os.path.realpath(filename)
118 if filename not in cached_results:
119 return True
120 with open(filename, 'rb') as f:
121 file_hash = hashlib.md5(f.read()).hexdigest()
122 return file_hash != cached_results[filename]
123
124
armvixl4a102ba2014-07-14 09:02:40 +0100125# Returns the total number of errors found in the files linted.
Alexandre Ramesb2746622016-07-11 16:12:39 +0100126# `cached_results` must be a dictionary, with the format:
127# { 'filename': file_hash, 'other_filename': other_hash, ... }
128# If not `None`, `cached_results` is used to avoid re-linting files, and new
129# results are stored in it.
130def LintFiles(files,
131 jobs = 1,
132 progress_prefix = '',
133 cached_results = None):
armvixldb644342015-07-21 11:37:10 +0100134 if not IsCppLintAvailable():
135 print(
136 printer.COLOUR_RED + \
137 ("cpplint.py not found. Please ensure the depot"
138 " tools are installed and in your PATH. See"
139 " http://dev.chromium.org/developers/how-tos/install-depot-tools for"
140 " details.") + \
141 printer.NO_COLOUR)
142 return -1
143
Alexandre Ramesb2746622016-07-11 16:12:39 +0100144 # Filter out directories.
snickolls-arm2decd2c2024-01-17 11:24:49 +0000145 files = list(filter(os.path.isfile, files))
Alexandre Ramesb2746622016-07-11 16:12:39 +0100146
147 # Filter out files for which we have a cached correct result.
148 if cached_results is not None and len(cached_results) != 0:
149 n_input_files = len(files)
snickolls-arm2decd2c2024-01-17 11:24:49 +0000150 files = [f for f in files if ShouldLint(f, cached_results)]
Alexandre Ramesb2746622016-07-11 16:12:39 +0100151 n_skipped_files = n_input_files - len(files)
152 if n_skipped_files != 0:
153 printer.Print(
154 progress_prefix +
155 'Skipping %d correct files that were already processed.' %
156 n_skipped_files)
157
armvixl4a102ba2014-07-14 09:02:40 +0100158 pool = multiprocessing.Pool(jobs)
159 # The '.get(9999999)' is workaround to allow killing the test script with
160 # ctrl+C from the shell. This bug is documented at
161 # http://bugs.python.org/issue8296.
armvixldb644342015-07-21 11:37:10 +0100162 tasks = [(f, progress_prefix) for f in files]
163 # Run under a try-catch to avoid flooding the output when the script is
164 # interrupted from the keyboard with ctrl+C.
165 try:
166 results = pool.map_async(LintWrapper, tasks).get(9999999)
167 pool.close()
168 pool.join()
169 except KeyboardInterrupt:
170 pool.terminate()
171 sys.exit(1)
Alexandre Ramesb2746622016-07-11 16:12:39 +0100172
snickolls-arm2decd2c2024-01-17 11:24:49 +0000173 n_errors = sum([filename_errors[1] for filename_errors in results])
Alexandre Ramesb2746622016-07-11 16:12:39 +0100174
175 if cached_results is not None:
176 for filename, errors in results:
177 if errors == 0:
178 with open(filename, 'rb') as f:
179 filename = os.path.realpath(filename)
180 file_hash = hashlib.md5(f.read()).hexdigest()
181 cached_results[filename] = file_hash
182
armvixl4a102ba2014-07-14 09:02:40 +0100183
184 printer.PrintOverwritableLine(
185 progress_prefix + 'Total errors found: %d' % n_errors)
186 printer.EnsureNewLine()
187 return n_errors
188
189
190def IsCppLintAvailable():
191 retcode, unused_output = util.getstatusoutput('which cpplint.py')
192 return retcode == 0
193
194
mmc28aff82b332025-02-06 16:16:15 +0000195CPP_EXT_REGEXP = re.compile(r'\.(cc|h)$')
Alexandre Rames61e54cf2016-07-08 14:32:36 +0100196def IsLinterInput(filename):
armvixldb644342015-07-21 11:37:10 +0100197 # lint all C++ files.
armvixl4a102ba2014-07-14 09:02:40 +0100198 return CPP_EXT_REGEXP.search(filename) != None
armvixldb644342015-07-21 11:37:10 +0100199
Alexandre Ramesb2746622016-07-11 16:12:39 +0100200
Alexandre Ramesb2746622016-07-11 16:12:39 +0100201cached_results_pkl_filename = \
202 os.path.join(config.dir_tools, '.cached_lint_results.pkl')
203
204
205def ReadCachedResults():
206 cached_results = {}
207 if os.path.isfile(cached_results_pkl_filename):
208 with open(cached_results_pkl_filename, 'rb') as pkl_file:
209 cached_results = pickle.load(pkl_file)
210 return cached_results
211
212
213def CacheResults(results):
214 with open(cached_results_pkl_filename, 'wb') as pkl_file:
215 pickle.dump(results, pkl_file)
216
217
Alexandre Rames61e54cf2016-07-08 14:32:36 +0100218def FilterOutTestTraceHeaders(files):
219 def IsTraceHeader(f):
220 relative_aarch32_traces_path = os.path.relpath(config.dir_aarch32_traces,'.')
221 relative_aarch64_traces_path = os.path.relpath(config.dir_aarch64_traces,'.')
222 return \
223 fnmatch.fnmatch(f, os.path.join(relative_aarch32_traces_path, '*.h')) or \
224 fnmatch.fnmatch(f, os.path.join(relative_aarch64_traces_path, '*.h'))
snickolls-arm2decd2c2024-01-17 11:24:49 +0000225 return [f for f in files if not IsTraceHeader(f)]
Alexandre Rames61e54cf2016-07-08 14:32:36 +0100226
227
Alexandre Ramesb2746622016-07-11 16:12:39 +0100228def RunLinter(files, jobs=1, progress_prefix='', cached=True):
229 results = {} if not cached else ReadCachedResults()
230
231 rc = LintFiles(files,
232 jobs=jobs,
233 progress_prefix=progress_prefix,
234 cached_results=results)
235
236 CacheResults(results)
237 return rc
238
239
armvixl4a102ba2014-07-14 09:02:40 +0100240if __name__ == '__main__':
241 # Parse the arguments.
242 args = BuildOptions()
243
Jacob Bramleyf89cb482019-06-28 15:53:18 +0100244 files = args.files or util.get_source_files()
Alexandre Ramesb2746622016-07-11 16:12:39 +0100245
246 cached = not args.no_cache
247 retcode = RunLinter(files, jobs=args.jobs, cached=cached)
248
armvixl4a102ba2014-07-14 09:02:40 +0100249 sys.exit(retcode)