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