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