Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      2 ##  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
      3 ##
      4 ##  Use of this source code is governed by a BSD-style license
      5 ##  that can be found in the LICENSE file in the root of the source
      6 ##  tree. An additional intellectual property rights grant can be found
      7 ##  in the file PATENTS.  All contributing project authors may
      8 ##  be found in the AUTHORS file in the root of the source tree.
      9 ##
     10 """Performs style checking on each diff hunk."""
     11 import getopt
     12 import os
     13 import StringIO
     14 import subprocess
     15 import sys
     16 
     17 import diff
     18 
     19 
     20 SHORT_OPTIONS = "h"
     21 LONG_OPTIONS = ["help"]
     22 
     23 TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"]
     24 DIFF_CMD = ["git", "diff"]
     25 DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"]
     26 SHOW_CMD = ["git", "show"]
     27 CPPLINT_FILTERS = ["-readability/casting"]
     28 
     29 
     30 class Usage(Exception):
     31     pass
     32 
     33 
     34 class SubprocessException(Exception):
     35     def __init__(self, args):
     36         msg = "Failed to execute '%s'"%(" ".join(args))
     37         super(SubprocessException, self).__init__(msg)
     38 
     39 
     40 class Subprocess(subprocess.Popen):
     41     """Adds the notion of an expected returncode to Popen."""
     42 
     43     def __init__(self, args, expected_returncode=0, **kwargs):
     44         self._args = args
     45         self._expected_returncode = expected_returncode
     46         super(Subprocess, self).__init__(args, **kwargs)
     47 
     48     def communicate(self, *args, **kwargs):
     49         result = super(Subprocess, self).communicate(*args, **kwargs)
     50         if self._expected_returncode is not None:
     51             try:
     52                 ok = self.returncode in self._expected_returncode
     53             except TypeError:
     54                 ok = self.returncode == self._expected_returncode
     55             if not ok:
     56                 raise SubprocessException(self._args)
     57         return result
     58 
     59 
     60 def main(argv=None):
     61     if argv is None:
     62         argv = sys.argv
     63     try:
     64         try:
     65             opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS)
     66         except getopt.error, msg:
     67             raise Usage(msg)
     68 
     69         # process options
     70         for o, _ in opts:
     71             if o in ("-h", "--help"):
     72                 print __doc__
     73                 sys.exit(0)
     74 
     75         if args and len(args) > 1:
     76             print __doc__
     77             sys.exit(0)
     78 
     79         # Find the fully qualified path to the root of the tree
     80         tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE)
     81         tl = tl.communicate()[0].strip()
     82 
     83         # See if we're working on the index or not.
     84         if args:
     85             diff_cmd = DIFF_CMD + [args[0] + "^!"]
     86         else:
     87             diff_cmd = DIFF_INDEX_CMD
     88 
     89         # Build the command line to execute cpplint
     90         cpplint_cmd = [os.path.join(tl, "tools", "cpplint.py"),
     91                        "--filter=" + ",".join(CPPLINT_FILTERS),
     92                        "-"]
     93 
     94         # Get a list of all affected lines
     95         file_affected_line_map = {}
     96         p = Subprocess(diff_cmd, stdout=subprocess.PIPE)
     97         stdout = p.communicate()[0]
     98         for hunk in diff.ParseDiffHunks(StringIO.StringIO(stdout)):
     99             filename = hunk.right.filename[2:]
    100             if filename not in file_affected_line_map:
    101                 file_affected_line_map[filename] = set()
    102             file_affected_line_map[filename].update(hunk.right.delta_line_nums)
    103 
    104         # Run each affected file through cpplint
    105         lint_failed = False
    106         for filename, affected_lines in file_affected_line_map.iteritems():
    107             if filename.split(".")[-1] not in ("c", "h", "cc"):
    108                 continue
    109 
    110             if args:
    111                 # File contents come from git
    112                 show_cmd = SHOW_CMD + [args[0] + ":" + filename]
    113                 show = Subprocess(show_cmd, stdout=subprocess.PIPE)
    114                 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
    115                                   stdin=show.stdout, stderr=subprocess.PIPE)
    116                 lint_out = lint.communicate()[1]
    117             else:
    118                 # File contents come from the working tree
    119                 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
    120                                   stdin=subprocess.PIPE, stderr=subprocess.PIPE)
    121                 stdin = open(os.path.join(tl, filename)).read()
    122                 lint_out = lint.communicate(stdin)[1]
    123 
    124             for line in lint_out.split("\n"):
    125                 fields = line.split(":")
    126                 if fields[0] != "-":
    127                     continue
    128                 warning_line_num = int(fields[1])
    129                 if warning_line_num in affected_lines:
    130                     print "%s:%d:%s"%(filename, warning_line_num,
    131                                       ":".join(fields[2:]))
    132                     lint_failed = True
    133 
    134         # Set exit code if any relevant lint errors seen
    135         if lint_failed:
    136             return 1
    137 
    138     except Usage, err:
    139         print >>sys.stderr, err
    140         print >>sys.stderr, "for help use --help"
    141         return 2
    142 
    143 if __name__ == "__main__":
    144     sys.exit(main())
    145