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