Rust Cross-Compilation for Mobile

SkillCloud & infra

Cross-compiling Rust crates for mobile (Android, iOS) and other non-host targets. Covers rustup target management, cargo-ndk for Android (NDK toolchain), iOS targets (aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios), `cross` for general cross-compile via Docker, lipo for iOS universal binaries, XCFramework packaging, sysroot configuration, dependency cross-compile gotchas (openssl, ring, C deps), and CI matrix patterns.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Rust Cross-Compilation for Mobile skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/build-tools/rust-cross-compile/SKILL.md and read by ahel’s review.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: rust-cross-compile or cargo-ndk.

Targets Cheat Sheet

TargetUse case
aarch64-linux-androidAndroid arm64 (modern devices)
armv7-linux-androideabiAndroid arm32 (older devices)
i686-linux-androidAndroid x86 emulator (32-bit)
x86_64-linux-androidAndroid x86_64 emulator
aarch64-apple-iosiOS device (iPhone arm64)
aarch64-apple-ios-simiOS Simulator on Apple Silicon Mac
x86_64-apple-iosiOS Simulator on Intel Mac (legacy)
aarch64-apple-darwinmacOS Apple Silicon
x86_64-apple-darwinmacOS Intel
wasm32-unknown-unknownBrowser WASM
wasm32-wasip1WASM with WASI
aarch64-unknown-linux-gnuLinux arm64 (Raspberry Pi 64-bit, ARM servers)
x86_64-unknown-linux-muslLinux static-link (Alpine, distroless)
rustup target list                              # all
rustup target list --installed
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android

Cargo.toml — Library for FFI

[package]
name = "bhodl-ffi"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "staticlib"]            # both for mobile bundling
name = "bhodl_ffi"

[dependencies]
uniffi = { version = "0.28", features = ["cli"] }
tokio = { version = "1", features = ["rt-multi-thread"] }

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true                                     # strip symbols for smaller binary
panic = "abort"                                  # smaller; use "unwind" if you catch panics

cdylib.so (Android), .dylib (macOS). staticlib.a (iOS, where dynamic libs are restricted).

Android — cargo-ndk

Wraps cargo with the Android NDK toolchain. Replaces older cargo build --target=... + manual env var dance.

# Install
cargo install cargo-ndk

# Set NDK path (or use ANDROID_NDK_HOME env)
export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/27.1.12297006

# Build for multiple ABIs
cargo ndk \
    -t arm64-v8a \
    -t armeabi-v7a \
    -t x86_64 \
    -t x86 \
    -o ./jniLibs \                              # output dir
    build --release

# Output structure (matches Android's expected layout):
# jniLibs/
# ├── arm64-v8a/libbhodl_ffi.so
# ├── armeabi-v7a/libbhodl_ffi.so
# ├── x86_64/libbhodl_ffi.so
# └── x86/libbhodl_ffi.so

In Gradle (Android module):

android {
    sourceSets["main"].jniLibs.srcDirs("../rust-ffi/jniLibs")
}

NDK Version Pinning

Specify NDK version in local.properties or via env:

# local.properties
ndk.dir=/Users/me/Library/Android/sdk/ndk/27.1.12297006

Or in app/build.gradle.kts:

android {
    ndkVersion = "27.1.12297006"
}

Android API Level

Set min API level for generated .so:

cargo ndk -t arm64-v8a --platform 26 -o ./jniLibs build --release
# Compatible with Android API 26+ (Android 8.0)

Match this to your Android minSdk.

iOS — Native Cargo

iOS uses staticlib only (Apple disallows dynamic libs in apps).

# Device
cargo build --release --target aarch64-apple-ios

# Simulator (Apple Silicon Mac)
cargo build --release --target aarch64-apple-ios-sim

# Simulator (Intel Mac, legacy)
cargo build --release --target x86_64-apple-ios

# Output:
# target/aarch64-apple-ios/release/libbhodl_ffi.a
# target/aarch64-apple-ios-sim/release/libbhodl_ffi.a
# target/x86_64-apple-ios/release/libbhodl_ffi.a

Universal Simulator Library (lipo)

Combine sim arm64 + sim x86_64 into single .a for unified simulator support:

mkdir -p target/universal/release
lipo -create \
    target/aarch64-apple-ios-sim/release/libbhodl_ffi.a \
    target/x86_64-apple-ios/release/libbhodl_ffi.a \
    -output target/universal/release/libbhodl_ffi.a

