Skip to content

CI: publish the zenoh-flat-jni the snapshot was built against - #703

Merged
milyin merged 2 commits into
mainfrom
ci/self-sufficient-snapshot
Aug 12, 2026
Merged

CI: publish the zenoh-flat-jni the snapshot was built against#703
milyin merged 2 commits into
mainfrom
ci/self-sufficient-snapshot

Conversation

@milyin

@milyin milyin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The failure

The snapshot publication has never got past compiling. gradle.properties names
org.eclipse.zenoh:zenoh-flat-jni:1.9.0, and nothing of zenoh-flat-jni has
ever been published under that version — in any form:

Could not find org.eclipse.zenoh:zenoh-flat-jni:1.9.0

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 PR
does, 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 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

The publication publishes what it depends on. On main, on every merge, it
builds zenoh-flat-jni from the pinned commit, uploads it as
1.9.0-kotlin-SNAPSHOT, then builds and uploads the SDK against that.

  • self-sufficient — it uses that repository's source at a commit we choose,
    never an artifact its CI produced. If zenoh-flat-jni's CI were switched off
    entirely, this still works.
  • coherent — the dependency our POM names is the code we compiled against.
when what happens
pull request, or push to a branch build and test; nothing is published
merge to main if the pinned commit moved, publish our zenoh-flat-jni copy; then publish the SDK snapshot; then consume it as an outsider
release workflow unchanged; resolves a released zenoh-flat-jni from Maven Central

What changes

Second commit, CI: publish the zenoh-flat-jni the snapshot was built against:

  • 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.
    ci/scripts/flat-jni-copy.bash --self-test covers the parsers.
  • 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. 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.tryFrom returns a Result,
    and an inline value class is awkward to call across the Java boundary.
  • 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, CI: let a downstream SDK publish its own copy of this library 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-resolves the
    zenoh git dependencies to their branch tip, which is the lockfile-sync bot's
    job, so they are pinned back.

First commit, CI: pin third-party actions, fix the triggers, drop both nightlieseclipse-zenoh/zenoh-java#526 ported:

  • Every third-party action pinned to a commit, version in a trailing comment.
    Pinning 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 ["**"], so a backport gets CI. Not
    push on ["**"] 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. 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.

Verification

$ bash ci/scripts/flat-jni-copy.bash --self-test
flat-jni-copy.bash self-test OK

$ bash ci/scripts/flat-jni-copy.bash
zenoh-flat-jni:1.9.0-kotlin-SNAPSHOT stamp is none, want 6b5c04c8…
commit=6b5c04c8… base=1.9.0 rebuild=true

Nothing is published under that coordinate yet, so the first merge to main is
the 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.md gains What publishing uses,
PUBLISHING.md gains The snapshot publication and What it does not
guarantee
(including what the non-atomic pair of uploads does not promise),
and README.md says what the default zenohFlatJniVersion now means.

milyin added 2 commits August 12, 2026 02:52
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 milyin left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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-test passes.
  • The live decision check reports all three 1.9.0-kotlin-SNAPSHOT coordinates absent and rebuild=true for 6b5c04c, which is the safe bootstrap behavior.
  • The standalone Kotlin consumer build configures successfully with Gradle.
  • git diff --check passes.
  • 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.flatJniCommit stamp.

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)

@milyin
milyin merged commit 07cbcc3 into main Aug 12, 2026
8 checks passed
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.

1 participant