190b0414b6c794be58f34813f84c2c06e6a15be91armvixl#!/usr/bin/env python2.7
290b0414b6c794be58f34813f84c2c06e6a15be91armvixl
30cc8b6ece4b3e757e11a906a81ece292437713abarmvixl# Copyright 2015, ARM Limited
490b0414b6c794be58f34813f84c2c06e6a15be91armvixl# All rights reserved.
590b0414b6c794be58f34813f84c2c06e6a15be91armvixl#
690b0414b6c794be58f34813f84c2c06e6a15be91armvixl# Redistribution and use in source and binary forms, with or without
790b0414b6c794be58f34813f84c2c06e6a15be91armvixl# modification, are permitted provided that the following conditions are met:
890b0414b6c794be58f34813f84c2c06e6a15be91armvixl#
990b0414b6c794be58f34813f84c2c06e6a15be91armvixl#   * Redistributions of source code must retain the above copyright notice,
1090b0414b6c794be58f34813f84c2c06e6a15be91armvixl#     this list of conditions and the following disclaimer.
1190b0414b6c794be58f34813f84c2c06e6a15be91armvixl#   * Redistributions in binary form must reproduce the above copyright notice,
1290b0414b6c794be58f34813f84c2c06e6a15be91armvixl#     this list of conditions and the following disclaimer in the documentation
1390b0414b6c794be58f34813f84c2c06e6a15be91armvixl#     and/or other materials provided with the distribution.
1490b0414b6c794be58f34813f84c2c06e6a15be91armvixl#   * Neither the name of ARM Limited nor the names of its contributors may be
1590b0414b6c794be58f34813f84c2c06e6a15be91armvixl#     used to endorse or promote products derived from this software without
1690b0414b6c794be58f34813f84c2c06e6a15be91armvixl#     specific prior written permission.
1790b0414b6c794be58f34813f84c2c06e6a15be91armvixl#
1890b0414b6c794be58f34813f84c2c06e6a15be91armvixl# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
1990b0414b6c794be58f34813f84c2c06e6a15be91armvixl# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2090b0414b6c794be58f34813f84c2c06e6a15be91armvixl# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2190b0414b6c794be58f34813f84c2c06e6a15be91armvixl# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
2290b0414b6c794be58f34813f84c2c06e6a15be91armvixl# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2390b0414b6c794be58f34813f84c2c06e6a15be91armvixl# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2490b0414b6c794be58f34813f84c2c06e6a15be91armvixl# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2590b0414b6c794be58f34813f84c2c06e6a15be91armvixl# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2690b0414b6c794be58f34813f84c2c06e6a15be91armvixl# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2790b0414b6c794be58f34813f84c2c06e6a15be91armvixl# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2890b0414b6c794be58f34813f84c2c06e6a15be91armvixl
2990b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport argparse
3090b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport multiprocessing
3190b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport re
325ee436a3a51863de7b5dc9326cf02895248050c2armvixlimport signal
3390b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport subprocess
3490b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport sys
3590b0414b6c794be58f34813f84c2c06e6a15be91armvixl
365ee436a3a51863de7b5dc9326cf02895248050c2armvixlimport config
3790b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport git
3890b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport printer
3990b0414b6c794be58f34813f84c2c06e6a15be91armvixlimport util
4090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
4190b0414b6c794be58f34813f84c2c06e6a15be91armvixl
425ee436a3a51863de7b5dc9326cf02895248050c2armvixl# Catch SIGINT to gracefully exit when ctrl+C is pressed.
435ee436a3a51863de7b5dc9326cf02895248050c2armvixldef sigint_handler(signal, frame):
445ee436a3a51863de7b5dc9326cf02895248050c2armvixl  sys.exit(1)
455ee436a3a51863de7b5dc9326cf02895248050c2armvixlsignal.signal(signal.SIGINT, sigint_handler)
4690b0414b6c794be58f34813f84c2c06e6a15be91armvixl
4790b0414b6c794be58f34813f84c2c06e6a15be91armvixldef BuildOptions():
4890b0414b6c794be58f34813f84c2c06e6a15be91armvixl  result = argparse.ArgumentParser(
4990b0414b6c794be58f34813f84c2c06e6a15be91armvixl      description =
5090b0414b6c794be58f34813f84c2c06e6a15be91armvixl      '''This tool lints the C++ files tracked by the git repository, and
5190b0414b6c794be58f34813f84c2c06e6a15be91armvixl      produces a summary of the errors found.''',
5290b0414b6c794be58f34813f84c2c06e6a15be91armvixl      # Print default values.
5390b0414b6c794be58f34813f84c2c06e6a15be91armvixl      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
5490b0414b6c794be58f34813f84c2c06e6a15be91armvixl  result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
5590b0414b6c794be58f34813f84c2c06e6a15be91armvixl                      default=1, const=multiprocessing.cpu_count(),
5690b0414b6c794be58f34813f84c2c06e6a15be91armvixl                      help='''Runs the tests using N jobs. If the option is set
5790b0414b6c794be58f34813f84c2c06e6a15be91armvixl                      but no value is provided, the script will use as many jobs
5890b0414b6c794be58f34813f84c2c06e6a15be91armvixl                      as it thinks useful.''')
5990b0414b6c794be58f34813f84c2c06e6a15be91armvixl  return result.parse_args()
6090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
6190b0414b6c794be58f34813f84c2c06e6a15be91armvixl
6290b0414b6c794be58f34813f84c2c06e6a15be91armvixl
6390b0414b6c794be58f34813f84c2c06e6a15be91armvixl__lint_results_lock__ = multiprocessing.Lock()
6490b0414b6c794be58f34813f84c2c06e6a15be91armvixl
6590b0414b6c794be58f34813f84c2c06e6a15be91armvixl# Returns the number of errors in the file linted.
665ee436a3a51863de7b5dc9326cf02895248050c2armvixldef Lint(filename, progress_prefix = ''):
675ee436a3a51863de7b5dc9326cf02895248050c2armvixl  command = ['cpplint.py', filename]
6890b0414b6c794be58f34813f84c2c06e6a15be91armvixl  process = subprocess.Popen(command,
6990b0414b6c794be58f34813f84c2c06e6a15be91armvixl                             stdout=subprocess.PIPE,
7090b0414b6c794be58f34813f84c2c06e6a15be91armvixl                             stderr=subprocess.PIPE)
7190b0414b6c794be58f34813f84c2c06e6a15be91armvixl
7290b0414b6c794be58f34813f84c2c06e6a15be91armvixl  # Use a lock to avoid mixing the output for different files.
7390b0414b6c794be58f34813f84c2c06e6a15be91armvixl  with __lint_results_lock__:
7490b0414b6c794be58f34813f84c2c06e6a15be91armvixl    # Process the output as the process is running, until it exits.
7590b0414b6c794be58f34813f84c2c06e6a15be91armvixl    LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$')
7690b0414b6c794be58f34813f84c2c06e6a15be91armvixl    LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing')
7790b0414b6c794be58f34813f84c2c06e6a15be91armvixl    LINT_STATUS_LINE_REGEXP = re.compile('Total errors found')
7890b0414b6c794be58f34813f84c2c06e6a15be91armvixl    while True:
7990b0414b6c794be58f34813f84c2c06e6a15be91armvixl      retcode = process.poll()
8090b0414b6c794be58f34813f84c2c06e6a15be91armvixl      while True:
8190b0414b6c794be58f34813f84c2c06e6a15be91armvixl        line = process.stderr.readline()
8290b0414b6c794be58f34813f84c2c06e6a15be91armvixl        if line == '': break
8390b0414b6c794be58f34813f84c2c06e6a15be91armvixl        output_line = progress_prefix + line.rstrip('\r\n')
8490b0414b6c794be58f34813f84c2c06e6a15be91armvixl
8590b0414b6c794be58f34813f84c2c06e6a15be91armvixl        if LINT_ERROR_LINE_REGEXP.search(line):
865ee436a3a51863de7b5dc9326cf02895248050c2armvixl          printer.PrintOverwritableLine(output_line,
875ee436a3a51863de7b5dc9326cf02895248050c2armvixl                                        type = printer.LINE_TYPE_LINTER)
8890b0414b6c794be58f34813f84c2c06e6a15be91armvixl          printer.EnsureNewLine()
8990b0414b6c794be58f34813f84c2c06e6a15be91armvixl        elif LINT_DONE_PROC_LINE_REGEXP.search(line):
905ee436a3a51863de7b5dc9326cf02895248050c2armvixl          printer.PrintOverwritableLine(output_line,
915ee436a3a51863de7b5dc9326cf02895248050c2armvixl                                        type = printer.LINE_TYPE_LINTER)
9290b0414b6c794be58f34813f84c2c06e6a15be91armvixl        elif LINT_STATUS_LINE_REGEXP.search(line):
9390b0414b6c794be58f34813f84c2c06e6a15be91armvixl          status_line = line
9490b0414b6c794be58f34813f84c2c06e6a15be91armvixl
9590b0414b6c794be58f34813f84c2c06e6a15be91armvixl      if retcode != None: break;
9690b0414b6c794be58f34813f84c2c06e6a15be91armvixl
9790b0414b6c794be58f34813f84c2c06e6a15be91armvixl    if retcode == 0:
9890b0414b6c794be58f34813f84c2c06e6a15be91armvixl      return 0
9990b0414b6c794be58f34813f84c2c06e6a15be91armvixl
10090b0414b6c794be58f34813f84c2c06e6a15be91armvixl    # Return the number of errors in this file.
10190b0414b6c794be58f34813f84c2c06e6a15be91armvixl    res = re.search('\d+$', status_line)
10290b0414b6c794be58f34813f84c2c06e6a15be91armvixl    n_errors_str = res.string[res.start():res.end()]
10390b0414b6c794be58f34813f84c2c06e6a15be91armvixl    n_errors = int(n_errors_str)
10490b0414b6c794be58f34813f84c2c06e6a15be91armvixl    status_line = \
10590b0414b6c794be58f34813f84c2c06e6a15be91armvixl        progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors)
1065ee436a3a51863de7b5dc9326cf02895248050c2armvixl    printer.PrintOverwritableLine(status_line, type = printer.LINE_TYPE_LINTER)
10790b0414b6c794be58f34813f84c2c06e6a15be91armvixl    printer.EnsureNewLine()
10890b0414b6c794be58f34813f84c2c06e6a15be91armvixl    return n_errors
10990b0414b6c794be58f34813f84c2c06e6a15be91armvixl
11090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
11190b0414b6c794be58f34813f84c2c06e6a15be91armvixl# The multiprocessing map_async function does not allow passing multiple
11290b0414b6c794be58f34813f84c2c06e6a15be91armvixl# arguments directly, so use a wrapper.
11390b0414b6c794be58f34813f84c2c06e6a15be91armvixldef LintWrapper(args):
1145ee436a3a51863de7b5dc9326cf02895248050c2armvixl  # Run under a try-catch  to avoid flooding the output when the script is
1155ee436a3a51863de7b5dc9326cf02895248050c2armvixl  # interrupted from the keyboard with ctrl+C.
1165ee436a3a51863de7b5dc9326cf02895248050c2armvixl  try:
1175ee436a3a51863de7b5dc9326cf02895248050c2armvixl    return Lint(*args)
1185ee436a3a51863de7b5dc9326cf02895248050c2armvixl  except:
1195ee436a3a51863de7b5dc9326cf02895248050c2armvixl    sys.exit(1)
12090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
12190b0414b6c794be58f34813f84c2c06e6a15be91armvixl
12290b0414b6c794be58f34813f84c2c06e6a15be91armvixl# Returns the total number of errors found in the files linted.
1235ee436a3a51863de7b5dc9326cf02895248050c2armvixldef LintFiles(files, jobs = 1, progress_prefix = ''):
1245ee436a3a51863de7b5dc9326cf02895248050c2armvixl  if not IsCppLintAvailable():
1255ee436a3a51863de7b5dc9326cf02895248050c2armvixl    print(
1265ee436a3a51863de7b5dc9326cf02895248050c2armvixl      printer.COLOUR_RED + \
1275ee436a3a51863de7b5dc9326cf02895248050c2armvixl      ("cpplint.py not found. Please ensure the depot"
1285ee436a3a51863de7b5dc9326cf02895248050c2armvixl       " tools are installed and in your PATH. See"
1295ee436a3a51863de7b5dc9326cf02895248050c2armvixl       " http://dev.chromium.org/developers/how-tos/install-depot-tools for"
1305ee436a3a51863de7b5dc9326cf02895248050c2armvixl       " details.") + \
1315ee436a3a51863de7b5dc9326cf02895248050c2armvixl      printer.NO_COLOUR)
1325ee436a3a51863de7b5dc9326cf02895248050c2armvixl    return -1
1335ee436a3a51863de7b5dc9326cf02895248050c2armvixl
13490b0414b6c794be58f34813f84c2c06e6a15be91armvixl  pool = multiprocessing.Pool(jobs)
13590b0414b6c794be58f34813f84c2c06e6a15be91armvixl  # The '.get(9999999)' is workaround to allow killing the test script with
13690b0414b6c794be58f34813f84c2c06e6a15be91armvixl  # ctrl+C from the shell. This bug is documented at
13790b0414b6c794be58f34813f84c2c06e6a15be91armvixl  # http://bugs.python.org/issue8296.
1385ee436a3a51863de7b5dc9326cf02895248050c2armvixl  tasks = [(f, progress_prefix) for f in files]
1395ee436a3a51863de7b5dc9326cf02895248050c2armvixl  # Run under a try-catch  to avoid flooding the output when the script is
1405ee436a3a51863de7b5dc9326cf02895248050c2armvixl  # interrupted from the keyboard with ctrl+C.
1415ee436a3a51863de7b5dc9326cf02895248050c2armvixl  try:
1425ee436a3a51863de7b5dc9326cf02895248050c2armvixl    results = pool.map_async(LintWrapper, tasks).get(9999999)
1435ee436a3a51863de7b5dc9326cf02895248050c2armvixl    pool.close()
1445ee436a3a51863de7b5dc9326cf02895248050c2armvixl    pool.join()
1455ee436a3a51863de7b5dc9326cf02895248050c2armvixl  except KeyboardInterrupt:
1465ee436a3a51863de7b5dc9326cf02895248050c2armvixl    pool.terminate()
1475ee436a3a51863de7b5dc9326cf02895248050c2armvixl    sys.exit(1)
14890b0414b6c794be58f34813f84c2c06e6a15be91armvixl  n_errors = sum(results)
14990b0414b6c794be58f34813f84c2c06e6a15be91armvixl
15090b0414b6c794be58f34813f84c2c06e6a15be91armvixl  printer.PrintOverwritableLine(
15190b0414b6c794be58f34813f84c2c06e6a15be91armvixl      progress_prefix + 'Total errors found: %d' % n_errors)
15290b0414b6c794be58f34813f84c2c06e6a15be91armvixl  printer.EnsureNewLine()
15390b0414b6c794be58f34813f84c2c06e6a15be91armvixl  return n_errors
15490b0414b6c794be58f34813f84c2c06e6a15be91armvixl
15590b0414b6c794be58f34813f84c2c06e6a15be91armvixl
15690b0414b6c794be58f34813f84c2c06e6a15be91armvixldef IsCppLintAvailable():
15790b0414b6c794be58f34813f84c2c06e6a15be91armvixl    retcode, unused_output = util.getstatusoutput('which cpplint.py')
15890b0414b6c794be58f34813f84c2c06e6a15be91armvixl    return retcode == 0
15990b0414b6c794be58f34813f84c2c06e6a15be91armvixl
16090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
16190b0414b6c794be58f34813f84c2c06e6a15be91armvixlCPP_EXT_REGEXP = re.compile('\.(cc|h)$')
16290b0414b6c794be58f34813f84c2c06e6a15be91armvixldef is_linter_input(filename):
1635ee436a3a51863de7b5dc9326cf02895248050c2armvixl  # lint all C++ files.
16490b0414b6c794be58f34813f84c2c06e6a15be91armvixl  return CPP_EXT_REGEXP.search(filename) != None
1655ee436a3a51863de7b5dc9326cf02895248050c2armvixl
1665ee436a3a51863de7b5dc9326cf02895248050c2armvixldef GetDefaultTrackedFiles():
1675ee436a3a51863de7b5dc9326cf02895248050c2armvixl  if git.is_git_repository_root(config.dir_root):
1685ee436a3a51863de7b5dc9326cf02895248050c2armvixl    default_tracked_files = git.get_tracked_files().split()
1695ee436a3a51863de7b5dc9326cf02895248050c2armvixl    default_tracked_files = filter(is_linter_input, default_tracked_files)
1705ee436a3a51863de7b5dc9326cf02895248050c2armvixl    return 0, default_tracked_files
1715ee436a3a51863de7b5dc9326cf02895248050c2armvixl  else:
1725ee436a3a51863de7b5dc9326cf02895248050c2armvixl    printer.Print(printer.COLOUR_ORANGE + 'WARNING: This script is not run ' \
1735ee436a3a51863de7b5dc9326cf02895248050c2armvixl                  'from its Git repository. The linter will not run.' + \
1745ee436a3a51863de7b5dc9326cf02895248050c2armvixl                  printer.NO_COLOUR)
1755ee436a3a51863de7b5dc9326cf02895248050c2armvixl    return 1, []
17690b0414b6c794be58f34813f84c2c06e6a15be91armvixl
17790b0414b6c794be58f34813f84c2c06e6a15be91armvixlif __name__ == '__main__':
17890b0414b6c794be58f34813f84c2c06e6a15be91armvixl  # Parse the arguments.
17990b0414b6c794be58f34813f84c2c06e6a15be91armvixl  args = BuildOptions()
18090b0414b6c794be58f34813f84c2c06e6a15be91armvixl
1815ee436a3a51863de7b5dc9326cf02895248050c2armvixl  retcode, default_tracked_files = GetDefaultTrackedFiles()
1825ee436a3a51863de7b5dc9326cf02895248050c2armvixl  if retcode:
1835ee436a3a51863de7b5dc9326cf02895248050c2armvixl    sys.exit(retcode)
18490b0414b6c794be58f34813f84c2c06e6a15be91armvixl  retcode = LintFiles(default_tracked_files,
1855ee436a3a51863de7b5dc9326cf02895248050c2armvixl                      jobs = args.jobs)
18690b0414b6c794be58f34813f84c2c06e6a15be91armvixl  sys.exit(retcode)
187