Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a relatively simple way to compile and generate Swift bindings. #1647

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/manual/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [Configuration](./swift/configuration.md)
- [Building a Swift module](./swift/module.md)
- [Integrating with Xcode](./swift/xcode.md)
- [Wrapped in Framework](./swift/framework.md)

# Internals
- [Design Principles](./internals/design_principles.md)
Expand Down
86 changes: 86 additions & 0 deletions docs/manual/src/swift/framework.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Wrapped in Framework

Wrap the Rust crate into an iOS Framework, allowing separate modifications to the related Rust code and UniFFI-generated program configurations for easier integration management and usage in the future.

Overall, you need:

1. Generate an Xcode project file for the Rust crate and compile it into a static library.
2. Create a new iOS Framework project and import the generated target dependencies.
3. Compile UDL file to generate related Swift bindings.
4. Import the generated binding header file into the public header files of the Framework.

## Compile Rust crate using `cargo-xcode`

First, we need to install `cargo-xcode`. This tool can help us generate Xcode project files and compile them into static libraries.

Run the command `cargo install cargo-xcode` to install.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to make it a code block (easier to copy and more obvious)

Suggested change
Run the command `cargo install cargo-xcode` to install.
```
cargo install cargo-xcode
```


We need to modify the `Cargo.toml` file and add crate-type = ["lib", "staticlib"] in the [lib] section. Here you can add other types according to your needs, but only `staticlib` and `cdylib` can be recognized by `cargo-xcode`.
Copy link
Member

@badboy badboy Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
We need to modify the `Cargo.toml` file and add crate-type = ["lib", "staticlib"] in the [lib] section. Here you can add other types according to your needs, but only `staticlib` and `cdylib` can be recognized by `cargo-xcode`.
Ensure your crate builds as a `cdylib` by adding the following to your `Cargo.toml` file:
```toml
[lib]
crate-type = ["lib", "staticlib"]
name = "<library name>"
```

Copy link
Author

@gezihuzi gezihuzi Jul 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestion, I am reviewing the relevant changes. I have found some issues with the description here. staticlib and cdylib correspond to *.a and *.dylib libraries respectively. The staticlib in the crate-type section does not match the described cdylib on documentation, so it needs to be modified.

Details:

Ensure your crate builds as a `staticlib` by adding the following to your `Cargo.toml` file: 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops, yes. That should of course be the same in text and code.


```toml
[lib]
crate-type = ["lib", "staticlib"]
```

Then run `cargo xcode`, which will generate `<rust-project-name>.xcodeproj` file.

## Create a Framework and add dependencies

Create a new iOS Framework project and drag the `<rust-project-name>.xcodeproj` mentioned above into it.

Add `<rust-project-name>-staticlib` to `Build Phases`-`Target Dependencies` in the iOS Framework.

Add `lib<rust-project-name>_static.a` to the `Link Binary With Libraries` in iOS Framework.

## Generate bindings

In the iOS Framework's `Build Rules`, add a `Run Script` to handle `*.udl` and generate the corresponding bindings.

* Add a build rule processing files with names matching *.udl.

* Use something like the following as the custom script:
* `$HOME/.cargo/bin/uniffi-bindgen-cli generate $INPUT_FILE_PATH --language swift --out-dir $INPUT_FILE_DIR/Generated`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't ship this CLI anymore. See https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html
So we need to rephrase that in a way to use a bundled CLI.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniffi-bindgen-cli here is actually a binary file generated by me based on the content in https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html. The code is provided by the documentation, with just the addition of "-cli" to its name. It is used for conveniently calling the uniffi-bindgen generation command with a fixed path in Xcode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but this binary won't be installed into $HOME/.cargo/bin/uniffi-bindgen-cli by default anymore, so we shouldn't recommend that in official documentation.


* Add both the .swift file and the generated bridging header as output files:
* `$(INPUT_FILE_DIR)/Generated/$(INPUT_FILE_BASE).swift`
* `$(INPUT_FILE_DIR)/Generated/$(INPUT_FILE_BASE)FFI.h`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

* Add your `.udl` file to the `Compile Sources` build phase for your framework, so that Xcode will process it using the new build rule and will include the resulting outputs in the build.

You do not need to add the generated Swift code to the list of `Compile Sources` and should not attempt to compile it explicitly; Xcode will figure out what it needs to do with this code based on it being generated from the Build Rule for your `.udl` file.

## Import header files

Import the generated header file in `<framework-name>.h` of iOS Framework.

```c
#import <Foundation/Foundation.h>

//! Project version number for <framework-name>.
FOUNDATION_EXPORT double <framework-name>VersionNumber;

//! Project version string for <framework-name>.
FOUNDATION_EXPORT const unsigned char <framework-name>VersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <framework-name>/PublicHeader.h>

#import "Generated/<rust-project-name>FFI.h"

```

For this to work without complaint from Xcode, you also need to add the generated header file as a Public header in the "Headers" build phase of your project (which is why it's useful to generate this file somewhere in your source tree, rather than in a temporary build directory).

## Examples

After completing the above steps, you can use your Framework by dragging it into your project and importing `<framework-name>`.

It also provides an [ios-with-framework](examples/app/ios-with-framework/) that you can check out under examples/app/ios-with-framework/.

* `ios-with-framework`: Root directory of the sample project

* `iOS-UniFFI-Framework`: Includes handling of compiling Rust crates into static libraries and generating bindings.
* `iOS-UniFFI-App`: Includes the use of the generated framework.
Comment on lines +76 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project does not build as is for me.
Let's rip that out for now and see if maybe we can add it as an example later. That way we can at least land the initial docs now.

Copy link
Author

@gezihuzi gezihuzi Jul 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's caused by the inability to invoke uniffi-bindgen-cli

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I actually fixed that.
I'd still say let's not overiterate on that, we can add the project in a followup PR


## Known issues

* If you encounter an error when generating bindings, please check if `uniffi-bindgen-cli` is installed. If the path is incorrect, please modify the script path in `Build Rules`.
Comment on lines +83 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above we don't ship that CLI anymore as is.

Loading