Skip to content

Release preparation: repair the release path and document publishing - #514

Open
milyin wants to merge 6 commits into
zenoh-flat-transitionfrom
release-preparation
Open

Release preparation: repair the release path and document publishing#514
milyin wants to merge 6 commits into
zenoh-flat-transitionfrom
release-preparation

Conversation

@milyin

@milyin milyin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Part of #482. The release path on zenoh-flat-transition is dead, not merely dated — every job would fail on the first step — because it still operates on the zenoh-jni crate that was removed when the SDK moved onto the generated bindings.

What was broken

ci/scripts/bump-and-tag.bash writes to zenoh-jni/Cargo.toml — gone, so the tag job fails immediately
publish-jvm.yml cross-builds zenoh-jni/Cargo.toml across six targets — gone
publish-android.yml installs four Rust Android targets — nothing to build
settings.gradle.kts includeBuild("../zenoh-flat-jni") unconditionally

That last one is the dangerous one. A release built with the composite active is
built against whatever sits on the builder's disk, while the POM claims the
resolved version.

What this changes

  • The composite build is opt-in: -PuseLocalFlatJni=true, off by default. CI
    passes it explicitly because it checks the sibling out; a release does not, so
    it resolves from Maven Central like any consumer.
  • The dependency version moves to gradle.properties (zenohFlatJniVersion),
    so the release can bump it and a rehearsal can point at a snapshot without
    editing tracked source.
  • bump-and-tag.bash is rewritten for a repository with no Cargo manifest: it
    bumps version.txt and that property, and refuses a -SNAPSHOT binding
    version outright
    .
  • zenoh-version becomes zenoh-flat-jni-version — the only upstream version
    this repository now chooses.
  • Both publish workflows drop their native toolchains, along with the
    rust-android-gradle plugin that was declared but never applied.

Why a release may not depend on a snapshot

Not a Sonatype validation rule — I checked Central's requirements page and it does
not say so. The binding reasons are simpler:

  • consumers do not have the Central snapshot repository configured, so the
    dependency fails to resolve for them;
  • snapshots are mutable and eventually removed.

So the ordering zenoh-flat-jnizenoh-java is a constraint, and the bump
script enforces it rather than trusting the operator.

PUBLISHING.md

Mirrors zenoh-flat-jni's guide in structure, links to it for the concepts it
already explains once (coordinates, staging, signing), and covers what is
specific here — above all how to rehearse before zenoh-flat-jni is released,
which is the question the transition keeps raising:

Rehearsal resolves zenoh-flat-jni from proves
local sibling checkout, -PuseLocalFlatJni=true code and tests
CI, no publication snapshot repo or sibling the artifact assembles
CI, snapshot publication zenoh-flat-jni:<v>-SNAPSHOT signing, credentials, a real upload
live release zenoh-flat-jni:<v> on Central blocked until it exists

A snapshot may depend on a snapshot, so only the live release is blocked.

Verified

  • Without the flag the build fails to resolve zenoh-flat-jni:1.9.0 — correct,
    since it is not on Central; with -PuseLocalFlatJni=true it compiles.
  • bump-and-tag.bash run against a scratch repository: rejects
    FLAT_JNI_VERSION=1.9.1-SNAPSHOT, and otherwise rewrites version.txt and
    gradle.properties in two separate commits.
  • All four workflow files parse; every internal anchor in the new document
    resolves.

Not verified, and stated in Known gaps: the repaired release path has never
run, there is no consumer test for the published artifact, and the Android
ndkVersion and NDK setup step are retained without confirming AGP still needs
them.

milyin added 3 commits August 9, 2026 22:16
The release path on this branch was dead, not merely dated. bump-and-tag
wrote to zenoh-jni/Cargo.toml, publish-jvm cross-built it and
publish-android installed Rust Android targets - and zenoh-jni/ was
removed when the SDK moved onto the generated bindings. The first job of
a release would have failed.

- settings.gradle.kts included ../zenoh-flat-jni unconditionally, so a
  release would have been built against whatever sat on the builder's
  disk. Now opt-in via -PuseLocalFlatJni=true; CI passes it explicitly
  because it checks the sibling out, and a release does not.
- The dependency version moves to gradle.properties, so the release can
  bump it and a rehearsal can point at a snapshot without editing
  tracked source.
