lint-hunks.py 4.97 KB
Newer Older
1 2
#!/usr/bin/python
##
3 4 5 6 7 8 9 10
## Copyright (c) 2016, Alliance for Open Media. All rights reserved
##
## This source code is subject to the terms of the BSD 2 Clause License and
## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
## was not distributed with this source code in the LICENSE file, you can
## obtain it at www.aomedia.org/license/software. If the Alliance for Open
## Media Patent License 1.0 was not distributed with this source code in the
## PATENTS file, you can obtain it at www.aomedia.org/license/patent.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
##
"""Performs style checking on each diff hunk."""
import getopt
import os
import StringIO
import subprocess
import sys

import diff


SHORT_OPTIONS = "h"
LONG_OPTIONS = ["help"]

TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"]
26
DIFF_CMD = ["git", "diff"]
27
DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"]
28
SHOW_CMD = ["git", "show"]
29
CPPLINT_FILTERS = ["-readability/casting"]
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66


class Usage(Exception):
    pass


class SubprocessException(Exception):
    def __init__(self, args):
        msg = "Failed to execute '%s'"%(" ".join(args))
        super(SubprocessException, self).__init__(msg)


class Subprocess(subprocess.Popen):
    """Adds the notion of an expected returncode to Popen."""

    def __init__(self, args, expected_returncode=0, **kwargs):
        self._args = args
        self._expected_returncode = expected_returncode
        super(Subprocess, self).__init__(args, **kwargs)

    def communicate(self, *args, **kwargs):
        result = super(Subprocess, self).communicate(*args, **kwargs)
        if self._expected_returncode is not None:
            try:
                ok = self.returncode in self._expected_returncode
            except TypeError:
                ok = self.returncode == self._expected_returncode
            if not ok:
                raise SubprocessException(self._args)
        return result


def main(argv=None):
    if argv is None:
        argv = sys.argv
    try:
        try:
67
            opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS)
68 69 70 71 72 73 74 75 76
        except getopt.error, msg:
            raise Usage(msg)

        # process options
        for o, _ in opts:
            if o in ("-h", "--help"):
                print __doc__
                sys.exit(0)

77 78 79 80
        if args and len(args) > 1:
            print __doc__
            sys.exit(0)

81 82 83 84
        # Find the fully qualified path to the root of the tree
        tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE)
        tl = tl.communicate()[0].strip()

85 86 87 88 89 90
        # See if we're working on the index or not.
        if args:
            diff_cmd = DIFF_CMD + [args[0] + "^!"]
        else:
            diff_cmd = DIFF_INDEX_CMD

91 92 93 94 95 96 97
        # Build the command line to execute cpplint
        cpplint_cmd = [os.path.join(tl, "tools", "cpplint.py"),
                       "--filter=" + ",".join(CPPLINT_FILTERS),
                       "-"]

        # Get a list of all affected lines
        file_affected_line_map = {}
98
        p = Subprocess(diff_cmd, stdout=subprocess.PIPE)
99 100 101 102 103 104 105 106 107 108 109 110
        stdout = p.communicate()[0]
        for hunk in diff.ParseDiffHunks(StringIO.StringIO(stdout)):
            filename = hunk.right.filename[2:]
            if filename not in file_affected_line_map:
                file_affected_line_map[filename] = set()
            file_affected_line_map[filename].update(hunk.right.delta_line_nums)

        # Run each affected file through cpplint
        lint_failed = False
        for filename, affected_lines in file_affected_line_map.iteritems():
            if filename.split(".")[-1] not in ("c", "h", "cc"):
                continue
111 112 113 114 115 116 117 118 119 120 121 122 123 124

            if args:
                # File contents come from git
                show_cmd = SHOW_CMD + [args[0] + ":" + filename]
                show = Subprocess(show_cmd, stdout=subprocess.PIPE)
                lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
                                  stdin=show.stdout, stderr=subprocess.PIPE)
                lint_out = lint.communicate()[1]
            else:
                # File contents come from the working tree
                lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
                                  stdin=subprocess.PIPE, stderr=subprocess.PIPE)
                stdin = open(os.path.join(tl, filename)).read()
                lint_out = lint.communicate(stdin)[1]
125 126 127 128 129 130 131 132 133

            for line in lint_out.split("\n"):
                fields = line.split(":")
                if fields[0] != "-":
                    continue
                warning_line_num = int(fields[1])
                if warning_line_num in affected_lines:
                    print "%s:%d:%s"%(filename, warning_line_num,
                                      ":".join(fields[2:]))
134
                    lint_failed = True
135

136
        # Set exit code if any relevant lint errors seen
137 138 139 140 141 142 143 144 145 146
        if lint_failed:
            return 1

    except Usage, err:
        print >>sys.stderr, err
        print >>sys.stderr, "for help use --help"
        return 2

if __name__ == "__main__":
    sys.exit(main())