# Verify
lipo -info target/universal/release/libbhodl_ffi.a
# → arm64 x86_64

XCFramework (Recommended Distribution)

Bundle device + simulator binaries with headers into single artifact:

mkdir -p Bhodl.xcframework
xcodebuild -create-xcframework \
    -library target/aarch64-apple-ios/release/libbhodl_ffi.a \
        -headers ./bindings/ios/include \
    -library target/universal/release/libbhodl_ffi.a \
        -headers ./bindings/ios/include \
    -output Bhodl.xcframework

Drop Bhodl.xcframework into Xcode project or vendor via Swift Package Manager.

For UniFFI: see languages/uniffi/quick-ref/kmp-bindings.md for the full pipeline.

cross — General-Purpose Cross-Compilation

For Linux/Windows/etc. targets without manual toolchain setup. Uses Docker to provide pre-built sysroots.

# Install
cargo install cross --git https://github.com/cross-rs/cross

# Build for ARM Linux
cross build --target aarch64-unknown-linux-gnu --release

# Build for musl (static, Alpine-friendly)
cross build --target x86_64-unknown-linux-musl --release

# Build for Windows from Linux
cross build --target x86_64-pc-windows-gnu --release

Cross.toml for custom config:

[target.aarch64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main"
pre-build = [
    "apt-get update",
    "apt-get install -y libssl-dev:arm64",
]

[build.env]
passthrough = ["CARGO_TERM_COLOR"]

cross requires Docker/Podman. Doesn't work for Apple targets (those need Xcode toolchain on macOS).

C Dependency Pitfalls

Crates depending on C libraries (openssl, sqlite, libsodium) often need extra config when cross-compiling.

OpenSSL

# Avoid system openssl on iOS / Android — use vendored
[dependencies]
openssl = { version = "0.10", features = ["vendored"] }

Or switch to rustls (pure Rust):

reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }

SQLCipher (rusqlite)

[dependencies]
rusqlite = { version = "0.32", features = ["bundled-sqlcipher"] }

bundled-sqlcipher compiles SQLCipher from source, statically linked — no system dep.

libsodium

[dependencies]
dryoc = "0.7"            # pure Rust, no C dependency, easiest to cross-compile

If you must use libsodium-sys:

[dependencies]
libsodium-sys-stable = { version = "1.20", features = ["fetch-latest"] }

The fetch-latest feature builds libsodium from source (no system dep).

Build Profiles for Mobile

[profile.release]
opt-level = "z"          # optimize for size (vs "3" for speed)
lto = true               # link-time optimization
codegen-units = 1        # better optimization, slower build
strip = true             # remove debug symbols
panic = "abort"          # smaller binary; no unwinding stacks

[profile.release-with-debug]
inherits = "release"
debug = true             # for profiling release builds
strip = false

For smallest mobile binary:

[profile.release]
opt-level = "z"
lto = "fat"
codegen-units = 1
strip = "symbols"
panic = "abort"
overflow-checks = false  # don't include checks (default in release)

Trade-off: slower compile, smaller binary, less debugging info.

Linker Configuration

.cargo/config.toml (per-project):

[target.aarch64-linux-android]
linker = "aarch64-linux-android26-clang"        # set by cargo-ndk

[target.aarch64-apple-ios]
rustflags = ["-C", "link-arg=-Wl,-application_extension"]   # for app extensions

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

cargo-ndk sets the right linker automatically when building Android targets.

Build Caching

sccache (incremental cross-target cache)

cargo install sccache
export RUSTC_WRAPPER=sccache

Speeds up cross-compile when targets share intermediate artifacts.

Target-specific build dirs

# Avoid clobbering host-target cache
cargo ndk ... build --target-dir target/android
cargo build --target aarch64-apple-ios --target-dir target/ios

CI Patterns

GitHub Actions — Multi-Target Build

# .github/workflows/rust-cross.yml
name: Rust cross-compile

on: [push, pull_request]

