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