A shared library written in Rust, linked into an iOS app (Swift) and an
Android app (Kotlin). Every build step is an ordinary shell script with a
few # once annotations, so the whole graph is cacheable and remotely
executable through Once without rewriting
anything as build rules.
This is the companion example for the blog post
Three build systems, one graph. Once is in a lab
phase, so treat this as an exploration rather than a stable setup. The
mise.toml pins Once 0.28.0, and the scripts use a /bin/bash shebang
because Once resolves a bare runtime name through mise, which has no bash
package.
core/src/lib.rs ──> build-core-apple ──┐
├──> build-apple-binary ──> build-apple-app ──> launch-apple
apple/Sources ──────────────────────────┘
core/src/lib.rs ─────> build-core-android ────┐
android/app/java ────> build-android-dex ──────┼──> build-android-package ──> build-android-sign ──> launch-android
AndroidManifest ─────> build-android-resources ┘
Each build declares what it needs with # once needs, so the edges are
real: running a node runs its dependencies first. Both platforms drive the
underlying tools directly, no Xcode and no Gradle: swiftc and rustc on
Apple, kotlinc, d8, aapt2, zipalign, and apksigner on Android.
Because the steps are ours, they are separate nodes: change Info.plist and
only the Apple bundle reassembles, change the Android manifest and the dex
stays cached while resources, packaging, and signing rerun, change the Rust
and the whole chain reruns.
Only the compilers pulled from mise carry a # once fingerprint, so their
version lands in the cache key: rustc, swiftc, kotlinc. The Android SDK
tools are referenced through the versioned build-tools/35.0.0/ path, so
their version is already in the script and does not need one.
core/— Rust library, built as astaticlibfor Apple and acdylibfor Android from one source. Exposes a C function for Apple and a JNI function for Android.apple/— UIKit app that calls the Rust greeting over the C FFI.android/— Kotlin app that calls the same greeting over JNI, with a committeddebug.keystoresoapksignercan sign the APK.scripts/— one annotated script per node, plusbuild.sh.mise.toml— the toolchain: Rust with the iOS and Android targets, Java, Kotlin, the Android SDK location, and Once itself.
mise install # toolchain
mise run setup-android # Android SDK packages (NDK, emulator, system image)
./scripts/build.sh # build the whole graph through Once
./scripts/launch-apple.sh # install and launch on a booted iOS simulator
./scripts/launch-android.sh # install and launch on a running Android emulator