1#!/usr/bin/env python3 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.""" 11import getopt 12import os 13import io 14import subprocess 15import sys 16 17import diff 18 19 20SHORT_OPTIONS = "h" 21LONG_OPTIONS = ["help"] 22 23TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"] 24DIFF_CMD = ["git", "diff"] 25DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"] 26SHOW_CMD = ["git", "show"] 27CPPLINT_FILTERS = ["-readability/casting"] 28 29 30class Usage(Exception): 31 pass 32 33 34class SubprocessException(Exception): 35 def __init__(self, args): 36 msg = "Failed to execute '%s'"%(" ".join(args)) 37 super(SubprocessException, self).__init__(msg) 38 39 40class 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 60def 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 as 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, text=True) 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, text=True) 97 stdout = p.communicate()[0] 98 for hunk in diff.ParseDiffHunks(io.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.items(): 107 if filename.split(".")[-1] not in ("c", "h", "cc"): 108 continue 109 if filename.startswith("third_party"): 110 continue 111 112 if args: 113 # File contents come from git 114 show_cmd = SHOW_CMD + [args[0] + ":" + filename] 115 show = Subprocess(show_cmd, stdout=subprocess.PIPE, text=True) 116 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1), 117 stdin=show.stdout, stderr=subprocess.PIPE, 118 text=True) 119 lint_out = lint.communicate()[1] 120 else: 121 # File contents come from the working tree 122 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1), 123 stdin=subprocess.PIPE, stderr=subprocess.PIPE, 124 text=True) 125 stdin = open(os.path.join(tl, filename)).read() 126 lint_out = lint.communicate(stdin)[1] 127 128 for line in lint_out.split("\n"): 129 fields = line.split(":") 130 if fields[0] != "-": 131 continue 132 warning_line_num = int(fields[1]) 133 if warning_line_num in affected_lines: 134 print("%s:%d:%s"%(filename, warning_line_num, 135 ":".join(fields[2:]))) 136 lint_failed = True 137 138 # Set exit code if any relevant lint errors seen 139 if lint_failed: 140 return 1 141 142 except Usage as err: 143 print(err, file=sys.stderr) 144 print("for help use --help", file=sys.stderr) 145 return 2 146 147if __name__ == "__main__": 148 sys.exit(main()) 149