Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 78 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ on:
env:
CARGO_TERM_COLOR: always

# A run on main publishes two coordinates from two separate uploads — our copy
# of zenoh-flat-jni, then this SDK naming it. Nothing makes that pair atomic, so
# runs are serialized rather than cancelled: cancelling a run mid-publication is
# exactly what leaves the two naming different commits. Per ref, so branches do
# not queue behind each other.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

# Third-party actions are pinned to a commit, with the version in a trailing
# comment: a tag is mutable, and a moved tag runs code nobody reviewed. Actions
# under eclipse-zenoh/ are ours and stay on a branch.
Expand Down Expand Up @@ -105,18 +114,81 @@ jobs:
- name: Check whether all jobs pass
run: echo '${{ toJson(needs) }}' | jq -e 'all(.result == "success")'

# Publish snapshot packages. Only triggered when CI runs on main.
# One job, because both publications now come from a single Gradle
# invocation — see .github/workflows/publish.yml.
publish_snapshot_package:
name: Publish snapshot package
# Everything below publishes, and only from main, on every merge there.
# Branches and pull requests publish nothing.
#
# The snapshot must be usable by someone who is not us, which means its
# zenoh-flat-jni dependency has to exist — and be the commit this SDK compiled
# against. It must also not wait on zenoh-flat-jni's CI. Both follow from one
# rule: this job publishes what it depends on. See CI.md.

# Which commit that is, and whether the copy already published is it.
flat_jni_pin:
name: Resolve the zenoh-flat-jni pin
if: contains(fromJSON('["refs/heads/main"]'), github.ref)
needs: ci
runs-on: ubuntu-latest
outputs:
commit: ${{ steps.pin.outputs.commit }}
base: ${{ steps.pin.outputs.base }}
qualifier: ${{ steps.pin.outputs.qualifier }}
rebuild: ${{ steps.pin.outputs.rebuild }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: pin
run: bash ci/scripts/flat-jni-copy.bash

# Our own copy, built from that commit by zenoh-flat-jni's own publication
# workflow — its cross-compilation matrix is six desktop targets and four
# Android ABIs, and duplicating it here is how the two would drift.
#
# The workflow file is taken from its main, reviewed like any dependency;
# `uses:` cannot hold an expression, so the pin cannot go there. What is built
# is `branch:`, and that is the whole of the coupling: this needs a *file* in
# that repository, never a run of its CI.
#
# Half an hour when it runs, so it runs only when the pin has moved.
publish_flat_jni_copy:
name: Publish our zenoh-flat-jni copy
needs: flat_jni_pin
if: needs.flat_jni_pin.outputs.rebuild == 'true'
uses: eclipse-zenoh/zenoh-flat-jni/.github/workflows/publish.yml@main
permissions:
contents: read
packages: write
with:
snapshot: true
# A called workflow runs with the caller's context, so it has to be told
# whose sources to check out.
source-repository: eclipse-zenoh/zenoh-flat-jni
branch: ${{ needs.flat_jni_pin.outputs.commit }}
version-qualifier: ${{ needs.flat_jni_pin.outputs.qualifier }}
# It derives the coordinate from its own version.txt; this is what we
# expect that to be, so a pin that moved past a version bump there fails
# before publishing something we cannot resolve.
expected-base-version: ${{ needs.flat_jni_pin.outputs.base }}
secrets: inherit

# Then the SDK snapshot, naming the copy above. Reached both ways: the copy
# was rebuilt, or it was already current and skipped.
publish_snapshot_package:
name: Publish snapshot package
needs: [flat_jni_pin, publish_flat_jni_copy]
if: >-
${{ !cancelled()
&& needs.flat_jni_pin.result == 'success'
&& needs.publish_flat_jni_copy.result != 'failure' }}
uses: ./.github/workflows/publish.yml
permissions:
contents: read
packages: write
with:
snapshot: true
branch: ${{ github.ref_name }}
# The commit that triggered this run, not `main`. Concurrency queues a
# newer run; it does not hold the branch still. Passing the branch name,
# this job would check out whatever `main` had become while the JNI copy
# was being built for half an hour — publishing SDK source B against the
# copy built from A's pin, which is the mismatch the whole job exists to
# prevent.
branch: ${{ github.sha }}
secrets: inherit
51 changes: 48 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
description: "If the publication is for a snapshot version."
default: false
branch:
description: Target branch
description: Branch, tag or commit to publish from
type: string
required: false
maven_publish:
Expand Down Expand Up @@ -57,8 +57,14 @@ jobs:
# Assembles the artifact and generates its POM without uploading, so a
# `maven_publish: false` rehearsal actually proves something. Only the
# remote upload below is gated.
#
# `--refresh-dependencies` because zenohFlatJniVersion is a snapshot that
# the job before this one may have just replaced. Gradle caches changing
# modules for 24 hours and setup-gradle restores that cache, so without it
# this could compile against yesterday's copy while publishing a POM that
# names the coordinate now holding today's.
- name: Assemble and verify the publication
run: ./gradlew publishJvmPublicationToMavenLocal publishAndroidReleasePublicationToMavenLocal --info -Pandroid=true
run: ./gradlew publishJvmPublicationToMavenLocal publishAndroidReleasePublicationToMavenLocal --info --refresh-dependencies -Pandroid=true

- name: Set pub mode env var
# Note: This step is intended to allow publishing snapshot packages.
Expand All @@ -77,7 +83,8 @@ jobs:
run: |
./gradlew publishJvmPublicationToSonatypeRepository \
publishAndroidReleasePublicationToSonatypeRepository \
${{ env.RELEASE }} --info -PremotePublication=true -Pandroid=true ${{ env.PUB_MODE }}
${{ env.RELEASE }} --info --refresh-dependencies \
-PremotePublication=true -Pandroid=true ${{ env.PUB_MODE }}
env:
CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME}}
CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }}
Expand All @@ -92,3 +99,41 @@ jobs:
with:
name: problem-reports-${{ github.job }}.zip
path: ${{ github.workspace }}/build/reports/problems/

