lint-hunks.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/python
  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."""
  11. import getopt
  12. import os
  13. import StringIO
  14. import subprocess
  15. import sys
  16. import diff
  17. SHORT_OPTIONS = "h"
  18. LONG_OPTIONS = ["help"]
  19. TOPLEVEL_CMD = ["git", "rev-parse", "--show-toplevel"]
  20. DIFF_CMD = ["git", "diff"]
  21. DIFF_INDEX_CMD = ["git", "diff-index", "-u", "HEAD", "--"]
  22. SHOW_CMD = ["git", "show"]
  23. CPPLINT_FILTERS = ["-readability/casting"]
  24. class Usage(Exception):
  25. pass
  26. class SubprocessException(Exception):
  27. def __init__(self, args):
  28. msg = "Failed to execute '%s'"%(" ".join(args))
  29. super(SubprocessException, self).__init__(msg)
  30. class Subprocess(subprocess.Popen):
  31. """Adds the notion of an expected returncode to Popen."""
  32. def __init__(self, args, expected_returncode=0, **kwargs):
  33. self._args = args
  34. self._expected_returncode = expected_returncode
  35. super(Subprocess, self).__init__(args, **kwargs)
  36. def communicate(self, *args, **kwargs):
  37. result = super(Subprocess, self).communicate(*args, **kwargs)
  38. if self._expected_returncode is not None:
  39. try:
  40. ok = self.returncode in self._expected_returncode
  41. except TypeError:
  42. ok = self.returncode == self._expected_returncode
  43. if not ok:
  44. raise SubprocessException(self._args)
  45. return result
  46. def main(argv=None):
  47. if argv is None:
  48. argv = sys.argv
  49. try:
  50. try:
  51. opts, args = getopt.getopt(argv[1:], SHORT_OPTIONS, LONG_OPTIONS)
  52. except getopt.error, msg:
  53. raise Usage(msg)
  54. # process options
  55. for o, _ in opts:
  56. if o in ("-h", "--help"):
  57. print __doc__
  58. sys.exit(0)
  59. if args and len(args) > 1:
  60. print __doc__
  61. sys.exit(0)
  62. # Find the fully qualified path to the root of the tree
  63. tl = Subprocess(TOPLEVEL_CMD, stdout=subprocess.PIPE)
  64. tl = tl.communicate()[0].strip()
  65. # See if we're working on the index or not.
  66. if args:
  67. diff_cmd = DIFF_CMD + [args[0] + "^!"]
  68. else:
  69. diff_cmd = DIFF_INDEX_CMD
  70. # Build the command line to execute cpplint
  71. cpplint_cmd = [os.path.join(tl, "tools", "cpplint.py"),
  72. "--filter=" + ",".join(CPPLINT_FILTERS),
  73. "-"]
  74. # Get a list of all affected lines
  75. file_affected_line_map = {}
  76. p = Subprocess(diff_cmd, stdout=subprocess.PIPE)
  77. stdout = p.communicate()[0]
  78. for hunk in diff.ParseDiffHunks(StringIO.StringIO(stdout)):
  79. filename = hunk.right.filename[2:]
  80. if filename not in file_affected_line_map:
  81. file_affected_line_map[filename] = set()
  82. file_affected_line_map[filename].update(hunk.right.delta_line_nums)
  83. # Run each affected file through cpplint
  84. lint_failed = False
  85. for filename, affected_lines in file_affected_line_map.iteritems():
  86. if filename.split(".")[-1] not in ("c", "h", "cc"):
  87. continue
  88. if args:
  89. # File contents come from git
  90. show_cmd = SHOW_CMD + [args[0] + ":" + filename]
  91. show = Subprocess(show_cmd, stdout=subprocess.PIPE)
  92. lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
  93. stdin=show.stdout, stderr=subprocess.PIPE)
  94. lint_out = lint.communicate()[1]
  95. else:
  96. # File contents come from the working tree
  97. lint = Subprocess(cpplint_cmd, expected_returncode=(0, 1),
  98. stdin=subprocess.PIPE, stderr=subprocess.PIPE)
  99. stdin = open(os.path.join(tl, filename)).read()
  100. lint_out = lint.communicate(stdin)[1]
  101. for line in lint_out.split("\n"):
  102. fields = line.split(":")
  103. if fields[0] != "-":
  104. continue
  105. warning_line_num = int(fields[1])
  106. if warning_line_num in affected_lines:
  107. print "%s:%d:%s"%(filename, warning_line_num,
  108. ":".join(fields[2:]))
  109. lint_failed = True
  110. # Set exit code if any relevant lint errors seen
  111. if lint_failed:
  112. return 1
  113. except Usage, err:
  114. print >>sys.stderr, err
  115. print >>sys.stderr, "for help use --help"
  116. return 2
  117. if __name__ == "__main__":
  118. sys.exit(main())