blob: d4c9f65b5c00433a3255f03c640602aba4e4d4c3 [file] [log] [blame]
armvixl4a102ba2014-07-14 09:02:40 +01001#!/usr/bin/env python2.7
2
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 git
42import printer
43import util
44
45
armvixldb644342015-07-21 11:37:10 +010046# Catch SIGINT to gracefully exit when ctrl+C is pressed.
47def sigint_handler(signal, frame):
48 sys.exit(1)
49signal.signal(signal.SIGINT, sigint_handler)
armvixl4a102ba2014-07-14 09:02:40 +010050
51def BuildOptions():
armvixl0f35e362016-05-10 13:57:58 +010052 parser = argparse.ArgumentParser(
armvixl4a102ba2014-07-14 09:02:40 +010053 description =
armvixl0f35e362016-05-10 13:57:58 +010054 '''This tool lints C++ files and produces a summary of the errors found.
Jacob Bramleyf89cb482019-06-28 15:53:18 +010055 If no files are provided on the command-line, all C++ source files are
56 processed, except for the test traces.
Alexandre Ramesb2746622016-07-11 16:12:39 +010057 Results are cached to speed up the process.
58 ''',
armvixl4a102ba2014-07-14 09:02:40 +010059 # Print default values.
60 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
armvixl0f35e362016-05-10 13:57:58 +010061 parser.add_argument('files', nargs = '*')
62 parser.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
63 default=multiprocessing.cpu_count(),
64 const=multiprocessing.cpu_count(),
armvixl4a102ba2014-07-14 09:02:40 +010065 help='''Runs the tests using N jobs. If the option is set
66 but no value is provided, the script will use as many jobs
67 as it thinks useful.''')
Alexandre Ramesb2746622016-07-11 16:12:39 +010068 parser.add_argument('--no-cache',
69 action='store_true', default=False,
70 help='Do not use cached lint results.')
armvixl0f35e362016-05-10 13:57:58 +010071 return parser.parse_args()
armvixl4a102ba2014-07-14 09:02:40 +010072
73
74
Alexandre Ramesb2746622016-07-11 16:12:39 +010075# Returns a tuple (filename, number of lint errors).
armvixldb644342015-07-21 11:37:10 +010076def Lint(filename, progress_prefix = ''):
77 command = ['cpplint.py', filename]
armvixl4a102ba2014-07-14 09:02:40 +010078 process = subprocess.Popen(command,
79 stdout=subprocess.PIPE,
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010080 stderr=subprocess.STDOUT)
armvixl4a102ba2014-07-14 09:02:40 +010081
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010082 outerr, _ = process.communicate()
armvixl4a102ba2014-07-14 09:02:40 +010083
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010084 if process.returncode == 0:
85 printer.PrintOverwritableLine(
86 progress_prefix + "Done processing %s" % filename,
87 type = printer.LINE_TYPE_LINTER)
88 return (filename, 0)
armvixl4a102ba2014-07-14 09:02:40 +010089
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010090 if progress_prefix:
91 outerr = re.sub('^', progress_prefix, outerr, flags=re.MULTILINE)
92 printer.Print(outerr)
armvixl4a102ba2014-07-14 09:02:40 +010093
Alexandre Ramesbf499cc2016-07-11 16:21:34 +010094 # Find the number of errors in this file.
Jacob Bramley4c550932020-09-10 10:46:06 +010095 res = re.search('Total errors found: (\d+)', outerr)
96 if res:
97 n_errors_str = res.string[res.start(1):res.end(1)]
98 n_errors = int(n_errors_str)
99 else:
100 print("Couldn't parse cpplint.py output.")
101 n_errors = -1
armvixl4a102ba2014-07-14 09:02:40 +0100102
Alexandre Ramesbf499cc2016-07-11 16:21:34 +0100103 return (filename, n_errors)
armvixl4a102ba2014-07-14 09:02:40 +0100104
105
106# The multiprocessing map_async function does not allow passing multiple
107# arguments directly, so use a wrapper.
108def LintWrapper(args):
armvixldb644342015-07-21 11:37:10 +0100109 # Run under a try-catch to avoid flooding the output when the script is
110 # interrupted from the keyboard with ctrl+C.
111 try:
112 return Lint(*args)
113 except:
114 sys.exit(1)
armvixl4a102ba2014-07-14 09:02:40 +0100115
116
Alexandre Ramesb2746622016-07-11 16:12:39 +0100117def ShouldLint(filename, cached_results):
118 filename = os.path.realpath(filename)
119 if filename not in cached_results:
120 return True
121 with open(filename, 'rb') as f:
122 file_hash = hashlib.md5(f.read()).hexdigest()
123 return file_hash != cached_results[filename]
124
125
armvixl4a102ba2014-07-14 09:02:40 +0100126# Returns the total number of errors found in the files linted.
Alexandre Ramesb2746622016-07-11 16:12:39 +0100127# `cached_results` must be a dictionary, with the format:
128# { 'filename': file_hash, 'other_filename': other_hash, ... }
129# If not `None`, `cached_results` is used to avoid re-linting files, and new
130# results are stored in it.
131def LintFiles(files,
132 jobs = 1,
133 progress_prefix = '',
134 cached_results = None):
armvixldb644342015-07-21 11:37:10 +0100135 if not IsCppLintAvailable():
136 print(
137 printer.COLOUR_RED + \
138 ("cpplint.py not found. Please ensure the depot"
139 " tools are installed and in your PATH. See"
140 " http://dev.chromium.org/developers/how-tos/install-depot-tools for"
141 " details.") + \
142 printer.NO_COLOUR)
143 return -1
144
Alexandre Ramesb2746622016-07-11 16:12:39 +0100145 # Filter out directories.
146 files = filter(os.path.isfile, files)
147
148 # Filter out files for which we have a cached correct result.
149 if cached_results is not None and len(cached_results) != 0:
150 n_input_files = len(files)
151 files = filter(lambda f: ShouldLint(f, cached_results), files)
152 n_skipped_files = n_input_files - len(files)
153 if n_skipped_files != 0:
154 printer.Print(
155 progress_prefix +
156 'Skipping %d correct files that were already processed.' %
157 n_skipped_files)
158
armvixl4a102ba2014-07-14 09:02:40 +0100159 pool = multiprocessing.Pool(jobs)
160 # The '.get(9999999)' is workaround to allow killing the test script with
161 # ctrl+C from the shell. This bug is documented at
162 # http://bugs.python.org/issue8296.
armvixldb644342015-07-21 11:37:10 +0100163 tasks = [(f, progress_prefix) for f in files]
164 # Run under a try-catch to avoid flooding the output when the script is
165 # interrupted from the keyboard with ctrl+C.
166 try:
167 results = pool.map_async(LintWrapper, tasks).get(9999999)
168 pool.close()
169 pool.join()
170 except KeyboardInterrupt:
171 pool.terminate()
172 sys.exit(1)
Alexandre Ramesb2746622016-07-11 16:12:39 +0100173
174 n_errors = sum(map(lambda (filename, errors): errors, results))
175
176 if cached_results is not None:
177 for filename, errors in results:
178 if errors == 0:
179 with open(filename, 'rb') as f:
180 filename = os.path.realpath(filename)
181 file_hash = hashlib.md5(f.read()).hexdigest()
182 cached_results[filename] = file_hash
183
armvixl4a102ba2014-07-14 09:02:40 +0100184
185 printer.PrintOverwritableLine(
186 progress_prefix + 'Total errors found: %d' % n_errors)
187 printer.EnsureNewLine()
188 return n_errors
189
190
191def IsCppLintAvailable():
192 retcode, unused_output = util.getstatusoutput('which cpplint.py')
193 return retcode == 0
194
195
196CPP_EXT_REGEXP = re.compile('\.(cc|h)$')
Alexandre Rames61e54cf2016-07-08 14:32:36 +0100197def IsLinterInput(filename):
armvixldb644342015-07-21 11:37:10 +0100198 # lint all C++ files.
armvixl4a102ba2014-07-14 09:02:40 +0100199 return CPP_EXT_REGEXP.search(filename) != None
armvixldb644342015-07-21 11:37:10 +0100200
Alexandre Ramesb2746622016-07-11 16:12:39 +0100201
Alexandre Ramesb2746622016-07-11 16:12:39 +0100202cached_results_pkl_filename = \
203 os.path.join(config.dir_tools, '.cached_lint_results.pkl')
204
205
206def ReadCachedResults():
207 cached_results = {}
208 if os.path.isfile(cached_results_pkl_filename):
209 with open(cached_results_pkl_filename, 'rb') as pkl_file:
210 cached_results = pickle.load(pkl_file)
211 return cached_results
212
213
214def CacheResults(results):
215 with open(cached_results_pkl_filename, 'wb') as pkl_file:
216 pickle.dump(results, pkl_file)
217
218
Alexandre Rames61e54cf2016-07-08 14:32:36 +0100219def FilterOutTestTraceHeaders(files):
220 def IsTraceHeader(f):
221 relative_aarch32_traces_path = os.path.relpath(config.dir_aarch32_traces,'.')
222 relative_aarch64_traces_path = os.path.relpath(config.dir_aarch64_traces,'.')
223 return \
224 fnmatch.fnmatch(f, os.path.join(relative_aarch32_traces_path, '*.h')) or \
225 fnmatch.fnmatch(f, os.path.join(relative_aarch64_traces_path, '*.h'))
226 return filter(lambda f: not IsTraceHeader(f), files)
227
228
Alexandre Ramesb2746622016-07-11 16:12:39 +0100229def RunLinter(files, jobs=1, progress_prefix='', cached=True):
230 results = {} if not cached else ReadCachedResults()
231
232 rc = LintFiles(files,
233 jobs=jobs,
234 progress_prefix=progress_prefix,
235 cached_results=results)
236
237 CacheResults(results)
238 return rc
239
240
armvixl4a102ba2014-07-14 09:02:40 +0100241if __name__ == '__main__':
242 # Parse the arguments.
243 args = BuildOptions()
244
Jacob Bramleyf89cb482019-06-28 15:53:18 +0100245 files = args.files or util.get_source_files()
Alexandre Ramesb2746622016-07-11 16:12:39 +0100246
247 cached = not args.no_cache
248 retcode = RunLinter(files, jobs=args.jobs, cached=cached)
249
armvixl4a102ba2014-07-14 09:02:40 +0100250 sys.exit(retcode)