Skip to content

Add support for pinning snapshots in gradle resolver#1412

Open
acecilia wants to merge 2 commits into
bazel-contrib:masterfrom
acecilia:3-snapshot_support
Open

Add support for pinning snapshots in gradle resolver#1412
acecilia wants to merge 2 commits into
bazel-contrib:masterfrom
acecilia:3-snapshot_support

Conversation

@acecilia

@acecilia acecilia commented Jul 11, 2025

Copy link
Copy Markdown

Hi 👋

This PR adds support for snapshot dependencies in the gradle resolver. It uses the repository timestamp of the snapshot as a version. I added this information in a new field with a generic name VersionRevision.

Related: #974

@acecilia
acecilia force-pushed the 3-snapshot_support branch from d2b0d98 to 7a13a1d Compare July 11, 2025 02:21
@acecilia
acecilia marked this pull request as ready for review July 11, 2025 02:48
@acecilia
acecilia requested review from cheister, jin and shs96c as code owners July 11, 2025 02:48
Comment thread tests/custom_maven_install/regression_testing_gradle_install.json Outdated
@shs96c

shs96c commented Jul 11, 2025

Copy link
Copy Markdown
Collaborator

Thank you for the PR. I think there's a couple of things that I think about as I read this:

  1. Snapshots aren't always stored with a version. Sometimes, the -SNAPSHOT.jar is just silently replaced
  2. We should have a consistent story for handling snapshots throughout the ruleset

You are correct that some maven repos do write snapshots versions like this, and that's what makes things tricky. I suspect that the correct fix will be to:

  1. Identify if a dep is a snapshot in some way. I think a boolean field is enough for that, and we can add it only for snapshots. For versioned snapshots, we might not need to do this, as I think they get stored in maven repos as the url expansions expect.
  2. When we create the lockfile, omit the sha256 of any snapshot jar that doesn't have a fixed version (likely still contains SNAPSHOT.jar in the file name). That means that if the version is updated in place, builds won't break unexpectedly.

What do you think?

@acecilia

acecilia commented Jul 11, 2025

Copy link
Copy Markdown
Author

Thanks for the feedback. Let me ask for some more context to understand a bit better your thoughts 😄

Snapshots aren't always stored with a version. Sometimes, the -SNAPSHOT.jar is just silently replaced

By "version", do you mean "timestamp"? If yes, is there maybe a public snapshot you could share that does not have timestamp, that I could use during development and to add tests?

We should have a consistent story for handling snapshots throughout the ruleset

What do you mean here, that snapshots should be handled by all resolvers? If yes, I agree. I was planning to look into the other resolvers after merging this PR. Is that okey, or would you prefer to do it differently?

When we create the lockfile, omit the sha256 of any snapshot jar that doesn't have a fixed version (likely still contains SNAPSHOT.jar in the file name). That means that if the version is updated in place, builds won't break unexpectedly.

Let me expand a bit on my comment here. How would this work in bazel?

  • Bazel uses http_file rule in here to download the artifacts. That rule has a sha256 property that is optional, but the docs mention that It is a security risk to omit the SHA-256 as remote files can change. At best omitting this field will make your build non-hermetic - we would be accepting these risks when adding support for such kind of snapshots
  • For http_file rules without sha256, bazel would keep the downloaded artifacts in the cache, and will never try to update them. How do you envision that someone would trigger a snapshot update in this case? My understanding is that gradle caches snapshots but still fetches them once a day - which is not the case for bazel
  • My current understanding is that time-stamping snapshots is the default behaviour for maven repositories. I acknowledge that when searching in google "maven snapshots without timestamp" there are some ways to do it mentioned (example here and here), but I also observed that those mentions are usually +10 years old. Furthermore, when trying to find more recent resources about how to do this, I found this from 3 years ago that mentions that Support for uniqueVersion was deprecated in Maven 2.x, and removed entirely in Maven 3, which is confirmed by maven documentation in here.

Integrating snapshots without timestamps goes against bazel fundamentals + it is deprecated from maven 3. This is what makes me question wether this ruleset wants/needs to support it. Wdyt?

@acecilia
acecilia force-pushed the 3-snapshot_support branch from 1f1457a to e16bd7b Compare July 11, 2025 13:17
@acecilia

Copy link
Copy Markdown
Author

@shs96c let me know 🙏

@acecilia
acecilia force-pushed the 3-snapshot_support branch from e16bd7b to 72ff1e8 Compare July 24, 2025 22:19
@shs96c

