• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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