| #!/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()) |