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