A cargo subcommand for compiling .ipa and .app files, for iOS and macOS (respectively).
cargo-ipa exclusively supports macOS, and will only ever support macOS. Because Apple is Apple, you can't compile iOS apps on non-mac systems (doing so requires the iOS SDK, which is closed source and only on macOS).
cargo-ipa isn't in a "finished" state yet, so it's not on crates.io. You can, however, still install from Git:
cargo install --git https://github.com/loki-chat/cargo-ipa.git
To install with swift-bridge integration:
cargo install --git https://github.com/loki-chat/cargo-ipa.git --feature swift-bridge
Currently, cargo-ipa can only build unsigned IPA and .app files. Hopefully, in the future, it'll support signing and installing IPAs as well.
For binary projects, just run cargo ipa build. For library examples, run cargo ipa build -e <example_name> (or --example instead of -e).
By default, cargo-ipa will make 4 files: an IPA for x86_64 iOS, an IPA for aarch64 iOS, an app for x86_64 macOS, and an app for aarch64 macOS. You can limit these with the -p/--platform and -a/--architecture flags; you can set the platform to just iOS or just macOS, and the architecture to just x86_64 or just aarch64 devices. For example, to compile your cool app for M1 (and later) macs, you could run:
cargo ipa build --platform macos --architecture aarch64
(or, if you're a normal person and find architecture impossible to spell: cargo ipa build -p macos -a aarch64.)
In the Info.plist, Apple requires both an app name (as an ID, eg "my-app"), and a human readable name (eg "My App"). cargo-ipa will set the ID to the package name in Cargo.toml, but needs a human readable name. You can either set this via the name setting (see Configuration), or pass the -n (or --name) argument to cargo-ipa.
cargo-ipa reads settings directly from your Cargo.toml. Simply add a package.metadata.cargo-ipa section in your Cargo.toml, and it'll read all the settings from there. For example, to set your app's name, you could add this to your Cargo.toml:
[package.metadata.cargo-ipa]
name = "My App"Every macOS/iOS app has an Info.plist file. By defualt, cargo-ipa will automatically set these settings in the Info.plist:
CFBundleExecutable: This is the name of the executable in the app. This gets set to the project's name (or library example's name, if you're compiling an example).CFBundleIdentifier: This is the bundle identifier for the app. By default, cargo-ipa sets this tocom.<binary-name>, where binary name is the project's name or library example's name.CFBundleName: This is a human-readable bundle identifier, and what appears as the app's name on the device's home screen/app list. cargo-ipa will load this from the-n/--nameargument, or the name setting in your configuration.CFBundleVersion: This is the app's version. cargo-ipa will load this from the version listed in yourCargo.toml.CFBundleShortVersionString: This is basically the same as above, but requires a<major version>.<minor version>.<patch version>format. cargo-ipa will load this fromCargo.tomljust like above; this can lead to issues if theCargo.tomlversion is not in the correct format, and in the future, cargo-ipa should be able to convert yourCargo.tomlversion into that format, so it's in the valid.
cargo-ipa won't set any other settings in the Info.plist. To set (or override) more settings in the Info.plist, you can use the properties section of cargo-ipa's configuration, like so:
[package.metadata.cargo-ipa.properties]
CFBundleShortVersionString = 0.1.0Since many Apple APIs still rely on Swift code, cargo-ipa can integrate with swift-bridge to compile Swift and Rust together. To use it, you need to install cargo-ipa with the swift-bridge feature, and then configure the swift-library and swift-bridges settings.
swift-library is literally just the folder that has your library in it; for example, if your project has my-swift-library/Sources/my-swift-library in it, then set swift-library = "my-swift-library in your Cargo.toml.
swift-bridges is a list of Rust files that actually use swift-bridge's FFI (via the #[swift_bridge::bridge] macro). These are indexed from the project root (the folder that houses Cargo.toml). For example, if you have a file called swift.rs that handles FFI, your setting will probably look like this: swift-bridges = ["src/swift.rs"].
Swift-bridge integration is disabled by default because it adds lots of dependencies, which hurts build times.
name: A string representing the app's name, as it appears in the app list or on the home screen. See App Name.properties: A table of keys/values to put in theInfo.plistfile. See Info.plist Overridesswift-bridges: A list of Rust files to compile using swift-bridge. See Swift-bridge integration.swift-library: The Swift package to compile using swift-bridge. See Swift-bridge integration.
Here's an example of all the cargo-ipa settings:
[package]
name = "my-app"
description = "My fancy app, compiled for iOS with cargo-ipa"
authors = ["Me"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/loki-chat/cargo-ipa"
[package.metadata.cargo-ipa]
name = "My App"
swift-bridges = ["src/swift.rs"]
swift-library = "swift-library"
[package.metadata.cargo-ipa.properties]
MinimumOSVersion = "14.0.0"Currently, cargo-ipa doesn't support signing the IPA files. It'll just spit out a raw, unsigned IPA file, which you can sign using any existing IPA signer. IPA will hopefully be added in the future.
If you need to link against a Swift library in build.rs, you can add cargo-ipa as a build dependency for macOS &/ iOS, then run cargo_ipa::compile_and_link_swift() in your build.rs. cargo-ipa will read what package to compile from your Cargo.toml, just like normal. This does not require the swift-bridge feature; to cut down compile times, you can generate your bindings beforehand and then use this afterwards to always link with those same bindings.
Example:
// In build.rs
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn main() {
cargo_ipa::compile_and_link_swift().unwrap();
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
fn main() {}# In Cargo.toml
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.build-dependencies]
cargo-ipa = { git = "https://github.com/loki-chat/cargo-ipa.git" }Note that this will only compile and link a Swift package; if you also need to generate FFI bindings, you'll need to run cargo_ipa::generate_bindings(), which requires the swift-bridge feature. To do that:
// In build.rs
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn main() {
cargo_ipa::generate_bindings().unwrap();
cargo_ipa::compile_and_link_swift().unwrap();
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
fn main() {}# In Cargo.toml
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.build-dependencies]
cargo-ipa = { git = "https://github.com/loki-chat/cargo-ipa.git", features = ["swift-bridge"] }This is not recommended, because the dependencies added by swift-bridge will hurt compile times.