| #!/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 signal |
| import subprocess |
| import sys |
| |
| import config |
| import git |
| import printer |
| import util |
| |
| |
| # Catch SIGINT to gracefully exit when ctrl+C is pressed. |
| def sigint_handler(signal, frame): |
| sys.exit(1) |
| signal.signal(signal.SIGINT, sigint_handler) |
| |
| def BuildOptions(): |
| parser = argparse.ArgumentParser( |
| description = |
| '''This tool lints C++ files and produces a summary of the errors found. |
| If no files are provided on the command-line, all C++ source files in the |
| repository are processed.''', |
| # Print default values. |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| parser.add_argument('files', nargs = '*') |
| parser.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?', |
| default=multiprocessing.cpu_count(), |
| 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.''') |
| return parser.parse_args() |
| |
| |
| |
| __lint_results_lock__ = multiprocessing.Lock() |
| |
| # Returns the number of errors in the file linted. |
| def Lint(filename, progress_prefix = ''): |
| command = ['cpplint.py', 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, |
| type = printer.LINE_TYPE_LINTER) |
| printer.EnsureNewLine() |
| elif LINT_DONE_PROC_LINE_REGEXP.search(line): |
| printer.PrintOverwritableLine(output_line, |
| type = printer.LINE_TYPE_LINTER) |
| 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, type = printer.LINE_TYPE_LINTER) |
| printer.EnsureNewLine() |
| return n_errors |
| |
| |
| # The multiprocessing map_async function does not allow passing multiple |
| # arguments directly, so use a wrapper. |
| def LintWrapper(args): |
| # Run under a try-catch to avoid flooding the output when the script is |
| # interrupted from the keyboard with ctrl+C. |
| try: |
| return Lint(*args) |
| except: |
| sys.exit(1) |
| |
| |
| # Returns the total number of errors found in the files linted. |
| def LintFiles(files, jobs = 1, progress_prefix = ''): |
| if not IsCppLintAvailable(): |
| print( |
| printer.COLOUR_RED + \ |
| ("cpplint.py not found. Please ensure the depot" |
| " tools are installed and in your PATH. See" |
| " http://dev.chromium.org/developers/how-tos/install-depot-tools for" |
| " details.") + \ |
| printer.NO_COLOUR) |
| return -1 |
| |
| 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, progress_prefix) for f in files] |
| # Run under a try-catch to avoid flooding the output when the script is |
| # interrupted from the keyboard with ctrl+C. |
| try: |
| results = pool.map_async(LintWrapper, tasks).get(9999999) |
| pool.close() |
| pool.join() |
| except KeyboardInterrupt: |
| pool.terminate() |
| sys.exit(1) |
| 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)$') |
| def is_linter_input(filename): |
| # lint all C++ files. |
| return CPP_EXT_REGEXP.search(filename) != None |
| |
| def GetDefaultTrackedFiles(): |
| if git.is_git_repository_root(config.dir_root): |
| default_tracked_files = git.get_tracked_files().split() |
| default_tracked_files = filter(is_linter_input, default_tracked_files) |
| return 0, default_tracked_files |
| else: |
| printer.Print(printer.COLOUR_ORANGE + 'WARNING: This script is not run ' \ |
| 'from its Git repository. The linter will not run.' + \ |
| printer.NO_COLOUR) |
| return 1, [] |
| |
| if __name__ == '__main__': |
| # Parse the arguments. |
| args = BuildOptions() |
| |
| files = args.files |
| if not files: |
| retcode, files = GetDefaultTrackedFiles() |
| if retcode: |
| sys.exit(retcode) |
| retcode = LintFiles(files, jobs = args.jobs) |
| sys.exit(retcode) |