Skip to content

fix: release artifacts and verify them more #253

Merged
dignifiedquire merged 30 commits into
mainfrom
ci/verify-release-artifacts
Jul 8, 2026
Merged

fix: release artifacts and verify them more #253
dignifiedquire merged 30 commits into
mainfrom
ci/verify-release-artifacts

Conversation

@dignifiedquire

@dignifiedquire dignifiedquire commented Jun 22, 2026

Copy link
Copy Markdown
Contributor
  • improve checks for kotlin and swift
  • use newer swift/xcode code structure, in line with macos guidelines

Consumes #251, #246

Closes #247

@n0bot n0bot Bot added this to iroh Jun 22, 2026
@github-project-automation github-project-automation Bot moved this to 🚑 Needs Triage in iroh Jun 22, 2026
@dignifiedquire dignifiedquire moved this from 🚑 Needs Triage to 🏗 In progress in iroh Jun 22, 2026
@dignifiedquire dignifiedquire self-assigned this Jun 22, 2026
@dignifiedquire dignifiedquire changed the title [WIP] fix release artifacts and verify them more fix: release artifacts and verify them more Jun 25, 2026
@dignifiedquire dignifiedquire marked this pull request as ready for review June 25, 2026 08:16
Comment thread kotlin/android-smoke/gradlew Outdated
Comment thread .github/workflows/ci_kotlin.yml Outdated
Comment thread .github/workflows/release_swift.yml
Comment thread kotlin/android-smoke/gradle/wrapper/gradle-wrapper.jar Outdated
Comment thread scripts/release/bump_version.py Outdated
Comment thread make_swift.sh
Comment thread Makefile.toml
dignifiedquire and others added 16 commits June 25, 2026 14:01
…nnels

Pre-publish verification, two layers per channel:
- Layout checks (cheap, deterministic) — assert artifact shape.
- Consumer smokes (real loads) — exercise the binding from a consumer
  perspective (iOS xcodebuild, Android emulator JNI, JNA from per-platform
  staged dir, fresh-venv wheel install).

Wired into release.yml as publish gates and into pre-release-check for local
runs. Catches the iroh-ffi#246 (missing platform slice) and #247 (wrong
bundle layout) bug classes, plus 'lib in jar but won't load' regressions.
The release only cross-built desktop cdylibs (linux/macos/windows), so
computer.iroh:iroh shipped no libiroh_ffi.so for Android. Consumer APKs
built fine but crashed at runtime with UnsatisfiedLinkError the moment
IrohAndroid runs System.loadLibrary("iroh_ffi").

