PRESUBMIT.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. # Copyright (c) 2021, Google Inc. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are
  5. # met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. #
  10. # * Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in
  12. # the documentation and/or other materials provided with the
  13. # distribution.
  14. #
  15. # * Neither the name of Google nor the names of its contributors may
  16. # be used to endorse or promote products derived from this software
  17. # without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  23. # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  24. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  25. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. """Top-level presubmit script for libwebp.
  31. See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
  32. details on the presubmit API built into depot_tools.
  33. """
  34. import re
  35. import subprocess2
  36. USE_PYTHON3 = True
  37. _BASH_INDENTATION = "2"
  38. _GIT_COMMIT_SUBJECT_LENGTH = 65
  39. _INCLUDE_BASH_FILES_ONLY = [r".*\.sh$"]
  40. _INCLUDE_MAN_FILES_ONLY = [r"man/.+\.1$"]
  41. _LIBWEBP_MAX_LINE_LENGTH = 80
  42. def _CheckCommitSubjectLength(input_api, output_api):
  43. """Ensures commit's subject length is no longer than 65 chars."""
  44. name = "git-commit subject"
  45. cmd = ["git", "log", "-1", "--pretty=%s"]
  46. start = input_api.time.time()
  47. proc = subprocess2.Popen(
  48. cmd,
  49. stderr=subprocess2.PIPE,
  50. stdout=subprocess2.PIPE,
  51. universal_newlines=True)
  52. stdout, _ = proc.communicate()
  53. duration = input_api.time.time() - start
  54. if not re.match(r"^Revert",
  55. stdout) and (len(stdout) - 1) > _GIT_COMMIT_SUBJECT_LENGTH:
  56. failure_msg = (
  57. "The commit subject: %s is too long (%d chars)\n"
  58. "Try to keep this to 50 or less (up to 65 is permitted for "
  59. "non-reverts).\n"
  60. "https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-"
  61. "Project#_commit_guidelines") % (stdout, len(stdout) - 1)
  62. return output_api.PresubmitError("%s\n (%4.2fs) failed\n%s" %
  63. (name, duration, failure_msg))
  64. return output_api.PresubmitResult("%s\n (%4.2fs) success" % (name, duration))
  65. def _GetFilesToSkip(input_api):
  66. return list(input_api.DEFAULT_FILES_TO_SKIP) + [
  67. r"swig/.*\.py$",
  68. r"\.pylintrc$",
  69. ]
  70. def _RunManCmd(input_api, output_api, man_file):
  71. """man command wrapper."""
  72. cmd = ["man", "--warnings", "-EUTF-8", "-l", "-Tutf8", "-Z", man_file]
  73. name = "Check %s file." % man_file
  74. start = input_api.time.time()
  75. output, _ = subprocess2.communicate(
  76. cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  77. duration = input_api.time.time() - start
  78. if output[1]:
  79. return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
  80. (name, " ".join(cmd), duration, output[1]))
  81. return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
  82. (name, " ".join(cmd), duration))
  83. def _RunShellCheckCmd(input_api, output_api, bash_file):
  84. """shellcheck command wrapper."""
  85. cmd = ["shellcheck", "-x", "-oall", "-sbash", bash_file]
  86. name = "Check %s file." % bash_file
  87. start = input_api.time.time()
  88. output, rc = subprocess2.communicate(
  89. cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  90. duration = input_api.time.time() - start
  91. if rc == 0:
  92. return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
  93. (name, " ".join(cmd), duration))
  94. return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
  95. (name, " ".join(cmd), duration, output[1]))
  96. def _RunShfmtCheckCmd(input_api, output_api, bash_file):
  97. """shfmt command wrapper."""
  98. cmd = [
  99. "shfmt", "-i", _BASH_INDENTATION, "-bn", "-ci", "-sr", "-kp", "-d",
  100. bash_file
  101. ]
  102. name = "Check %s file." % bash_file
  103. start = input_api.time.time()
  104. output, rc = subprocess2.communicate(
  105. cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  106. duration = input_api.time.time() - start
  107. if rc == 0:
  108. return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
  109. (name, " ".join(cmd), duration))
  110. return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
  111. (name, " ".join(cmd), duration, output[1]))
  112. def _RunCmdOnCheckedFiles(input_api, output_api, run_cmd, files_to_check):
  113. """Ensure that libwebp/ files are clean."""
  114. file_filter = lambda x: input_api.FilterSourceFile(
  115. x, files_to_check=files_to_check, files_to_skip=None)
  116. affected_files = input_api.change.AffectedFiles(file_filter=file_filter)
  117. results = [
  118. run_cmd(input_api, output_api, f.AbsoluteLocalPath())
  119. for f in affected_files
  120. ]
  121. return results
  122. def _CommonChecks(input_api, output_api):
  123. """Ensures this patch does not have trailing spaces, extra EOLs,
  124. or long lines.
  125. """
  126. results = []
  127. results.extend(
  128. input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol(
  129. input_api, output_api))
  130. results.extend(
  131. input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api))
  132. results.extend(
  133. input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
  134. input_api, output_api))
  135. results.append(_CheckCommitSubjectLength(input_api, output_api))
  136. source_file_filter = lambda x: input_api.FilterSourceFile(
  137. x, files_to_skip=_GetFilesToSkip(input_api))
  138. results.extend(
  139. input_api.canned_checks.CheckLongLines(
  140. input_api,
  141. output_api,
  142. maxlen=_LIBWEBP_MAX_LINE_LENGTH,
  143. source_file_filter=source_file_filter))
  144. results.extend(
  145. input_api.canned_checks.CheckPatchFormatted(
  146. input_api,
  147. output_api,
  148. check_clang_format=False,
  149. check_python=True,
  150. result_factory=output_api.PresubmitError))
  151. results.extend(
  152. _RunCmdOnCheckedFiles(input_api, output_api, _RunManCmd,
  153. _INCLUDE_MAN_FILES_ONLY))
  154. # Run pylint.
  155. results.extend(
  156. input_api.canned_checks.RunPylint(
  157. input_api,
  158. output_api,
  159. files_to_skip=_GetFilesToSkip(input_api),
  160. pylintrc=".pylintrc",
  161. version="2.7"))
  162. # Binaries shellcheck and shfmt are not installed in depot_tools.
  163. # Installation is needed
  164. try:
  165. subprocess2.communicate(["shellcheck", "--version"])
  166. results.extend(
  167. _RunCmdOnCheckedFiles(input_api, output_api, _RunShellCheckCmd,
  168. _INCLUDE_BASH_FILES_ONLY))
  169. print("shfmt")
  170. subprocess2.communicate(["shfmt", "-version"])
  171. results.extend(
  172. _RunCmdOnCheckedFiles(input_api, output_api, _RunShfmtCheckCmd,
  173. _INCLUDE_BASH_FILES_ONLY))
  174. except OSError as os_error:
  175. results.append(
  176. output_api.PresubmitPromptWarning(
  177. "%s\nPlease install missing binaries locally." % os_error.args[0]))
  178. return results
  179. def CheckChangeOnUpload(input_api, output_api):
  180. results = []
  181. results.extend(_CommonChecks(input_api, output_api))
  182. return results
  183. def CheckChangeOnCommit(input_api, output_api):
  184. results = []
  185. results.extend(_CommonChecks(input_api, output_api))
  186. return results