# Everything above runs inside this repository's own build. What a user gets
# is the published coordinate and whatever it transitively pulls in — which is
# where a snapshot has failed before, by naming a zenoh-flat-jni that did not
# exist. So resolve it as an outsider would and run it.
#
# Snapshots only: a release goes to a staging repository and is not public at
# this point.
consumer_test:
name: Consume the published snapshot
needs: publish_package
if: ${{ inputs.snapshot == true && inputs.maven_publish == true }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.branch }}

- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: 11

- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2

# A separate Gradle build with no path or composite dependency on this
# one, resolving from the snapshot repository with `--refresh-dependencies`
# so a cached copy of the same coordinates cannot stand in for what was
# just uploaded.
- name: Resolve and run it
working-directory: ci/consumer-smoke-test
run: |
set -euo pipefail
version="$(tr -d '[:space:]' < "$GITHUB_WORKSPACE/version.txt")-SNAPSHOT"
echo "Consuming org.eclipse.zenoh:zenoh-java:$version" >> "$GITHUB_STEP_SUMMARY"
../../gradlew run --no-daemon --refresh-dependencies -PcandidateVersion="$version"
45 changes: 32 additions & 13 deletions CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ source yourself, [README.md](README.md#where-the-native-library-comes-from).
- [The pin](#the-pin)
- [Lockfile synchronization](#lockfile-synchronization)
- [Moving the pin by hand](#moving-the-pin-by-hand)
- [Publishing does not use any of this](#publishing-does-not-use-any-of-this)
- [What publishing uses](#what-publishing-uses)

## What CI runs

Expand Down Expand Up @@ -151,18 +151,37 @@ freezes resolution at a commit, so the sync can no longer move the pin and the
bot goes silent. (The other way to defeat it — committing the `path = "…"` form —
is covered above.)

## Publishing does not use any of this
## What publishing uses

A release resolves `org.eclipse.zenoh:zenoh-flat-jni:$zenohFlatJniVersion` from
Maven Central, like any other consumer. The pin, the lockfile and the composite
build play no part in it — they exist so that *testing* against unreleased
bindings is reproducible.
A **release** does not use any of this. It resolves
`org.eclipse.zenoh:zenoh-flat-jni:$zenohFlatJniVersion` from Maven Central like
any other consumer; the pin, the lockfile and the composite build play no part.
There, `zenohFlatJniVersion` says which **release** the SDK is published against
and `Cargo.lock` says which **commit** it is tested against, and moving one does
not move the other.

The two are deliberately independent: `zenohFlatJniVersion` in
`gradle.properties` says which **release** this SDK is built and published
against, `Cargo.lock` says which **commit** it is tested against, and moving one
does not move the other. A release must in fact avoid the composite build
entirely — the artifact would be built from source on the builder's disk while
the POM still claimed the released version — so `build.gradle.kts` fails any
`publish*` task while an included build is present. See
A **snapshot** makes them the same commit, by construction. It has to satisfy
two things at once — it must publish even if zenoh-flat-jni's CI has never run,
and the dependency it names must exist and be the code it compiled against — and
the only construction that does both is to publish what it depends on:

```text
Cargo.lock pin ──> build zenoh-flat-jni from that commit
publish it as 1.9.0-java-SNAPSHOT
|
v
build the SDK against that coordinate
publish zenoh-java:<version>-SNAPSHOT
```

So on `main`, the pin drives the publication as well as the tests, and
`gradle.properties` names our own copy rather than a zenoh-flat-jni release.
The mechanics — the qualifier, the commit stamp that decides whether the copy
needs rebuilding, what the pairing does and does not guarantee — are in
[PUBLISHING.md](PUBLISHING.md#the-snapshot-publication).

Either way a publication must avoid the composite build: the artifact would be
built from source on the builder's disk while the POM still claimed a resolved
version, so `build.gradle.kts` fails any `publish*` task while an included build
is present. See
[PUBLISHING.md](PUBLISHING.md#building-against-zenoh-flat-jni-source).
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading