Skip to content

Commit

Permalink
Swift package book chapter (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jomy10 committed Feb 17, 2022
1 parent ecc6704 commit 4d0a18f
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// We expose this to the `rust_function_return_swift_type.rs` test.
class SomeSwiftType {
public class SomeSwiftType {
var text: String

init() {
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Building](./building/README.md)
- [Xcode + Cargo](./building/xcode-and-cargo/README.md)
- [swiftc + Cargo](./building/swiftc-and-cargo/README.md)
- [Swift Package](./building/swift-package/README.md)

- [The Bridge Module](./bridge-module/README.md)
- [extern "Rust"](./bridge-module/extern-rust/README.md)
Expand Down
253 changes: 253 additions & 0 deletions book/src/building/swift-package/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Bundling Rust code as a Swift package

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

Note that Swift Packages that contain native libraries only work on Apple hardware.

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

## Project setup

```bash
mkdir rust-swift-package && cd rust-swift-package
```

### Rust project setup

```bash
cargo new my-rust-lib --lib
cd my_rust_lib
```

```toml
# my-rust-lib/Cargo.toml

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

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

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

In `src/lib.rs`, add the following:

```rust
// my-rust-lib/src/lib.rs
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn hello_rust() -> String;
}
}

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

Add a new `build.rs` file (`touch build.rs`):
```rust
// my-rust-lib/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"));
}
```

Build the project for the desired platforms:

```bash
export SWIFT_BRIDGE_OUT_DIR="$(pwd)/generated"
cargo build --target x86_64-apple-darwin
cargo build --target aarch64-apple-ios
cargo build --target x86_64-apple-ios
```

## Creating the XCFramework

Go back to the root of the project and make a new directory `cd ..`.

```bash
mkdir MyFramework && cd $_
```

Copy the generated libraries and the headers to this folder:
```bash
mkdir include
touch include/module.modulemap
cp ../my-rust-lib/generated/SwiftBridgeCore.h ./include
cp ../my-rust-lib/generated/my_rust_lib/my_rust_lib.h ./includ
mkdir ios
cp ../my-rust-lib/target/aarch64-apple-ios/debug/libmy_rust_lib.a ./ios
mkdir macos
cp ../my-rust-lib/target/x86_64-apple-darwin/debug/libmy_rust_lib.a ./macos
mkdir simulator
cp ../my-rust-lib/target/x86_64-apple-ios/debug/libmy_rust_lib.a ./simulator
```

This should result in the follwing folder structure:
```
MyFramework
├── include
│ ├── SwiftBridgeCore.h
│ ├── module.modulemap
│ └── my_rust_lib.h
├── ios
│ └── libmy_rust_lib.a
├── macos
│ └── libmy_rust_lib.a
└── simulator
└── libmy_rust_lib.a
```

Edit `include/module.modulemap`:

```modulemap
module MyRustLib {
header "my_rust_lib.h"
header "SwiftBridgeCore.h"
export *
}
```

Now it is time to build the xcframework:

```bash
xcodebuild -create-xcframework \
-library simulator/libmy_rust_lib.a \
-headers include \
-library ios/libmy_rust_lib.a \
-headers include \
-library macos/libmy_rust_lib.a \
-headers include \
-output MyRustLib.xcframework
```

*The order of the `library` tags is important, but we don't currently know why*

## Creating the Swift package

Go back to the root of the project (`cd ..`) and create a new Swift package:

```bash
mkdir MySwiftPackage && cd MySwiftPackage
```

Now either do `mkdir -r Sources/MySwiftPackage` and `touch Package.swift`, or use `swift package init --type library`.

Copy the xcframework and generated swift files to this folder:
```bash
cp -r ../MyFramework/MyRustLib.xcframework ./
cp ../my-rust-lib/generated/SwiftBridgeCore.swift Sources/MySwiftPackage
cp ../my-rust-lib/generated/my_rust_lib/my_rust_lib.swift Sources/MySwiftPackage
```

The folder structure should be:
```
MySwiftPackage
├── Sources
│ └── MySwiftPackage
│ └── SwiftBridgeCore.swift
│ └── my_rust_lib.swift
├── MyRustLib.b.xcframework
└── Package.swift
```

Add the framework as a binary target to `Package.swift`:
```swift
// MySwiftPackage/Package.swift

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

],
targets: [
.binaryTarget(
name: "MyRustLib",
path: "MyRustLib.xcframework"
),
.target(
name: "MySwiftPackge",
dependencies: ["MyRustLib"]),
]
)
```

<!--TODO: better way of dealing with this instead of editing the generated files-->
We will need to import our rust library in `my_rust_lib.swift` and `SwiftBridgeCore.swift`:

```swift
// MySwiftPackage/Sources/MySwiftPackage/SwiftBridgeCore.swift
import MyRustLib
```

```swift
// MySwiftPackage/Sources/MySwiftPackage/my_rust_lib.swift
import MyRustLib
```

## Using the Swift Package

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

### Example: MacOS executable
Here is an example of an executable project located in `rust-swift-project/testPackage`.

```swift
// testPackage/Package.swift

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

```swift
// testPackage/Sources/testPackage/main.swift
import MySwiftPackage

print(hello_rust().toString())
```

```
$ swift run
Hello Rust!
```

### Example: iOS app

To add the package to an iOS app in XCode, go to the target's general panel, click the `+` button in the `Frameworks, Libraries, and Embedded Content` section. Then, click `Add Other` and choose `Add Package Dependency`.

Import and use it in the same way as the executable.
14 changes: 7 additions & 7 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,19 @@ fn conform_to_vectorizable(swift_ty: &str, rust_ty: &str) -> String {
format!(
r#"
extension {swift_ty}: Vectorizable {{
static func vecOfSelfNew() -> UnsafeMutableRawPointer {{
public static func vecOfSelfNew() -> UnsafeMutableRawPointer {{
__swift_bridge__$Vec_{rust_ty}$new()
}}
static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {{
public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {{
__swift_bridge__$Vec_{rust_ty}$_free(vecPtr)
}}
static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {{
public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {{
__swift_bridge__$Vec_{rust_ty}$push(vecPtr, value)
}}
static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {{
public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {{
let val = __swift_bridge__$Vec_{rust_ty}$pop(vecPtr)
if val.is_some {{
return val.val
Expand All @@ -164,7 +164,7 @@ extension {swift_ty}: Vectorizable {{
}}
}}
static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
let val = __swift_bridge__$Vec_{rust_ty}$get(vecPtr, index)
if val.is_some {{
return val.val
Expand All @@ -173,7 +173,7 @@ extension {swift_ty}: Vectorizable {{
}}
}}
static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
let val = __swift_bridge__$Vec_{rust_ty}$get_mut(vecPtr, index)
if val.is_some {{
return val.val
Expand All @@ -182,7 +182,7 @@ extension {swift_ty}: Vectorizable {{
}}
}}
static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {{
public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {{
__swift_bridge__$Vec_{rust_ty}$len(vecPtr)
}}
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ public class SomeType: SomeTypeRefMut {
}
}
extension SomeType {
func a() {
public func a() {
__swift_bridge__$SomeType$a({isOwned = false; return ptr;}())
}
func b() {
public func b() {
__swift_bridge__$SomeType$b({isOwned = false; return ptr;}())
}
}
Expand All @@ -64,11 +64,11 @@ public class SomeTypeRefMut: SomeTypeRef {
}
}
extension SomeTypeRefMut {
func e() {
public func e() {
__swift_bridge__$SomeType$e(ptr)
}
func f() {
public func f() {
__swift_bridge__$SomeType$f(ptr)
}
}
Expand All @@ -80,11 +80,11 @@ public class SomeTypeRef {
}
}
extension SomeTypeRef {
func c() {
public func c() {
__swift_bridge__$SomeType$c(ptr)
}
func d() {
public func d() {
__swift_bridge__$SomeType$d(ptr)
}
}
Expand Down

0 comments on commit 4d0a18f

Please sign in to comment.