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