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