# Copyright (c) 2021, Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # * Neither the name of Google nor the names of its contributors may # be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Top-level presubmit script for libwebp. See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for details on the presubmit API built into depot_tools. """ import re import subprocess2 USE_PYTHON3 = True _BASH_INDENTATION = "2" _GIT_COMMIT_SUBJECT_LENGTH = 65 _INCLUDE_BASH_FILES_ONLY = [r".*\.sh$"] _INCLUDE_MAN_FILES_ONLY = [r"man/.+\.1$"] _INCLUDE_SOURCE_FILES_ONLY = [r".*\.[ch]$"] _LIBWEBP_MAX_LINE_LENGTH = 80 def _CheckCommitSubjectLength(input_api, output_api): """Ensures commit's subject length is no longer than 65 chars.""" name = "git-commit subject" cmd = ["git", "log", "-1", "--pretty=%s"] start = input_api.time.time() proc = subprocess2.Popen( cmd, stderr=subprocess2.PIPE, stdout=subprocess2.PIPE, universal_newlines=True) stdout, _ = proc.communicate() duration = input_api.time.time() - start if not re.match(r"^Revert", stdout) and (len(stdout) - 1) > _GIT_COMMIT_SUBJECT_LENGTH: failure_msg = ( "The commit subject: %s is too long (%d chars)\n" "Try to keep this to 50 or less (up to 65 is permitted for " "non-reverts).\n" "https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-" "Project#_commit_guidelines") % (stdout, len(stdout) - 1) return output_api.PresubmitError("%s\n (%4.2fs) failed\n%s" % (name, duration, failure_msg)) return output_api.PresubmitResult("%s\n (%4.2fs) success" % (name, duration)) def _CheckDuplicateFiles(input_api, output_api): """Ensures there are not repeated filenames.""" all_files = [] for f in input_api.change.AllFiles(): for include_file in _INCLUDE_SOURCE_FILES_ONLY: if re.match(include_file, f): all_files.append(f) break basename_to_path = {} for f in all_files: basename_file = input_api.basename(f) if basename_file in basename_to_path: basename_to_path[basename_file].append(f) else: basename_to_path[basename_file] = [f] dupes = [] for files in basename_to_path.values(): if len(files) > 1: dupes.extend(files) if dupes: return output_api.PresubmitError( "Duplicate source files, rebase or rename some to make them unique:\n%s" % dupes) return output_api.PresubmitResult("No duplicates, success\n") def _GetFilesToSkip(input_api): return list(input_api.DEFAULT_FILES_TO_SKIP) + [ r"swig/.*\.py$", r"\.pylintrc$", ] def _RunManCmd(input_api, output_api, man_file): """man command wrapper.""" cmd = ["man", "--warnings", "-EUTF-8", "-l", "-Tutf8", "-Z", man_file] name = "Check %s file." % man_file start = input_api.time.time() output, _ = subprocess2.communicate( cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True) duration = input_api.time.time() - start if output[1]: return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" % (name, " ".join(cmd), duration, output[1])) return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" % (name, " ".join(cmd), duration)) def _RunShellCheckCmd(input_api, output_api, bash_file): """shellcheck command wrapper.""" cmd = ["shellcheck", "-x", "-oall", "-sbash", bash_file] name = "Check %s file." % bash_file start = input_api.time.time() output, rc = subprocess2.communicate( cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True) duration = input_api.time.time() - start if rc == 0: return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" % (name, " ".join(cmd), duration)) return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" % (name, " ".join(cmd), duration, output[1])) def _RunShfmtCheckCmd(input_api, output_api, bash_file): """shfmt command wrapper.""" cmd = [ "shfmt", "-i", _BASH_INDENTATION, "-bn", "-ci", "-sr", "-kp", "-d", bash_file ] name = "Check %s file." % bash_file start = input_api.time.time() output, rc = subprocess2.communicate( cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True) duration = input_api.time.time() - start if rc == 0: return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" % (name, " ".join(cmd), duration)) return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" % (name, " ".join(cmd), duration, output[1])) def _RunCmdOnCheckedFiles(input_api, output_api, run_cmd, files_to_check): """Ensure that libwebp/ files are clean.""" file_filter = lambda x: input_api.FilterSourceFile( x, files_to_check=files_to_check, files_to_skip=None) affected_files = input_api.change.AffectedFiles(file_filter=file_filter) results = [ run_cmd(input_api, output_api, f.AbsoluteLocalPath()) for f in affected_files ] return results def _CommonChecks(input_api, output_api): """Ensures this patch does not have trailing spaces, extra EOLs, or long lines. """ results = [] results.extend( input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( input_api, output_api)) results.extend( input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api)) results.extend( input_api.canned_checks.CheckChangeHasNoStrayWhitespace( input_api, output_api)) results.append(_CheckCommitSubjectLength(input_api, output_api)) results.append(_CheckDuplicateFiles(input_api, output_api)) source_file_filter = lambda x: input_api.FilterSourceFile( x, files_to_skip=_GetFilesToSkip(input_api)) results.extend( input_api.canned_checks.CheckLongLines( input_api, output_api, maxlen=_LIBWEBP_MAX_LINE_LENGTH, source_file_filter=source_file_filter)) results.extend( input_api.canned_checks.CheckPatchFormatted( input_api, output_api, check_clang_format=False, check_python=True, result_factory=output_api.PresubmitError)) results.extend( _RunCmdOnCheckedFiles(input_api, output_api, _RunManCmd, _INCLUDE_MAN_FILES_ONLY)) # Run pylint. results.extend( input_api.canned_checks.RunPylint( input_api, output_api, files_to_skip=_GetFilesToSkip(input_api), pylintrc=".pylintrc", version="2.7")) # Binaries shellcheck and shfmt are not installed in depot_tools. # Installation is needed try: subprocess2.communicate(["shellcheck", "--version"]) results.extend( _RunCmdOnCheckedFiles(input_api, output_api, _RunShellCheckCmd, _INCLUDE_BASH_FILES_ONLY)) print("shfmt") subprocess2.communicate(["shfmt", "-version"]) results.extend( _RunCmdOnCheckedFiles(input_api, output_api, _RunShfmtCheckCmd, _INCLUDE_BASH_FILES_ONLY)) except OSError as os_error: results.append( output_api.PresubmitPromptWarning( "%s\nPlease install missing binaries locally." % os_error.args[0])) return results def CheckChangeOnUpload(input_api, output_api): results = [] results.extend(_CommonChecks(input_api, output_api)) return results def CheckChangeOnCommit(input_api, output_api): results = [] results.extend(_CommonChecks(input_api, output_api)) return results