- bump-and-tag bumps version.txt and that property, and refuses a
  -SNAPSHOT dependency outright: consumers do not configure the snapshot
  repository, and snapshots mutate and expire, so a release that
  depended on one would be broken for everyone but us.
- The zenoh-version input becomes zenoh-flat-jni-version, which is the
  only upstream version this repository now chooses.
- Both publish workflows drop their native toolchains; the libraries
  arrive inside the zenoh-flat-jni artifact. The unapplied
  rust-android-gradle plugin goes with them.

Verified: the build fails to resolve without the flag (as a release
should), succeeds with it, and the bump script rejects a snapshot and
rewrites both files otherwise.
Mirrors zenoh-flat-jni's guide in structure and intent, but the pipeline
here is much smaller and the document says why: this repository builds
no native code, so there is no matrix, no cross-compilation and no
artifact to inspect - the libraries arrive inside the zenoh-flat-jni
dependency.

The concepts that guide already explains once - coordinates, staging,
signing - are linked rather than restated.

What is specific here is the ordering constraint, which is the question
the transition keeps raising: zenoh-java cannot be released until the
zenoh-flat-jni version it depends on is really on Central, because
consumers do not configure the snapshot repository and snapshots mutate
and expire. Rehearsals have no such constraint, and the document gives
the ladder - local composite, snapshot dependency, snapshot publication
- with what each one actually proves.

Known gaps are stated rather than implied: the repaired release path has
never run, there is no consumer test, and the Android NDK configuration
is retained unverified.
The guide told the reader to add the snapshot repository 'for the
duration of a rehearsal' - editing tracked source, which is the thing
the rest of this change removes. That was not a procedure, it was a
gap.

The Central snapshot repository is now declared conditionally: present
only when zenohFlatJniVersion ends in -SNAPSHOT, and content-filtered to
that single module. A rehearsal names the version on the command line
and nothing is edited; a release version never ends in -SNAPSHOT, so a
release build cannot reach a mutable artifact even if someone tried. The
guarantee is structural rather than procedural.

Verified against the snapshot the zenoh-flat-jni rehearsal actually
published:

  -PzenohFlatJniVersion=1.9.0-rc4-SNAPSHOT  ->  112 tests pass, no
                                                composite build
  default (1.9.0)                           ->  unresolvable, and the
                                                snapshot repository is
                                                absent from the searched
                                                locations
@milyin

milyin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Re-reviewed the complete PR at 50cea09884d202b8723eba67e2ed31cb919c3187.

The new conditional snapshot repository does fix direct snapshot consumption: dependencyInsight resolved zenoh-flat-jni:1.9.0-rc4-SNAPSHOT, and the full jvmTest suite passed against it (112 tests). I still found the following release blockers/correctness issues.

  1. [P1] The release-workflow snapshot rehearsal documented by this PR is still rejected before Gradle runs. PUBLISHING.md says to pass the snapshot as zenoh-flat-jni-version, and the workflow forwards that value as FLAT_JNI_VERSION, but the bump script rejects every *-SNAPSHOT value, including when LIVE_RUN=false. I reproduced this with the exact script in a scratch repository; it exits with error: refusing to release against a snapshot dependency. The new repository configuration therefore helps manual Gradle commands but not the advertised CI rehearsal. Please either apply this rejection only to live releases or carry a rehearsal-only Gradle override without committing it.

  2. [P1] The Android publication depends on the desktop JNI artifact, so Android consumers do not receive the Android native libraries. The dependency is declared once in commonMain as org.eclipse.zenoh:zenoh-flat-jni. I generated androidRelease's POM at this head and it contains that same desktop coordinate. However, zenoh-flat-jni publishes the Android AAR—with jni/<abi>/libzenoh_flat_jni.so—under the separate org.eclipse.zenoh:zenoh-flat-jni-android coordinate. This also makes PUBLISHING.md's claim that one dependency contains both the six desktop and four Android binaries factually incorrect. The platform publications need platform-specific dependencies; once fixed, the conditional snapshot repository must also allow zenoh-flat-jni-android.

  3. [P1] Supplying the already-configured flat-JNI version aborts a real release. The script always runs git commit gradle.properties after sed. When the input equals the existing property (currently 1.9.0), there is no diff, git commit exits 1, and set -e terminates the release before tagging/publishing. I reproduced this independently with FLAT_JNI_VERSION=1.9.0. Please commit only when the file actually changed; a no-op version input should be valid.

  4. [P2] A rehearsal with maven_publish=false does not assemble or verify an artifact. In both reusable workflows, the only Gradle invocation is guarded by inputs.maven_publish == true (JVM, Android). With the box unchecked, the jobs merely check out/setup tools and then succeed, contrary to the table saying this proves that the artifact assembles. Add an unconditional build/publication-generation verification step and guard only the remote upload.

  5. [P2] JVM and Android are closed and released from independent jobs/staging repositories. release.yml starts the two publications independently, and each invokes its publication task together with closeAndReleaseSonatypeStagingRepository. A live run can therefore make one coordinate public while the other job fails; the pair is not released atomically. Both publication tasks exist in an Android-enabled Gradle invocation, so they can be uploaded together and followed by one close/release, as the linked zenoh-flat-jni process already does.

  6. [P2] The README still tells users/builders the removed native-build story and obsolete coordinates. The new note says natives are no longer built here, but the immediately following JVM section says publishing compiles Zenoh JNI locally; the Android section still requires NDK/Rust targets and says Gradle builds the Android native binaries; and its dependency example names zenoh-kotlin-android. The import examples also use the old zenoh-java-jvm coordinate while this PR documents/publishes zenoh-java. Since this PR explicitly repairs and documents the post-extraction build, these adjacent instructions should be updated or clearly marked as applying only to old releases.

