| #!/usr/bin/env python3 |
| ## |
| ## 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. |
| ## |
| """Performs style checking on each diff hunk.""" |
| import getopt |
| import os |
| import io |
| import subprocess |
| import sys |
| |
| import diff |
| |
| |
| SHORT_OPTIONS = "h" |
| LONG_OPTIONS = ["help"] |
| |
| TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"] |
| DIFF_CMD = ["git", "diff"] |
| DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"] |
| SHOW_CMD = ["git", "show"] |
| CPPLINT_FILTERS = ["-readability/casting"] |
| |
| |
| 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: |
| opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS) |
| except getopt.error as msg: |
| raise Usage(msg) |
| |
| # process options |
| for o, _ in opts: |
| if o in ("-h", "--help"): |
| print(__doc__) |
| sys.exit(0) |
| |
| if args and len(args) > 1: |
| print(__doc__) |
| sys.exit(0) |
| |
| # Find the fully qualified path to the root of the tree |
| tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE, text=True) |
| tl = tl.communicate()[0].strip() |
| |
| # See if we're working on the index or not. |
| if args: |
| diff_cmd = DIFF_CMD + [args[0] + "^!"] |
| else: |
| diff_cmd = DIFF_INDEX_CMD |
| |
| # 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 = {} |
| p = Subprocess(diff_cmd, stdout=subprocess.PIPE, text=True) |
| stdout = p.communicate()[0] |
| for hunk in diff.ParseDiffHunks(io.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.items(): |
| if filename.split(".")[-1] not in ("c", "h", "cc"): |
| continue |
| if filename.startswith("third_party"): |
| continue |
| |
| if args: |
| # File contents come from git |
| show_cmd = SHOW_CMD + [args[0] + ":" + filename] |
| show = Subprocess(show_cmd, stdout=subprocess.PIPE, text=True) |
| lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1), |
| stdin=show.stdout, stderr=subprocess.PIPE, |
| text=True) |
| 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, |
| text=True) |
| stdin = open(os.path.join(tl, filename)).read() |
| lint_out = lint.communicate(stdin)[1] |
| |
| 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:]))) |
| lint_failed = True |
| |
| # Set exit code if any relevant lint errors seen |
| if lint_failed: |
| return 1 |
| |
| except Usage as err: |
| print(err, file=sys.stderr) |
| print("for help use --help", file=sys.stderr) |
| return 2 |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |