xref: /aosp_15_r20/external/vixl/tools/lint.py (revision f5c631da2f1efdd72b5fd1e20510e4042af13d77)
1*f5c631daSSadaf Ebrahimi#!/usr/bin/env python2.7
2*f5c631daSSadaf Ebrahimi
3*f5c631daSSadaf Ebrahimi# Copyright 2015, VIXL authors
4*f5c631daSSadaf Ebrahimi# All rights reserved.
5*f5c631daSSadaf Ebrahimi#
6*f5c631daSSadaf Ebrahimi# Redistribution and use in source and binary forms, with or without
7*f5c631daSSadaf Ebrahimi# modification, are permitted provided that the following conditions are met:
8*f5c631daSSadaf Ebrahimi#
9*f5c631daSSadaf Ebrahimi#   * Redistributions of source code must retain the above copyright notice,
10*f5c631daSSadaf Ebrahimi#     this list of conditions and the following disclaimer.
11*f5c631daSSadaf Ebrahimi#   * Redistributions in binary form must reproduce the above copyright notice,
12*f5c631daSSadaf Ebrahimi#     this list of conditions and the following disclaimer in the documentation
13*f5c631daSSadaf Ebrahimi#     and/or other materials provided with the distribution.
14*f5c631daSSadaf Ebrahimi#   * Neither the name of ARM Limited nor the names of its contributors may be
15*f5c631daSSadaf Ebrahimi#     used to endorse or promote products derived from this software without
16*f5c631daSSadaf Ebrahimi#     specific prior written permission.
17*f5c631daSSadaf Ebrahimi#
18*f5c631daSSadaf Ebrahimi# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
19*f5c631daSSadaf Ebrahimi# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20*f5c631daSSadaf Ebrahimi# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21*f5c631daSSadaf Ebrahimi# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22*f5c631daSSadaf Ebrahimi# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23*f5c631daSSadaf Ebrahimi# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24*f5c631daSSadaf Ebrahimi# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25*f5c631daSSadaf Ebrahimi# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26*f5c631daSSadaf Ebrahimi# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*f5c631daSSadaf Ebrahimi# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*f5c631daSSadaf Ebrahimi
29*f5c631daSSadaf Ebrahimiimport argparse
30*f5c631daSSadaf Ebrahimiimport fnmatch
31*f5c631daSSadaf Ebrahimiimport hashlib
32*f5c631daSSadaf Ebrahimiimport multiprocessing
33*f5c631daSSadaf Ebrahimiimport os
34*f5c631daSSadaf Ebrahimiimport pickle
35*f5c631daSSadaf Ebrahimiimport re
36*f5c631daSSadaf Ebrahimiimport signal
37*f5c631daSSadaf Ebrahimiimport subprocess
38*f5c631daSSadaf Ebrahimiimport sys
39*f5c631daSSadaf Ebrahimi
40*f5c631daSSadaf Ebrahimiimport config
41*f5c631daSSadaf Ebrahimiimport git
42*f5c631daSSadaf Ebrahimiimport printer
43*f5c631daSSadaf Ebrahimiimport util
44*f5c631daSSadaf Ebrahimi
45*f5c631daSSadaf Ebrahimi
46*f5c631daSSadaf Ebrahimi# Catch SIGINT to gracefully exit when ctrl+C is pressed.
47*f5c631daSSadaf Ebrahimidef sigint_handler(signal, frame):
48*f5c631daSSadaf Ebrahimi  sys.exit(1)
49*f5c631daSSadaf Ebrahimisignal.signal(signal.SIGINT, sigint_handler)
50*f5c631daSSadaf Ebrahimi
51*f5c631daSSadaf Ebrahimidef BuildOptions():
52*f5c631daSSadaf Ebrahimi  parser = argparse.ArgumentParser(
53*f5c631daSSadaf Ebrahimi      description =
54*f5c631daSSadaf Ebrahimi      '''This tool lints C++ files and produces a summary of the errors found.
55*f5c631daSSadaf Ebrahimi      If no files are provided on the command-line, all C++ source files are
56*f5c631daSSadaf Ebrahimi      processed, except for the test traces.
57*f5c631daSSadaf Ebrahimi      Results are cached to speed up the process.
58*f5c631daSSadaf Ebrahimi      ''',
59*f5c631daSSadaf Ebrahimi      # Print default values.
60*f5c631daSSadaf Ebrahimi      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
61*f5c631daSSadaf Ebrahimi  parser.add_argument('files', nargs = '*')
62*f5c631daSSadaf Ebrahimi  parser.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
63*f5c631daSSadaf Ebrahimi                      default=multiprocessing.cpu_count(),
64*f5c631daSSadaf Ebrahimi                      const=multiprocessing.cpu_count(),
65*f5c631daSSadaf Ebrahimi                      help='''Runs the tests using N jobs. If the option is set
66*f5c631daSSadaf Ebrahimi                      but no value is provided, the script will use as many jobs
67*f5c631daSSadaf Ebrahimi                      as it thinks useful.''')
68*f5c631daSSadaf Ebrahimi  parser.add_argument('--no-cache',
69*f5c631daSSadaf Ebrahimi                      action='store_true', default=False,
70*f5c631daSSadaf Ebrahimi                      help='Do not use cached lint results.')
71*f5c631daSSadaf Ebrahimi  return parser.parse_args()
72*f5c631daSSadaf Ebrahimi
73*f5c631daSSadaf Ebrahimi
74*f5c631daSSadaf Ebrahimi
75*f5c631daSSadaf Ebrahimi# Returns a tuple (filename, number of lint errors).
76*f5c631daSSadaf Ebrahimidef Lint(filename, progress_prefix = ''):
77*f5c631daSSadaf Ebrahimi  command = ['cpplint.py', filename]
78*f5c631daSSadaf Ebrahimi  process = subprocess.Popen(command,
79*f5c631daSSadaf Ebrahimi                             stdout=subprocess.PIPE,
80*f5c631daSSadaf Ebrahimi                             stderr=subprocess.STDOUT)
81*f5c631daSSadaf Ebrahimi
82*f5c631daSSadaf Ebrahimi  outerr, _ = process.communicate()
83*f5c631daSSadaf Ebrahimi
84*f5c631daSSadaf Ebrahimi  if process.returncode == 0:
85*f5c631daSSadaf Ebrahimi    printer.PrintOverwritableLine(
86*f5c631daSSadaf Ebrahimi      progress_prefix + "Done processing %s" % filename,
87*f5c631daSSadaf Ebrahimi      type = printer.LINE_TYPE_LINTER)
88*f5c631daSSadaf Ebrahimi    return (filename, 0)
89*f5c631daSSadaf Ebrahimi
90*f5c631daSSadaf Ebrahimi  if progress_prefix:
91*f5c631daSSadaf Ebrahimi    outerr = re.sub('^', progress_prefix, outerr, flags=re.MULTILINE)
92*f5c631daSSadaf Ebrahimi  printer.Print(outerr)
93*f5c631daSSadaf Ebrahimi
94*f5c631daSSadaf Ebrahimi  # Find the number of errors in this file.
95*f5c631daSSadaf Ebrahimi  res = re.search('Total errors found: (\d+)', outerr)
96*f5c631daSSadaf Ebrahimi  if res:
97*f5c631daSSadaf Ebrahimi    n_errors_str = res.string[res.start(1):res.end(1)]
98*f5c631daSSadaf Ebrahimi    n_errors = int(n_errors_str)
99*f5c631daSSadaf Ebrahimi  else:
100*f5c631daSSadaf Ebrahimi    print("Couldn't parse cpplint.py output.")
101*f5c631daSSadaf Ebrahimi    n_errors = -1
102*f5c631daSSadaf Ebrahimi
103*f5c631daSSadaf Ebrahimi  return (filename, n_errors)
104*f5c631daSSadaf Ebrahimi
105*f5c631daSSadaf Ebrahimi
106*f5c631daSSadaf Ebrahimi# The multiprocessing map_async function does not allow passing multiple
107*f5c631daSSadaf Ebrahimi# arguments directly, so use a wrapper.
108*f5c631daSSadaf Ebrahimidef LintWrapper(args):
109*f5c631daSSadaf Ebrahimi  # Run under a try-catch  to avoid flooding the output when the script is
110*f5c631daSSadaf Ebrahimi  # interrupted from the keyboard with ctrl+C.
111*f5c631daSSadaf Ebrahimi  try:
112*f5c631daSSadaf Ebrahimi    return Lint(*args)
113*f5c631daSSadaf Ebrahimi  except:
114*f5c631daSSadaf Ebrahimi    sys.exit(1)
115*f5c631daSSadaf Ebrahimi
116*f5c631daSSadaf Ebrahimi
117*f5c631daSSadaf Ebrahimidef ShouldLint(filename, cached_results):
118*f5c631daSSadaf Ebrahimi  filename = os.path.realpath(filename)
119*f5c631daSSadaf Ebrahimi  if filename not in cached_results:
120*f5c631daSSadaf Ebrahimi    return True
121*f5c631daSSadaf Ebrahimi  with open(filename, 'rb') as f:
122*f5c631daSSadaf Ebrahimi    file_hash = hashlib.md5(f.read()).hexdigest()
123*f5c631daSSadaf Ebrahimi  return file_hash != cached_results[filename]
124*f5c631daSSadaf Ebrahimi
125*f5c631daSSadaf Ebrahimi
126*f5c631daSSadaf Ebrahimi# Returns the total number of errors found in the files linted.
127*f5c631daSSadaf Ebrahimi# `cached_results` must be a dictionary, with the format:
128*f5c631daSSadaf Ebrahimi#     { 'filename': file_hash, 'other_filename': other_hash, ... }
129*f5c631daSSadaf Ebrahimi# If not `None`, `cached_results` is used to avoid re-linting files, and new
130*f5c631daSSadaf Ebrahimi# results are stored in it.
131*f5c631daSSadaf Ebrahimidef LintFiles(files,
132*f5c631daSSadaf Ebrahimi              jobs = 1,
133*f5c631daSSadaf Ebrahimi              progress_prefix = '',
134*f5c631daSSadaf Ebrahimi              cached_results = None):
135*f5c631daSSadaf Ebrahimi  if not IsCppLintAvailable():
136*f5c631daSSadaf Ebrahimi    print(
137*f5c631daSSadaf Ebrahimi      printer.COLOUR_RED + \
138*f5c631daSSadaf Ebrahimi      ("cpplint.py not found. Please ensure the depot"
139*f5c631daSSadaf Ebrahimi       " tools are installed and in your PATH. See"
140*f5c631daSSadaf Ebrahimi       " http://dev.chromium.org/developers/how-tos/install-depot-tools for"
141*f5c631daSSadaf Ebrahimi       " details.") + \
142*f5c631daSSadaf Ebrahimi      printer.NO_COLOUR)
143*f5c631daSSadaf Ebrahimi    return -1
144*f5c631daSSadaf Ebrahimi
145*f5c631daSSadaf Ebrahimi  # Filter out directories.
146*f5c631daSSadaf Ebrahimi  files = filter(os.path.isfile, files)
147*f5c631daSSadaf Ebrahimi
148*f5c631daSSadaf Ebrahimi  # Filter out files for which we have a cached correct result.
149*f5c631daSSadaf Ebrahimi  if cached_results is not None and len(cached_results) != 0:
150*f5c631daSSadaf Ebrahimi    n_input_files = len(files)
151*f5c631daSSadaf Ebrahimi    files = filter(lambda f: ShouldLint(f, cached_results), files)
152*f5c631daSSadaf Ebrahimi    n_skipped_files = n_input_files - len(files)
153*f5c631daSSadaf Ebrahimi    if n_skipped_files != 0:
154*f5c631daSSadaf Ebrahimi      printer.Print(
155*f5c631daSSadaf Ebrahimi        progress_prefix +
156*f5c631daSSadaf Ebrahimi        'Skipping %d correct files that were already processed.' %
157*f5c631daSSadaf Ebrahimi        n_skipped_files)
158*f5c631daSSadaf Ebrahimi
159*f5c631daSSadaf Ebrahimi  pool = multiprocessing.Pool(jobs)
160*f5c631daSSadaf Ebrahimi  # The '.get(9999999)' is workaround to allow killing the test script with
161*f5c631daSSadaf Ebrahimi  # ctrl+C from the shell. This bug is documented at
162*f5c631daSSadaf Ebrahimi  # http://bugs.python.org/issue8296.
163*f5c631daSSadaf Ebrahimi  tasks = [(f, progress_prefix) for f in files]
164*f5c631daSSadaf Ebrahimi  # Run under a try-catch  to avoid flooding the output when the script is
165*f5c631daSSadaf Ebrahimi  # interrupted from the keyboard with ctrl+C.
166*f5c631daSSadaf Ebrahimi  try:
167*f5c631daSSadaf Ebrahimi    results = pool.map_async(LintWrapper, tasks).get(9999999)
168*f5c631daSSadaf Ebrahimi    pool.close()
169*f5c631daSSadaf Ebrahimi    pool.join()
170*f5c631daSSadaf Ebrahimi  except KeyboardInterrupt:
171*f5c631daSSadaf Ebrahimi    pool.terminate()
172*f5c631daSSadaf Ebrahimi    sys.exit(1)
173*f5c631daSSadaf Ebrahimi
174*f5c631daSSadaf Ebrahimi  n_errors = sum(map(lambda (filename, errors): errors, results))
175*f5c631daSSadaf Ebrahimi
176*f5c631daSSadaf Ebrahimi  if cached_results is not None:
177*f5c631daSSadaf Ebrahimi    for filename, errors in results:
178*f5c631daSSadaf Ebrahimi      if errors == 0:
179*f5c631daSSadaf Ebrahimi        with open(filename, 'rb') as f:
180*f5c631daSSadaf Ebrahimi          filename = os.path.realpath(filename)
181*f5c631daSSadaf Ebrahimi          file_hash = hashlib.md5(f.read()).hexdigest()
182*f5c631daSSadaf Ebrahimi          cached_results[filename] = file_hash
183*f5c631daSSadaf Ebrahimi
184*f5c631daSSadaf Ebrahimi
185*f5c631daSSadaf Ebrahimi  printer.PrintOverwritableLine(
186*f5c631daSSadaf Ebrahimi      progress_prefix + 'Total errors found: %d' % n_errors)
187*f5c631daSSadaf Ebrahimi  printer.EnsureNewLine()
188*f5c631daSSadaf Ebrahimi  return n_errors
189*f5c631daSSadaf Ebrahimi
190*f5c631daSSadaf Ebrahimi
191*f5c631daSSadaf Ebrahimidef IsCppLintAvailable():
192*f5c631daSSadaf Ebrahimi    retcode, unused_output = util.getstatusoutput('which cpplint.py')
193*f5c631daSSadaf Ebrahimi    return retcode == 0
194*f5c631daSSadaf Ebrahimi
195*f5c631daSSadaf Ebrahimi
196*f5c631daSSadaf EbrahimiCPP_EXT_REGEXP = re.compile('\.(cc|h)$')
197*f5c631daSSadaf Ebrahimidef IsLinterInput(filename):
198*f5c631daSSadaf Ebrahimi  # lint all C++ files.
199*f5c631daSSadaf Ebrahimi  return CPP_EXT_REGEXP.search(filename) != None
200*f5c631daSSadaf Ebrahimi
201*f5c631daSSadaf Ebrahimi
202*f5c631daSSadaf Ebrahimicached_results_pkl_filename = \
203*f5c631daSSadaf Ebrahimi  os.path.join(config.dir_tools, '.cached_lint_results.pkl')
204*f5c631daSSadaf Ebrahimi
205*f5c631daSSadaf Ebrahimi
206*f5c631daSSadaf Ebrahimidef ReadCachedResults():
207*f5c631daSSadaf Ebrahimi  cached_results = {}
208*f5c631daSSadaf Ebrahimi  if os.path.isfile(cached_results_pkl_filename):
209*f5c631daSSadaf Ebrahimi    with open(cached_results_pkl_filename, 'rb') as pkl_file:
210*f5c631daSSadaf Ebrahimi      cached_results = pickle.load(pkl_file)
211*f5c631daSSadaf Ebrahimi  return cached_results
212*f5c631daSSadaf Ebrahimi
213*f5c631daSSadaf Ebrahimi
214*f5c631daSSadaf Ebrahimidef CacheResults(results):
215*f5c631daSSadaf Ebrahimi  with open(cached_results_pkl_filename, 'wb') as pkl_file:
216*f5c631daSSadaf Ebrahimi    pickle.dump(results, pkl_file)
217*f5c631daSSadaf Ebrahimi
218*f5c631daSSadaf Ebrahimi
219*f5c631daSSadaf Ebrahimidef FilterOutTestTraceHeaders(files):
220*f5c631daSSadaf Ebrahimi  def IsTraceHeader(f):
221*f5c631daSSadaf Ebrahimi    relative_aarch32_traces_path = os.path.relpath(config.dir_aarch32_traces,'.')
222*f5c631daSSadaf Ebrahimi    relative_aarch64_traces_path = os.path.relpath(config.dir_aarch64_traces,'.')
223*f5c631daSSadaf Ebrahimi    return \
224*f5c631daSSadaf Ebrahimi      fnmatch.fnmatch(f, os.path.join(relative_aarch32_traces_path, '*.h')) or \
225*f5c631daSSadaf Ebrahimi      fnmatch.fnmatch(f, os.path.join(relative_aarch64_traces_path, '*.h'))
226*f5c631daSSadaf Ebrahimi  return filter(lambda f: not IsTraceHeader(f), files)
227*f5c631daSSadaf Ebrahimi
228*f5c631daSSadaf Ebrahimi
229*f5c631daSSadaf Ebrahimidef RunLinter(files, jobs=1, progress_prefix='', cached=True):
230*f5c631daSSadaf Ebrahimi  results = {} if not cached else ReadCachedResults()
231*f5c631daSSadaf Ebrahimi
232*f5c631daSSadaf Ebrahimi  rc = LintFiles(files,
233*f5c631daSSadaf Ebrahimi                 jobs=jobs,
234*f5c631daSSadaf Ebrahimi                 progress_prefix=progress_prefix,
235*f5c631daSSadaf Ebrahimi                 cached_results=results)
236*f5c631daSSadaf Ebrahimi
237*f5c631daSSadaf Ebrahimi  CacheResults(results)
238*f5c631daSSadaf Ebrahimi  return rc
239*f5c631daSSadaf Ebrahimi
240*f5c631daSSadaf Ebrahimi
241*f5c631daSSadaf Ebrahimiif __name__ == '__main__':
242*f5c631daSSadaf Ebrahimi  # Parse the arguments.
243*f5c631daSSadaf Ebrahimi  args = BuildOptions()
244*f5c631daSSadaf Ebrahimi
245*f5c631daSSadaf Ebrahimi  files = args.files or util.get_source_files()
246*f5c631daSSadaf Ebrahimi
247*f5c631daSSadaf Ebrahimi  cached = not args.no_cache
248*f5c631daSSadaf Ebrahimi  retcode = RunLinter(files, jobs=args.jobs, cached=cached)
249*f5c631daSSadaf Ebrahimi
250*f5c631daSSadaf Ebrahimi  sys.exit(retcode)
251