Additional checks: the generated JVM and Android POM tasks passed, all workflow YAML parses, and git diff --check is clean. At the time of review, both Ubuntu CI builds and markdown checks had passed; the two macOS builds were still pending.

— Codex (GPT-5)

milyin added 2 commits August 9, 2026 23:03
Three defects the review found in the release script and workflows, all
of which would have stopped a release or a rehearsal outright.

The snapshot rejection was unconditional, so the rehearsal this PR
documents - pass the snapshot as zenoh-flat-jni-version - was refused
before Gradle ran. It now applies only to live runs, which is where the
reasoning actually holds: a release may not depend on a snapshot, a
rehearsal is how the SDK is exercised before the binding exists at all.

git commit on an unchanged gradle.properties exits non-zero, so
supplying the version already configured aborted the release under
set -e before tagging. It commits only when the file moved.

Neither publish workflow ran Gradle at all unless maven_publish was on,
so an unchecked rehearsal proved nothing while the guide claimed it
proved the artifact assembles. Both now assemble and generate the POM
unconditionally; only the upload is gated.

The guide also claimed one binding artifact carries both desktop and
Android natives. It does not - they are separate coordinates, and the
Android publication currently depends on the desktop one, so Android
consumers get no jni/<abi>/ libraries. Recorded as a defect with the
release blocked on it rather than described as working.
The README described the build that was removed: publishing 'triggers
the compilation of Zenoh-JNI in release', the Android section required
an NDK and four Rust targets, and a warning said the result was tied to
the builder's rustup target. None of that is true now - the libraries
arrive inside the zenoh-flat-jni artifacts, cross-compiled elsewhere,
which is precisely why the result is *not* machine-specific.

Also fixes coordinates that were wrong independently of this work: the
Android dependency example named zenoh-kotlin-android in the zenoh-java
README, and the JVM examples used zenoh-java-jvm, which is not what this
repository publishes.
@milyin

milyin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

All six confirmed. Four fixed here; two are real defects I have recorded as release blockers rather than half-fixed, with reasons below.

Fixed

[P1] The snapshot rejection blocked the rehearsal this PR documents. Exactly right, and self-contradictory — I wrote both the guard and the instruction to pass a snapshot as zenoh-flat-jni-version. The guard now applies only when live_run is true, which is where the reasoning holds: a release may not depend on a snapshot, a rehearsal is how the SDK is exercised before the binding exists at all. Verified all three paths against a scratch repository:

rehearsal + snapshot   → note: rehearsing against snapshot 1.9.0-rc4-SNAPSHOT
live run  + snapshot   → error: refusing to release against a snapshot dependency
live run  + unchanged  → note: already building against zenoh-flat-jni 1.9.0

[P1] A no-op version input aborted the release. Correct — git commit on an unchanged file exits non-zero and set -e killed the run before tagging. It now commits only when the file moved; the third line above is that case.

