A release ships a new version of the iroh-ffi crate's surface to every binding
host (npm, PyPI, Maven Central, GitHub Releases for Swift) plus refreshed docs
on GitHub Pages. The tag is the trigger — pushing v<VERSION> fans out to all
publish workflows independently.
The repo manifest stays self-consistent at every commit. Version literals in
Cargo.toml, iroh-js/package.json, etc. are written by a local script
before the release PR is opened; the Swift xcframework SHA-256 in
Package.swift is written by PR CI (one bot commit on the release branch).
CI never writes to main.
- Local shell with
cargo,yarn, and Python 3 (no Xcode needed — prepare-release no longer builds the xcframework). - Push rights on the repo + the relevant publisher accounts (npm
@number0/iroh; PyPI trusted publisher foriroh; Sonatype Central forcomputer.iroh). mainis green at the commit you want to release from.
-
Local dry-run (recommended, macOS only — needs Xcode for the swift slot). Validates every artifact builds without touching any registry:
# the armored private key (same contents as the SIGNING_KEY CI secret) export ORG_GRADLE_PROJECT_signingInMemoryKey="$(cat ~/.keys/maven_key.sec.asc)" cargo make pre-release-check
Builds the Swift xcframework (sanity check only — the published zip comes from PR CI, not here), the Python wheel + import smoke, the napi addon +
npm publish --dry-run, andgradle publishToMavenLocal+ JNA-from-per-platform-staged smoke. Catches config typos, signing failures, manifest errors before the real publish. -
Bump versions on a release branch.
git checkout main && git pull cargo make prepare-release 1.0.0-rc.1 # no leading v git checkout -b release/v1.0.0-rc.1 git commit -am "chore(release): v1.0.0-rc.1" git push -u origin release/v1.0.0-rc.1 gh pr create --base main --fill --title "chore(release): v1.0.0-rc.1"
prepare-releaseonly rewrites version literals + lockfiles. Inspect the diff (git diff) — expected files only:Cargo.toml,Cargo.lock,iroh-js/package.json+iroh-js/Cargo.toml,iroh-js/yarn.lock,pyproject.toml,kotlin/lib/build.gradle.kts,Package.swift(releaseTag only — the checksum is filled in by CI in step 2). -
Wait for PR CI to bake the Swift xcframework SHA.
release_swift.ymlfires on push torelease/v*branches. It builds the xcframework on a self-hosted macOS runner, runs the verify gates (layout + iOS sim test), zips deterministically, and:- commits
chore(release): bake Swift xcframework SHA <hex> [skip swift-release]to the release branch (overwrites the placeholder checksum inPackage.swift); - uploads
IrohLib.xcframework.zipto a draft GitHub release namedv1.0.0-rc.1.
Review the bot's commit + the draft release asset (downloadable from the GitHub Releases page) before merging. The SHA in
Package.swiftis exactly the SHA of the zip on the draft release — no cross-host determinism game. - commits
-
Merge to
main. Squash or merge-commit; both keepPackage.swiftconsistent with the draft release. -
Tag the merge commit and push.
git checkout main && git pull git tag v1.0.0-rc.1 git push origin v1.0.0-rc.1 -
Watch the fan-out. The tag triggers:
Workflow Publishes release.ymlpromotes the draft GH release v to published; uploads per-OS C lib archives ci_js.ymlpublish@number0/irohto npm (--provenance, OIDC)wheels.ymlpublishirohwheels to PyPI (OIDC)release.ymlbuild-and-publish-kotlincomputer.iroh:iroh(JVM JAR) +computer.iroh:iroh-android(AAR) to Maven Central — one./gradlew publishAndReleaseToMavenCentralinvocation, both subprojects published in the same Sonatype stagingdocs.ymlGitHub Pages site refresh The Swift xcframework zip is not rebuilt at tag time —
release.ymlcreate-releasejust promotes the PR-CI-built draft.If any publish fails, fix and re-tag with the next patch version (do not re-use a tag).
The release publishes both computer.iroh:iroh (JVM JAR, for desktop
Java/Kotlin consumers — JNA-loaded desktop natives only) and
computer.iroh:iroh-android (AAR, for Android consumers — bundles
libiroh_ffi.so per ABI at jni/<abi>/ plus the IrohAndroid initializer
class). The AAR api-depends on the JAR, so an Android consumer that pulls
in iroh-android transitively gets the full Kotlin API surface.
Migrating an Android consumer from iroh → iroh-android: one-line
dependency swap. IrohAndroid moved from computer.iroh (in the JAR) to
computer.iroh.android … actually still computer.iroh (the package
didn't change, only which artifact ships the class). Imports stay
unchanged; only the Gradle coordinate moves:
// before
implementation("computer.iroh:iroh:<VERSION>")
// after (Android consumers — apps + library modules with
// com.android.application / com.android.library)
implementation("computer.iroh:iroh-android:<VERSION>")JVM consumers stay on computer.iroh:iroh and see a smaller JAR (no
Android natives bundled in).
Binary SwiftPM packages need the manifest checksum to match the uploaded
zip byte-for-byte. The previous flow built the xcframework locally during
prepare-release, baked the SHA into Package.swift, then rebuilt on
tag-CI and expected the SHA to still match — which required cross-host
build determinism (matching Xcode patch version, macOS SDK, rustc, cargo
deps, etc.). That kept failing in practice.
Single source of truth: PR CI builds the zip once, bakes its SHA into the
PR, uploads to a draft release. Tag CI just promotes the draft. The SHA
in Package.swift is the SHA of the bytes on the release — they were
literally the same zip. No build-environment skew can break consumers.
The trade-off is that the release branch picks up one bot commit
(bake Swift xcframework SHA <hex>), which is reviewable in the PR diff.
[skip swift-release] in the commit message prevents the workflow from
re-firing on its own output.
- npm — Trusted Publishing configured at npmjs.com (no token).
- PyPI — register a trusted publisher at
https://pypi.org/manage/account/publishing/: project
iroh, ownern0-computer, repositoryiroh-ffi, workflowwheels.yml. - Maven Central —
computer.irohnamespace claimed via DNS TXT oniroh.computer; armored PGP key inSIGNING_KEYsecret; Sonatype Central API creds.