Skip to content

Add support for pinning snapshots in gradle resolver #1412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -753,12 +753,15 @@ dev_maven.install(
"androidx.annotation:annotation:1.6.0",
# https://github.com/bazel-contrib/rules_jvm_external/issues/1409
"com.squareup.okhttp3:okhttp:4.12.0",
# Snapshot pinning support: https://github.com/bazel-contrib/rules_jvm_external/pull/1412
"com.google.guava:guava:999.0.0-HEAD-jre-SNAPSHOT",
],
generate_compat_repositories = True,
lock_file = "//tests/custom_maven_install:regression_testing_gradle_install.json",
repositories = [
"https://repo1.maven.org/maven2",
"https://maven.google.com",
"https://oss.sonatype.org/content/repositories/snapshots",
],
resolver = "gradle",
)
Expand Down
4 changes: 3 additions & 1 deletion private/rules/v2_lock_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ def _compute_lock_file_hash(lock_file_contents):
return hash(repr(to_hash))

def _to_m2_path(unpacked):
path = "{group}/{artifact}/{version}/{artifact}-{version}".format(
path = "{group}/{artifact}/{version}/{artifact}-{version_revision}".format(
artifact = unpacked["artifact"],
group = unpacked["group"].replace(".", "/"),
version = unpacked["version"],
version_revision = unpacked.get("version_revision") or unpacked["version"]
)

classifier = unpacked.get("classifier", "jar")
Expand Down Expand Up @@ -138,6 +139,7 @@ def _get_artifacts(lock_file_contents):
"group": parts[0],
"artifact": parts[1],
"version": data["version"],
"version_revision": data.get("version_revision"),
}
if len(parts) > 2:
root_unpacked["packaging"] = parts[2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class Coordinates implements Comparable<Coordinates> {
private final String groupId;
private final String artifactId;
private final String version;
private final String versionRevision;
private final String classifier;
private final String extension;

Expand Down Expand Up @@ -59,16 +60,23 @@ public Coordinates(String coordinates) {
classifier = "jar".equals(parts[3]) ? "" : parts[3];
version = parts[4];
}
this.versionRevision = null;
}

public Coordinates(
String groupId, String artifactId, String extension, String classifier, String version) {
this(groupId, artifactId, extension, classifier, version, null);
}

public Coordinates(
String groupId, String artifactId, String extension, String classifier, String version, String versionRevision) {
this.groupId = Objects.requireNonNull(groupId, "Group ID");
this.artifactId = Objects.requireNonNull(artifactId, "Artifact ID");
this.extension = extension == null || extension.isEmpty() ? "jar" : extension;
this.classifier =
classifier == null || classifier.isEmpty() || "jar".equals(classifier) ? "" : classifier;
this.version = version == null || version.isEmpty() ? "" : version;
this.versionRevision = versionRevision;
}

public String getGroupId() {
Expand All @@ -88,21 +96,29 @@ public String getClassifier() {
}

public Coordinates setClassifier(String classifier) {
return new Coordinates(getGroupId(), getArtifactId(), getExtension(), classifier, getVersion());
return new Coordinates(getGroupId(), getArtifactId(), getExtension(), classifier, getVersion(), getVersionRevision());
}

public Coordinates setExtension(String extension) {
return new Coordinates(getGroupId(), getArtifactId(), extension, getClassifier(), getVersion());
return new Coordinates(getGroupId(), getArtifactId(), extension, getClassifier(), getVersion(), getVersionRevision());
}

public Coordinates setVersion(String version) {
return new Coordinates(getGroupId(), getArtifactId(), getExtension(), getClassifier(), version);
return new Coordinates(getGroupId(), getArtifactId(), getExtension(), getClassifier(), version, getVersionRevision());
}

public Coordinates setVersionRevision(String versionRevision) {
return new Coordinates(getGroupId(), getArtifactId(), getExtension(), getClassifier(), getVersion(), versionRevision);
}

public String getExtension() {
return extension;
}

public String getVersionRevision() {
return versionRevision;
}

public String asKey() {
StringBuilder coords = new StringBuilder();
coords.append(groupId).append(":").append(artifactId);
Expand Down Expand Up @@ -133,7 +149,7 @@ public String toRepoPath() {
.append("/")
.append(getArtifactId())
.append("-")
.append(getVersion());
.append(isNullOrEmpty(getVersionRevision()) ? getVersion() : getVersionRevision());

String classifier = getClassifier();

Expand Down Expand Up @@ -178,14 +194,15 @@ public boolean equals(Object o) {
return getGroupId().equals(that.getGroupId())
&& getArtifactId().equals(that.getArtifactId())
&& Objects.equals(getVersion(), that.getVersion())
&& Objects.equals(getVersionRevision(), that.getVersionRevision())
&& Objects.equals(getClassifier(), that.getClassifier())
&& Objects.equals(getExtension(), that.getExtension());
}

@Override
public int hashCode() {
return Objects.hash(
getGroupId(), getArtifactId(), getVersion(), getClassifier(), getExtension());
getGroupId(), getArtifactId(), getVersion(), getVersionRevision(), getClassifier(), getExtension());
}

private boolean isNullOrEmpty(String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ private ResolutionResult parseDependencies(
gradleCoordinates.getArtifactId(),
extension,
classifier,
gradleCoordinates.getVersion());
gradleCoordinates.getVersion(),
dependency.getVersionRevision());
addDependency(graph, coordinates, dependency, conflicts, requestedDeps, visited);
// if there's a conflict and the conflicting version isn't one that's actually requested
// then it's an actual conflict we want to report
Expand Down Expand Up @@ -275,7 +276,8 @@ private void addDependency(
childCoordinates.getArtifactId(),
extension,
childCoordinates.getClassifier(),
childCoordinates.getVersion());
childCoordinates.getVersion(),
childInfo.getVersionRevision());
graph.addNode(child);
graph.putEdge(parent, child);
// if there's a conflict and the conflicting version isn't one that's actually requested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public interface GradleResolvedDependency {

void setVersion(String version);

String getVersionRevision();

void setVersionRevision(String versionRevision);

Set<String> getRequestedVersions();

void addRequestedVersion(String requestedVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class GradleResolvedDependencyImpl implements Serializable, GradleResolve
private String group;
private String name;
private String version;
private String versionRevision;
private Set<String> requestedVersions;
private boolean conflict;
private List<GradleResolvedDependency> children;
Expand Down Expand Up @@ -61,6 +62,14 @@ public void setVersion(String version) {
this.version = version;
}

public String getVersionRevision() {
return versionRevision;
}

public void setVersionRevision(String versionRevision) {
this.versionRevision = versionRevision;
}

public Set<String> getRequestedVersions() {
return requestedVersions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,17 @@ private GradleResolvedDependency walkResolvedComponent(
info = new GradleResolvedDependencyImpl();
info.setGroup(component.getModuleVersion().getGroup());
info.setName(component.getModuleVersion().getName());
info.setVersion(component.getModuleVersion().getVersion());
String version = component.getModuleVersion().getVersion();
info.setVersion(version);

// For snapshot dependencies, extract the timestamped version from the component ID
// Note: this is unsafe, is there a better way of obtaining the timestamp for an snapshot? I could not find any
if (component.getId().getClass().getName().contains("MavenUniqueSnapshotComponentIdentifier")) {
String snapshotVersion = version.substring(0, version.length() - "-SNAPSHOT".length());
// Extract timestamped version from format: group:artifact:version:timestamp-buildnum
String snapshotId = snapshotVersion + "-" + component.getId().toString().split(":")[3];
info.setVersionRevision(snapshotId);
}
}

info.addRequestedVersion(info.getVersion()); // add a new version that may have been requested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ public Map<String, Object> render() {
Map<String, Object> artifactValue =
artifacts.computeIfAbsent(shortKey, k -> new TreeMap<>());
artifactValue.put("version", coords.getVersion());

// Add version_revision for reproducible builds when available
if (coords.getVersionRevision() != null && !coords.getVersionRevision().isEmpty()) {
artifactValue.put("version_revision", coords.getVersionRevision());
}

String classifier;
if (coords.getClassifier() == null || coords.getClassifier().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,7 @@ public DownloadResult download(Coordinates coords) {
return result;
}

// Are we dealing with a packaging dep? Download the `pom.xml` and check
String originalTarget = coords.toRepoPath();
String pomName = String.format("%s-%s.pom", coords.getArtifactId(), coords.getVersion());
String pom = Paths.get(originalTarget).getParent().resolve(pomName).toString();

String pom = coords.setExtension("pom").toRepoPath();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why the special handling of POM files above 🤔

DownloadResult pomResult = performDownload(coords, pom);
if (pomResult == null) {
System.out.println(
Expand Down
95 changes: 84 additions & 11 deletions tests/custom_maven_install/regression_testing_gradle_install.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
"__INPUT_ARTIFACTS_HASH": -1648244252,
"__RESOLVED_ARTIFACTS_HASH": -1940227237,
"__INPUT_ARTIFACTS_HASH": 325832765,
"__RESOLVED_ARTIFACTS_HASH": 1086745,
"artifacts": {
"androidx.activity:activity-ktx:aar": {
"shasums": {
Expand Down Expand Up @@ -345,11 +345,36 @@
},
"version": "1.1.1"
},
"com.google.errorprone:error_prone_annotations": {
"shasums": {
"jar": "77440e270b0bc9a249903c5a076c36a722c4886ca4f42675f2903a1c53ed61a5"
},
"version": "2.36.0"
},
"com.google.guava:failureaccess": {
"shasums": {
"jar": "cbfc3906b19b8f55dd7cfd6dfe0aa4532e834250d7f080bd8d211a3e246b59cb"
},
"version": "1.0.3"
},
"com.google.guava:guava": {
"shasums": {
"jar": "79abe7953803b20d1deb5f36283d4d640c7aacbdb1ddd7601aae369b1633e903"
},
"version": "999.0.0-HEAD-jre-SNAPSHOT",
"version_revision": "999.0.0-HEAD-jre-20250623.150948-114"
Copy link
Author

@acecilia acecilia Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is how the snapshot version pinning looks like, including the timestamp

},
"com.google.guava:listenablefuture": {
"shasums": {
"jar": "e4ad7607e5c0477c6f890ef26a49cb8d1bb4dffb650bab4502afee64644e3069"
"jar": "b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99"
},
"version": "9999.0-empty-to-avoid-conflict-with-guava"
},
"com.google.j2objc:j2objc-annotations": {
"shasums": {
"jar": "88241573467ddca44ffd4d74aa04c2bbfd11bf7c17e0c342c94c9de7a70a7c64"
},
"version": "1.0"
"version": "3.0.0"
},
"com.squareup.okhttp3:okhttp": {
"shasums": {
Expand Down Expand Up @@ -422,6 +447,12 @@
"jar": "ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478"
},
"version": "13.0"
},
"org.jspecify:jspecify": {
"shasums": {
"jar": "1fad6e6be7557781e4d33729d49ae1cdc8fdda6fe477bb0cc68ce351eafdfbab"
},
"version": "1.0.0"
}
},
"conflict_resolution": {
Expand Down Expand Up @@ -668,8 +699,7 @@
"org.jetbrains.kotlinx:kotlinx-coroutines-core"
],
"androidx.concurrent:concurrent-futures": [
"androidx.annotation:annotation",
"com.google.guava:listenablefuture"
"androidx.annotation:annotation"
],
"androidx.core:core-ktx:aar": [
"androidx.annotation:annotation",
Expand Down Expand Up @@ -820,8 +850,7 @@
"androidx.profileinstaller:profileinstaller:aar": [
"androidx.annotation:annotation",
"androidx.concurrent:concurrent-futures",
"androidx.startup:startup-runtime:aar",
"com.google.guava:listenablefuture"
"androidx.startup:startup-runtime:aar"
],
"androidx.savedstate:savedstate-ktx:aar": [
"androidx.savedstate:savedstate:aar",
Expand All @@ -845,6 +874,13 @@
"androidx.annotation:annotation",
"androidx.collection:collection"
],
"com.google.guava:guava": [
"com.google.errorprone:error_prone_annotations",
"com.google.guava:failureaccess",
"com.google.guava:listenablefuture",
"com.google.j2objc:j2objc-annotations",
"org.jspecify:jspecify"
],
"com.squareup.okhttp3:okhttp": [
"com.squareup.okio:okio",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8"
Expand Down Expand Up @@ -901,8 +937,35 @@
"androidx.lifecycle:lifecycle-common": [
"androidx.lifecycle"
],
"com.google.guava:listenablefuture": [
"com.google.common.util.concurrent"
"com.google.errorprone:error_prone_annotations": [
"com.google.errorprone.annotations",
"com.google.errorprone.annotations.concurrent"
],
"com.google.guava:failureaccess": [
"com.google.common.util.concurrent.internal"
],
"com.google.guava:guava": [
"com.google.common.annotations",
"com.google.common.base",
"com.google.common.base.internal",
"com.google.common.cache",
"com.google.common.collect",
"com.google.common.escape",
"com.google.common.eventbus",
"com.google.common.graph",
"com.google.common.hash",
"com.google.common.html",
"com.google.common.io",
"com.google.common.math",
"com.google.common.net",
"com.google.common.primitives",
"com.google.common.reflect",
"com.google.common.util.concurrent",
"com.google.common.xml",
"com.google.thirdparty.publicsuffix"
],
"com.google.j2objc:j2objc-annotations": [
"com.google.j2objc.annotations"
],
"com.squareup.okhttp3:okhttp": [
"okhttp3",
Expand Down Expand Up @@ -991,6 +1054,9 @@
"org.jetbrains:annotations": [
"org.intellij.lang.annotations",
"org.jetbrains.annotations"
],
"org.jspecify:jspecify": [
"org.jspecify.annotations"
]
},
"repositories": {
Expand Down Expand Up @@ -1053,8 +1119,14 @@
"androidx.tracing:tracing:aar",
"androidx.versionedparcelable:versionedparcelable:aar"
],
"https://oss.sonatype.org/content/repositories/snapshots/": [
"com.google.guava:guava"
],
"https://repo1.maven.org/maven2/": [
"com.google.errorprone:error_prone_annotations",
"com.google.guava:failureaccess",
"com.google.guava:listenablefuture",
"com.google.j2objc:j2objc-annotations",
"com.squareup.okhttp3:okhttp",
"com.squareup.okio:okio",
"com.squareup.okio:okio-jvm",
Expand All @@ -1066,7 +1138,8 @@
"org.jetbrains.kotlinx:kotlinx-coroutines-bom",
"org.jetbrains.kotlinx:kotlinx-coroutines-core",
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm",
"org.jetbrains:annotations"
"org.jetbrains:annotations",
"org.jspecify:jspecify"
]
},
"services": {
Expand Down