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 subprocess
33import sys
34
35import git
36import printer
37import util
38
39
40# Google's cpplint.py from depot_tools is the linter used here.
41# These are positive rules, added to the set of rules that the linter checks.
42CPP_LINTER_RULES = '''
43build/class
44build/deprecated
45build/endif_comment
46build/forward_decl
47build/include_order
48build/printf_format
49build/storage_class
50legal/copyright
51readability/boost
52readability/braces
53readability/casting
54readability/constructors
55readability/fn_size
56readability/function
57readability/multiline_comment
58readability/multiline_string
59readability/streams
60readability/utf8
61runtime/arrays
62runtime/casting
63runtime/deprecated_fn
64runtime/explicit
65runtime/int
66runtime/memset
67runtime/mutex
68runtime/nonconf
69runtime/printf
70runtime/printf_format
71runtime/references
72runtime/rtti
73runtime/sizeof
74runtime/string
75runtime/virtual
76runtime/vlog
77whitespace/blank_line
78whitespace/braces
79whitespace/comma
80whitespace/comments
81whitespace/end_of_line
82whitespace/ending_newline
83whitespace/indent
84whitespace/labels
85whitespace/line_length
86whitespace/newline
87whitespace/operators
88whitespace/parens
89whitespace/tab
90whitespace/todo
91'''.split()
92
93
94
95def BuildOptions():
96  result = argparse.ArgumentParser(
97      description =
98      '''This tool lints the C++ files tracked by the git repository, and
99      produces a summary of the errors found.''',
100      # Print default values.
101      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
102  result.add_argument('--jobs', '-j', metavar='N', type=int, nargs='?',
103                      default=1, const=multiprocessing.cpu_count(),
104                      help='''Runs the tests using N jobs. If the option is set
105                      but no value is provided, the script will use as many jobs
106                      as it thinks useful.''')
107  result.add_argument('--verbose', action='store_true',
108                      help='Print verbose output.')
109  return result.parse_args()
110
111
112
113__lint_results_lock__ = multiprocessing.Lock()
114
115# Returns the number of errors in the file linted.
116def Lint(filename, lint_options, progress_prefix = '', verbose = False):
117  command = ['cpplint.py', lint_options, filename]
118  process = subprocess.Popen(command,
119                             stdout=subprocess.PIPE,
120                             stderr=subprocess.PIPE)
121
122  # Use a lock to avoid mixing the output for different files.
123  with __lint_results_lock__:
124    # Process the output as the process is running, until it exits.
125    LINT_ERROR_LINE_REGEXP = re.compile('\[[1-5]\]$')
126    LINT_DONE_PROC_LINE_REGEXP = re.compile('Done processing')
127    LINT_STATUS_LINE_REGEXP = re.compile('Total errors found')
128    while True:
129      retcode = process.poll()
130      while True:
131        line = process.stderr.readline()
132        if line == '': break
133        output_line = progress_prefix + line.rstrip('\r\n')
134
135        if LINT_ERROR_LINE_REGEXP.search(line):
136          printer.PrintOverwritableLine(output_line, verbose = verbose)
137          printer.EnsureNewLine()
138        elif LINT_DONE_PROC_LINE_REGEXP.search(line):
139          printer.PrintOverwritableLine(output_line, verbose = verbose)
140        elif LINT_STATUS_LINE_REGEXP.search(line):
141          status_line = line
142
143      if retcode != None: break;
144
145    if retcode == 0:
146      return 0
147
148    # Return the number of errors in this file.
149    res = re.search('\d+$', status_line)
150    n_errors_str = res.string[res.start():res.end()]
151    n_errors = int(n_errors_str)
152    status_line = \
153        progress_prefix + 'Total errors found in %s : %d' % (filename, n_errors)
154    printer.PrintOverwritableLine(status_line, verbose = verbose)
155    printer.EnsureNewLine()
156    return n_errors
157
158
159# The multiprocessing map_async function does not allow passing multiple
160# arguments directly, so use a wrapper.
161def LintWrapper(args):
162  return Lint(*args)
163
164
165# Returns the total number of errors found in the files linted.
166def LintFiles(files, lint_args = CPP_LINTER_RULES, jobs = 1, verbose = False,
167              progress_prefix = ''):
168  lint_options = '--filter=-,+' + ',+'.join(lint_args)
169  pool = multiprocessing.Pool(jobs)
170  # The '.get(9999999)' is workaround to allow killing the test script with
171  # ctrl+C from the shell. This bug is documented at
172  # http://bugs.python.org/issue8296.
173  tasks = [(f, lint_options, progress_prefix, verbose) for f in files]
174  results = pool.map_async(LintWrapper, tasks).get(9999999)
175  n_errors = sum(results)
176
177  printer.PrintOverwritableLine(
178      progress_prefix + 'Total errors found: %d' % n_errors)
179  printer.EnsureNewLine()
180  return n_errors
181
182
183def IsCppLintAvailable():
184    retcode, unused_output = util.getstatusoutput('which cpplint.py')
185    return retcode == 0
186
187
188CPP_EXT_REGEXP = re.compile('\.(cc|h)$')
189SIM_TRACES_REGEXP = re.compile('trace-a64\.h$')
190def is_linter_input(filename):
191  # Don't lint the simulator traces file; it takes a very long time to check
192  # and it's (mostly) generated automatically anyway.
193  if SIM_TRACES_REGEXP.search(filename): return False
194  # Otherwise, lint all C++ files.
195  return CPP_EXT_REGEXP.search(filename) != None
196default_tracked_files = git.get_tracked_files().split()
197default_tracked_files = filter(is_linter_input, default_tracked_files)
198
199if __name__ == '__main__':
200  # Parse the arguments.
201  args = BuildOptions()
202
203  retcode = LintFiles(default_tracked_files,
204                      jobs = args.jobs, verbose = args.verbose)
205  sys.exit(retcode)
206