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