[P2] maven_publish: false proved nothing. Correct, and the same defect this pipeline's sibling had. Both workflows now run publish…PublicationToMavenLocal unconditionally — assembling the artifact and generating the POM — with only the remote upload gated. Confirmed the task exists and succeeds locally.

[P2] The README described the removed build. All of it: "triggers the compilation of Zenoh-JNI in release", the NDK and four Rust targets, and the warning that the artifact is tied to the builder's rustup target — which is now the opposite of true. Also corrected coordinates that were wrong independently of this work: zenoh-kotlin-android in the zenoh-java README, and zenoh-java-jvm where this repository publishes zenoh-java.

Recorded as blockers, not fixed

[P1] The Android publication depends on the desktop binding. Confirmed, and worse than a documentation error — I checked the snapshot repository and org.eclipse.zenoh:zenoh-flat-jni-android is a distinct coordinate. The dependency sits in commonMain, so the Android POM inherits the desktop artifact and an Android consumer receives no jni/<abi>/ libraries.

I have not fixed it because it is not a one-line change: commonMain source references those classes, so moving the dependency into per-platform source sets is a Kotlin Multiplatform restructuring, and validating an Android publication needs an Android SDK I cannot exercise here. Guessing at it and declaring it done would be worse than saying so. Known gaps now states that no Android release may be cut before it is fixed, and the checklist has a line requiring the Android POM to reference zenoh-flat-jni-android.

The claim that one artifact carries both desktop and Android natives is removed from the guide, replaced with the two-coordinate table and the defect note.

[P2] JVM and Android are not released atomically. Also correct, and the same problem zenoh-flat-jni had — one coordinate can go public while the other job fails. The fix is the one that worked there: publish both from a single Gradle invocation into one staging repository, followed by one close/release. It is coupled to the item above, because it requires an Android-enabled build to produce both publications, so both are recorded together.

Neither is a regression from this PR; both predate it and are now visible instead of implied.

@milyin

milyin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Re-reviewed the changes through dd7df4a0f297efb69e1857a3940b6294578864f7.

The fixes for prior findings 1, 3, and 4 are correct. In isolated Git repositories with local-only remotes I verified that:

  • a rehearsal against 1.9.0-rc4-SNAPSHOT succeeds;
  • a live run against that snapshot is still rejected;
  • supplying the unchanged 1.9.0 dependency succeeds without an empty commit.

The new JVM publish…ToMavenLocal rehearsal step also succeeds against the snapshot and emits the expected JAR, sources JAR, POM, and Gradle metadata. All current PR CI checks pass, although the JVM/Android publication jobs are skipped on PRs.

The following issues remain.

  1. [P1] The Android artifact still has no Android JNI runtime. This was correctly acknowledged in PUBLISHING.md, but the implementation is unchanged: commonMain still supplies the desktop zenoh-flat-jni coordinate, so the Android POM still points at the desktop JAR rather than zenoh-flat-jni-android. Documenting the defect is useful, but it remains an operative blocker because every live release still launches the Android publication. The README also presents the intended state as current fact when it says the Android natives arrive transitively from zenoh-flat-jni-android; they currently do not.

  2. [P2] JVM and Android are still released from separate staging repositories. This is now accurately listed as a known gap, but release.yml still starts two independent publishing jobs, each with its own closeAndReleaseSonatypeStagingRepository. A live release can still make one coordinate public while the other fails. This remains unresolved rather than fixed.

  3. [P2] The published javadoc JAR is empty. javadocJar packages build/dokka/html, but dokkaGeneratePublicationJavadoc writes to build/dokka/javadoc (the same directory used by publish-dokka.yml). Running the newly added JVM Maven-local rehearsal logged that build/dokka/html does not exist, then published a zenoh-java-1.9.0-javadoc.jar containing only META-INF/MANIFEST.MF (25 bytes). Point the archive at the actual Dokka output and make it depend on the corresponding generation task; the rehearsal should inspect the archive so this cannot silently pass again.

  4. [P2] The README cleanup is incomplete and its copy-paste dependency examples do not resolve. It still says Rust is a general prerequisite, says the Android binding is built as an NDK library, says the Android publication compiles Zenoh JNI and contains the native binaries, and says jvmTest compiles a native library in debug mode. Those statements contradict the new text and the post-extraction build. In addition, every import/local-publication example still uses 1.1.1; both org.eclipse.zenoh:zenoh-java:1.1.1 and org.eclipse.zenoh:zenoh-java-android:1.1.1 return 404 from Maven Central, while this branch and the current published artifacts use 1.9.0. Please finish removing the native-build language and use a real version or a <version> placeholder.

