11# Releasing iroh-ffi
22
3- A release ships a new version of the iroh-ffi crate's surface to every
4- binding host (npm, PyPI, GitHub Releases for Swift) plus refreshed docs on
5- GitHub Pages. The tag is the trigger — pushing ` v<VERSION> ` fans out to all
6- four publish workflows independently.
3+ A release ships a new version of the iroh-ffi crate's surface to every binding
4+ host (npm, PyPI, Maven Central, GitHub Releases for Swift) plus refreshed docs
5+ on GitHub Pages. The tag is the trigger — pushing ` v<VERSION> ` fans out to all
6+ publish workflows independently.
77
8- The repo manifest stays ** self-consistent at every commit** : the version
9- literals in ` Cargo.toml ` , ` iroh-js/package.json ` , and ` Package.swift ` (tag +
10- checksum) are written by a local script before the release PR is opened. CI
11- never writes to ` main ` .
8+ The repo manifest stays self-consistent at every commit. Version literals in
9+ ` Cargo.toml ` , ` iroh-js/package.json ` , etc. are written by a local script
10+ before the release PR is opened; the Swift xcframework SHA-256 in
11+ ` Package.swift ` is written by PR CI (one bot commit on the release branch).
12+ CI never writes to ` main ` .
1213
1314## Prerequisites
1415
15- - macOS workstation with Xcode (the prepare-release script builds the Apple
16- xcframework to compute the SwiftPM checksum ).
16+ - Local shell with ` cargo ` , ` yarn ` , and Python 3 (no Xcode needed —
17+ prepare-release no longer builds the xcframework ).
1718- Push rights on the repo + the relevant publisher accounts (npm
1819 ` @number0/iroh ` ; PyPI trusted publisher for ` iroh ` ; Sonatype Central for
19- ` computer.iroh ` once Maven publishing is wired ).
20+ ` computer.iroh ` ).
2021- ` main ` is green at the commit you want to release from.
2122
2223## Steps
2324
24- 0 . ** Local dry-run** (recommended). Validates every artifact builds without
25- touching any registry:
25+ 0 . ** Local dry-run** (recommended, macOS only — needs Xcode for the swift slot).
26+ Validates every artifact builds without touching any registry:
2627
2728 ``` sh
2829 # the armored private key (same contents as the SIGNING_KEY CI secret)
@@ -31,37 +32,47 @@ never writes to `main`.
3132 cargo make pre-release-check
3233 ```
3334
34- Builds the Swift xcframework + deterministic zip, the Python wheel,
35- the napi addon + ` npm publish --dry-run ` , and `gradle
36- publishToMavenLocal`. Catches config typos, signing failures, manifest
37- errors before the real publish.
35+ Builds the Swift xcframework (sanity check only — the published zip
36+ comes from PR CI, not here), the Python wheel + import smoke, the napi
37+ addon + ` npm publish --dry-run ` , and ` gradle publishToMavenLocal ` +
38+ JNA-from-per-platform-staged smoke. Catches config typos, signing
39+ failures, manifest errors before the real publish.
3840
39- 1 . ** Prepare the release branch + commit .**
41+ 1 . ** Bump versions on a release branch .**
4042
4143 ``` sh
4244 git checkout main && git pull
4345 cargo make prepare-release 1.0.0-rc.1 # no leading v
44- ```
45-
46- This rewrites the version literals, refreshes lockfiles, builds a
47- deterministic ` IrohLib.xcframework.zip ` , and bakes its SHA-256 into
48- ` Package.swift ` . Inspect the diff (` git diff ` ) — expected files only:
49- ` Cargo.toml ` , ` Cargo.lock ` , ` iroh-js/package.json ` , ` iroh-js/yarn.lock ` ,
50- ` Package.swift ` .
51-
52- 2 . ** Open the release PR.**
53-
54- ``` sh
5546 git checkout -b release/v1.0.0-rc.1
5647 git commit -am " chore(release): v1.0.0-rc.1"
5748 git push -u origin release/v1.0.0-rc.1
5849 gh pr create --base main --fill --title " chore(release): v1.0.0-rc.1"
5950 ```
6051
61- Get all CI green on the PR.
52+ ` prepare-release ` only rewrites version literals + lockfiles. Inspect
53+ the diff (` git diff ` ) — expected files only: ` Cargo.toml ` , ` Cargo.lock ` ,
54+ ` iroh-js/package.json ` + ` iroh-js/Cargo.toml ` , ` iroh-js/yarn.lock ` ,
55+ ` pyproject.toml ` , ` kotlin/lib/build.gradle.kts ` , ` Package.swift `
56+ (releaseTag only — the checksum is filled in by CI in step 2).
6257
63- 3 . ** Merge to ` main ` .** Squash or merge-commit — both work; whichever lands
64- keeps Package.swift consistent.
58+ 2 . ** Wait for PR CI to bake the Swift xcframework SHA.**
59+
60+ ` release_swift.yml ` fires on push to ` release/v* ` branches. It builds
61+ the xcframework on a self-hosted macOS runner, runs the verify gates
62+ (layout + iOS sim test), zips deterministically, and:
63+ - commits `chore(release): bake Swift xcframework SHA <hex >
64+ [ skip swift-release] ` to the release branch (overwrites the
65+ placeholder checksum in ` Package.swift ` );
66+ - uploads ` IrohLib.xcframework.zip ` to a draft GitHub release named
67+ ` v1.0.0-rc.1 ` .
68+
69+ Review the bot's commit + the draft release asset (downloadable from
70+ the GitHub Releases page) before merging. The SHA in ` Package.swift `
71+ is exactly the SHA of the zip on the draft release — no cross-host
72+ determinism game.
73+
74+ 3 . ** Merge to ` main ` .** Squash or merge-commit; both keep ` Package.swift `
75+ consistent with the draft release.
6576
66774 . ** Tag the merge commit and push.**
6778
@@ -75,32 +86,43 @@ never writes to `main`.
7586
7687 | Workflow | Publishes |
7788 | ---| ---|
78- | ` release.yml ` | GitHub Release + per-OS binary assets + ` IrohLib.xcframework.zip ` |
79- | ` ci_js.yml ` ` publish ` | ` @number0/iroh ` to npm (` --provenance ` , ` NPM_TOKEN ` ) |
80- | ` wheels.yml ` ` publish ` | ` iroh ` wheels to PyPI (Trusted Publishing / OIDC — no token) |
89+ | ` release.yml ` | promotes the draft GH release v<version > to published; uploads per-OS C lib archives |
90+ | ` ci_js.yml ` ` publish ` | ` @number0/iroh ` to npm (` --provenance ` , OIDC) |
91+ | ` wheels.yml ` ` publish ` | ` iroh ` wheels to PyPI (OIDC) |
92+ | ` release.yml ` ` build-and-publish-kotlin ` | ` computer.iroh:iroh ` to Maven Central |
8193 | ` docs.yml ` | GitHub Pages site refresh |
82- | _ (future)_ ` release.yml ` ` build-and-publish-kotlin ` | Maven Central ` computer.iroh:iroh ` (blocked on #147 ) |
8394
84- If any publish fails, fix and re-tag with the next patch version (do not
85- re-use a tag).
95+ The Swift xcframework zip is ** not** rebuilt at tag time — ` release.yml `
96+ ` create-release ` just promotes the PR-CI-built draft.
97+
98+ If any publish fails, fix and re-tag with the next patch version (do
99+ not re-use a tag).
86100
87- ## Why local-only prepare
101+ ## Why PR-CI builds the Swift xcframework (not local, not tag-CI)
88102
89103Binary SwiftPM packages need the manifest checksum to match the uploaded
90- zip byte-for-byte. We use a deterministic zip recipe
91- (` scripts/release/zip_xcframework.sh ` : fixed mtimes + sorted file list + no
92- zip extra fields) so the local zip and the one rebuilt by ` release.yml `
93- hash identically. This lets ` Package.swift ` carry the correct checksum
94- from the release commit forward — no CI write-back to ` main ` , no
95- intermediate broken state. Matches the firebase-ios-sdk / Sentry-Cocoa
96- release pattern for binary SwiftPM packages.
104+ zip byte-for-byte. The previous flow built the xcframework locally during
105+ ` prepare-release ` , baked the SHA into ` Package.swift ` , then rebuilt on
106+ tag-CI and expected the SHA to still match — which required cross-host
107+ build determinism (matching Xcode patch version, macOS SDK, rustc, cargo
108+ deps, etc.). That kept failing in practice.
109+
110+ Single source of truth: PR CI builds the zip once, bakes its SHA into the
111+ PR, uploads to a draft release. Tag CI just promotes the draft. The SHA
112+ in ` Package.swift ` is the SHA of the bytes on the release — they were
113+ literally the same zip. No build-environment skew can break consumers.
114+
115+ The trade-off is that the release branch picks up one bot commit
116+ (` bake Swift xcframework SHA <hex> ` ), which is reviewable in the PR diff.
117+ ` [skip swift-release] ` in the commit message prevents the workflow from
118+ re-firing on its own output.
97119
98120## One-time publisher setup (per host)
99121
100- - ** npm** — ` NPM_TOKEN ` repo secret with publish rights on ` @number0/iroh ` .
122+ - ** npm** — Trusted Publishing configured at npmjs.com (no token) .
101123- ** PyPI** — register a trusted publisher at
102124 < https://pypi.org/manage/account/publishing/ > : project ` iroh ` , owner
103- ` n0-computer ` , repository ` iroh-ffi ` , workflow ` wheels.yml ` . No long-lived
104- token needed. Until registered, the first publish job will fail loudly.
105- - ** Maven Central ** _ (future, blocked on # 147 ) _ — claim ` computer.iroh `
106- namespace via DNS TXT on ` iroh.computer ` ; GPG signing key; OSSRH creds.
125+ ` n0-computer ` , repository ` iroh-ffi ` , workflow ` wheels.yml ` .
126+ - ** Maven Central ** — ` computer.iroh ` namespace claimed via DNS TXT on
127+ ` iroh.computer ` ; armored PGP key in ` SIGNING_KEY ` secret; Sonatype
128+ Central API creds.
0 commit comments