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 29 import argparse 30 import multiprocessing 31 import re 32 import subprocess 33 import sys 34 35 import git 36 import printer 37 import 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. 42 CPP_LINTER_RULES = ''' 43 build/class 44 build/deprecated 45 build/endif_comment 46 build/forward_decl 47 build/include_order 48 build/printf_format 49 build/storage_class 50 legal/copyright 51 readability/boost 52 readability/braces 53 readability/casting 54 readability/constructors 55 readability/fn_size 56 readability/function 57 readability/multiline_comment 58 readability/multiline_string 59 readability/streams 60 readability/utf8 61 runtime/arrays 62 runtime/casting 63 runtime/deprecated_fn 64 runtime/explicit 65 runtime/int 66 runtime/memset 67 runtime/mutex 68 runtime/nonconf 69 runtime/printf 70 runtime/printf_format 71 runtime/references 72 runtime/rtti 73 runtime/sizeof 74 runtime/string 75 runtime/virtual 76 runtime/vlog 77 whitespace/blank_line 78 whitespace/braces 79 whitespace/comma 80 whitespace/comments 81 whitespace/end_of_line 82 whitespace/ending_newline 83 whitespace/indent 84 whitespace/labels 85 whitespace/line_length 86 whitespace/newline 87 whitespace/operators 88 whitespace/parens 89 whitespace/tab 90 whitespace/todo 91 '''.split() 92 93 94 95 def 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. 116 def 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. 161 def LintWrapper(args): 162 return Lint(*args) 163 164 165 # Returns the total number of errors found in the files linted. 166 def 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 183 def IsCppLintAvailable(): 184 retcode, unused_output = util.getstatusoutput('which cpplint.py') 185 return retcode == 0 186 187 188 CPP_EXT_REGEXP = re.compile('\.(cc|h)$') 189 SIM_TRACES_REGEXP = re.compile('trace-a64\.h$') 190 def 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 196 default_tracked_files = git.get_tracked_files().split() 197 default_tracked_files = filter(is_linter_input, default_tracked_files) 198 199 if __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