shs96c commented Jul 25, 2025

Copy link
Copy Markdown
Collaborator

I know this is a long reply, but please know that I'm generally supportive of the idea of allowing snapshots to be used, but I'm very wary about how that support should be added. I'm really happy to work with you to find a solution that works for everyone.

@acecilia, an example of where the SNAPSHOT suffix is kept and no date-specific version is generated is anything stored on the sonatype OSS snapshot library. Selenium is an example https://oss.sonatype.org/#view-repositories;snapshots~browsestorage~org/seleniumhq/selenium/selenium-java (you may need to use the "path lookup" to search for org/seleniumhq/selenium/selenium-java) Every time a new snapshot is released, the new version overwrites the old one, meaning that the sha256 can't ever be trusted.

My other concern is that snapshot servers seldom keep an extensive history of snapshots; they get removed fairly swiftly. In one company I worked for, the lifetime of a snapshot was at most 30 days. Using snapshots necessarily means that we've given up on historical builds of our project. That's not an objection to providing support for them, but it's something we should call out in the docs.

We should have a consistent story for handling snapshots throughout the ruleset

It would be nice if the resolvers all supported snapshots, but the main thing is that we should be able to handle both kinds of snapshots (the ephemeral dated ones and the ones that are being replaced) with similar logic. The only safe way to do that is to set the checksum to None.

Bazel uses http_file rule in here to download the artifacts. That rule has a sha256 property that is optional, but the docs mention that It is a security risk to omit the SHA-256 as remote files can change. At best omitting this field will make your build non-hermetic - we would be accepting these risks when adding support for such kind of snapshots

Yes. This is true.

Having said that, there are notable rulesets in the bazel world that already omit the shasum check by necessity (for example, when rules_go fetches the list of Go SDKs) Apparently it's something the community is happy with.

For http_file rules without sha256, bazel would keep the downloaded artifacts in the cache, and will never try to update them. How do you envision that someone would trigger a snapshot update in this case? My understanding is that gradle caches snapshots but still fetches them once a day - which is not the case for bazel

I believe that Bazel will refetch the resource every time the daemon is started. It should be sufficient to do a bazel shutdown to force a reload of the artifact.

My current understanding is that time-stamping snapshots is the default behaviour for maven repositories.

Sadly this isn't the case in the wild. Many OSS projects have ephemeral snapshots that are replaced with each new version, and from experience I know that there are corporate servers that work the same way.

Integrating snapshots without timestamps goes against bazel fundamentals + it is deprecated from maven 3. This is what makes me question wether this ruleset wants/needs to support it. Wdyt?

Given that we recently had a request to support java 8, I don't think everyone will migrate to maven 3 in a hurry. If we're going to support snapshots, we need to support all of them.

So far, I've never implemented snapshot support for these reasons:

  1. "Replaced" snapshots (without a date identifier) cannot be supported with a shasum with bazel as-is.
    1. As you point out, this breaks the guarantees that Bazel makes for you
    2. This also means that you can be sure you can repeat a build. Even the same commit of a repo could end up with a different dependency
  2. Versioned snapshots (with a date identifier) are frequently removed from snapshot repos.
    1. Historical builds become far harder to do
  3. Modifying each of our resolvers to properly mark snapshots is a challenge
    1. We may be able to apply heuristics to make this a problem we solve in starlark when locking

The inability to rely on a build is a real problem for me, but I acknowledge that people who deliberately use snapshots accept this risk, so that's not a blocking issue for me.

Of these reasons, I think we have a path forward for the first ("just" don't set the checksum), the second is a quality of life thing users of snapshots will have to accept, and you're making a start on the third. I think we can figure this out :)

@acecilia
acecilia force-pushed the 3-snapshot_support branch from 72ff1e8 to d049174 Compare August 8, 2025 21:00
@acecilia

acecilia commented Aug 8, 2025

Copy link
Copy Markdown
Author

Got it thanks for that detailed answer, I was not aware non-version snapshots were still so widely used 🙏

Updated the PR and added support for non-versioned snapshots in gradle resolver

@acecilia
acecilia force-pushed the 3-snapshot_support branch from e4b6af6 to d95d82b Compare August 8, 2025 22:43
Comment thread tests/bazel_run_tests.sh Outdated
@acecilia
acecilia force-pushed the 3-snapshot_support branch from ffdb54e to 213df86 Compare August 8, 2025 23:37
@acecilia

