Apple Darwin
How Platrium SDK compiles to native Apple platforms — covering the Rust build pipeline, xcframework packaging, UniFFI Swift bindings, and Xcode integration.
The Apple Darwin platform target is the SDK distribution for macOS, iOS, and iPadOS — covering both Apple Silicon and Intel Macs, physical iPhone/iPad devices, and simulators. It produces a single, distributable XCFramework + Swift Package that an Xcode project can consume with a single local package import!
Platform Support Matrix
| Platform | Architecture | Minimum Version |
|---|---|---|
| macOS | arm64 (Apple Silicon) | macOS 11.0 Big Sur |
| macOS | x86_64 (Intel) | macOS 11.0 Big Sur |
| iOS / iPadOS | arm64 (Physical Device) | iOS 16.0 |
| iOS Simulator | arm64 (Apple Silicon Mac) | iOS 16.0 |
| iOS Simulator | x86_64 (Intel Mac) | iOS 16.0 |
How It All Fits Together
The SDK is a Rust crate at its core. On Apple platforms, it compiles to a native static library (.a) for each target, which gets bundled into a single XCFramework. UniFFI auto-generates the Swift glue layer so you can call into it from Xcode like regular Swift code.
Required Toolchain
To build the Darwin SDK, you need Rust stable with all Apple targets installed. The sdk/rust-toolchain.toml pins this automatically — rustup picks it up without any manual steps:
[toolchain]
channel = "stable"
components = ["rustfmt", "clippy"]
targets = [
# macOS (Apple Silicon + Intel)
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# iOS & iPadOS (Physical devices)
"aarch64-apple-ios",
# iOS & iPadOS Simulators (Apple Silicon + Intel)
"aarch64-apple-ios-sim",
"x86_64-apple-ios",
]You also need Xcode installed (for xcodebuild, lipo, and the iOS SDKs) and UniFFI (pulled in as a Cargo dependency — no separate install needed).
The Build Script
Everything is orchestrated by sdk/scripts/build_darwin.sh. Run it via Nx:
nx run sdk:build:darwinStep 1 — Compile for All Targets
Each target gets its own scoped cargo build with the deployment target set inline as an env var. This is critical — setting it globally (e.g. via .cargo/config.toml) contaminates build scripts for proc macros that run on the host machine, causing cryptic linker failures!
MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target aarch64-apple-darwin
MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target x86_64-apple-darwin
IPHONEOS_DEPLOYMENT_TARGET=16.0 cargo build --release --target aarch64-apple-ios
IPHONEOS_DEPLOYMENT_TARGET=16.0 cargo build --release --target aarch64-apple-ios-sim
IPHONEOS_DEPLOYMENT_TARGET=16.0 cargo build --release --target x86_64-apple-iosStep 2 — Generate Swift Bindings
UniFFI inspects the compiled macOS ARM64 library and spits out two things:
platrium_sdk.swift— the high-level Swift wrapper (types, async methods, errors, etc.)platrium_sdkFFI.h— the low-level C bridge header with all raw FFI function declarations
cargo run --manifest-path uniffi/Cargo.toml -- generate \
--library target/aarch64-apple-darwin/release/libplatrium_sdk.a \
--config uniffi/uniffi.toml \
--language swift \
--out-dir _ffi/darwin/build/bindingsThe generated platrium_sdk.swift is then copied to _ffi/darwin/Sources/PlatriumSDK/ — the SPM Sources directory.
Step 3 — Create Universal Binaries with lipo
The macOS targets and iOS Simulator targets are merged into fat/universal .a files using lipo:
lipo -create -output _ffi/darwin/build/lipo/macos/libplatrium_sdk.a \
target/aarch64-apple-darwin/release/libplatrium_sdk.a \
target/x86_64-apple-darwin/release/libplatrium_sdk.a
lipo -create -output _ffi/darwin/build/lipo/ios_sim/libplatrium_sdk.a \
target/aarch64-apple-ios-sim/release/libplatrium_sdk.a \
target/x86_64-apple-ios/release/libplatrium_sdk.aThe iOS device binary (arm64 only) is used as-is — no merging needed.
Step 4 — Package the XCFramework
xcodebuild bundles all three slices (macOS universal, iOS device, iOS Simulator universal) plus their C headers into a single XCFramework:
# modulemap must be named module.modulemap for Xcode/SPM to auto-discover it
cp _ffi/darwin/build/bindings/*.modulemap _ffi/darwin/build/headers/module.modulemap
xcodebuild -create-xcframework \
-library _ffi/darwin/build/lipo/macos/libplatrium_sdk.a \
-headers _ffi/darwin/build/headers \
-library _ffi/darwin/build/lipo/ios/libplatrium_sdk.a \
-headers _ffi/darwin/build/headers \
-library _ffi/darwin/build/lipo/ios_sim/libplatrium_sdk.a \
-headers _ffi/darwin/build/headers \
-output _ffi/darwin/PlatriumSDKFFI.xcframeworkThe modulemap must be renamed to module.modulemap before being passed into xcodebuild. If it keeps the UniFFI-generated name (platrium_sdkFFI.modulemap), Xcode won't auto-discover the module, canImport(platrium_sdkFFI) silently evaluates to false, and you get a wall of "Cannot find type 'RustBuffer' in scope" errors!
The Swift Package (sdk/_ffi/darwin/)
The output of the build script lives in sdk/_ffi/darwin/ and is structured as a Swift Package:
sdk/_ffi/darwin/
├── Package.swift # SPM manifest
├── PlatriumSDKFFI.xcframework/ # compiled native binary (gitignored)
│ ├── ios-arm64/Headers/
│ ├── ios-arm64_x86_64-simulator/Headers/
│ └── macos-arm64_x86_64/Headers/
└── Sources/
└── PlatriumSDK/
└── platrium_sdk.swift # generated Swift glue (gitignored)Package.swift wires everything together. The binary target name must exactly match the module name declared in the xcframework's modulemap (platrium_sdkFFI) for Swift's canImport to resolve it at compile time:
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "PlatriumSDK",
platforms: [
.iOS(.v16),
.macOS(.v11)
],
products: [
.library(name: "PlatriumSDK", targets: ["PlatriumSDK"]),
],
targets: [
// High-level Swift wrapper (generated platrium_sdk.swift)
.target(
name: "PlatriumSDK",
dependencies: ["platrium_sdkFFI"],
path: "Sources/PlatriumSDK"
),
// The compiled Rust static library (C FFI bridge)
// Name MUST match the module name inside the xcframework modulemap!
.binaryTarget(
name: "platrium_sdkFFI",
path: "PlatriumSDKFFI.xcframework"
)
]
)How the Two Targets Work Together
The generated platrium_sdk.swift has this at the top:
#if canImport(platrium_sdkFFI)
import platrium_sdkFFI
#endifThis is UniFFI's "dual-mode" guard. When compiled as part of the SPM package (where platrium_sdkFFI is a declared binary dependency), the canImport check is true, and RustBuffer, ForeignBytes, RustCallStatus and all the raw C FFI types are imported from the xcframework module. Without this import, none of the high-level Swift types (PlatriumClient, etc.) can be constructed!
Adding to an Xcode Project
In Xcode, go to your target's Package Dependencies and add a local Swift Package pointing to:
sdk/_ffi/darwin/Then add PlatriumSDK as a framework dependency for your app target. That's it!
import PlatriumSDK
let client = try PlatriumClient(baseUrl: "https://my.platrium.app")Auto-Rebuilding in Xcode
You can hook the build script into Xcode so the SDK is always in sync with the latest Rust code. Add a Run Script Phase (drag it to the very top of Build Phases, before Compile Sources) with:
export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.cargo/bin:$PATH"
cd "$SRCROOT/.."
./nx run sdk:build:darwinSince Nx and Cargo both have solid incremental caching, this typically takes under a second when nothing has changed!