| # 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 |