Add a build-kotlin-android job that cross-builds all four ABIs with
cargo-ndk, and stage them into the jar under lib/<abi>/, where AGP merges
lib/<abi>/*.so from JAR dependencies into the consumer APK.

Verified locally: AGP merges lib/<abi>/*.so from a plain jar into the
APK, and a real arm64-v8a build loads on an emulator — the hello-iroh-ffi
Android demo binds an endpoint and renders its id ("Ready") instead of
crashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… smoke

- ci_swift.yml: run verify-swift-xcframework + verify-swift-consumer after
  test-swift. The new verify-swift-consumer boots an iOS Simulator and runs
  IrohLibTests on the iOS runtime (not just a build smoke), catching link
  errors + iOS startup regressions that the macOS-only test misses.

- ci_kotlin.yml: new kotlin-android-consumer-smoke job on ubuntu-latest with
  KVM-backed emulator. Mirrors release.yml's tag-only verify gate so every PR
  exercises the full Android consumer path (AGP merge from JAR → APK →
  emulator boot → JNI call), not just at release time.
New kotlin-jvm-consumer-smoke matrix in ci_kotlin.yml runs on linux x86_64,
linux aarch64, macOS arm64, windows x86_64 — one runner per JNA platform
the Maven jar ships. Each builds its host's release cdylib, stages it
under the per-platform subdir, hides the dev-mode flat lib, and runs the
gradle test suite. Catches 'JNA-platform name wrong' / 'loads only via
flat fallback' on every PR for every shipped host.

verify-kotlin-consumer now uses script_runner="bash" with a Windows case
branch (uname returns MINGW64_NT-* in Git Bash) and a path-separator
normalization for cargo metadata's backslash output on Windows. cp+mkdir
replaces install -D -m644 to avoid relying on coreutils install on Git
Bash (where its presence is busybox-implementation-dependent).
davidB/rust-cargo-make@v1 silently fails on linux-aarch64 self-hosted
runners (downloads x86_64 binary only). taiki-e/install-action uses
cargo-binstall to fetch the right precompiled binary for the host arch.
Without it, cargo-make on Windows writes the script to a .cmd file and
runs it through cmd.exe, which choked on the bash case statement
(esac literal token).
…, detect iOS SDK)

Three orthogonal CI fixes for jobs added in this PR:

- Windows JVM smoke: cargo-make's script_extension=sh didn't switch the
  shell on Windows; cmd.exe still parsed the bash script and choked on
  'case'. Extract to scripts/verify_kotlin_consumer.sh and invoke via
  bash; @shell runner on cmd happily launches bash.exe (Git Bash is
  on PATH on the windows-latest image).

- linux-aarch64 JVM smoke: taiki-e/install-action lacks a precompiled
  cargo-make for aarch64-linux and the cargo-binstall fallback errored
  on stale runner state. Replace with 'cargo install --locked
  cargo-make' guarded by 'command -v' so self-hosted runners reuse
  the binary between jobs.

- Android emulator smoke: reactivecircus/android-emulator-runner runs
  each line of script: as a separate sh -c, so V=... + cd + ./gradlew
  lost state. Extract to scripts/run_android_smoke.sh.

- verify-swift-consumer: add a precheck for iOS SDK presence so the
  failure is one clear error instead of buried-in-xcodebuild-output.
  The xcode16 self-hosted runner needs 'sudo xcodebuild
  -downloadPlatform iOS' run once; ci_swift.yml will go red until
  then.
The script form writes to a temp file with platform-default extension
(.cmd on Windows) even when script_runner is set; cmd.exe then parses
the bash body and dies on case/esac. command=bash + args=[script.sh]
runs bash directly with no shell wrapper.
…smoke

Two more fixes for the JVM consumer smoke matrix:

- bindgen-kotlin (a dependency of verify-kotlin-consumer) had the same
  @shell + bash-script pattern that breaks on Windows. Extract to
  scripts/bindgen_kotlin.sh and switch to command=bash/args.

- The self-hosted macOS-arm runner is running its agent under Rosetta:
  uname -sm reports 'Darwin x86_64' even though the hardware is M-series
  and the installed JVM is the arm64 build. The verify script was
  staging the cargo-default-host (x86_64) dylib under darwin-x86-64/,
  but the arm64 JVM's JNA looked at darwin-aarch64/ → UnsatisfiedLink.
  Use sysctl -n hw.machine on Darwin (immune to Rosetta) + explicit
  cargo --target per matrix entry. Matrix gains a cargo_target column
  passed to dtolnay/rust-toolchain so the target is installed before
  the script runs.
The unlabeled self-hosted macOS/ARM64 pool runs its agent under Rosetta,
which broke arch matching for the JVM smoke. xcode16 is the
known-native-arm pool (release runners use it too).
On windows-latest GitHub runners, bash.exe in PATH resolves to WSL's
launcher (System32) — which fails with 'no installed distributions'.
Git for Windows ships sh.exe but no equivalent bash conflict, so 'sh'
reliably reaches Git Bash. Scripts are POSIX-compatible (no bashisms
in the case patterns), validated with sh -n.
kotlin/lib's transitive net.java.dev.jna:jna:5.15.0 brings in the JVM
JAR variant. The Android consumer needs the AAR variant (carries
libjnidispatch.so per-ABI). AGP errors on both flavors in the classpath
with 'Duplicate class com.sun.jna.*'. Exclude the transitive and pin
the AAR explicitly.
kotlin/lib compiles with Kotlin 2.2.20 (per libs.versions.toml); the
consumer at 2.0.21 rejected its 2.2.0 metadata. Bump Android module's
Kotlin to 2.2.20, AGP to 8.13.0 (first version with K2.2 support),
Gradle wrapper to 8.13 (AGP 8.13 requirement).
… -library

Replaces the hand-laid Iroh.framework skeleton (one .framework directory
per slice with a per-platform layout to maintain) with the Apple-blessed
flat library form: each slice ships lib<name>.a + Headers/ at the slice
root, no .framework subdirectory anywhere.

- make_swift.sh: assemble per-slice headers (uniffi's iroh_ffiFFI.h +
  Export.h umbrella + module.modulemap declaring "module Iroh"), lipo
  the two iOS sim arches together, then one xcodebuild
  -create-xcframework invocation produces the whole xcframework with the
  outer Info.plist auto-generated.

- Delete the committed Iroh.xcframework/ skeleton entirely — gitignore
  the whole directory, treat it as a build artifact. xcframework is
  regenerated from scratch on every build.

- Delete make_swift_macos.sh: docs-swift now depends on swift-xcframework
  (the full 4-target build), so there's no separate macOS-only fast path
  to maintain.

- verify-swift-xcframework: rewrite for the flat layout (slice has lib.a
  + Headers/{iroh_ffiFFI.h, Export.h, module.modulemap}; assert no
  Iroh.framework/ leftover).

- Package.swift: local-built detection points at libiroh_ffi.a instead
  of the .framework/Iroh binary path.

Removes the iroh-ffi#247 bug class entirely: with no .framework dir,
there's no deep-vs-shallow bundle layout for Xcode to reject. Apple
regenerates AvailableLibraries metadata against the current Xcode SDK
each build, so future Xcode majors don't need a hand-fix.

Validated locally:
  cargo make swift-xcframework — OK (4-target build → flat layout)
  cargo make verify-swift-xcframework — OK
  cargo make test-swift — OK (xcodebuild test macOS; `import Iroh` resolves)
  scripts/release/zip_xcframework.sh twice — same SHA-256
    (same-host deterministic)
…ation

Hardcoded `name=iPhone 16` worked on CI runners but not on developer
machines that have e.g. iPhone 17 Pro / Air installed. `OS=latest`
without a name doesn't resolve either. Query simctl for any available
iOS sim and pass its UDID — locally and on CI, both find a valid
destination.
…elease flow)

The Swift xcframework was the only artifact this repo built twice — once
locally during prepare-release to bake the SHA into Package.swift, once
on tag-CI to upload the zip — with the two builds expected to produce
byte-identical bytes. Cross-host determinism kept failing (Xcode patch
versions, macOS SDK skew), forcing manual zip-clobber rituals
post-publish.

Single build, single source of truth:

- release_swift.yml: new workflow that fires on push to release/v*
  branches (the convention prepare-release sets up). Builds xcframework
  + verifies layout + iOS sim test, computes SHA-256, commits the SHA
  into Package.swift on the PR branch (a bot commit marked
  [skip swift-release] so it doesn't re-fire), uploads the zip to a
  draft GitHub release v<version>.

- release.yml's create-release: replaced actions/create-release@v1
  (deprecated) with gh CLI that promotes the existing draft release to
  published — the swift zip baked in PR-CI is already attached. Falls
  through to fresh-create if no draft exists (manual tag without PR).

- release.yml's build-and-publish-libs: switched
  actions/upload-release-asset to `gh release upload --clobber` since
  the new create-release no longer produces an upload_url output.

- release.yml's build-and-publish-swift: deleted. The swift zip ships
  with the promoted draft.

- prepare-release: stripped the xcframework build + checksum bake. No
  Xcode required locally anymore — versions-only edit, PR-CI fills the
  SHA when you push.
…w flow

- pre-release-check: clarify that swift xcframework is built here as a
  sanity check only; the shipping zip comes from release_swift.yml on PR
  CI. Same-host determinism check kept (catches stale caches).

- RELEASING.md: rewrite for the two-phase version bump. Local
  prepare-release does versions only (no Xcode required); PR CI's
  release_swift.yml bakes the Swift xcframework SHA into Package.swift on
  the release branch and uploads the zip to a draft GitHub release. Tag
  CI just promotes the draft. New 'Why PR-CI builds the Swift xcframework'
  section documents the determinism trade-off the old local-baked flow
  kept hitting.

- Package.swift, bump_version.py, zip_xcframework.sh: comments updated to
  match the new flow (releaseChecksum is written by PR CI, not local;
  zip_xcframework's determinism is now same-host sanity, not cross-host
  reproducibility).
… arch detect

sysctl -n hw.machine and uname -m both report the *process* arch, not the
hardware — so on Apple Silicon under Rosetta, both return x86_64 and the
script stages an x86_64 dylib that the native-arm64 JVM can't load (18
JNA tests fail). hw.optional.arm64 returns "1" on Apple Silicon hardware
regardless of process arch, which is the actual question.
Stops bundling Android-only types + native libs into the JVM-only
computer.iroh:iroh JAR. Android consumers will depend on a new
computer.iroh:iroh-android AAR (D2 stages the .so files into the AAR's
jni/<abi>/ path; D3 wires the consumer smoke to the new coordinate).

- Gradle 9.0 -> 8.13: AGP 8.13 is the latest stable that supports Kotlin
  2.2.x, and it requires Gradle 8.13. All other plugins (kotlin-jvm
  2.2.20, dokka 2.0, vanniktech-publish 0.34) work on 8.13.

- New kotlin/build.gradle.kts at the root declares every plugin with
  apply false so subprojects share one classloader per plugin. Without
  it Vanniktech's MavenCentralBuildService loads twice (once per
  subproject) and Gradle errors on the conflicting plugin instances.

- New kotlin/android/ subproject (com.android.library + kotlin-android +
  vanniktech AndroidSingleVariantLibrary). Publishes computer.iroh:iroh
  -android, has IrohAndroid moved over from :lib, api(:lib) so Android
  consumers transitively get the JVM Kotlin API. JNA AAR explicit (with
  the JAR excluded from :lib transitive) to avoid duplicate-class.

- :lib loses compileOnly(com.google.android:android:4.1.1.4) since
  IrohAndroid is no longer there.

- bump_version.py rewrites coordinates in both kotlin/lib/build.gradle.kts
  and kotlin/android/build.gradle.kts so the two artifact versions stay
  in lockstep.

Validated locally with `./gradlew :lib:tasks` and `:android:tasks` —
both BUILD SUCCESSFUL. D2 will move the Android native staging path
from kotlin/lib/src/main/resources/lib/<abi>/ to
kotlin/android/src/main/jniLibs/<abi>/.
AGP requires the Android SDK at *configuration* time, so Gradle errors
with 'SDK location not found' on jobs that previously didn't need it
(JVM test, JVM consumer smoke matrix, build-and-publish-kotlin) because
they now have to configure the :android subproject alongside :lib.
D2 — Android native staging path moves from
  kotlin/lib/src/main/resources/lib/<abi>/   (JVM JAR's lib/<abi>/)
to
  kotlin/android/src/main/jniLibs/<abi>/      (AAR source set; AGP
                                              packages into the AAR's
                                              jni/<abi>/, then merged
                                              into consumer APKs).
Updated:
  - cargo make kotlin-android task (writes to jniLibs path).
  - release.yml stage-natives step (writes both JVM resources + AAR
    jniLibs from the cdylib + android-jniLibs artifacts).
  - ci_kotlin.yml kotlin-android-consumer-smoke job (same).
  - verify-kotlin-artifact (validates both source trees instead of
    just kotlin/lib/src/main/resources).
  - publish step: ./gradlew publishAndReleaseToMavenCentral now (no
    :lib: prefix) so both subprojects publish.
  - .gitignore: kotlin/android/src/main/jniLibs/ is CI-generated.

D3 — kotlin/android-smoke consumes the new AAR coordinate. Composite
build substitutes computer.iroh:iroh-android → :android (was iroh →
:lib). app/build.gradle.kts depends on iroh-android (which transitively
brings in :lib + JNA AAR correctly).

Also: make :android subproject conditionally included (skip on hosts
without an Android SDK, e.g. the self-hosted linux-aarch64 runner
where setup-android@v3 fails). Lets :lib build + test on that runner
without the SDK. JVM smoke + JVM test jobs skip setup-android on
linux-aarch64.
- Workflow table cell now names both artifacts.
- New 'Kotlin: two artifacts, one publish' section explaining the JAR
  vs AAR split and the one-line dep migration for Android consumers.
…orted

Gradle 8.13 + AGP 8.13 + Kotlin 2.2.20 require Java 17-21. Many macOS
devs have Homebrew openjdk (currently 26) as their default JAVA_HOME;
gradle dies with a one-line '26.0.1' error message that doesn't say
why. Detect the situation via /usr/libexec/java_home -v 17 and
override locally. CI is unaffected (setup-java@v4 sets JAVA_HOME=21
explicitly before the task runs).

Also scope test-kotlin to :lib:test specifically rather than the
root 'test' alias — the :android subproject's empty test task would
configure unnecessarily.
…se_swift shell

Five review threads:

- bump_version.py docstring: make it explicit that --checksum returns
  without bumping any version literals; that path is purely for
  release_swift.yml on PR CI.

- kotlin/android-smoke: was a separate Gradle root with its own wrapper
  (8 binary + script files checked in). Convert to a subproject of the
  kotlin/ root — share the parent wrapper. depends on :android via
  project(":android"), composite-build substitution gone. delete the
  duplicate gradle wrapper jar + gradlew + settings.gradle.kts +
  gradle.properties + .gitignore. CI workflows + Makefile.toml +
  scripts/run_android_smoke.sh now invoke
  ./gradlew :android-smoke:connectedDebugAndroidTest from kotlin/.

- ci_kotlin.yml comments trimmed: most one-liners or removed (the
  reasoning lives in commit history). yaml ~30% shorter.

- release_swift.yml inline bash → scripts/release/swift_bake_sha.sh and
  swift_upload_draft.sh. Both reusable for local dry-runs:
    cargo make swift-xcframework && bash scripts/release/zip_xcframework.sh
    bash scripts/release/swift_bake_sha.sh
    bash scripts/release/swift_upload_draft.sh

- bonus: extract scripts/stage_android_natives.sh (shared by
  ci_kotlin.yml's smoke and release.yml's verify) and
  scripts/release/stage_kotlin_publish_natives.sh (release.yml publish
  staging). yaml files now mostly thin orchestration around scripts.
When :android-smoke was a separate Gradle root it had its own
kotlin/android-smoke/gradle.properties with android.useAndroidX=true.
Flattening into a subproject lost that file; the consolidated root
kotlin/gradle.properties didn't yet carry the AGP requirements.
@computermeowse

Copy link
Copy Markdown

ty for the ping!

Build via Xcode fails for me on both iPhone and Mac targets with two FFI errors:

  • Cannot find 'uniffi_iroh_ffi_checksum_method_endpointbuilder_bind' in scope (IrohLib.swift:9446)
  • Cannot find 'uniffi_iroh_ffi_checksum_constructor_endpointbuilder_new' in scope (IrohLib.swift:9611)

Package version: 82754a1

@jj10133

jj10133 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@iLynxcat sorry I didn't get that error?

@computermeowse

Copy link
Copy Markdown

strange - I attempted to clear DerivedData and build caches several times to ensure it wasn't a cache issue and I still got it. Will take more of a look soon, ty for the heads up.

@matheus23

Copy link
Copy Markdown
Member
  • Cannot find 'uniffi_iroh_ffi_checksum_method_endpointbuilder_bind' in scope (IrohLib.swift:9446)

  • Cannot find 'uniffi_iroh_ffi_checksum_constructor_endpointbuilder_new' in scope (IrohLib.swift:9611)

These functions were introduced in #257 so it seems like this must be related to recent changes? Or maybe it is some build cache issue?

@dignifiedquire

Copy link
Copy Markdown
Contributor Author

@iLynxcat how did you load it, did you run `cargo make swift-xcframework? that is needed to generate the latest version, otherwise it will pick up the published version likely

@computermeowse

Copy link
Copy Markdown

I tried this morning in a fresh Xcode project, fresh DerivedData, fresh cargo registry, and ran into the same two errors. I did cd into the package checkout path to run cargo make swift-xcframework:

$ rm -rf ~/Library/Developer/Xcode/DerivedData ~/.cargo/registry
#-- opened new Cocoa (macOS) project in Xcode, added package dependency for iroh-ffi.git @ 82754a1336eef947066ad1197de1baa52a2e5011 --
$ cd ~/Library/Developer/Xcode/DerivedData/MyApp-[…]/SourcePackages/checkouts/iroh-ffi
#-- attempting to resolve, I try to build the framework manually from source --
$ cargo make swift-xcframework
[cargo-make] INFO - cargo make 0.37.24
[cargo-make] INFO -
[cargo-make] INFO - Project: iroh-ffi
[cargo-make] INFO - Build File: Makefile.toml
[cargo-make] INFO - Task: swift-xcframework
[cargo-make] INFO - Profile: development
[cargo-make] INFO - Running Task: swift-xcframework
    Finished [`dev` profile [unoptimized + debuginfo]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 0.25s
Building aarch64-apple-ios
   Compiling proc-macro2 v1.0.106
…
   Compiling thiserror-impl v2.0.18
   Compiling tracing-attributes v0.1.31
   Compiling derive_more-impl v2.1.1
   Compiling zeroize v1.9.0
   Compiling uniffi_internal_macros v0.31.1
error[E0463]: can't find crate for `zeroize_derive`
   --> /cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zeroize-1.9.0/src/lib.rs:214:9
    |
214 | pub use zeroize_derive::{Zeroize, ZeroizeOnDrop};
    |         ^^^^^^^^^^^^^^ can't find crate

For more information about this error, try `rustc --explain E0463`.
error: could not compile `zeroize` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
Error while executing command, exit code: 101

I do think it was pulling the release version of iroh itself, this build error seems like a separate issue. Sorry that I don't have much time to look into this further atm, I hope this helps point us in the right direction

@dignifiedquire

Copy link
Copy Markdown
Contributor Author

thanks @iLynxcat gonna merge this anyway, and hopefully once it is published it will be fixed

@dignifiedquire dignifiedquire merged commit 468e8ed into main Jul 8, 2026
84 of 86 checks passed
@github-project-automation github-project-automation Bot moved this from 🏗 In progress to ✅ Done in iroh Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Swift (macOS): Build fails with "platform does not use shallow bundles"

5 participants