From 2f03d558041c0d570fbe4d966e32757694704443 Mon Sep 17 00:00:00 2001 From: Max R Date: Thu, 25 Apr 2024 12:52:24 -0400 Subject: [PATCH] Add checksum --- README.md | 40 +++++++++++++++++++ .../pretty_format_java.py | 10 +++++ .../pretty_format_kotlin.py | 23 +++++++++-- language_formatters_pre_commit_hooks/utils.py | 12 ++++++ tests/pretty_format_java_test.py | 18 +++++++++ tests/pretty_format_kotlin_test.py | 25 ++++++++++++ tests/utils_test.py | 14 +++++++ 7 files changed, 138 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61b24a0..f48c65b 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,46 @@ You can pass the jar file path to the `--ktfmt-jar` argument: args: [--ktfmt, --ktfmt-jar=/usr/bin/ktfmt-0.47.jar] ``` +### How can I verify the checksum of the jar? + +_Only supported for the `pretty-format-java` and `pretty-format-kotlin-hooks`_ + +Use the corresponding `[...]-checksum` argument + +```yaml + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: ... + hooks: + - id: pretty-format-java + args: [ + --google-java-formatter-version=1.17.0, + --formatter-jar-checksum=33068bbbdce1099982ec1171f5e202898eb35f2919cf486141e439fc6e3a4203, + ] +``` + +```yaml + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: ... + hooks: + - id: pretty-format-kotlin + args: [ + --ktlint-version=1.2.1, + --formatter-jar-checksum=2e28cf46c27d38076bf63beeba0bdef6a845688d6c5dccd26505ce876094eb92, + ] +``` + +```yaml + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: ... + hooks: + - id: pretty-format-kotlin + args: [ + --ktfmt, + --ktfmt-version=0.47, + --formatter-jar-checksum=af61161faacd74ac56374e0b43003dbe742ddc0d6a7e2c1fe43e15415e65ffbd, + ] +``` + ### How to use ktfmt instead of ktlint? ```yaml diff --git a/language_formatters_pre_commit_hooks/pretty_format_java.py b/language_formatters_pre_commit_hooks/pretty_format_java.py index 232ed6c..e22304c 100644 --- a/language_formatters_pre_commit_hooks/pretty_format_java.py +++ b/language_formatters_pre_commit_hooks/pretty_format_java.py @@ -9,6 +9,7 @@ from language_formatters_pre_commit_hooks import _get_default_version from language_formatters_pre_commit_hooks.pre_conditions import assert_max_jdk_version from language_formatters_pre_commit_hooks.pre_conditions import java_required +from language_formatters_pre_commit_hooks.utils import does_checksum_match from language_formatters_pre_commit_hooks.utils import download_url from language_formatters_pre_commit_hooks.utils import run_command @@ -65,6 +66,12 @@ def pretty_format_java(argv: typing.Optional[typing.List[str]] = None) -> int: default=None, help="Path to Google Java Formatter jar file. Will be downloaded if not defined. Note that --google-java-formatter-version will be ignored if this parameter is defined (default: %(default)).", ) + parser.add_argument( + "--formatter-jar-checksum", + dest="checksum", + default=None, + help="The SHA256 checksum of the jar", + ) parser.add_argument( "--autofix", action="store_true", @@ -94,6 +101,9 @@ def pretty_format_java(argv: typing.Optional[typing.List[str]] = None) -> int: else: google_java_formatter_jar = args.google_java_formatter_jar + if args.checksum and not does_checksum_match(google_java_formatter_jar, args.checksum): + return 1 + cmd_args = [ "java", # export JDK internal classes for Java 16+ diff --git a/language_formatters_pre_commit_hooks/pretty_format_kotlin.py b/language_formatters_pre_commit_hooks/pretty_format_kotlin.py index 3022c50..19f6aec 100644 --- a/language_formatters_pre_commit_hooks/pretty_format_kotlin.py +++ b/language_formatters_pre_commit_hooks/pretty_format_kotlin.py @@ -6,6 +6,7 @@ from language_formatters_pre_commit_hooks import _get_default_version from language_formatters_pre_commit_hooks.pre_conditions import java_required +from language_formatters_pre_commit_hooks.utils import does_checksum_match from language_formatters_pre_commit_hooks.utils import download_url from language_formatters_pre_commit_hooks.utils import run_command @@ -88,6 +89,12 @@ def pretty_format_kotlin(argv: typing.Optional[typing.List[str]] = None) -> int: dest="ktfmt", help="Use ktfmt", ) + parser.add_argument( + "--formatter-jar-checksum", + dest="formatter_jar_checksum", + default=None, + help="The SHA256 checksum of the jar", + ) parser.add_argument( "--ktfmt-style", choices=["dropbox", "google", "kotlinlang"], @@ -98,13 +105,18 @@ def pretty_format_kotlin(argv: typing.Optional[typing.List[str]] = None) -> int: args = parser.parse_args(argv) if args.ktfmt: jar = args.ktfmt_jar or _download_ktfmt_formatter_jar(args.kftmt_version) - return run_ktfmt(jar, args.filenames, args.ktfmt_style, args.autofix) + return run_ktfmt(jar, args.formatter_jar_checksum, args.filenames, args.ktfmt_style, args.autofix) else: jar = args.ktlint_jar or _download_ktlint_formatter_jar(args.ktlint_version) - return run_ktlint(jar, args.filenames, args.autofix) + return run_ktlint(jar, args.formatter_jar_checksum, args.filenames, args.autofix) -def run_ktfmt(jar: str, filenames: typing.Iterable[str], ktfmt_style: typing.Optional[str], autofix: bool) -> int: +def run_ktfmt( + jar: str, checksum: typing.Optional[str], filenames: typing.Iterable[str], ktfmt_style: typing.Optional[str], autofix: bool +) -> int: + if checksum and not does_checksum_match(jar, checksum): + return 1 + ktfmt_args = ["--set-exit-if-changed"] if ktfmt_style is not None: ktfmt_args.append(f"--{ktfmt_style}-style") @@ -121,7 +133,10 @@ def run_ktfmt(jar: str, filenames: typing.Iterable[str], ktfmt_style: typing.Opt return return_code -def run_ktlint(jar: str, filenames: typing.Iterable[str], autofix: bool): +def run_ktlint(jar: str, checksum: typing.Optional[str], filenames: typing.Iterable[str], autofix: bool): + if checksum and not does_checksum_match(jar, checksum): + return 1 + jvm_args = ["--add-opens", "java.base/java.lang=ALL-UNNAMED"] # ktlint does not return exit-code!=0 if we're formatting them. diff --git a/language_formatters_pre_commit_hooks/utils.py b/language_formatters_pre_commit_hooks/utils.py index aa8370d..a8637bd 100644 --- a/language_formatters_pre_commit_hooks/utils.py +++ b/language_formatters_pre_commit_hooks/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import hashlib import os import shutil import subprocess # nosec B404 B603 @@ -84,3 +85,14 @@ def remove_trailing_whitespaces_and_set_new_line_ending(string: str) -> str: return "{content}\n".format( content="\n".join(line.rstrip() for line in string.splitlines()).rstrip(), ) + + +def does_checksum_match(path: str, expected: str) -> bool: + with open(path, "rb") as f: + actual = hashlib.sha256(f.read()).hexdigest() + + if actual != expected: + print(f"Expected {path!r} to have checksum {expected!r} but got {actual!r}", file=sys.stderr) + return False + + return True diff --git a/tests/pretty_format_java_test.py b/tests/pretty_format_java_test.py index 3476d98..e2886b5 100644 --- a/tests/pretty_format_java_test.py +++ b/tests/pretty_format_java_test.py @@ -48,6 +48,24 @@ def test__download_google_java_formatter_jar(ensure_download_possible, version): ( (["invalid.java"], 1), (["pretty-formatted.java"], 0), + pytest.param( + [ + "pretty-formatted.java", + "--google-java-formatter-version=1.21.0", + "--formatter-jar-checksum=1e69f8b63c39a5124a8efb7bad213eb9ac03944339eb9580ae210b0c60565d9b", + ], + 0, + id="valid checksum", + ), + pytest.param( + [ + "pretty-formatted.java", + "--google-java-formatter-version=1.21.0", + "--formatter-jar-checksum=2d32af8ef04ffbf0ae77fc7953e86871b85143b29d51f9794466842f68f5fb48", + ], + 1, + id="invalid checksum", + ), (["not-pretty-formatted.java"], 1), (["not-pretty-formatted_fixed.java"], 0), ), diff --git a/tests/pretty_format_kotlin_test.py b/tests/pretty_format_kotlin_test.py index 08dedc9..d141efa 100644 --- a/tests/pretty_format_kotlin_test.py +++ b/tests/pretty_format_kotlin_test.py @@ -77,6 +77,17 @@ def test_pretty_format_kotlin_ktlint(undecorate_method, filename, expected_retva assert undecorate_method([filename]) == expected_retval +@pytest.mark.parametrize( + ("checksum", "expected_retval"), + ( + ("2e28cf46c27d38076bf63beeba0bdef6a845688d6c5dccd26505ce876094eb92", 0), + ("2d32af8ef04ffbf0ae77fc7953e86871b85143b29d51f9794466842f68f5fb48", 1), + ), +) +def test_pretty_format_kotlin_checksum(undecorate_method, checksum, expected_retval): + assert undecorate_method(["--ktlint-version=1.2.1", f"--formatter-jar-checksum={checksum}", "PrettyPormatted.kt"]) == expected_retval + + @pytest.mark.parametrize( ("filename", "expected_retval"), ( @@ -91,6 +102,20 @@ def test_pretty_format_kotlin_ktfmt(undecorate_method, filename, expected_retval assert undecorate_method(["--ktfmt", filename]) == expected_retval +@pytest.mark.parametrize( + ("checksum", "expected_retval"), + ( + ("af61161faacd74ac56374e0b43003dbe742ddc0d6a7e2c1fe43e15415e65ffbd", 0), + ("2d32af8ef04ffbf0ae77fc7953e86871b85143b29d51f9794466842f68f5fb48", 1), + ), +) +def test_pretty_format_kotlin_ktfmt_checksum(undecorate_method, checksum, expected_retval): + actual = undecorate_method( + ["--ktfmt", "--ktfmt-version=0.47", f"--formatter-jar-checksum={checksum}", "NotPrettyFormattedFixedKtfmtGoogle.kt"] + ) + assert actual == expected_retval + + @pytest.mark.parametrize( ("filename", "expected_retval"), ( diff --git a/tests/utils_test.py b/tests/utils_test.py index 1dd3b6a..3af1ebc 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -7,6 +7,7 @@ import pytest +from language_formatters_pre_commit_hooks.utils import does_checksum_match from language_formatters_pre_commit_hooks.utils import download_url from language_formatters_pre_commit_hooks.utils import run_command @@ -44,3 +45,16 @@ def test_download_url(mock_requests, mock_shutil, tmpdir, url, does_file_already assert not mock_requests.get.called else: mock_requests.get.assert_called_once_with(url, stream=True) + + +@pytest.mark.parametrize( + ("checksum", "expected"), + ( + ("486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7", True), + ("2d32af8ef04ffbf0ae77fc7953e86871b85143b29d51f9794466842f68f5fb48", False), + ), +) +def test_does_checksum_match(tmpdir, checksum, expected): + hello = tmpdir.join("hello.txt") + hello.write("world") + assert does_checksum_match(str(hello), checksum) is expected