jobs:
  android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android

      - name: Setup Android NDK
        uses: nttld/setup-ndk@v1
        with:
          ndk-version: r27c

      - name: Install cargo-ndk
        run: cargo install cargo-ndk

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Build Android libs
        env:
          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
        run: |
          cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o jniLibs build --release

      - uses: actions/upload-artifact@v4
        with:
          name: android-libs
          path: jniLibs/

  ios:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Build iOS libs
        run: |
          cargo build --release --target aarch64-apple-ios
          cargo build --release --target aarch64-apple-ios-sim
          cargo build --release --target x86_64-apple-ios

      - name: Build XCFramework
        run: |
          mkdir -p target/universal/release
          lipo -create \
              target/aarch64-apple-ios-sim/release/libbhodl_ffi.a \
              target/x86_64-apple-ios/release/libbhodl_ffi.a \
              -output target/universal/release/libbhodl_ffi.a

          xcodebuild -create-xcframework \
              -library target/aarch64-apple-ios/release/libbhodl_ffi.a \
                -headers bindings/ios/include \
              -library target/universal/release/libbhodl_ffi.a \
                -headers bindings/ios/include \
              -output Bhodl.xcframework

      - uses: actions/upload-artifact@v4
        with:
          name: ios-xcframework
          path: Bhodl.xcframework/

Build Matrix

strategy:
  matrix:
    include:
      - target: aarch64-linux-android
        runner: ubuntu-latest
        ndk: true
      - target: x86_64-linux-android
        runner: ubuntu-latest
        ndk: true
      - target: aarch64-apple-ios
        runner: macos-14
      - target: x86_64-unknown-linux-musl
        runner: ubuntu-latest
        cross: true

Inspecting Output

# Check archive contents
ar -t libbhodl_ffi.a | head

# Check symbols
nm -gU libbhodl_ffi.a | head             # exported globals
nm -gU libbhodl_ffi.a | grep wallet

# iOS — check architecture
lipo -info libbhodl_ffi.a
# → Architectures in the fat file: libbhodl_ffi.a are: arm64

# Android — check ABI
file libbhodl_ffi.so
# → ELF 64-bit LSB shared object, ARM aarch64

Anti-Patterns

Anti-patternWhy it's badCorrect approach
Building all Android ABIs in devSlowBuild only emulator ABI in dev (--targets x86_64)
Single .a for both device + sim (lipo merge with arm64s)Conflict (both have arm64)XCFramework (separates by platform)
openssl system dep on Android/iOSCross-compile breaksUse rustls or vendored openssl
Forgetting staticlib crate-type for iOSNo .a producedAdd crate-type = ["cdylib", "staticlib"]
Dynamic libs (cdylib) on iOSApp Store rejectionUse staticlib for iOS
Building debug profile for release distributionHuge binariesAlways --release for distribution
Not stripping symbolsLarger binary, info leakstrip = true in [profile.release]
Hardcoded NDK pathNot portableUse ANDROID_NDK_HOME env or local.properties
Running cargo directly for AndroidWrong toolchainUse cargo-ndk
Skipping aarch64-apple-ios-sim targetCrashes on M-series Mac simBuild all sim arches you support

Troubleshooting

SymptomCauseFix
linker 'aarch64-linux-android21-clang' not foundNDK not installed or wrong pathSet ANDROID_NDK_HOME, use cargo-ndk
Undefined symbols for architecture arm64 (iOS link)C dependency missing for targetVendor or rebuild with target sysroot
building for iOS Simulator-arm64 but linking with libs built for iOS-arm64Wrong target combinedBuild separately, lipo only sim arches
No such file or directory: 'libssl.so'System openssl not presentUse vendored: features = ["vendored"]
no entry in dyld shared cacheMissing iOS framework linkAdd linkerOpts = -framework Security in def or build script
Build slow on every CI runNo cacheUse Swatinem/rust-cache@v2
cargo-ndk: command not found after installCargo bin not in PATHexport PATH="$HOME/.cargo/bin:$PATH"
INSTALL_FAILED_NO_MATCHING_ABISAPK doesn't have lib for emulator's ABIBuild the matching ABI
iOS device build works, sim arm64 doesn'tTarget not addedrustup target add aarch64-apple-ios-sim
cross Docker permission deniedDocker not running / user not in docker groupStart Docker, add user to docker group
Huge .a filesDebug symbols includedSet strip = true in release profile

When NOT to Use This Skill

ScenarioUse Instead
KMP Gradle configbuild-tools/gradle-kmp
UniFFI binding generationlanguages/uniffi
Pure Rust languagelanguages/rust
Rust web (WASM)wasm-bindgen specific
Reproducible Linux buildsinfrastructure/reproducible-builds

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
rust-cross-compile
Source
github.com/claude-dev-suite/claude-dev-suite