1#!/usr/bin/env python2.7 2 3# Copyright 2015, ARM Limited 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, 10# this list of conditions and the following disclaimer. 11# * Redistributions in binary form must reproduce the above copyright notice, 12# this list of conditions and the following disclaimer in the documentation 13# and/or other materials provided with the distribution. 14# * Neither the name of ARM Limited nor the names of its contributors may be 15# used to endorse or promote products derived from this software without 16# specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND 19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29import argparse 30import multiprocessing 31import re 32import subprocess 33import sys 34 35import git 36import printer 37import util 38 39 40# Google's cpplint.py from depot_tools is the linter used here. 41# These are positive rules, added to the set of rules that the linter checks. 42CPP_LINTER_RULES = ''' 43build/class 44build/deprecated 45build/endif_comment 46build/forward_decl 47build/include_order 48build/printf_format 49build/storage_class 50legal/copyright 51readability/boost 52readability/braces 53readability/casting 54readability/constructors 55readability/fn_size 56readability/function 57readability/multiline_comment 58readability/multiline_string 59readability/streams 60readability/utf8 61runtime/arrays 62runtime/casting 63runtime/deprecated_fn 64runtime/explicit 65runtime/int 66runtime/memset 67runtime/mutex 68runtime/nonconf 69runtime/printf 70runtime/printf_format 71runtime/references 72runtime/rtti 73runtime/sizeof 74runtime/string 75runtime/virtual 76runtime/vlog 77whitespace/blank_line 78whitespace/braces 79whitespace/comma 80whitespace/comments 81whitespace/end_of_line 82whitespace/ending_newline 83whitespace/indent 84whitespace/labels 85whitespace/line_length 86whitespace/newline 87whitespace/operators 88whitespace/parens 89whitespace/tab 90whitespace/todo 91'''.split() 92 93 94 95def BuildOptions(): 96 result = argparse.ArgumentParser( 97 description = 98 '''This tool lints the C++ files tracked by the git repository, and 99 produces a summary of the errors found.''', 100 # Print default values. 101 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 102 result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', 103 default=1, const=multiprocessing.cpu_count(), 104 help='''Runs the tests using N jobs. If the option is set 105 but no value is provided, the script will use as many jobs 106 as it thinks useful.''') 107 result.add_argument('--verbose', action='store_true', 108 help='Print verbose output.') 109 return result.parse_args() 110 111 112 113__lint_results_lock__ = multiprocessing.Lock() 114 115# Returns the number of errors in the file linted. 116def Lint(filename, lint_options, progress_prefix = '', verbose = False): 117 command = ['cpplint.py', lint_options, filename] 118 process = subprocess.Popen(command, 119 stdout=subprocess.PIPE, 120 stderr=subprocess.PIPE) 121 122 # Use a lock to avoid mixing the output for different files. 123 with __lint_results_lock__: 124 # Process the output as the process is running, until it exits. 125 LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$') 126 LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing') 127 LINT_STATUS_LINE_REGEXP = re.compile('Total errors found') 128 while True: 129 retcode = process.poll() 130 while True: 131 line = process.stderr.readline() 132 if line == '': break 133 output_line = progress_prefix + line.rstrip('\r\n') 134 135 if LINT_ERROR_LINE_REGEXP.search(line): 136 printer.PrintOverwritableLine(output_line, verbose = verbose) 137 printer.EnsureNewLine() 138 elif LINT_DONE_PROC_LINE_REGEXP.search(line): 139 printer.PrintOverwritableLine(output_line, verbose = verbose) 140 elif LINT_STATUS_LINE_REGEXP.search(line): 141 status_line = line 142 143 if retcode != None: break; 144 145 if retcode == 0: 146 return 0 147 148 # Return the number of errors in this file. 149 res = re.search('\d+$', status_line) 150 n_errors_str = res.string[res.start():res.end()] 151 n_errors = int(n_errors_str) 152 status_line = \ 153 progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors) 154 printer.PrintOverwritableLine(status_line, verbose = verbose) 155 printer.EnsureNewLine() 156 return n_errors 157 158 159# The multiprocessing map_async function does not allow passing multiple 160# arguments directly, so use a wrapper. 161def LintWrapper(args): 162 return Lint(*args) 163 164 165# Returns the total number of errors found in the files linted. 166def LintFiles(files, lint_args = CPP_LINTER_RULES, jobs = 1, verbose = False, 167 progress_prefix = ''): 168 lint_options = '--filter=-,+' + ',+'.join(lint_args) 169 pool = multiprocessing.Pool(jobs) 170 # The '.get(9999999)' is workaround to allow killing the test script with 171 # ctrl+C from the shell. This bug is documented at 172 # http://bugs.python.org/issue8296. 173 tasks = [(f, lint_options, progress_prefix, verbose) for f in files] 174 results = pool.map_async(LintWrapper, tasks).get(9999999) 175 n_errors = sum(results) 176 177 printer.PrintOverwritableLine( 178 progress_prefix + 'Total errors found: %d' % n_errors) 179 printer.EnsureNewLine() 180 return n_errors 181 182 183def IsCppLintAvailable(): 184 retcode, unused_output = util.getstatusoutput('which cpplint.py') 185 return retcode == 0 186 187 188CPP_EXT_REGEXP = re.compile('\.(cc|h)$') 189SIM_TRACES_REGEXP = re.compile('trace-a64\.h$') 190def is_linter_input(filename): 191 # Don't lint the simulator traces file; it takes a very long time to check 192 # and it's (mostly) generated automatically anyway. 193 if SIM_TRACES_REGEXP.search(filename): return False 194 # Otherwise, lint all C++ files. 195 return CPP_EXT_REGEXP.search(filename) != None 196default_tracked_files = git.get_tracked_files().split() 197default_tracked_files = filter(is_linter_input, default_tracked_files) 198 199if __name__ == '__main__': 200 # Parse the arguments. 201 args = BuildOptions() 202 203 retcode = LintFiles(default_tracked_files, 204 jobs = args.jobs, verbose = args.verbose) 205 sys.exit(retcode) 206