blob: d02bee16cebe9a13a2f2dac047bc914da93ad74b [file] [log] [blame]
John Koleszar6c776b22012-07-13 15:14:17 -07001#!/usr/bin/python
John Koleszar6c776b22012-07-13 15:14:17 -07002##
Yaowu Xu9c01aa12016-09-01 14:32:49 -07003## 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.
John Koleszar6c776b22012-07-13 15:14:17 -070011##
12"""Performs style checking on each diff hunk."""
13import getopt
14import os
15import StringIO
16import subprocess
17import sys
18
19import diff
20
21
22SHORT_OPTIONS = "h"
23LONG_OPTIONS = ["help"]
24
25TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"]
John Koleszarb1cb0072012-10-17 14:23:08 -070026DIFF_CMD = ["git", "diff"]
John Koleszar6e364a52012-10-18 14:34:53 -070027DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"]
John Koleszar6c776b22012-07-13 15:14:17 -070028SHOW_CMD = ["git", "show"]
Guillaume Martres650caec2013-10-15 15:43:42 -070029CPPLINT_FILTERS = ["-readability/casting"]
John Koleszar6c776b22012-07-13 15:14:17 -070030
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:
John Koleszarb1cb0072012-10-17 14:23:08 -070067 opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS)
John Koleszar6c776b22012-07-13 15:14:17 -070068 except getopt.error, 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
John Koleszarb1cb0072012-10-17 14:23:08 -070077 if args and len(args) > 1:
78 print __doc__
79 sys.exit(0)
80
John Koleszar6c776b22012-07-13 15:14:17 -070081 # Find the fully qualified path to the root of the tree
82 tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE)
83 tl = tl.communicate()[0].strip()
84
John Koleszarb1cb0072012-10-17 14:23:08 -070085 # 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
John Koleszar6c776b22012-07-13 15:14:17 -070091 # 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 = {}
John Koleszarb1cb0072012-10-17 14:23:08 -070098 p = Subprocess(diff_cmd, stdout=subprocess.PIPE)
John Koleszar6c776b22012-07-13 15:14:17 -070099 stdout = p.communicate()[0]
100 for hunk in diff.ParseDiffHunks(StringIO.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.iteritems():
109 if filename.split(".")[-1] not in ("c", "h", "cc"):
110 continue
John Koleszar6e364a52012-10-18 14:34:53 -0700111
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)
116 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
117 stdin=show.stdout, stderr=subprocess.PIPE)
118 lint_out = lint.communicate()[1]
119 else:
120 # File contents come from the working tree
121 lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
122 stdin=subprocess.PIPE, stderr=subprocess.PIPE)
123 stdin = open(os.path.join(tl, filename)).read()
124 lint_out = lint.communicate(stdin)[1]
John Koleszar6c776b22012-07-13 15:14:17 -0700125
126 for line in lint_out.split("\n"):
127 fields = line.split(":")
128 if fields[0] != "-":
129 continue
130 warning_line_num = int(fields[1])
131 if warning_line_num in affected_lines:
132 print "%s:%d:%s"%(filename, warning_line_num,
133 ":".join(fields[2:]))
John Koleszar13d69d42012-10-17 21:43:18 -0700134 lint_failed = True
John Koleszar6c776b22012-07-13 15:14:17 -0700135
John Koleszar13d69d42012-10-17 21:43:18 -0700136 # Set exit code if any relevant lint errors seen
John Koleszar6c776b22012-07-13 15:14:17 -0700137 if lint_failed:
138 return 1
139
140 except Usage, err:
141 print >>sys.stderr, err
142 print >>sys.stderr, "for help use --help"
143 return 2
144
145if __name__ == "__main__":
146 sys.exit(main())