Validation notes: the full PR passes git diff --check; all seven workflow files parse. JVM Maven-local publication passed. Android Maven-local publication could not be completed on this host because no Android SDK is installed; it fails at compileReleaseAidl with “SDK location not found,” and the PR CI does not exercise the publication workflows.

— Codex (GPT-5)

The Android POM named the desktop coordinate, so an Android consumer
received an artifact with six desktop libraries and no jni/<abi>/ - it
could not load anything. The build now selects zenoh-flat-jni-android
when -Pandroid=true and zenoh-flat-jni otherwise, and the conditional
snapshot repository serves both modules.

Verified by generating both POMs, which turns out not to need an Android
SDK even though building the AAR does:

  jvm/pom-default.xml            -> zenoh-flat-jni
  androidRelease/pom-default.xml -> zenoh-flat-jni-android

The selection is build-wide rather than per source set because
commonMain references the generated classes, and Kotlin Multiplatform
cannot see a dependency declared only in platform source sets. That is
also the reason the two publications cannot come from one Gradle
invocation, so the atomicity gap stays - now with its cause recorded and
the upstream fix named: variant-aware module metadata in zenoh-flat-jni
would collapse the two coordinates into one.

Also fixes the javadoc JAR, which shipped empty: it packaged
build/dokka/html, which Dokka never writes. Pointed at dokka/javadoc,
with a check that fails the build rather than publishing 25 bytes again
- now 780 KB across 172 files.

README: Rust is no longer a prerequisite, the Android section no longer
claims to build an NDK library, jvmTest no longer 'compiles the native
library in debug mode', and the examples use 1.9.0, which resolves,
instead of 1.1.1, which 404s.
@milyin

milyin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Three fixed in 40ef9c8, including the P1 — which turned out to be verifiable here after all. The fourth is now understood rather than merely listed.

[P1] The Android artifact now depends on the Android binding

You were right that documenting it was not enough, and right that a live release launches the Android publication regardless. Fixed:

val flatJniArtifact = if (androidEnabled) "zenoh-flat-jni-android" else "zenoh-flat-jni"
implementation("org.eclipse.zenoh:$flatJniArtifact:$zenohFlatJniVersion")

I had assumed this needed an Android SDK to validate. It does not — POM generation configures without one, even though building the AAR does not:

jvm/pom-default.xml             → zenoh-flat-jni
androidRelease/pom-default.xml  → zenoh-flat-jni-android

So both published POMs are now correct, and the conditional snapshot repository serves both modules. The README statement you flagged as aspirational is now simply true.

[P2] The javadoc JAR was empty — fixed

Verified exactly as you described: dokkaGeneratePublicationJavadoc writes build/dokka/javadoc, the archive packaged build/dokka/html, which Dokka never produces. Repointed, with dependsOn on the generating task, and a doFirst check that fails the build if the directory is missing or empty — publishing 25 bytes should not be something that passes quietly.

before: 25 bytes, META-INF/MANIFEST.MF only
after:  780,667 bytes, 172 files

[P2] README — finished

Rust removed from the prerequisites, the Android section no longer claims to build an NDK library or to contain the native binaries, and jvmTest no longer "compiles the native library on debug mode". Every example moves from 1.1.1 — which 404s — to 1.9.0, which I confirmed resolves for both zenoh-java and zenoh-java-android.

[P2] Atomicity — still open, and now with its cause

Not fixed, but no longer just an entry in a list. Fixing the Android dependency entrenches it, and it is worth being explicit about why: the two publications need different binding coordinates, and the choice is a build-wide flag rather than a per-source-set dependency — because commonMain references the generated classes, and Kotlin Multiplatform cannot see a dependency declared only in platform source sets. One Gradle invocation therefore cannot produce both publications correctly, which is exactly what zenoh-flat-jni's single-staging-repository fix requires.

The real fix is upstream: if zenoh-flat-jni published one coordinate with Gradle module metadata carrying jvm and android variants, this SDK would declare a single dependency, commonMain would resolve it per target, and both publications could come from one invocation — closing this gap and removing the flag at the same time. That is now written in Known gaps rather than left for the next reader to rediscover.

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