Creating Swift Packages

In this chapter we'll walk through bundling your Rust library into a Swift Package.

Swift Packages that contain binary dependencies are only available on Apple platforms.

You cannot bundle your Rust code into a Swift Package if you plan to target Linux, Windows or any other non-Apple target.

Instead, use a building approach from one of the other building chapters.

Project setup

mkdir my-rust-lib && cd my-rust-lib
# Cargo.toml

[package]
name = "my-rust-lib"

[lib]
crate-type = ["staticlib"]

[build-dependencies]
swift-bridge-build = "0.1"

[dependencies]
swift-bridge = "0.1"

In src/lib.rs, add the following:


#![allow(unused)]
fn main() {
// src/lib.rs

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn hello_rust() -> String;
    }
}

fn hello_rust() -> String {
    String::from("Hello from Rust!")
}
}

Create a new build.rs file with the following contents:

touch build.rs
// build.rs

use std::path::PathBuf;

fn main() {
    let out_dir = PathBuf::from("./generated");

    let bridges = vec!["src/lib.rs"];
    for path in &bridges {
        println!("cargo:rerun-if-changed={}", path);
    }

    swift_bridge_build::parse_bridges(bridges)
        .write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}

Create a new bash script for building our Rust native libraries along with a folder that we'll write our parsed bridges too.

touch build-rust.sh
chmod +x build-rust.sh
mkdir generated
# build-rust.sh

#!/bin/bash

set -e

THISDIR=$(dirname $0)
cd $THISDIR

# Build the project for the desired platforms:
cargo build --target x86_64-apple-darwin
cargo build --target aarch64-apple-darwin
mkdir -p ./target/universal-macos/debug

lipo \
    ./target/aarch64-apple-darwin/debug/libmy_rust_lib.a \
    ./target/x86_64-apple-darwin/debug/libmy_rust_lib.a -create -output \
    ./target/universal-macos/debug/libmy_rust_lib.a

cargo build --target aarch64-apple-ios

cargo build --target x86_64-apple-ios
cargo build --target aarch64-apple-ios-sim
mkdir -p ./target/universal-ios/debug

lipo \
    ./target/aarch64-apple-ios-sim/debug/libmy_rust_lib.a \
    ./target/x86_64-apple-ios/debug/libmy_rust_lib.a -create -output \
    ./target/universal-ios/debug/libmy_rust_lib.a

Install Rust toolchains for the desired platforms:

rustup target add x86_64-apple-darwin aarch64-apple-darwin aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

Run the script to build our Rust libraries:

./build-rust.sh

We can now use the API or the CLI to package the generated bridging code and the Rust libraries into a Swift Package.

API

Here's an example of using the API to package up our generated bridging code and our Rust libraries into a Swift Package.

use std::path::PathBuf;
use std::collections::HashMap;
use swift_bridge_build::{CreatePackageConfig, ApplePlatform};
fn main() {
    swift_bridge_build::create_package(CreatePackageConfig {
        bridge_dir: PathBuf::from("./generated"),
        paths: HashMap::from([
            (ApplePlatform::IOS, "target/aarch64-apple-ios/debug/libmy_rust_lib.a".into()),
            (ApplePlatform::Simulator, "target/universal-ios/debug/libmy_rust_lib.a".into()),
            (ApplePlatform::MacOS, "target/universal-macos/debug/libmy_rust_lib.a".into()),
        ]),
        out_dir: PathBuf::from("MySwiftPackage"),
        package_name: PathBuf::from("MySwiftPackage")
    });
}

CLI

You can use the swift-bridge CLI's create-package command in order to create a Swift Package.

First, install the CLI.

cargo install -f swift-bridge-cli
swift-bridge-cli --help

Then, run the following to package up your generated bridges and your Rust libraries into a Swift Package.

swift-bridge-cli create-package \
  --bridges-dir ./generated \
  --out-dir MySwiftPackage \
  --ios target/aarch64-apple-ios/debug/libmy_rust_lib.a \
  --simulator target/universal-ios/debug/libmy_rust_lib.a \
  --macos target/universal-macos/debug/libmy_rust_lib.a \
  --name MySwiftPackage

Using the Swift Package

We now have a Swift Package (in the MySwiftPackage directory) which we can include in other projects using the Swift Package Manager.

Using the package in an Xcode project

To add the package to an iOS app in Xcode, first open your project's .xcworkspace file.

Next, go to the package dependencies panel, and click on + -> Add Local -> and select the MySwiftPackage directory.

Next, go to the target's general panel and click the + button in the Frameworks, Libraries, and Embedded Content section.

Select Workspace -> MySwiftPackage -> MySwiftPackage.

Import and use it in the same way as the executable.

Using the package in an executable Swift project

Here is an example of an executable Swift project that depends on our newly created MySwiftPackage.

mkdir SwiftProject
touch SwiftProject/Package.swift
mkdir -p SwiftProject/Sources/SwiftProject
touch SwiftProject/Sources/SwiftProject/main.swift

Add these contents to SwiftProject/Package.swift.

// SwiftProject/Package.swift

// swift-tools-version:5.5.0
import PackageDescription
let package = Package(
    name: "SwiftProject",
    dependencies: [
        .package(path: "../MySwiftPackage")
    ],
    targets: [
        .executableTarget(
            name: "SwiftProject",
            dependencies: [
                .product(name: "MySwiftPackage", package: "MySwiftPackage")
            ])
    ]
)

And then add this to our SwiftProject/Sources/SwiftProject/main.swift file.

// SwiftProject/Sources/SwiftProject/main.swift

import MySwiftPackage

print(hello_rust().toString())

And now you can run your Swift project that depends on your Rust based Swift Package:

cd SwiftProject
swift run
# You should see "Hello from Rust!" in your terminal.