• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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