CI: publish a snapshot from main, same rule as the other JVM repos - #36
Merged
Conversation
diogomatsubara
suggested changes
Aug 11, 2026
zenoh-java and zenoh-kotlin have always published their snapshot from main only - gated on refs/heads/main, on every merge there plus the weekday 06:00 nightly, never from a branch or a pull request. This repository has no publication in CI at all: publish.yml is workflow_call, and release.yml is the only caller, so the only builds that ever reached Central are two hand-dispatched rehearsals (1.9.0-rc4, 1.9.0-rc8) under versions nothing tracks. Apply the same rule here. <version.txt>-SNAPSHOT from main, through the same publish.yml a rehearsal runs, so the upload path stays exercised between releases and a consumer has something to resolve before the first release exists. One mutable coordinate set, rewritten in place.
milyin
force-pushed
the
ci/snapshot-publish
branch
from
August 11, 2026 12:46
81ee85a to
f523eba
Compare
11 tasks
…t-cache Three things the review asked for, all of them the rule the rest of the org already follows: - A `ci` aggregate job, as in zenoh-java. Branch protection lists required checks by job name, and three of the four jobs are 3-OS matrices, so without it the list has to enumerate nine names that change whenever the matrix does. The snapshot publication now gates on that one job instead of on four. - Third-party actions pinned to a commit, version in a trailing comment, as in zenoh-pico#1281. A tag is mutable; a moved tag runs code nobody reviewed. eclipse-zenoh/* actions are ours and stay on a branch. This also picked up four majors that had gone stale - setup-java v3/v4 and setup-gradle v3 were on retired Node runtimes. setup-gradle stops at v5: v6 moved caching into a proprietary component under Gradle's own terms of use, which is not ours to accept for an Eclipse project. - No toolchain action. rust-toolchain.toml pins the channel, the runner ships rustup, and `rustup show` installs what the file says - which is what publish.yml already did. The workflow named 1.97.1 a second time, a pin that could disagree with the file; the components move into the file with it. - swatinem/rust-cache in place of three hand-rolled actions/cache blocks per job: keyed on the toolchain and the lockfile, and it prunes what a plain `path: target` cache grows forever.
The triggers follow the main zenoh repository, as the review asked, with one deliberate difference and one addition. - `release/*` on push. A release is built from a branch the shared create-release-branch action creates, and CI never ran on it. The dry-run branches that same action produces are excluded. - `pull_request` on every branch, so a backport against a release branch gets CI too. Not `push` on every branch: with pull_request already on, that runs the nine-job matrix twice per branch. - The weekday nightly stays, which zenoh has no need of. This repository tracks zenoh, zenoh-ext and zenoh-flat by git *branch*, so a nightly build is the only thing that catches one of them moving under us between merges. The publication rides along on a trigger that has to exist anyway. - `concurrency`, which zenoh has and this repository did not. Cancelling reaches a snapshot publication in flight, and that is safe: no build resolves the coordinate this publishes, so a cancelled upload leaves an artifact nobody consumes, in a mutable repository with no staging state to unwind. That last point is the one the comments got wrong, so they are rewritten here. Downstream SDKs publish their own copy of this library from the commit each one pins - they do not resolve ours, and their CI does not wait on it. What this publication is for is the upload path itself: signing, credentials and what Central accepts are otherwise exercised only when someone dispatches a release by hand, which makes a release the moment a break in them is discovered.
The previous commit justified the schedule by saying a nightly build is what
catches zenoh, zenoh-ext or zenoh-flat moving under us, since all three are
`branch = "main"` dependencies. That is wrong. Cargo.lock pins each to a commit:
source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd…"
and Cargo re-resolves a git dependency only on `cargo update` or a missing lock
entry, so a timed build rebuilds exactly what the last merge built. Upstream
drift arrives here as a lockfile-sync pull request, which runs CI like anything
else.
What is left is an expired Central token or GPG key, caught only in a week with
no merges at all - every merge already exercises them. That is a thin canary
against five ten-target publications a week, so the trigger goes. Dropping it
also makes the trigger block exactly the one the review asked for, with no
difference left to defend. workflow_dispatch still runs the path on demand, and
a weekly schedule would buy the canary at a fifth of the cost if it is ever
wanted.
PUBLISHING.md records the reasoning, because "why does this repository not have
the nightly the other JVM repositories have" is a question that will be asked
again.
milyin
commented
Aug 11, 2026
milyin
left a comment
Contributor
Author
There was a problem hiding this comment.
@diogomatsubara Thank you for review! All changes applied
#37 is now stacked on this branch rather than merging before it, so the PUBLISHING.md link to "Downstream copies of the snapshot" and the ci.yml comment naming it both pointed at something that would not exist on main until #37 lands. The statement they support - that the SDKs publish their own copies and do not resolve ours - stands on its own, so it stays and the pointer goes. #37 adds the link back in the commit that adds the section.
milyin
force-pushed
the
ci/snapshot-publish
branch
from
August 11, 2026 22:25
49108b3 to
99caec1
Compare
milyin
added a commit
that referenced
this pull request
Aug 11, 2026
Stacked on #36, which pins every third-party action in the three workflows, this branch arrives with one exception: the checkout in the `resolve-source` job it introduces, which did not exist on #36's base and so could not be pinned there. Same SHA and trailing-comment format as the other seventeen. The PUBLISHING.md pointer to "Downstream copies of the snapshot" comes back here too. #36 carries the sentence without it, because the section it names is the one this branch adds.
This was referenced Aug 11, 2026
"The other JVM repositories in the org publish their snapshot on a weekday 06:00 schedule" was true when written and is not any more: zenoh-java#526 drops its nightly on exactly the argument this section makes - its Cargo.lock pins the zenoh-flat-jni commit, so a timed build there rebuilds what the last merge built too. zenoh-kotlin is the one that is left, so name it, and record that the reasoning travelled rather than being a local peculiarity.
milyin
added a commit
that referenced
this pull request
Aug 11, 2026
* CI: publish a snapshot from main, same rule as the other JVM repos
zenoh-java and zenoh-kotlin have always published their snapshot from main
only - gated on refs/heads/main, on every merge there plus the weekday 06:00
nightly, never from a branch or a pull request. This repository has no
publication in CI at all: publish.yml is workflow_call, and release.yml is the
only caller, so the only builds that ever reached Central are two hand-dispatched
rehearsals (1.9.0-rc4, 1.9.0-rc8) under versions nothing tracks.
Apply the same rule here. <version.txt>-SNAPSHOT from main, through the same
publish.yml a rehearsal runs, so the upload path stays exercised between
releases and a consumer has something to resolve before the first release
exists. One mutable coordinate set, rewritten in place.
* CI: review follow-ups - status-check job, pinned actions, rustup, rust-cache
Three things the review asked for, all of them the rule the rest of the org
already follows:
- A `ci` aggregate job, as in zenoh-java. Branch protection lists required
checks by job name, and three of the four jobs are 3-OS matrices, so without
it the list has to enumerate nine names that change whenever the matrix does.
The snapshot publication now gates on that one job instead of on four.
- Third-party actions pinned to a commit, version in a trailing comment, as in
zenoh-pico#1281. A tag is mutable; a moved tag runs code nobody reviewed.
eclipse-zenoh/* actions are ours and stay on a branch. This also picked up
four majors that had gone stale - setup-java v3/v4 and setup-gradle v3 were
on retired Node runtimes. setup-gradle stops at v5: v6 moved caching into a
proprietary component under Gradle's own terms of use, which is not ours to
accept for an Eclipse project.
- No toolchain action. rust-toolchain.toml pins the channel, the runner ships
rustup, and `rustup show` installs what the file says - which is what
publish.yml already did. The workflow named 1.97.1 a second time, a pin that
could disagree with the file; the components move into the file with it.
- swatinem/rust-cache in place of three hand-rolled actions/cache blocks per
job: keyed on the toolchain and the lockfile, and it prunes what a plain
`path: target` cache grows forever.
* CI: triggers and concurrency, and what the snapshot is actually for
The triggers follow the main zenoh repository, as the review asked, with one
deliberate difference and one addition.
- `release/*` on push. A release is built from a branch the shared
create-release-branch action creates, and CI never ran on it. The dry-run
branches that same action produces are excluded.
- `pull_request` on every branch, so a backport against a release branch gets
CI too. Not `push` on every branch: with pull_request already on, that runs
the nine-job matrix twice per branch.
- The weekday nightly stays, which zenoh has no need of. This repository tracks
zenoh, zenoh-ext and zenoh-flat by git *branch*, so a nightly build is the
only thing that catches one of them moving under us between merges. The
publication rides along on a trigger that has to exist anyway.
- `concurrency`, which zenoh has and this repository did not. Cancelling
reaches a snapshot publication in flight, and that is safe: no build resolves
the coordinate this publishes, so a cancelled upload leaves an artifact
nobody consumes, in a mutable repository with no staging state to unwind.
That last point is the one the comments got wrong, so they are rewritten here.
Downstream SDKs publish their own copy of this library from the commit each one
pins - they do not resolve ours, and their CI does not wait on it. What this
publication is for is the upload path itself: signing, credentials and what
Central accepts are otherwise exercised only when someone dispatches a release
by hand, which makes a release the moment a break in them is discovered.
* CI: drop the nightly, and correct the reason given for it
The previous commit justified the schedule by saying a nightly build is what
catches zenoh, zenoh-ext or zenoh-flat moving under us, since all three are
`branch = "main"` dependencies. That is wrong. Cargo.lock pins each to a commit:
source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#773126fd…"
and Cargo re-resolves a git dependency only on `cargo update` or a missing lock
entry, so a timed build rebuilds exactly what the last merge built. Upstream
drift arrives here as a lockfile-sync pull request, which runs CI like anything
else.
What is left is an expired Central token or GPG key, caught only in a week with
no merges at all - every merge already exercises them. That is a thin canary
against five ten-target publications a week, so the trigger goes. Dropping it
also makes the trigger block exactly the one the review asked for, with no
difference left to defend. workflow_dispatch still runs the path on demand, and
a weekly schedule would buy the canary at a fifth of the cost if it is ever
wanted.
PUBLISHING.md records the reasoning, because "why does this repository not have
the nightly the other JVM repositories have" is a question that will be asked
again.
* docs: do not point forward to a section this branch does not add
#37 is now stacked on this branch rather than merging before it, so the
PUBLISHING.md link to "Downstream copies of the snapshot" and the ci.yml
comment naming it both pointed at something that would not exist on main until
#37 lands. The statement they support - that the SDKs publish their own copies
and do not resolve ours - stands on its own, so it stays and the pointer goes.
#37 adds the link back in the commit that adds the section.
* CI: let a downstream SDK publish its own copy of this library
A downstream snapshot cannot depend on this repository's CI having run, and
its dependency has to be the commit it compiled against. Both follow if the
SDK builds and publishes its own copy from the commit it pins — which needs
three things this workflow did not have.
- `source-repository`: a called reusable workflow runs with the caller's
context, so the unqualified checkouts fetched the *caller's* repository.
Defaults to `github.repository`, so a fork still builds its own sources.
- `version-qualifier`: inserted before `-SNAPSHOT`, so the copies do not
overwrite each other or ours. Rejected for a non-snapshot: releases come
from here only.
- `zenoh.flatJniCommit` in every POM, from `git rev-parse HEAD`, so a
publisher can tell whether the published copy is already current without
downloading a 39 MB jar. Absent outside a git checkout, which reads as
unknown and makes a comparing consumer rebuild rather than reuse.
Verified locally: `generatePomFileFor{Jvm,KotlinMultiplatform}Publication
-PSNAPSHOT -PversionQualifier=java` produce 1.9.0-java-SNAPSHOT carrying the
commit property, and the same without -PSNAPSHOT fails the guard.
* CI: resolve the source once, and keep the qualifier out of the shell
Review of #37.
- **Mixed-commit artifacts.** `branch` accepts a branch name and every job
resolved it independently, over a workflow that runs about half an hour: `main`
moving mid-run would package native libraries from one commit into an artifact
whose POM is stamped with another, and a downstream stamp check would read that
as current and reuse a JNI-incompatible build. `resolve-source` now resolves it
to a commit once — `git ls-remote` for a branch or tag, as-is for a SHA, the
caller's own commit when nothing is given — and every checkout takes that.
- **The downstream pin has to be new enough.** The qualifier and the stamp are
produced by the *checked-out* `build.gradle.kts`, so a caller pinning an older
commit would publish an unqualified, unstamped artifact and then fail resolving
a coordinate nobody uploaded. Checked before anything is built, with a message
saying what to do.
- **Injection.** `version-qualifier` is caller input and was interpolated into
script text, unquoted. It now travels by environment variable, is validated
against `^[a-z0-9]+(-[a-z0-9]+)*$` before any job builds, and is expanded as a
single array element rather than spliced into a command line.
Also resolves the conflict with main, keeping #34/#35's `shell: bash` and
`-PprebuiltAndroidLibs=true` on the dry-run publish.
* CI: judge the whole request before spending half an hour on it
Re-review of #37.
- **`git ls-remote <name>` matches by ref pattern**, so a tag name could resolve
to `refs/heads/release/dry-run/<name>` instead. `resolve-source` now checks out
with the same `actions/checkout` every job below uses and reports `git rev-parse
HEAD`, so the ref resolves exactly once and exactly the way it would have there.
Shorter than the ls-remote branch it replaces, and it subsumes the second
checkout the capability check needed.
- **A qualifier with `snapshot: false`** was rejected by build.gradle.kts, but not
until the consumer test configured — after six desktop targets and the Android
build. Rejected in the preflight now, with an explicit `branch`, which a
qualified copy also needs: without one the checkout takes the default branch,
which is the moving target pinning was meant to avoid.
- **`expected-base-version`**: the coordinate comes from the checked-out
version.txt while the caller names a fixed version of its own. Let the pin move
past a version bump here and the run publishes 1.10.0-java-SNAPSHOT to an SDK
still resolving 1.9.0-java-SNAPSHOT, failing only after the upload. The caller
passes what it expects and the mismatch is caught in the preflight.
* CI: no caller string in script text, and no optional half of a check
Re-review of #37.
- `branch` was interpolated into the preflight's own `[[ -n … ]]`, and git
permits a branch name with quotes and metacharacters — `git check-ref-format
'refs/heads/x";id;#'` succeeds, and this step runs before the ref has to exist.
It goes through the environment now, like the qualifier. So does the qualifier
in the version-mismatch message, which had the same shape.
- `expected-base-version` was compared only when non-empty, so a qualified caller
that omitted it recreated exactly the drift the input was added to catch. It is
now required whenever a qualifier is given, and the comparison is
unconditional.
* CI: pin the action this branch adds, and link the section it adds
Stacked on #36, which pins every third-party action in the three workflows, this
branch arrives with one exception: the checkout in the `resolve-source` job it
introduces, which did not exist on #36's base and so could not be pinned there.
Same SHA and trailing-comment format as the other seventeen.
The PUBLISHING.md pointer to "Downstream copies of the snapshot" comes back here
too. #36 carries the sentence without it, because the section it names is the
one this branch adds.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is missing
This repository has no publication in CI at all.
publish.ymlisworkflow_call,release.ymlis its only caller, and that isworkflow_dispatch— so the only builds that ever reached Central are twohand-dispatched rehearsals, under versions nothing tracks:
So the upload path — signing, credentials, and what Central accepts — is only
ever exercised when someone runs a release by hand. A break in it is discovered
during a release, which is the worst moment to discover it.
What this PR does
Publishes
<version.txt>-SNAPSHOTfrommainthrough the samepublish.ymlarehearsal runs (
snapshot: true→ mutable snapshot repository, no staging, noclose/release):
Three mutable coordinates, rewritten in place, so storage does not grow with
merge rate. This is the repository's own upload test — that is the whole
argument for it. It moves the discovery of a broken publication path to the
merge that caused it.
The rule
zenoh-javaandzenoh-kotlinhave published their snapshot the same way sincelong before the flat transition — gated to
main, on every merge there; a branchor a pull request never publishes. This PR applies that rule here.
Without their nightly, though. The obvious argument for one is that
zenoh,zenoh-extandzenoh-flatarebranch = "main"dependencies, so a timedrebuild would catch one of them moving. It would not:
Cargo.lockpins each to acommit, and Cargo re-resolves a git dependency only on
cargo update, so a timedbuild rebuilds exactly what the last merge built. Upstream drift arrives as a
lockfile-sync PR, which runs CI like anything else. What is left is an expired
Central token or GPG key, caught only in a week with no merges at all — a thin
canary against five ten-target publications a week.
PUBLISHING.mdrecords this,since "why is there no nightly here" will be asked again.
Update: that reasoning holds one repository further down.
eclipse-zenoh/zenoh-java#526 drops zenoh-java's weekday nightly for the same
reason — its
Cargo.lockpins thezenoh-flat-jnicommit, so a timed buildthere rebuilds what the last merge built too. So "without their nightly" is now
"without zenoh-kotlin's": zenoh-kotlin is the only JVM repository still on a
schedule, and it is the same candidate for the same argument. The
PUBLISHING.mdsection this PR adds is updated to say so.Publication is gated on the
cistatus-check job, matching how the downstreamrepositories gate theirs.
Relationship to #37
No SDK consumes this coordinate, and none should. #37 is what makes that
true: each SDK builds and publishes its own copy of this library from the commit
its
Cargo.lockpins, as1.9.0-java-SNAPSHOT/1.9.0-kotlin-SNAPSHOT. TheirCI does not wait on ours, and their snapshot depends on the commit it actually
compiled against rather than on whatever
mainheld at the time.That leaves this PR standing on the upload test alone, which is what the sections
above now say — an earlier revision of this description led with "a consumer has
nothing to resolve", and #37 is the real fix for that.
#37 is stacked on this branch — its base is
ci/snapshot-publish, so thisone merges first and GitHub retargets it to
mainautomatically. That orderingalso finishes the action pinning: #37's
resolve-sourcejob adds a checkout thatdoes not exist on this branch, so it pins that one itself and
publish.ymlreaches
mainwith nothing unpinned.Review follow-ups included
@diogomatsubara's review, applied — all of it in a second commit so the delta is
readable:
cistatus-check job, as in zenoh-java. Branch protection lists requiredchecks by job name, and three of the four jobs are 3-OS matrices, so without it
the list has to enumerate nine names that change whenever the matrix does. The
publication now gates on that one job.
pushon["main", "release/*", "!release/dry-run/*"]— a release is built from abranch
create-release-branchmakes, and CI never ran on it — andpull_requeston["**"], so a backport against a release branch gets CI. Notpushon["**"]: withpull_requestalready on, that runs the nine-jobmatrix twice per branch. Plus
concurrency, which zenoh has and thisrepository did not; cancelling reaches a publication in flight, which is safe
precisely because nothing consumes the coordinate.
comment, as in zenoh-pico#1281, across all three workflows.
eclipse-zenoh/*actions are ours and stay on a branch. This also picked upfour stale majors —
setup-javav3/v4 andsetup-gradlev3 were on retiredNode runtimes.
setup-gradlestops at v5: v6 moved caching into aproprietary component under Gradle's own terms of use, which is not ours to
accept for an Eclipse project.
rust-toolchain.tomlpins the channel, the runnerships rustup, and
rustup showinstalls what the file says — which is whatpublish.ymlalready did. The workflow named1.97.1a second time, a pinthat could disagree with the file; the components move into the file with it.
swatinem/rust-cachein place of nine hand-rolledactions/cacheblocks:keyed on the toolchain and the lockfile, and it prunes what a plain
path: targetcache grows forever.ci.ymlcomes out at −104/+66.