Build an iOS Simulator application
From a generated Rust project on macOS:
cargo ferry build ios --simulator
The build produces:
target/ferry/ios/debug/<binary>.app
Use --release for target/ferry/ios/release/<binary>.app. Building neither boots nor installs a Simulator.
Pipeline
- Validate
ferry.toml, Cargo selectors, versions, identifiers, paths, permissions, and extension settings. - Discover full Xcode and the
iphonesimulatorSDK throughxcrun. - Confirm
aarch64-apple-ios-simis installed. - Select
CompiledCatalogwhen discovery finds an available iOS runtime; otherwise select the explicitSdkOnlyResourcesfallback. Render the corresponding deterministicFerryHost.xcodeproj, scheme,Info.plist, resources, entitlements, and enabled extension sources belowtarget/ferry/ios/generated/. - Run Cargo for
aarch64-apple-ios-simwith an isolatedtarget/ferry/ios/cargo/target directory. - Stage Cargo’s executable under its validated binary name for Xcode’s argument-free Copy Files phase.
- Run
xcodebuild -target FerryApp -sdk iphonesimulatorwith deterministic ad-hoc signing. Target-based SDK selection avoids booting or selecting a Simulator device. RustFerry requires an available runtime before selecting catalog compilation; the SDK-only fallback avoids that tool step. - Xcode creates the bundle, processes
Info.plist, compiles or copies the selected asset representation, builds and signs nested dependencies, installs the prebuilt Rust executable at$TARGET_BUILD_DIR/$EXECUTABLE_PATH, and signs the application last. - When WidgetKit is enabled, ad-hoc re-sign the widget with
Widget.entitlements, then the application withApp.entitlements. Non-widget builds skip this step; signing never uses--deep. - Independently inspect the app and fail unless every invariant matches.
This follows Slint’s iOS architecture: the application is a Rust executable built for the simulator, using Slint’s Winit backend and Skia renderer, placed at Xcode’s expected executable path. The generated Xcode project contains packaging metadata and minimal platform extension code, not user business logic.
Asset packaging is explicit in the build plan and validation report:
CompiledCataloggeneratesAssets.xcassets, configuresAppIconandFerryLaunch, and requiresAssets.carplus the matching processed plist values. This mode is selected only when an available iOS Simulator runtime is reported.SdkOnlyResourcespreserves SDK-only builds on hosts with no runtime. It copiesFerryIcon.pngandFerrySplash.png, records their plist references, byte-compares them with the validated project inputs, and rejects a strayAssets.car.
The two modes use separate Xcode intermediate directories, so switching runtime availability cannot reuse stale catalog output.
Validation evidence
A successful result records and checks:
.appis a real directory, not a symlink;Info.plistpassesplutil -lint;- exact
CFBundleIdentifier,CFBundleExecutable, andCFBundlePackageType=APPL; - executable exists, is non-empty, and has executable permission bits;
xcrun lipo -archsreturns exactlyarm64;- the pre-sign embedded executable matches Cargo’s output byte-for-byte and the signed executable retains its Mach-O UUID;
FerryResources.jsonexists;- asset evidence matches the selected mode: either
Assets.carwithAppIcon/FerryLaunch, or exactFerryIcon.png/FerrySplash.pngbytes with the SDK-only plist keys; - each top-level framework/library is inspected;
- expected
.appexcount, identifiers, executable names, extension point, and architectures match. - app, runtime framework, and each
.appexhave sealed plists/resources, exact signature identifiers, and strict-valid ad-hoc signatures; - the application passes
codesign --verify --deep --strict; - application and widget signatures contain exactly the configured application group when enabled.
Command logs live below target/ferry/ios/logs/<profile>/. Logged argv/environment values use the plan’s redaction metadata.
Dry run
The Apple build API exposes a stable schema-versioned plan containing generated paths, Cargo/Xcode argument arrays, environment overrides, the selected asset packaging mode, the executable staging copy, and expected artifact path. A dry run returns this plan with no artifact or validation claim and performs no generation/build after read-only discovery.
Developer Experience 0.2 asset evidence
On the current Xcode 26.6 host, simctl reports no installed Simulator runtimes. The real smoke build therefore selected SdkOnlyResources and produced an arm64 .app. Independent inspection confirmed exact icon/splash bytes, bundle metadata, the Cargo Mach-O UUID, the generated runtime framework, and strict/deep ad-hoc signatures. The CompiledCatalog project/catalog/cache path is implemented and tested, but a complete Assets.car application artifact was not produced in this environment.
Verified environment
At commit 8ed0192, Platform artifacts run 30719811812 built and validated RustFerry-named arm64 Starter and Kitchen Sink .app bundles. FerryRuntimeBridge.framework, its required exports and application hook, WidgetKit and ActivityKit .appex products, Activity framework linkage, signatures, sealed resources, and application-group entitlements passed inspection. Before the rename, the equivalent pipeline also built and validated arm64 base, Slint 1.17.1, and extension-bearing .app artifacts with Xcode 26.6 and the iPhoneSimulator 26.5 SDK on 2026-08-01. Neither validation used a Simulator runtime or device, so install, launch, callbacks, UI, and other runtime interaction remain unvalidated.