fix: sort repositories in v3 lock file hash to match Java resolver order (#1557)
diff --git a/MODULE.bazel b/MODULE.bazel
index 57eb663..9122aaf 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -478,6 +478,27 @@
lock_file = "//tests/custom_maven_install:maven_install.json",
)
dev_maven.install(
+ name = "multi_repo_hash",
+ artifacts = ["junit:junit:4.13.2"],
+ fail_if_repin_required = True,
+ lock_file = "//tests/custom_maven_install:multi_repo_hash_install.json",
+ repositories = [
+ "https://repo1.maven.org/maven2/",
+ "https://repo.maven.apache.org/maven2/",
+ ],
+ resolver = "maven",
+)
+dev_maven.install(
+ name = "legacy_multi_repo_hash",
+ artifacts = ["junit:junit:4.13.2"],
+ fail_if_repin_required = True,
+ lock_file = "//tests/custom_maven_install:legacy_multi_repo_hash_install.json",
+ repositories = [
+ "https://repo1.maven.org/maven2/",
+ "https://repo.maven.apache.org/maven2/",
+ ],
+)
+dev_maven.install(
# This name matches the one in `tests/integration/bzlmod_lock_files`
name = "multiple_lock_files",
artifacts = ["org.zeromq:jeromq:0.5.4"],
@@ -1098,6 +1119,10 @@
"unpinned_v1_lock_file_format",
"v1_lock_file_format",
"version_interval_testing",
+ "multi_repo_hash",
+ "unpinned_multi_repo_hash",
+ "legacy_multi_repo_hash",
+ "unpinned_legacy_multi_repo_hash",
)
http_file(
diff --git a/private/rules/coursier.bzl b/private/rules/coursier.bzl
index 78d623b..f1be5a6 100644
--- a/private/rules/coursier.bzl
+++ b/private/rules/coursier.bzl
@@ -684,7 +684,17 @@
elif importer.compute_lock_file_hash(maven_install_json_content) != dep_tree_signature:
# Then, validate that the signature provided matches the contents of the dependency_tree.
# This is to stop users from manually modifying maven_install.json.
- if _get_fail_if_repin_required(repository_ctx):
+ legacy_hash_fn = getattr(importer, "compute_lock_file_hash_legacy", None)
+ is_legacy_hash = legacy_hash_fn != None and legacy_hash_fn(maven_install_json_content) == dep_tree_signature
+ if is_legacy_hash:
+ # The lock file was written by an older version of rules_jvm_external that used
+ # repository insertion order when computing hashes. The file is valid but needs
+ # to be regenerated to use the current hash algorithm.
+ print(
+ "NOTE: %s_install.json was generated by an older version of rules_jvm_external " % user_provided_name +
+ "and needs to be regenerated. To update, run 'bazel run %s'." % pin_target,
+ )
+ elif _get_fail_if_repin_required(repository_ctx):
computed_hash = importer.compute_lock_file_hash(maven_install_json_content)
to_print = importer.print_friendly_hash_difference(dep_tree_signature, computed_hash)
fail(
diff --git a/private/rules/v3_lock_file.bzl b/private/rules/v3_lock_file.bzl
index 43b4369..7ec4123 100644
--- a/private/rules/v3_lock_file.bzl
+++ b/private/rules/v3_lock_file.bzl
@@ -131,7 +131,7 @@
return final_hashes
-def _compute_lock_file_hash_v3(lock_file_contents):
+def _compute_lock_file_hash_v3_impl(lock_file_contents, repo_keys):
all_infos = dict()
for dep, dep_info in lock_file_contents["artifacts"].items():
@@ -149,8 +149,8 @@
type_info["sha"] = sha
all_infos[dep + suffix] = type_info
- for repo, artifacts in lock_file_contents["repositories"].items():
- for artifact in artifacts:
+ for repo in repo_keys:
+ for artifact in lock_file_contents["repositories"][repo]:
all_infos[artifact]["repository"] = repo
for dep, dep_info in lock_file_contents["dependencies"].items():
@@ -158,6 +158,26 @@
return _compute_final_hash(all_infos)
+def _compute_lock_file_hash_v3(lock_file_contents):
+ # Sort repositories to match the order used by the Java resolver binary (AbstractMain.calculateArtifactHash
+ # uses sortMapRecursively which sorts alphabetically). Without sorting, artifacts present in
+ # multiple repositories get different "repository" values depending on iteration order, causing
+ # hash mismatches between the stored value (written by Java) and the value computed here.
+ return _compute_lock_file_hash_v3_impl(
+ lock_file_contents,
+ sorted(lock_file_contents["repositories"].keys()),
+ )
+
+def _compute_lock_file_hash_v3_legacy(lock_file_contents):
+ # Computes the hash using repository insertion order. Used to detect lock
+ # files written by older versions of rules_jvm_external (before the fix
+ # that aligned Starlark hash order with the Java resolver). If the current
+ # hash does not match but this one does, the file needs to be repinned.
+ return _compute_lock_file_hash_v3_impl(
+ lock_file_contents,
+ lock_file_contents["repositories"].keys(),
+ )
+
def _to_m2_path(unpacked):
path = "{group}/{artifact}/{version}/{artifact}-{version}".format(
artifact = unpacked["artifact"],
@@ -329,6 +349,7 @@
get_lock_file_hash = _get_lock_file_hash,
print_friendly_hash_difference = _print_friendly_hash_difference_v3,
compute_lock_file_hash = _compute_lock_file_hash_v3,
+ compute_lock_file_hash_legacy = _compute_lock_file_hash_v3_legacy,
get_artifacts = _get_artifacts,
get_netrc_entries = _get_netrc_entries,
render_lock_file = _render_lock_file,
diff --git a/tests/bazel_run_tests.sh b/tests/bazel_run_tests.sh
index dc13e73..c6bc81e 100755
--- a/tests/bazel_run_tests.sh
+++ b/tests/bazel_run_tests.sh
@@ -369,6 +369,19 @@
bazel build @from_files//:all
}
+function test_hash_verification_stable_for_artifact_in_multiple_repos() {
+ force_bzlmod_lock_file_to_be_regenerated
+
+ bazel build @multi_repo_hash//:junit_junit >> "$TEST_LOG" 2>&1
+}
+
+function test_legacy_multi_repo_hash_accepted_by_fallback() {
+ force_bzlmod_lock_file_to_be_regenerated
+
+ bazel build @legacy_multi_repo_hash//:junit_junit >> "$TEST_LOG" 2>&1
+ expect_log "NOTE: legacy_multi_repo_hash_install.json was generated by an older version of rules_jvm_external"
+}
+
TESTS=(
"test_coursier_resolution_with_boms"
"test_maven_resolution"
@@ -396,6 +409,8 @@
# "test_gradle_metadata_is_resolved_correctly_for_aar_artifact"
"test_gradle_metadata_is_resolved_correctly_for_jvm_artifact"
"test_gradle_versions_catalog"
+ "test_hash_verification_stable_for_artifact_in_multiple_repos"
+ "test_legacy_multi_repo_hash_accepted_by_fallback"
)
function run_tests() {
diff --git a/tests/custom_maven_install/legacy_multi_repo_hash_install.json b/tests/custom_maven_install/legacy_multi_repo_hash_install.json
new file mode 100644
index 0000000..86b8a29
--- /dev/null
+++ b/tests/custom_maven_install/legacy_multi_repo_hash_install.json
@@ -0,0 +1,83 @@
+{
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
+ "__INPUT_ARTIFACTS_HASH": {
+ "junit:junit": -744267592,
+ "repositories": 1879491525
+ },
+ "__RESOLVED_ARTIFACTS_HASH": {
+ "junit:junit": 712711302,
+ "org.hamcrest:hamcrest-core": 866476646
+ },
+ "artifacts": {
+ "junit:junit": {
+ "shasums": {
+ "jar": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3"
+ },
+ "version": "4.13.2"
+ },
+ "org.hamcrest:hamcrest-core": {
+ "shasums": {
+ "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9"
+ },
+ "version": "1.3"
+ }
+ },
+ "dependencies": {
+ "junit:junit": [
+ "org.hamcrest:hamcrest-core"
+ ]
+ },
+ "packages": {
+ "junit:junit": [
+ "junit.extensions",
+ "junit.framework",
+ "junit.runner",
+ "junit.textui",
+ "org.junit",
+ "org.junit.experimental",
+ "org.junit.experimental.categories",
+ "org.junit.experimental.max",
+ "org.junit.experimental.results",
+ "org.junit.experimental.runners",
+ "org.junit.experimental.theories",
+ "org.junit.experimental.theories.internal",
+ "org.junit.experimental.theories.suppliers",
+ "org.junit.function",
+ "org.junit.internal",
+ "org.junit.internal.builders",
+ "org.junit.internal.management",
+ "org.junit.internal.matchers",
+ "org.junit.internal.requests",
+ "org.junit.internal.runners",
+ "org.junit.internal.runners.model",
+ "org.junit.internal.runners.rules",
+ "org.junit.internal.runners.statements",
+ "org.junit.matchers",
+ "org.junit.rules",
+ "org.junit.runner",
+ "org.junit.runner.manipulation",
+ "org.junit.runner.notification",
+ "org.junit.runners",
+ "org.junit.runners.model",
+ "org.junit.runners.parameterized",
+ "org.junit.validator"
+ ],
+ "org.hamcrest:hamcrest-core": [
+ "org.hamcrest",
+ "org.hamcrest.core",
+ "org.hamcrest.internal"
+ ]
+ },
+ "repositories": {
+ "https://repo1.maven.org/maven2/": [
+ "junit:junit",
+ "org.hamcrest:hamcrest-core"
+ ],
+ "https://repo.maven.apache.org/maven2/": [
+ "junit:junit",
+ "org.hamcrest:hamcrest-core"
+ ]
+ },
+ "services": {},
+ "version": "3"
+}
diff --git a/tests/custom_maven_install/multi_repo_hash_install.json b/tests/custom_maven_install/multi_repo_hash_install.json
new file mode 100644
index 0000000..cd2dabf
--- /dev/null
+++ b/tests/custom_maven_install/multi_repo_hash_install.json
@@ -0,0 +1,84 @@
+{
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
+ "__INPUT_ARTIFACTS_HASH": {
+ "junit:junit": -744267592,
+ "repositories": 1879491525
+ },
+ "__RESOLVED_ARTIFACTS_HASH": {
+ "junit:junit": -1256429642,
+ "org.hamcrest:hamcrest-core": 649657847
+ },
+ "artifacts": {
+ "junit:junit": {
+ "shasums": {
+ "jar": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3"
+ },
+ "version": "4.13.2"
+ },
+ "org.hamcrest:hamcrest-core": {
+ "shasums": {
+ "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9"
+ },
+ "version": "1.3"
+ }
+ },
+ "dependencies": {
+ "junit:junit": [
+ "org.hamcrest:hamcrest-core"
+ ]
+ },
+ "packages": {
+ "junit:junit": [
+ "junit.extensions",
+ "junit.framework",
+ "junit.runner",
+ "junit.textui",
+ "org.junit",
+ "org.junit.experimental",
+ "org.junit.experimental.categories",
+ "org.junit.experimental.max",
+ "org.junit.experimental.results",
+ "org.junit.experimental.runners",
+ "org.junit.experimental.theories",
+ "org.junit.experimental.theories.internal",
+ "org.junit.experimental.theories.suppliers",
+ "org.junit.function",
+ "org.junit.internal",
+ "org.junit.internal.builders",
+ "org.junit.internal.management",
+ "org.junit.internal.matchers",
+ "org.junit.internal.requests",
+ "org.junit.internal.runners",
+ "org.junit.internal.runners.model",
+ "org.junit.internal.runners.rules",
+ "org.junit.internal.runners.statements",
+ "org.junit.matchers",
+ "org.junit.rules",
+ "org.junit.runner",
+ "org.junit.runner.manipulation",
+ "org.junit.runner.notification",
+ "org.junit.runners",
+ "org.junit.runners.model",
+ "org.junit.runners.parameterized",
+ "org.junit.validator"
+ ],
+ "org.hamcrest:hamcrest-core": [
+ "org.hamcrest",
+ "org.hamcrest.core",
+ "org.hamcrest.internal"
+ ]
+ },
+ "repositories": {
+ "https://repo1.maven.org/maven2/": [
+ "junit:junit",
+ "org.hamcrest:hamcrest-core"
+ ],
+ "https://repo.maven.apache.org/maven2/": [
+ "junit:junit",
+ "org.hamcrest:hamcrest-core"
+ ]
+ },
+ "services": {},
+ "skipped": [],
+ "version": "3"
+}