CI: publish the zenoh-flat-jni the snapshot was built against - #703
Conversation
The same three changes eclipse-zenoh/zenoh-java#526 made, for the same reasons. - Every third-party action pinned to a commit, version in a trailing comment. A tag is mutable, and these workflows hold the signing key and the Central token. eclipse-zenoh/* actions are ours and stay on a branch. Pinning also picked up stale majors: setup-java v4 -> v5.7.0, setup-gradle v4 -> v5.0.2, upload-artifact v4 -> v7.0.1, actions-gh-pages v3 -> v4.1.0. 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. - Triggers follow the main zenoh repository. `push` on main and release branches - a release is built from a branch create-release-branch makes, and CI never ran on it - and `pull_request` on every branch, so a backport against a release branch gets CI. Not `push` on every branch as well: with pull_request on, that ran the whole matrix twice per branch. - Both nightlies go. ci.yml's would have rebuilt exactly what the last merge built, because Cargo.lock pins zenoh-flat-jni to a commit and Cargo re-resolves a git dependency only on `cargo update` - upstream drift arrives as a lockfile-sync pull request instead. release.yml's was worse than useless: a scheduled run passes no inputs, so every night it resolved the unreleased fallback in gradle.properties and died compiling. Releases and rehearsals are both deliberate acts, and both are `Run workflow`.
Closes the zenoh-kotlin half of eclipse-zenoh/zenoh-java#524, the same way eclipse-zenoh/zenoh-java#525 closes the other. gradle.properties named `zenoh-flat-jni:1.9.0`, a version that exists in no form, so the snapshot publication died in compileKotlinJvm every time it ran. Naming zenoh-flat-jni's own `1.9.0-SNAPSHOT` instead would fix the symptom and break two things: this repository's CI would wait on that repository's CI, and that coordinate always holds the tip of *its* main while this SDK compiles against the commit Cargo.lock pins. JNI being a binary contract, the mismatch surfaces as UnsatisfiedLinkError at runtime rather than as a build failure. The rule instead: **the publication publishes what it depends on.** On main, on every merge, build zenoh-flat-jni from the pinned commit, upload it as 1.9.0-kotlin-SNAPSHOT, then build and upload the SDK against that. - `flat_jni_pin` reads the pin from Cargo.lock and the commit stamp from all three published coordinates. Rebuilding is ten cross-compiled targets and about half an hour, and the pin moves roughly once a day, so it happens only when the published copy is not already that commit. Anything missing or unreadable reads as "not ours" and rebuilds - the safe direction. - `publish_flat_jni_copy` calls zenoh-flat-jni's own publication workflow rather than duplicating its six-target/four-ABI matrix, which is how the two would drift. - `-kotlin` keeps our copy from overwriting the one zenoh-flat-jni publishes or zenoh-java's; the three can legitimately pin different commits at once. The name is fixed, so it is overwritten rather than accumulated. - `--refresh-dependencies` on both SDK invocations. Gradle caches changing modules for 24 hours and setup-gradle restores that cache, so without it a run could upload copy B, compile against cached copy A, and publish a POM naming the coordinate that now resolves to B. - `ci/consumer-smoke-test` - a separate Gradle build with no path, project or composite connection to this one. It resolves the published snapshot from the snapshot repository and takes a key expression through JNI. Kotlin rather than Java because KeyExpr.tryFrom returns a Result, which an inline value class makes awkward to call from Java. - `concurrency` with cancel-in-progress false. The two uploads are not atomic, and cancelling a run mid-publication is exactly what leaves them naming different commits. - The release guard in bump-and-tag.bash now checks the value gradle.properties ends up with rather than the workflow input, because main inherits a snapshot and omitting the input is how a release would reach one. The pin moves to 6b5c04c, eclipse-zenoh/zenoh-flat-jni#37's merge commit: the earlier e75529c predates the version-qualifier inputs and the commit stamp this depends on, and that repository's preflight rejects it by design. Only that line of Cargo.lock moves - `cargo update -p` also re-resolved the zenoh git dependencies to their branch tip, which is the lockfile-sync bot's job, so they are pinned back.
milyin
left a comment
There was a problem hiding this comment.
Review of cb7be72: no findings.
The publication chain is coherent end to end: Cargo.lock pins zenoh-flat-jni's merged #37 commit (6b5c04c), the reusable workflow receives that immutable commit plus the kotlin qualifier and expected base version, and the SDK publication checks out the immutable triggering SHA. The copy validator matches the hardened zenoh-java implementation and requires each coordinate's POM, module metadata, and primary binary to belong to one snapshot build before trusting its commit stamp. The consumer build is isolated from the main/composite build and exercises the published Kotlin API through JNI.
Validation performed:
flat-jni-copy.bash --self-testpasses.- The live decision check reports all three
1.9.0-kotlin-SNAPSHOTcoordinates absent andrebuild=truefor6b5c04c, which is the safe bootstrap behavior. - The standalone Kotlin consumer build configures successfully with Gradle.
git diff --checkpasses.- The PR's Ubuntu/macOS builds, markdown lint, aggregate CI check, and ECA check are green.
- Third-party workflow actions are pinned; the pinned upstream commit exposes the called workflow inputs and emits both the qualifier and
zenoh.flatJniCommitstamp.
The only remaining validation boundary is inherent to the trigger design: publication jobs are intentionally skipped on pull requests, so the first merge to main will perform the initial ten-target copy publication and external consumer run. I do not see a change needed for that.
— Codex (GPT-5)
The failure
The snapshot publication has never got past compiling.
gradle.propertiesnamesorg.eclipse.zenoh:zenoh-flat-jni:1.9.0, and nothing ofzenoh-flat-jnihasever been published under that version — in any form:
This is the zenoh-kotlin half of eclipse-zenoh/zenoh-java#524. The other half is
eclipse-zenoh/zenoh-java#525, and this PR is that construction ported here,
together with the CI hygiene from eclipse-zenoh/zenoh-java#526.
It supersedes #702. Naming zenoh-flat-jni's own
1.9.0-SNAPSHOT, as that PRdoes, fixes the symptom and breaks two things: this repository's CI would wait on
that repository's CI, and that coordinate always holds the tip of its
mainwhile this SDK compiles against the commit
Cargo.lockpins. JNI being a binarycontract, the mismatch surfaces as
UnsatisfiedLinkErrorat runtime rather thanas a build failure.
The rule
The publication publishes what it depends on. On
main, on every merge, itbuilds
zenoh-flat-jnifrom the pinned commit, uploads it as1.9.0-kotlin-SNAPSHOT, then builds and uploads the SDK against that.never an artifact its CI produced. If zenoh-flat-jni's CI were switched off
entirely, this still works.
mainzenoh-flat-jnicopy; then publish the SDK snapshot; then consume it as an outsiderzenoh-flat-jnifrom Maven CentralWhat changes
Second commit,
CI: publish the zenoh-flat-jni the snapshot was built against:flat_jni_pinreads the pin fromCargo.lockand the commit stamp fromall three published coordinates. Rebuilding is ten cross-compiled targets and
about half an hour, and the pin moves roughly once a day, so it happens only
when the published copy is not already that commit. Anything missing or
unreadable reads as "not ours" and rebuilds — the safe direction.
ci/scripts/flat-jni-copy.bash --self-testcovers the parsers.publish_flat_jni_copycalls zenoh-flat-jni's own publication workflowrather than duplicating its six-target/four-ABI matrix, which is how the two
would drift.
-kotlinkeeps our copy from overwriting the one zenoh-flat-jni publishesor zenoh-java's; the three can legitimately pin different commits at once. The
name is fixed, so it is overwritten rather than accumulated.
--refresh-dependencieson both SDK invocations. Gradle caches changingmodules for 24 hours and
setup-gradlerestores that cache, so without it arun could upload copy B, compile against cached copy A, and publish a POM
naming the coordinate that now resolves to B.
ci/consumer-smoke-test— a separate Gradle build with no path, project orcomposite connection to this one. It resolves the published snapshot from the
snapshot repository and takes a key expression through JNI. It is the only
check that the whole chain works for someone who is not us. Written in Kotlin
rather than Java, unlike zenoh-java's:
KeyExpr.tryFromreturns aResult,and an inline value class is awkward to call across the Java boundary.
concurrencywithcancel-in-progress: false. The two uploads are notatomic, and cancelling a run mid-publication is exactly what leaves them naming
different commits.
bump-and-tag.bashnow checks the valuegradle.propertiesends up with rather than the workflow input, becausemaininherits a snapshot and omitting the input is how a release would reach one.
6b5c04c, CI: let a downstream SDK publish its own copy of this library zenoh-flat-jni#37's merge commit.The earlier
e75529cpredates theversion-qualifierinputs and the commitstamp this depends on, and that repository's preflight rejects it by design.
Only that line of
Cargo.lockmoves —cargo update -palso re-resolves thezenohgit dependencies to their branch tip, which is the lockfile-sync bot'sjob, so they are pinned back.
First commit,
CI: pin third-party actions, fix the triggers, drop both nightlies— eclipse-zenoh/zenoh-java#526 ported:Pinning picked up stale majors:
setup-javav4 → v5.7.0,setup-gradlev4 → v5.0.2,
upload-artifactv4 → v7.0.1,actions-gh-pagesv3 → v4.1.0.setup-gradlestops at v5: v6 moved caching into a proprietary componentunder Gradle's own terms of use, which is not ours to accept for an Eclipse
project.
pushonmainand releasebranches — a release is built from a branch
create-release-branchmakes, andCI never ran on it — and
pull_requeston["**"], so a backport gets CI. Notpushon["**"]as well: withpull_requeston, that ran the whole matrixtwice per branch.
ci.yml's would have rebuilt exactly what the lastmerge built, because
Cargo.lockpinszenoh-flat-jnito a commit and Cargore-resolves a git dependency only on
cargo update.release.yml's was worsethan useless: a scheduled run passes no inputs, so every night it resolved the
unreleased fallback in
gradle.propertiesand died compiling.Verification
Nothing is published under that coordinate yet, so the first merge to
mainisthe expensive one: the pin check finds no stamp, and the full ten-target matrix
runs before the SDK is published and consumed. It bootstraps itself — nothing
needs publishing by hand first.
Documentation is updated alongside:
CI.mdgains What publishing uses,PUBLISHING.mdgains The snapshot publication and What it does notguarantee (including what the non-atomic pair of uploads does not promise),
and
README.mdsays what the defaultzenohFlatJniVersionnow means.