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 signal 33import subprocess 34import sys 35 36import config 37import git 38import printer 39import util 40 41 42# Catch SIGINT to gracefully exit when ctrl+C is pressed. 43def sigint_handler(signal, frame): 44 sys.exit(1) 45signal.signal(signal.SIGINT, sigint_handler) 46 47def BuildOptions(): 48 result = argparse.ArgumentParser( 49 description = 50 '''This tool lints the C++ files tracked by the git repository, and 51 produces a summary of the errors found.''', 52 # Print default values. 53 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 54 result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', 55 default=1, const=multiprocessing.cpu_count(), 56 help='''Runs the tests using N jobs. If the option is set 57 but no value is provided, the script will use as many jobs 58 as it thinks useful.''') 59 return result.parse_args() 60 61 62 63__lint_results_lock__ = multiprocessing.Lock() 64 65# Returns the number of errors in the file linted. 66def Lint(filename, progress_prefix = ''): 67 command = ['cpplint.py', filename] 68 process = subprocess.Popen(command, 69 stdout=subprocess.PIPE, 70 stderr=subprocess.PIPE) 71 72 # Use a lock to avoid mixing the output for different files. 73 with __lint_results_lock__: 74 # Process the output as the process is running, until it exits. 75 LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$') 76 LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing') 77 LINT_STATUS_LINE_REGEXP = re.compile('Total errors found') 78 while True: 79 retcode = process.poll() 80 while True: 81 line = process.stderr.readline() 82 if line == '': break 83 output_line = progress_prefix + line.rstrip('\r\n') 84 85 if LINT_ERROR_LINE_REGEXP.search(line): 86 printer.PrintOverwritableLine(output_line, 87 type = printer.LINE_TYPE_LINTER) 88 printer.EnsureNewLine() 89 elif LINT_DONE_PROC_LINE_REGEXP.search(line): 90 printer.PrintOverwritableLine(output_line, 91 type = printer.LINE_TYPE_LINTER) 92 elif LINT_STATUS_LINE_REGEXP.search(line): 93 status_line = line 94 95 if retcode != None: break; 96 97 if retcode == 0: 98 return 0 99 100 # Return the number of errors in this file. 101 res = re.search('\d+$', status_line) 102 n_errors_str = res.string[res.start():res.end()] 103 n_errors = int(n_errors_str) 104 status_line = \ 105 progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors) 106 printer.PrintOverwritableLine(status_line, type = printer.LINE_TYPE_LINTER) 107 printer.EnsureNewLine() 108 return n_errors 109 110 111# The multiprocessing map_async function does not allow passing multiple 112# arguments directly, so use a wrapper. 113def LintWrapper(args): 114 # Run under a try-catch to avoid flooding the output when the script is 115 # interrupted from the keyboard with ctrl+C. 116 try: 117 return Lint(*args) 118 except: 119 sys.exit(1) 120 121 122# Returns the total number of errors found in the files linted. 123def LintFiles(files, jobs = 1, progress_prefix = ''): 124 if not IsCppLintAvailable(): 125 print( 126 printer.COLOUR_RED + \ 127 ("cpplint.py not found. Please ensure the depot" 128 " tools are installed and in your PATH. See" 129 " http://dev.chromium.org/developers/how-tos/install-depot-tools for" 130 " details.") + \ 131 printer.NO_COLOUR) 132 return -1 133 134 pool = multiprocessing.Pool(jobs) 135 # The '.get(9999999)' is workaround to allow killing the test script with 136 # ctrl+C from the shell. This bug is documented at 137 # http://bugs.python.org/issue8296. 138 tasks = [(f, progress_prefix) for f in files] 139 # Run under a try-catch to avoid flooding the output when the script is 140 # interrupted from the keyboard with ctrl+C. 141 try: 142 results = pool.map_async(LintWrapper, tasks).get(9999999) 143 pool.close() 144 pool.join() 145 except KeyboardInterrupt: 146 pool.terminate() 147 sys.exit(1) 148 n_errors = sum(results) 149 150 printer.PrintOverwritableLine( 151 progress_prefix + 'Total errors found: %d' % n_errors) 152 printer.EnsureNewLine() 153 return n_errors 154 155 156def IsCppLintAvailable(): 157 retcode, unused_output = util.getstatusoutput('which cpplint.py') 158 return retcode == 0 159 160 161CPP_EXT_REGEXP = re.compile('\.(cc|h)$') 162def is_linter_input(filename): 163 # lint all C++ files. 164 return CPP_EXT_REGEXP.search(filename) != None 165 166def GetDefaultTrackedFiles(): 167 if git.is_git_repository_root(config.dir_root): 168 default_tracked_files = git.get_tracked_files().split() 169 default_tracked_files = filter(is_linter_input, default_tracked_files) 170 return 0, default_tracked_files 171 else: 172 printer.Print(printer.COLOUR_ORANGE + 'WARNING: This script is not run ' \ 173 'from its Git repository. The linter will not run.' + \ 174 printer.NO_COLOUR) 175 return 1, [] 176 177if __name__ == '__main__': 178 # Parse the arguments. 179 args = BuildOptions() 180 181 retcode, default_tracked_files = GetDefaultTrackedFiles() 182 if retcode: 183 sys.exit(retcode) 184 retcode = LintFiles(default_tracked_files, 185 jobs = args.jobs) 186 sys.exit(retcode) 187