acecilia commented Aug 9, 2025

Copy link
Copy Markdown
Author

For some reason the PR is failing when validating with bazel 6.4.0. I spent some time but I dont manage to understand why, any help is much appreciated

@acecilia

Copy link
Copy Markdown
Author

@smocherla-brex would appreciate your feedback here 🙏 Thanks!

Comment thread private/rules/coursier.bzl Outdated
Comment thread private/rules/v2_lock_file.bzl Outdated
@acecilia
acecilia force-pushed the 3-snapshot_support branch 2 times, most recently from ee6f9c0 to 7b9e605 Compare August 30, 2025 22:11
@acecilia

acecilia commented Sep 5, 2025

Copy link
Copy Markdown
Author

I am keen to complete this PR with your guidance, have a look when you have a chance please 🙏

Comment thread tests/custom_maven_install/regression_testing_gradle_install.json Outdated
Comment thread MODULE.bazel Outdated
@shs96c

shs96c commented Oct 6, 2025

Copy link
Copy Markdown
Collaborator

Apologies for the long delay getting back to you. I've been traveling and sick. I want to review the version_revision as it's making me uneasy, and I'd like to be satisfied it's required.

@shs96c
shs96c force-pushed the 3-snapshot_support branch from 4a04e54 to ed26238 Compare October 7, 2025 13:01
@acecilia
acecilia force-pushed the 3-snapshot_support branch 6 times, most recently from 1fd3886 to 9fef226 Compare June 27, 2026 19:05
// POM-only result rather than null so the caller can still use the POM.
if (hadPomOnlyKnownPath) {
return new DownloadResult(coordsToUse, Set.of(), null, null);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Check the commit message in c1d50b0 for an explanation of this change (is fixing an existing bug introduced in the last master commit)

@acecilia

Copy link
Copy Markdown
Author

Ready for another review round

"artifacts": {
"com.example:snapshot": {
"shasums": {
"jar": null

@acecilia acecilia Jun 27, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here is how a non-timestamped snapshot dependency shows: with the shasum set to null

@acecilia
acecilia force-pushed the 3-snapshot_support branch 3 times, most recently from 308026e to 50422f3 Compare July 5, 2026 02:27
"shasums": {
"jar": "9f71d9a756937d784cd175af7e3a1992f088321435b880af02488003da4c43b2"
},
"version": "999.0.0-HEAD-jre-20260701.164618-384"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here is how a timestamped snapshot dependency shows: with the timestamp as part of the version

@acecilia
acecilia force-pushed the 3-snapshot_support branch from 50422f3 to 370e322 Compare July 5, 2026 03:05
@acecilia
acecilia force-pushed the 3-snapshot_support branch from 370e322 to d556007 Compare July 20, 2026 16:49
@acecilia

Copy link
Copy Markdown
Author

@shs96c Would appreciate a review when you have a chance

@acecilia
acecilia force-pushed the 3-snapshot_support branch from d556007 to 62149ec Compare July 21, 2026 23:37
acecilia added 2 commits July 24, 2026 22:28
When Gradle resolves a Kotlin Multiplatform (KMP) module like
com.squareup.okio:okio, the JVM variant has no jar (the actual jar
lives in a separate okio-jvm module). The only file Gradle provides
for the root okio coordinate is a .pom file.

Commit 723d203 added an isPomPathForNonPomCoordinates check in
Downloader.performDownload that short-circuits when the known path is
a POM for a non-POM coordinate, returning DownloadResult with null
sha256 without trying to download the actual jar from the repos.

This caused okio to get null sha256 in the regenerated lockfile,
making it a java_library instead of jvm_import.

723d203 passed CI because the test at that time only checked that the
okio target exists (via plain 'bazel query'), not its rule kind. With
null sha256, okio is java_library but still exists, so the test passed.
This branch tightened the test to use 'bazel query --output=label_kind'
and expect 'jvm_import rule', which exposed the null sha256 bug.

The fix: instead of returning immediately, skip the POM known-path
and fall through to the download loop so the real jar can be fetched
from the remote repos. If the jar download succeeds, its sha256 is
computed normally. If it fails (e.g. jar genuinely missing), return
the POM-only result (null sha256) as before.

Also adds a new test verifying that when the jar IS available on the
repo, the downloader fetches it and returns a real sha256.
@acecilia
acecilia force-pushed the 3-snapshot_support branch from 62149ec to 02ef750 Compare July 24, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants