Home | History | Annotate | Download | only in tools
      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 
     29 import argparse
     30 import multiprocessing
     31 import re
     32 import subprocess
     33 import sys
     34 
     35 import git
     36 import printer
     37 import 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.
     42 CPP_LINTER_RULES = '''
     43 build/class
     44 build/deprecated
     45 build/endif_comment
     46 build/forward_decl
     47 build/include_order
     48 build/printf_format
     49 build/storage_class
     50 legal/copyright
     51 readability/boost
     52 readability/braces
     53 readability/casting
     54 readability/constructors
     55 readability/fn_size
     56 readability/function
     57 readability/multiline_comment
     58 readability/multiline_string
     59 readability/streams
     60 readability/utf8
     61 runtime/arrays
     62 runtime/casting
     63 runtime/deprecated_fn
     64 runtime/explicit
     65 runtime/int
     66 runtime/memset
     67 runtime/mutex
     68 runtime/nonconf
     69 runtime/printf
     70 runtime/printf_format
     71 runtime/references
     72 runtime/rtti
     73 runtime/sizeof
     74 runtime/string
     75 runtime/virtual
     76 runtime/vlog
     77 whitespace/blank_line
     78 whitespace/braces
     79 whitespace/comma
     80 whitespace/comments
     81 whitespace/end_of_line
     82 whitespace/ending_newline
     83 whitespace/indent
     84 whitespace/labels
     85 whitespace/line_length
     86 whitespace/newline
     87 whitespace/operators
     88 whitespace/parens
     89 whitespace/tab
     90 whitespace/todo
     91 '''.split()
     92 
     93 
     94 
     95 def 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.
    116 def 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.
    161 def LintWrapper(args):
    162   return Lint(*args)
    163 
    164 
    165 # Returns the total number of errors found in the files linted.
    166 def 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 
    183 def IsCppLintAvailable():
    184     retcode, unused_output = util.getstatusoutput('which cpplint.py')
    185     return retcode == 0
    186 
    187 
    188 CPP_EXT_REGEXP = re.compile('\.(cc|h)$')
    189 SIM_TRACES_REGEXP = re.compile('trace-a64\.h$')
    190 def 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
    196 default_tracked_files = git.get_tracked_files().split()
    197 default_tracked_files = filter(is_linter_input, default_tracked_files)
    198 
    199 if __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