Cargo runner for Apple targets
Easily bundle, sign and launch binaries on Apple targets, including on the simulator and on real devices.
Install with:
cargo install cargo-apple-runnerAnd add the following to your project's .cargo/config.toml:
[target.'cfg(target_vendor = "apple")']
runner = "cargo-apple-runner"Now you can test and run your (GUI) applications on the iOS simulator with:
cargo test --target aarch64-apple-ios-sim --target aarch64-apple-visionos-sim
cargo run --target aarch64-apple-ios-sim
# etc.Or on Mac Catalyst with:
cargo run --example my_example --target aarch64-apple-ios-macabiHost: Requires at least macOS 10.12, same as rustc.
Targets: macOS, Mac Catalyst, iOS, tvOS, watchOS and visionOS.
Simulators: Uses xcrun simctl, only tested on Xcode 9.2 and above.
Devices: Yet unsupported, will use devicectl (see #1) and fall back to ios-deploy (see #2) on older Xcode.
cargo-apple-runner will inspect your binary, and guess whether it needs to bundle it based on a few factors:
- Whether your binary links AppKit, UIKit, WatchKit and similar system GUI frameworks.
- TODO: Maybe something more?
- TODO: Allow overriding this somehow?
Note that this might mean that for documentation tests to be runnable in parallel with cargo nextest, you might need to use the standalone_crate attribute on GUI tests to avoid these making your other doc tests need to be launched as well.
Most real-world applications will want to modify the data in the application's Info.plist, you can use the embed_plist crate to do so:
embed_plist::embed_plist!("Info.plist");If this is not done, cargo-apple-runner will generate a reasonable Info.plist for you.
cargo-apple-runner will sign your application with whatever signing identity is passed in the CODE_SIGN_IDENTITY environment variable. If not set, it will default to "ad-hoc" signing.
In some cases, you might need to request different entitlements for your application.
You can use the embed_entitlements crate to do so:
embed_entitlements::embed_entitlements!("my_app.entitlements");Note that when building for a real (non-simulator) device, you will need to configure a provisioning profile with those entitlements allowed. On macOS, certain entitlements are allowed by default, see this tech note.
As a small optimization when using entitlements, you can consider adding the following to .cargo/config.toml to reduce link-time (since signing will be done by the runner):
[target.'cfg(all(target_vendor = "apple", not(target_env = "sim")))']
# Signing is done by `cargo-apple-runner`.
rustflags = ["-Clink-arg=-Wl,-no_adhoc_codesign"]Similar to when bundling, cargo-apple-runner will also guess whether it needs to launch your binary, or whether it can simply spawn it.
Spawning is generally more efficient, since it can be done in parallel, while launching must be serialized (as only a single application can be the frontmost application).
Example GitHub Actions workflow that runs tests on macOS, Mac Catalyst, the iOS simulator, the tvOS simulator and the visionOS simulator.
name: CI
permissions:
contents: read
on:
pull_request:
push:
branches:
- main
jobs:
test:
runs-on: macos-latest
strategy:
matrix:
include:
- target: aarch64-apple-darwin
- target: aarch64-apple-ios-macabi
- target: aarch64-apple-ios-sim
simulator: "iPhone 17"
- target: aarch64-apple-tvos-sim
simulator: "Apple TV"
- target: aarch64-apple-visionos-sim
simulator: "Apple Vision Pro"
- target: aarch64-apple-watchos-sim
simulator: "Apple Watch SE 3 (40mm)"
env:
# Configure the job to use `cargo-apple-runner` when launching our binaries.
# (Alternatively, you could commit the `.cargo/config.toml` above).
CARGO_TARGET_AARCH64_APPLE_DARWIN_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_IOS_MACABI_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_IOS_SIM_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_TVOS_SIM_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_VISIONOS_SIM_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_WATCHOS_SIM_RUNNER: cargo-apple-runner
CARGO_TARGET_AARCH64_APPLE_WATCHOS_SIM_RUNNER: cargo-apple-runner
# Make `--target` default to the target from the matrix.
CARGO_BUILD_TARGET: ${{ matrix.target }}
steps:
- uses: taiki-e/checkout-action@v1
- name: Install `cargo-apple-runner`
uses: taiki-e/install-action@cargo-apple-runner
- name: Install Rustup target
run: rustup target add ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
# You can find names of existing simulator devices at:
# https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md#installed-simulators
- name: Start simulator
if: ${{ matrix.simulator }}
run: xcrun simctl boot ${{ matrix.simulator }}
- name: Build
run: cargo build
- name: Test
run: cargo testThis is intended as a development tool only; when deploying on real devices, consider using something else. I can recommend cargo-xcode, this gives the most control and helps with the complex process of notarizing and submitting to the App Store.
Will only supports bundled assets (at least after #5), reading from other directories may fail (we don't copy the entire workspace to the device).
Only a few Cargo environment variables are automatically passed onwards to the simulator, use SIMCTL_CHILD_* to explicitly pass the environment variables you want to pass to the program being run.
This project is trio-licensed under the Zlib, Apache-2.0 or MIT license, at your option.