#!/usr/bin/env python2.7 # Copyright 2015, ARM Limited # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of ARM Limited nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import argparse import multiprocessing import re import subprocess import sys import git import printer import util # Google's cpplint.py from depot_tools is the linter used here. # These are positive rules, added to the set of rules that the linter checks. CPP_LINTER_RULES = ''' build/class build/deprecated build/endif_comment build/forward_decl build/include_order build/printf_format build/storage_class legal/copyright readability/boost readability/braces readability/casting readability/constructors readability/fn_size readability/function readability/multiline_comment readability/multiline_string readability/streams readability/utf8 runtime/arrays runtime/casting runtime/deprecated_fn runtime/explicit runtime/int runtime/memset runtime/mutex runtime/nonconf runtime/printf runtime/printf_format runtime/references runtime/rtti runtime/sizeof runtime/string runtime/virtual runtime/vlog whitespace/blank_line whitespace/braces whitespace/comma whitespace/comments whitespace/end_of_line whitespace/ending_newline whitespace/indent whitespace/labels whitespace/line_length whitespace/newline whitespace/operators whitespace/parens whitespace/tab whitespace/todo '''.split() def BuildOptions(): result = argparse.ArgumentParser( description = '''This tool lints the C++ files tracked by the git repository, and produces a summary of the errors found.''', # Print default values. formatter_class=argparse.ArgumentDefaultsHelpFormatter) result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', default=1, const=multiprocessing.cpu_count(), help='''Runs the tests using N jobs. If the option is set but no value is provided, the script will use as many jobs as it thinks useful.''') result.add_argument('--verbose', action='store_true', help='Print verbose output.') return result.parse_args() __lint_results_lock__ = multiprocessing.Lock() # Returns the number of errors in the file linted. def Lint(filename, lint_options, progress_prefix = '', verbose = False): command = ['cpplint.py', lint_options, filename] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Use a lock to avoid mixing the output for different files. with __lint_results_lock__: # Process the output as the process is running, until it exits. LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$') LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing') LINT_STATUS_LINE_REGEXP = re.compile('Total errors found') while True: retcode = process.poll() while True: line = process.stderr.readline() if line == '': break output_line = progress_prefix + line.rstrip('\r\n') if LINT_ERROR_LINE_REGEXP.search(line): printer.PrintOverwritableLine(output_line, verbose = verbose) printer.EnsureNewLine() elif LINT_DONE_PROC_LINE_REGEXP.search(line): printer.PrintOverwritableLine(output_line, verbose = verbose) elif LINT_STATUS_LINE_REGEXP.search(line): status_line = line if retcode != None: break; if retcode == 0: return 0 # Return the number of errors in this file. res = re.search('\d+$', status_line) n_errors_str = res.string[res.start():res.end()] n_errors = int(n_errors_str) status_line = \ progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors) printer.PrintOverwritableLine(status_line, verbose = verbose) printer.EnsureNewLine() return n_errors # The multiprocessing map_async function does not allow passing multiple # arguments directly, so use a wrapper. def LintWrapper(args): return Lint(*args) # Returns the total number of errors found in the files linted. def LintFiles(files, lint_args = CPP_LINTER_RULES, jobs = 1, verbose = False, progress_prefix = ''): lint_options = '--filter=-,+' + ',+'.join(lint_args) pool = multiprocessing.Pool(jobs) # The '.get(9999999)' is workaround to allow killing the test script with # ctrl+C from the shell. This bug is documented at # http://bugs.python.org/issue8296. tasks = [(f, lint_options, progress_prefix, verbose) for f in files] results = pool.map_async(LintWrapper, tasks).get(9999999) n_errors = sum(results) printer.PrintOverwritableLine( progress_prefix + 'Total errors found: %d' % n_errors) printer.EnsureNewLine() return n_errors def IsCppLintAvailable(): retcode, unused_output = util.getstatusoutput('which cpplint.py') return retcode == 0 CPP_EXT_REGEXP = re.compile('\.(cc|h)$') SIM_TRACES_REGEXP = re.compile('trace-a64\.h$') def is_linter_input(filename): # Don't lint the simulator traces file; it takes a very long time to check # and it's (mostly) generated automatically anyway. if SIM_TRACES_REGEXP.search(filename): return False # Otherwise, lint all C++ files. return CPP_EXT_REGEXP.search(filename) != None default_tracked_files = git.get_tracked_files().split() default_tracked_files = filter(is_linter_input, default_tracked_files) if __name__ == '__main__': # Parse the arguments. args = BuildOptions() retcode = LintFiles(default_tracked_files, jobs = args.jobs, verbose = args.verbose) sys.exit(retcode)