blob: fa995252a634283a7ffe987b2c3f13a19e361d5d [file] [log] [blame]
armvixl4a102ba2014-07-14 09:02:40 +01001#!/usr/bin/env python2.7
2
armvixl5289c592015-03-02 13:52:04 +00003# Copyright 2015, ARM Limited
armvixl4a102ba2014-07-14 09:02:40 +01004# 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
armvixldb644342015-07-21 11:37:10 +010032import signal
armvixl4a102ba2014-07-14 09:02:40 +010033import subprocess
34import sys
35
armvixldb644342015-07-21 11:37:10 +010036import config
armvixl4a102ba2014-07-14 09:02:40 +010037import git
38import printer
39import util
40
41
armvixldb644342015-07-21 11:37:10 +010042# 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)
armvixl4a102ba2014-07-14 09:02:40 +010046
47def BuildOptions():
armvixl0f35e362016-05-10 13:57:58 +010048 parser = argparse.ArgumentParser(
armvixl4a102ba2014-07-14 09:02:40 +010049 description =
armvixl0f35e362016-05-10 13:57:58 +010050 '''This tool lints C++ files and produces a summary of the errors found.
51 If no files are provided on the command-line, all C++ source files in the
52 repository are processed.''',
armvixl4a102ba2014-07-14 09:02:40 +010053 # Print default values.
54 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
armvixl0f35e362016-05-10 13:57:58 +010055 parser.add_argument('files', nargs = '*')
56 parser.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
57 default=multiprocessing.cpu_count(),
58 const=multiprocessing.cpu_count(),
armvixl4a102ba2014-07-14 09:02:40 +010059 help='''Runs the tests using N jobs. If the option is set
60 but no value is provided, the script will use as many jobs
61 as it thinks useful.''')
armvixl0f35e362016-05-10 13:57:58 +010062 return parser.parse_args()
armvixl4a102ba2014-07-14 09:02:40 +010063
64
65
66__lint_results_lock__ = multiprocessing.Lock()
67
68# Returns the number of errors in the file linted.
armvixldb644342015-07-21 11:37:10 +010069def Lint(filename, progress_prefix = ''):
70 command = ['cpplint.py', filename]
armvixl4a102ba2014-07-14 09:02:40 +010071 process = subprocess.Popen(command,
72 stdout=subprocess.PIPE,
73 stderr=subprocess.PIPE)
74
75 # Use a lock to avoid mixing the output for different files.
76 with __lint_results_lock__:
77 # Process the output as the process is running, until it exits.
78 LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$')
79 LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing')
80 LINT_STATUS_LINE_REGEXP = re.compile('Total errors found')
81 while True:
82 retcode = process.poll()
83 while True:
84 line = process.stderr.readline()
85 if line == '': break
86 output_line = progress_prefix + line.rstrip('\r\n')
87
88 if LINT_ERROR_LINE_REGEXP.search(line):
armvixldb644342015-07-21 11:37:10 +010089 printer.PrintOverwritableLine(output_line,
90 type = printer.LINE_TYPE_LINTER)
armvixl4a102ba2014-07-14 09:02:40 +010091 printer.EnsureNewLine()
92 elif LINT_DONE_PROC_LINE_REGEXP.search(line):
armvixldb644342015-07-21 11:37:10 +010093 printer.PrintOverwritableLine(output_line,
94 type = printer.LINE_TYPE_LINTER)
armvixl4a102ba2014-07-14 09:02:40 +010095 elif LINT_STATUS_LINE_REGEXP.search(line):
96 status_line = line
97
98 if retcode != None: break;
99
100 if retcode == 0:
101 return 0
102
103 # Return the number of errors in this file.
104 res = re.search('\d+$', status_line)
105 n_errors_str = res.string[res.start():res.end()]
106 n_errors = int(n_errors_str)
107 status_line = \
108 progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors)
armvixldb644342015-07-21 11:37:10 +0100109 printer.PrintOverwritableLine(status_line, type = printer.LINE_TYPE_LINTER)
armvixl4a102ba2014-07-14 09:02:40 +0100110 printer.EnsureNewLine()
111 return n_errors
112
113
114# The multiprocessing map_async function does not allow passing multiple
115# arguments directly, so use a wrapper.
116def LintWrapper(args):
armvixldb644342015-07-21 11:37:10 +0100117 # Run under a try-catch to avoid flooding the output when the script is
118 # interrupted from the keyboard with ctrl+C.
119 try:
120 return Lint(*args)
121 except:
122 sys.exit(1)
armvixl4a102ba2014-07-14 09:02:40 +0100123
124
125# Returns the total number of errors found in the files linted.
armvixldb644342015-07-21 11:37:10 +0100126def LintFiles(files, jobs = 1, progress_prefix = ''):
127 if not IsCppLintAvailable():
128 print(
129 printer.COLOUR_RED + \
130 ("cpplint.py not found. Please ensure the depot"
131 " tools are installed and in your PATH. See"
132 " http://dev.chromium.org/developers/how-tos/install-depot-tools for"
133 " details.") + \
134 printer.NO_COLOUR)
135 return -1
136
armvixl4a102ba2014-07-14 09:02:40 +0100137 pool = multiprocessing.Pool(jobs)
138 # The '.get(9999999)' is workaround to allow killing the test script with
139 # ctrl+C from the shell. This bug is documented at
140 # http://bugs.python.org/issue8296.
armvixldb644342015-07-21 11:37:10 +0100141 tasks = [(f, progress_prefix) for f in files]
142 # Run under a try-catch to avoid flooding the output when the script is
143 # interrupted from the keyboard with ctrl+C.
144 try:
145 results = pool.map_async(LintWrapper, tasks).get(9999999)
146 pool.close()
147 pool.join()
148 except KeyboardInterrupt:
149 pool.terminate()
150 sys.exit(1)
armvixl4a102ba2014-07-14 09:02:40 +0100151 n_errors = sum(results)
152
153 printer.PrintOverwritableLine(
154 progress_prefix + 'Total errors found: %d' % n_errors)
155 printer.EnsureNewLine()
156 return n_errors
157
158
159def IsCppLintAvailable():
160 retcode, unused_output = util.getstatusoutput('which cpplint.py')
161 return retcode == 0
162
163
164CPP_EXT_REGEXP = re.compile('\.(cc|h)$')
armvixl4a102ba2014-07-14 09:02:40 +0100165def is_linter_input(filename):
armvixldb644342015-07-21 11:37:10 +0100166 # lint all C++ files.
armvixl4a102ba2014-07-14 09:02:40 +0100167 return CPP_EXT_REGEXP.search(filename) != None
armvixldb644342015-07-21 11:37:10 +0100168
169def GetDefaultTrackedFiles():
170 if git.is_git_repository_root(config.dir_root):
171 default_tracked_files = git.get_tracked_files().split()
172 default_tracked_files = filter(is_linter_input, default_tracked_files)
173 return 0, default_tracked_files
174 else:
175 printer.Print(printer.COLOUR_ORANGE + 'WARNING: This script is not run ' \
176 'from its Git repository. The linter will not run.' + \
177 printer.NO_COLOUR)
178 return 1, []
armvixl4a102ba2014-07-14 09:02:40 +0100179
180if __name__ == '__main__':
181 # Parse the arguments.
182 args = BuildOptions()
183
armvixl0f35e362016-05-10 13:57:58 +0100184 files = args.files
185 if not files:
186 retcode, files = GetDefaultTrackedFiles()
187 if retcode:
188 sys.exit(retcode)
189 retcode = LintFiles(files, jobs = args.jobs)
armvixl4a102ba2014-07-14 09:02:40 +0100190 sys.exit(retcode)