Skip to content

Commit c0b40e3

Browse files
committed
Implement the new artifacts hashing algorithm
1 parent f3dee26 commit c0b40e3

26 files changed

+607
-75
lines changed

MODULE.bazel

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,35 @@ dev_maven.install(
738738
],
739739
)
740740

741+
# These repos are used for testing the artifact hash functions. See
742+
# `ArtifactsHashTest.java`
743+
dev_maven.install(
744+
name = "artifacts_hash_no_deps",
745+
artifacts = [
746+
# Has no dependencies
747+
"org.hamcrest:hamcrest-core:3.0",
748+
],
749+
fail_if_repin_required = True,
750+
lock_file = "//tests/custom_maven_install:artifacts_hash_no_deps_install.json",
751+
)
752+
dev_maven.install(
753+
name = "artifacts_hash_with_deps",
754+
artifacts = [
755+
"com.google.code.gson:gson:2.11.0",
756+
],
757+
fail_if_repin_required = True,
758+
lock_file = "//tests/custom_maven_install:artifacts_hash_with_deps_install.json",
759+
)
760+
dev_maven.install(
761+
name = "artifacts_hash_with_deps_from_maven",
762+
artifacts = [
763+
"com.google.guava:guava:33.3.1-jre",
764+
],
765+
fail_if_repin_required = True,
766+
lock_file = "//tests/custom_maven_install:artifacts_hash_with_deps_from_maven_install.json",
767+
resolver = "maven",
768+
)
769+
741770
# Where there are file locks, the pinned and unpinned repos are listed
742771
# next to each other. Where compat repositories are created, they are
743772
# listed next to the repo that created them. The list is otherwise kept
@@ -746,6 +775,9 @@ dev_maven.install(
746775
# want it to
747776
use_repo(
748777
dev_maven,
778+
"artifacts_hash_no_deps",
779+
"artifacts_hash_with_deps",
780+
"artifacts_hash_with_deps_from_maven",
749781
"duplicate_version_warning",
750782
"duplicate_version_warning_same_version",
751783
"exclusion_testing",
@@ -837,6 +869,7 @@ use_repo(
837869

838870
http_file(
839871
name = "com.google.ar.sceneform_rendering",
872+
dev_dependency = True,
840873
downloaded_file_path = "rendering-1.10.0.aar",
841874
sha256 = "d2f6cd1d54eee0d5557518d1edcf77a3ba37494ae94f9bb862e570ee426a3431",
842875
urls = [
@@ -846,6 +879,7 @@ http_file(
846879

847880
http_file(
848881
name = "hamcrest_core_for_test",
882+
dev_dependency = True,
849883
downloaded_file_path = "hamcrest-core-1.3.jar",
850884
sha256 = "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9",
851885
urls = [
@@ -855,6 +889,7 @@ http_file(
855889

856890
http_file(
857891
name = "hamcrest_core_srcs_for_test",
892+
dev_dependency = True,
858893
downloaded_file_path = "hamcrest-core-1.3-sources.jar",
859894
sha256 = "e223d2d8fbafd66057a8848cc94222d63c3cedd652cc48eddc0ab5c39c0f84df",
860895
urls = [
@@ -864,6 +899,7 @@ http_file(
864899

865900
http_file(
866901
name = "gson_for_test",
902+
dev_dependency = True,
867903
downloaded_file_path = "gson-2.9.0.jar",
868904
sha256 = "c96d60551331a196dac54b745aa642cd078ef89b6f267146b705f2c2cbef052d",
869905
urls = [
@@ -873,6 +909,7 @@ http_file(
873909

874910
http_file(
875911
name = "junit_platform_commons_for_test",
912+
dev_dependency = True,
876913
downloaded_file_path = "junit-platform-commons-1.8.2.jar",
877914
sha256 = "d2e015fca7130e79af2f4608dc54415e4b10b592d77333decb4b1a274c185050",
878915
urls = [
@@ -883,6 +920,7 @@ http_file(
883920
# https://github.com/bazelbuild/rules_jvm_external/issues/865
884921
http_file(
885922
name = "google_api_services_compute_javadoc_for_test",
923+
dev_dependency = True,
886924
downloaded_file_path = "google-api-services-compute-v1-rev235-1.25.0-javadoc.jar",
887925
sha256 = "b03be5ee8effba3bfbaae53891a9c01d70e2e3bd82ad8889d78e641b22bd76c2",
888926
urls = [
@@ -892,6 +930,7 @@ http_file(
892930

893931
http_file(
894932
name = "lombok_for_test",
933+
dev_dependency = True,
895934
downloaded_file_path = "lombok-1.18.22.jar",
896935
sha256 = "ecef1581411d7a82cc04281667ee0bac5d7c0a5aae74cfc38430396c91c31831",
897936
urls = [

private/lib/coordinates.bzl

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,30 @@ def unpack_coordinates(coords):
5757
def _is_version_number(part):
5858
return part[0].isdigit()
5959

60+
def _unpack_if_necssary(coords):
61+
if type(coords) == "string":
62+
unpacked = unpack_coordinates(coords)
63+
elif type(coords) == "dict":
64+
unpacked = struct(
65+
group = coords.get("group"),
66+
artifact = coords.get("artifact"),
67+
version = coords.get("version", None),
68+
classifier = coords.get("classifier", None),
69+
extension = coords.get("extension", None),
70+
)
71+
else:
72+
unpacked = coords
73+
74+
return unpacked
75+
6076
def to_external_form(coords):
6177
"""Formats `coords` as a string suitable for use by tools such as Gradle.
6278
6379
The returned format matches Gradle's "external dependency" short-form
6480
syntax: `group:name:version:classifier@packaging`
6581
"""
6682

67-
if type(coords) == "string":
68-
unpacked = unpack_coordinates(coords)
69-
else:
70-
unpacked = coords
83+
unpacked = _unpack_if_necssary(coords)
7184

7285
to_return = "%s:%s:%s" % (unpacked.group, unpacked.artifact, unpacked.version)
7386

@@ -81,6 +94,21 @@ def to_external_form(coords):
8194

8295
return to_return
8396

97+
# This matches the `Coordinates#asKey` method in the Java tree, and the
98+
# implementations must be kept in sync.
99+
def to_key(coords):
100+
unpacked = _unpack_if_necssary(coords)
101+
102+
key = unpacked.group + ":" + unpacked.artifact
103+
104+
if unpacked.classifier and "jar" != unpacked.classifier:
105+
extension = unpacked.packaging if unpacked.packaging else "jar"
106+
key += ":" + unpacked.packaging + ":" + unpacked.classifier
107+
elif unpacked.packaging and "jar" != unpacked.packaging:
108+
key += ":" + unpacked.packaging
109+
110+
return key
111+
84112
_DEFAULT_PURL_REPOS = [
85113
"https://repo.maven.apache.org/maven2",
86114
"https://repo.maven.apache.org/maven2/",

private/rules/coursier.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ def _pinned_coursier_fetch_impl(repository_ctx):
531531
"This feature ensures that the file is not modified manually. To generate this " +
532532
"signature, run 'bazel run %s'." % pin_target,
533533
)
534-
elif importer.compute_lock_file_hash(maven_install_json_content) != dep_tree_signature:
534+
elif not importer.validate_lock_file_hash(maven_install_json_content, dep_tree_signature):
535535
# Then, validate that the signature provided matches the contents of the dependency_tree.
536536
# This is to stop users from manually modifying maven_install.json.
537537
if _get_fail_if_repin_required(repository_ctx):

private/rules/v1_lock_file.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ def _compute_lock_file_hash(lock_file_contents):
6969
signature_inputs.append(":".join(artifact_group))
7070
return hash(repr(sorted(signature_inputs)))
7171

72+
def _validate_lock_file_hash(lock_file_contents, expected_hash):
73+
return _compute_lock_file_hash(lock_file_contents) == expected_hash
74+
7275
def create_dependency(dep):
7376
url = dep.get("url")
7477
if url:
@@ -139,6 +142,7 @@ v1_lock_file = struct(
139142
get_input_artifacts_hash = _get_input_artifacts_hash,
140143
get_lock_file_hash = _get_lock_file_hash,
141144
compute_lock_file_hash = _compute_lock_file_hash,
145+
validate_lock_file_hash = _validate_lock_file_hash,
142146
get_artifacts = _get_artifacts,
143147
get_netrc_entries = _get_netrc_entries,
144148
has_m2local = _has_m2local,

private/rules/v2_lock_file.bzl

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313

14+
load("//private/lib:coordinates.bzl", "to_external_form", "to_key")
15+
1416
_REQUIRED_KEYS = ["artifacts", "dependencies", "repositories"]
1517

1618
def _is_valid_lock_file(lock_file_contents):
@@ -35,7 +37,7 @@ def _get_input_artifacts_hash(lock_file_contents):
3537
def _get_lock_file_hash(lock_file_contents):
3638
return lock_file_contents.get("__RESOLVED_ARTIFACTS_HASH")
3739

38-
def _compute_lock_file_hash(lock_file_contents):
40+
def _original_compute_lock_file_hash(lock_file_contents):
3941
to_hash = {}
4042
for key in sorted(_REQUIRED_KEYS):
4143
value = lock_file_contents.get(key)
@@ -45,6 +47,27 @@ def _compute_lock_file_hash(lock_file_contents):
4547
to_hash.update({key: json.decode(json.encode(value))})
4648
return hash(repr(to_hash))
4749

50+
def _compute_lock_file_hash(lock_file_contents):
51+
lines = []
52+
artifacts = _get_artifacts(lock_file_contents)
53+
54+
for artifact in artifacts:
55+
line = "%s | %s | " % (to_external_form(artifact["coordinates"]), artifact["sha256"] if artifact["sha256"] else "")
56+
deps = []
57+
for dep in artifact["deps"]:
58+
deps.append(to_key(dep))
59+
line += ",".join(sorted(deps))
60+
61+
lines.append(line)
62+
63+
lines = sorted(lines)
64+
to_hash = "\n".join(lines)
65+
66+
return hash(to_hash)
67+
68+
def _validate_lock_file_hash(lock_file_contents, expected_hash):
69+
return _compute_lock_file_hash(lock_file_contents) == expected_hash or _original_compute_lock_file_hash(lock_file_contents) == expected_hash
70+
4871
def _to_m2_path(unpacked):
4972
path = "{group}/{artifact}/{version}/{artifact}-{version}".format(
5073
artifact = unpacked["artifact"],
@@ -192,6 +215,7 @@ v2_lock_file = struct(
192215
get_input_artifacts_hash = _get_input_artifacts_hash,
193216
get_lock_file_hash = _get_lock_file_hash,
194217
compute_lock_file_hash = _compute_lock_file_hash,
218+
validate_lock_file_hash = _validate_lock_file_hash,
195219
get_artifacts = _get_artifacts,
196220
get_netrc_entries = _get_netrc_entries,
197221
render_lock_file = _render_lock_file,

private/tools/java/com/github/bazelbuild/rules_jvm_external/Coordinates.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,64 @@ public Coordinates(String coordinates) {
3636
"Bad artifact coordinates "
3737
+ coordinates
3838
+ ", expected format is"
39-
+ " <groupId>:<artifactId>[:<extension>[:<classifier>][:<version>]");
39+
+ " <groupId>:<artifactId>[:<version>][:<classifier>][@<extension>");
4040
}
4141

4242
groupId = Objects.requireNonNull(parts[0]);
4343
artifactId = Objects.requireNonNull(parts[1]);
4444

45+
boolean isGradle =
46+
coordinates.contains("@")
47+
|| (parts.length > 2 && !parts[2].isEmpty() && Character.isDigit(parts[2].charAt(0)));
48+
49+
String version = null;
50+
String extension = "jar";
51+
String classifier = "jar";
52+
4553
if (parts.length == 2) {
4654
extension = "jar";
4755
classifier = "";
4856
version = "";
49-
} else if (parts.length == 3) {
50-
extension = "jar";
51-
classifier = "";
52-
version = parts[2];
53-
} else if (parts.length == 4) {
54-
extension = parts[2];
55-
classifier = "";
56-
version = parts[3];
57-
} else {
57+
} else if (parts.length == 5) { // Unambiguously the original format
5858
extension = "".equals(parts[2]) ? "jar" : parts[2];
5959
classifier = "jar".equals(parts[3]) ? "" : parts[3];
6060
version = parts[4];
61+
} else if (parts.length == 3) {
62+
// Could either be g:a:e or g:a:v or g:a:v@e
63+
if (isGradle) {
64+
classifier = "";
65+
66+
if (parts[2].contains("@")) {
67+
String[] subparts = parts[2].split("@", 2);
68+
version = subparts[0];
69+
extension = subparts[1];
70+
} else {
71+
extension = "jar";
72+
version = parts[2];
73+
}
74+
}
75+
} else {
76+
// Could be either g:a:e:c or g:a:v:c or g:a:v:c@e
77+
if (isGradle) {
78+
version = parts[2];
79+
if (parts[3].contains("@")) {
80+
String[] subparts = parts[3].split("@", 2);
81+
classifier = subparts[0];
82+
extension = subparts[1];
83+
} else {
84+
classifier = parts[3];
85+
extension = "jar";
86+
}
87+
} else {
88+
extension = parts[2];
89+
classifier = "";
90+
version = parts[3];
91+
}
6192
}
93+
94+
this.version = version;
95+
this.classifier = classifier;
96+
this.extension = extension;
6297
}
6398

6499
public Coordinates(
@@ -103,6 +138,7 @@ public String getExtension() {
103138
return extension;
104139
}
105140

141+
// This method matches `coordinates.bzl#to_key`. Any changes here must be matched there.
106142
public String asKey() {
107143
StringBuilder coords = new StringBuilder();
108144
coords.append(groupId).append(":").append(artifactId);
@@ -155,13 +191,23 @@ public int compareTo(Coordinates o) {
155191
}
156192

157193
public String toString() {
158-
String versionless = asKey();
194+
StringBuilder builder = new StringBuilder();
195+
196+
builder.append(getGroupId()).append(":").append(getArtifactId());
197+
198+
if (getVersion() != null && !getVersion().isEmpty()) {
199+
builder.append(":").append(getVersion());
200+
}
201+
202+
if (getClassifier() != null && !getClassifier().isEmpty() && !"jar".equals(getClassifier())) {
203+
builder.append(":").append(getClassifier());
204+
}
159205

160-
if (version != null && !version.isEmpty()) {
161-
return versionless + ":" + version;
206+
if (getExtension() != null && !getExtension().isEmpty() && !"jar".equals(getExtension())) {
207+
builder.append("@").append(getExtension());
162208
}
163209

164-
return versionless;
210+
return builder.toString();
165211
}
166212

167213
@Override

private/tools/java/com/github/bazelbuild/rules_jvm_external/coursier/LockFileConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static void main(String[] args) {
100100
Set<DependencyInfo> infos = converter.getDependencies();
101101
Set<Conflict> conflicts = converter.getConflicts();
102102

103-
Map<String, Object> rendered = new V2LockFile(repositories, infos, conflicts).render();
103+
Map<String, Object> rendered = new V2LockFile(-1, repositories, infos, conflicts).render();
104104

105105
String converted =
106106
new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(rendered);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.bazelbuild.rules_jvm_external.resolver;
2+
3+
import com.github.bazelbuild.rules_jvm_external.Coordinates;
4+
import java.util.Collection;
5+
import java.util.Set;
6+
import java.util.TreeSet;
7+
import java.util.stream.Collectors;
8+
9+
public class ArtifactsHash {
10+
11+
private ArtifactsHash() {
12+
// utility class
13+
}
14+
15+
public static int calculateArtifactsHash(Collection<DependencyInfo> infos) {
16+
Set<String> lines = new TreeSet<>();
17+
18+
for (DependencyInfo info : infos) {
19+
StringBuilder line = new StringBuilder();
20+
line.append(info.getCoordinates().toString())
21+
.append(" | ")
22+
.append(info.getSha256().orElseGet(() -> ""))
23+
.append(" | ");
24+
25+
line.append(
26+
info.getDependencies().stream()
27+
.map(Coordinates::asKey)
28+
.sorted()
29+
.collect(Collectors.joining(",")));
30+
31+
lines.add(line.toString());
32+
}
33+
34+
return String.join("\n", lines).hashCode();
35+
}
36+
}

0 commit comments

Comments
 (0)