xref: /aosp_15_r20/external/libaom/tools/lint-hunks.py (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
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