xref: /aosp_15_r20/external/webp/PRESUBMIT.py (revision b2055c353e87c8814eb2b6b1b11112a1562253bd)
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
32See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
33details on the presubmit API built into depot_tools.
34"""
35
36import re
37import subprocess2
38
39USE_PYTHON3 = True
40_BASH_INDENTATION = "2"
41_GIT_COMMIT_SUBJECT_LENGTH = 65
42_INCLUDE_BASH_FILES_ONLY = [r".*\.sh$"]
43_INCLUDE_MAN_FILES_ONLY = [r"man/.+\.1$"]
44_INCLUDE_SOURCE_FILES_ONLY = [r".*\.[ch]$"]
45_LIBWEBP_MAX_LINE_LENGTH = 80
46
47
48def _CheckCommitSubjectLength(input_api, output_api):
49  """Ensures commit's subject length is no longer than 65 chars."""
50  name = "git-commit subject"
51  cmd = ["git", "log", "-1", "--pretty=%s"]
52  start = input_api.time.time()
53  proc = subprocess2.Popen(
54      cmd,
55      stderr=subprocess2.PIPE,
56      stdout=subprocess2.PIPE,
57      universal_newlines=True)
58
59  stdout, _ = proc.communicate()
60  duration = input_api.time.time() - start
61
62  if not re.match(r"^Revert",
63                  stdout) and (len(stdout) - 1) > _GIT_COMMIT_SUBJECT_LENGTH:
64    failure_msg = (
65        "The commit subject: %s is too long (%d chars)\n"
66        "Try to keep this to 50 or less (up to 65 is permitted for "
67        "non-reverts).\n"
68        "https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-"
69        "Project#_commit_guidelines") % (stdout, len(stdout) - 1)
70    return output_api.PresubmitError("%s\n (%4.2fs) failed\n%s" %
71                                     (name, duration, failure_msg))
72
73  return output_api.PresubmitResult("%s\n (%4.2fs) success" % (name, duration))
74
75
76def _CheckDuplicateFiles(input_api, output_api):
77  """Ensures there are not repeated filenames."""
78  all_files = []
79  for f in input_api.change.AllFiles():
80    for include_file in _INCLUDE_SOURCE_FILES_ONLY:
81      if re.match(include_file, f):
82        all_files.append(f)
83        break
84
85  basename_to_path = {}
86  for f in all_files:
87    basename_file = input_api.basename(f)
88    if basename_file in basename_to_path:
89      basename_to_path[basename_file].append(f)
90    else:
91      basename_to_path[basename_file] = [f]
92
93  dupes = []
94  for files in basename_to_path.values():
95    if len(files) > 1:
96      dupes.extend(files)
97
98  if dupes:
99    return output_api.PresubmitError(
100        "Duplicate source files, rebase or rename some to make them unique:\n%s"
101        % dupes)
102  return output_api.PresubmitResult("No duplicates, success\n")
103
104
105def _GetFilesToSkip(input_api):
106  return list(input_api.DEFAULT_FILES_TO_SKIP) + [
107      r"swig/.*\.py$",
108      r"\.pylintrc$",
109  ]
110
111
112def _RunManCmd(input_api, output_api, man_file):
113  """man command wrapper."""
114  cmd = ["man", "--warnings", "-EUTF-8", "-l", "-Tutf8", "-Z", man_file]
115  name = "Check %s file." % man_file
116  start = input_api.time.time()
117  output, _ = subprocess2.communicate(
118      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
119  duration = input_api.time.time() - start
120  if output[1]:
121    return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
122                                     (name, " ".join(cmd), duration, output[1]))
123  return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
124                                    (name, " ".join(cmd), duration))
125
126
127def _RunShellCheckCmd(input_api, output_api, bash_file):
128  """shellcheck command wrapper."""
129  cmd = ["shellcheck", "-x", "-oall", "-sbash", bash_file]
130  name = "Check %s file." % bash_file
131  start = input_api.time.time()
132  output, rc = subprocess2.communicate(
133      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
134  duration = input_api.time.time() - start
135  if rc == 0:
136    return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
137                                      (name, " ".join(cmd), duration))
138  return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
139                                   (name, " ".join(cmd), duration, output[1]))
140
141
142def _RunShfmtCheckCmd(input_api, output_api, bash_file):
143  """shfmt command wrapper."""
144  cmd = [
145      "shfmt", "-i", _BASH_INDENTATION, "-bn", "-ci", "-sr", "-kp", "-d",
146      bash_file
147  ]
148  name = "Check %s file." % bash_file
149  start = input_api.time.time()
150  output, rc = subprocess2.communicate(
151      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
152  duration = input_api.time.time() - start
153  if rc == 0:
154    return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
155                                      (name, " ".join(cmd), duration))
156  return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
157                                   (name, " ".join(cmd), duration, output[1]))
158
159
160def _RunCmdOnCheckedFiles(input_api, output_api, run_cmd, files_to_check):
161  """Ensure that libwebp/ files are clean."""
162  file_filter = lambda x: input_api.FilterSourceFile(
163      x, files_to_check=files_to_check, files_to_skip=None)
164
165  affected_files = input_api.change.AffectedFiles(file_filter=file_filter)
166  results = [
167      run_cmd(input_api, output_api, f.AbsoluteLocalPath())
168      for f in affected_files
169  ]
170  return results
171
172
173def _CommonChecks(input_api, output_api):
174  """Ensures this patch does not have trailing spaces, extra EOLs,
175     or long lines.
176  """
177  results = []
178  results.extend(
179      input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol(
180          input_api, output_api))
181  results.extend(
182      input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api))
183  results.extend(
184      input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
185          input_api, output_api))
186  results.append(_CheckCommitSubjectLength(input_api, output_api))
187  results.append(_CheckDuplicateFiles(input_api, output_api))
188
189  source_file_filter = lambda x: input_api.FilterSourceFile(
190      x, files_to_skip=_GetFilesToSkip(input_api))
191  results.extend(
192      input_api.canned_checks.CheckLongLines(
193          input_api,
194          output_api,
195          maxlen=_LIBWEBP_MAX_LINE_LENGTH,
196          source_file_filter=source_file_filter))
197
198  results.extend(
199      input_api.canned_checks.CheckPatchFormatted(
200          input_api,
201          output_api,
202          check_clang_format=False,
203          check_python=True,
204          result_factory=output_api.PresubmitError))
205  results.extend(
206      _RunCmdOnCheckedFiles(input_api, output_api, _RunManCmd,
207                            _INCLUDE_MAN_FILES_ONLY))
208  # Run pylint.
209  results.extend(
210      input_api.canned_checks.RunPylint(
211          input_api,
212          output_api,
213          files_to_skip=_GetFilesToSkip(input_api),
214          pylintrc=".pylintrc",
215          version="2.7"))
216
217  # Binaries shellcheck and shfmt are not installed in depot_tools.
218  # Installation is needed
219  try:
220    subprocess2.communicate(["shellcheck", "--version"])
221    results.extend(
222        _RunCmdOnCheckedFiles(input_api, output_api, _RunShellCheckCmd,
223                              _INCLUDE_BASH_FILES_ONLY))
224    print("shfmt")
225    subprocess2.communicate(["shfmt", "-version"])
226    results.extend(
227        _RunCmdOnCheckedFiles(input_api, output_api, _RunShfmtCheckCmd,
228                              _INCLUDE_BASH_FILES_ONLY))
229  except OSError as os_error:
230    results.append(
231        output_api.PresubmitPromptWarning(
232            "%s\nPlease install missing binaries locally." % os_error.args[0]))
233  return results
234
235
236def CheckChangeOnUpload(input_api, output_api):
237  results = []
238  results.extend(_CommonChecks(input_api, output_api))
239  return results
240
241
242def CheckChangeOnCommit(input_api, output_api):
243  results = []
244  results.extend(_CommonChecks(input_api, output_api))
245  return results
246