Address feedback
This commit is contained in:
parent
eb86f20875
commit
d15bea31e9
|
@ -15,7 +15,8 @@ excerpt: |
|
||||||
into an iOS application.
|
into an iOS application.
|
||||||
---
|
---
|
||||||
|
|
||||||
(“This Week in Glean” is a series of blog posts that the Glean Team at Mozilla is using to try to communicate better about our work. They could be release notes, documentation, hopes, dreams, or whatever: so long as it is inspired by Glean.)
|
(“This Week in Glean” is a series of blog posts that the Glean Team at Mozilla is using to try to communicate better about our work.
|
||||||
|
They could be release notes, documentation, hopes, dreams, or whatever: so long as it is inspired by Glean.)
|
||||||
All "This Week in Glean" blog posts are listed in the [TWiG index](https://mozilla.github.io/glean/book/appendix/twig.html)
|
All "This Week in Glean" blog posts are listed in the [TWiG index](https://mozilla.github.io/glean/book/appendix/twig.html)
|
||||||
(and on the [Mozilla Data blog](https://blog.mozilla.org/data/category/glean/)).
|
(and on the [Mozilla Data blog](https://blog.mozilla.org/data/category/glean/)).
|
||||||
This article is [cross-posted on the Mozilla Data blog][datablog].
|
This article is [cross-posted on the Mozilla Data blog][datablog].
|
||||||
|
@ -26,7 +27,7 @@ This article is [cross-posted on the Mozilla Data blog][datablog].
|
||||||
|
|
||||||
We ship the Glean SDK for multiple platforms, one of them being iOS applications.
|
We ship the Glean SDK for multiple platforms, one of them being iOS applications.
|
||||||
Previously I talked about [how we got it to build on the Apple ARM machines](/2021/04/16/rustc-ios-and-an-m1/).
|
Previously I talked about [how we got it to build on the Apple ARM machines](/2021/04/16/rustc-ios-and-an-m1/).
|
||||||
Today we will take a closer look at how we bundle a Rust static library into an iOS application.
|
Today we will take a closer look at how to bundle a Rust static library into an iOS application.
|
||||||
|
|
||||||
The Glean SDK project was set up in 2019 and we have evolved its project configuration over time.
|
The Glean SDK project was set up in 2019 and we have evolved its project configuration over time.
|
||||||
A lot has changed in Xcode since then, so for this article we're starting with a fresh Xcode project, a fresh Rust library and put it all together step by step.
|
A lot has changed in Xcode since then, so for this article we're starting with a fresh Xcode project, a fresh Rust library and put it all together step by step.
|
||||||
|
@ -116,18 +117,19 @@ pub extern "C" fn shipping_rust_addition(a: c_int, b: c_int) -> c_int {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `no_mangle` ensures the name lands in the compiled library as is
|
The `no_mangle` ensures the name lands in the compiled library as-is
|
||||||
and the `extern "C"` makes sure it uses the right ABI.
|
and the `extern "C"` makes sure it uses the right ABI.
|
||||||
|
|
||||||
We now got a Rust library, exporting a C-ABI compatible interface.
|
We now have a Rust library exporting a C-ABI compatible interface.
|
||||||
We can now consume this in our iOS application.
|
We can now consume this in our iOS application.
|
||||||
|
|
||||||
## The Xcode parts
|
## The Xcode parts
|
||||||
|
|
||||||
Before we can use the code we need a bit more setup.
|
Before we can use the code we need a bit more setup.
|
||||||
|
Strap in, there's a lot of fiddly manual steps now[^1].
|
||||||
|
|
||||||
We start by linking against the `libshipping_rust_ffi.a` library.
|
We start by linking against the `libshipping_rust_ffi.a` library.
|
||||||
In your Xcode project open your target configuration[^1],
|
In your Xcode project open your target configuration[^2],
|
||||||
go to "Build Phases", then look for "Link Binary with Libraries".
|
go to "Build Phases", then look for "Link Binary with Libraries".
|
||||||
Add a new one, in the popup select "Add files" on the bottom left
|
Add a new one, in the popup select "Add files" on the bottom left
|
||||||
and look for the `target/debug/libshipping_rust_ffi.a` file.
|
and look for the `target/debug/libshipping_rust_ffi.a` file.
|
||||||
|
@ -135,16 +137,17 @@ Yes, that's actually for the wrong target. This is just for the name, we'll fix
|
||||||
Go to "Build Settings" and search for "Library Search Paths".
|
Go to "Build Settings" and search for "Library Search Paths".
|
||||||
It probably has the path to file in there right now for both `Debug` and `Release` builds.
|
It probably has the path to file in there right now for both `Debug` and `Release` builds.
|
||||||
Remove that one for `Debug`, then add a new row by clicking the small `+` symbol.
|
Remove that one for `Debug`, then add a new row by clicking the small `+` symbol.
|
||||||
Select any matcher, e.g. `Any Driverkit`. It doesn't matter what value you give it.
|
Select the `Any Driverkit` matcher.
|
||||||
We will manually modify that entry next.
|
It doesn't matter which matcher you choose or what value you give it,
|
||||||
|
but when we overwrite this manually in the next step I'll assume you chose `Any Driverkit`.
|
||||||
Do the same for the `Release` configuration.
|
Do the same for the `Release` configuration.
|
||||||
|
|
||||||
Once that's done, save your project and go back to your project directory.
|
Once that's done, save your project and go back to your project directory.
|
||||||
We will modify the project configuration to have Xcode look for the library based on the target it is building for[^2].
|
We will modify the project configuration to have Xcode look for the library based on the target it is building for[^3].
|
||||||
Open up `ShippingRust.xcodeproj/project.pbxproj` in a text editor,
|
Open up `ShippingRust.xcodeproj/project.pbxproj` in a text editor,
|
||||||
then search for the first line with `"LIBRARY_SEARCH_PATHS[sdk=driverkit*]"`.
|
then search for the first line with `"LIBRARY_SEARCH_PATHS[sdk=driverkit*]"`.
|
||||||
It should be in a section saying `/* Debug */`.
|
It should be in a section saying `/* Debug */`.
|
||||||
Remove that line and add 3 new ones:
|
Remove the `LIBRARY_SEARCH_PATHS` line and add 3 new ones:
|
||||||
|
|
||||||
```
|
```
|
||||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/target/aarch64-apple-ios/debug";
|
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/target/aarch64-apple-ios/debug";
|
||||||
|
@ -160,14 +163,16 @@ Look for the next line with `"LIBRARY_SEARCH_PATHS[sdk=driverkit*]"`, now in a `
|
||||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/target/x86_64-apple-ios/release";
|
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/target/x86_64-apple-ios/release";
|
||||||
```
|
```
|
||||||
|
|
||||||
Save the file and return back to Xcode.
|
Save the file and return focus back to Xcode.
|
||||||
If you didn't make any typos Xcode should still be able to open your project.
|
If you didn't make any typos Xcode should still have your project open.
|
||||||
In the settings you will find the library search paths as we've just defined them.
|
In the settings you will find the library search paths as we've just defined them.
|
||||||
|
If you messed something up Xcode will complain that it cannot read the project file if you try to go to the settings.
|
||||||
|
|
||||||
Next we need to teach Xcode how to compile Rust code.
|
Next we need to teach Xcode how to compile Rust code.
|
||||||
Once again go to your target settings, selecting the "Build Phases" tab again.
|
Once again go to your target settings, selecting the "Build Phases" tab again.
|
||||||
|
|
||||||
There add a new "Run Script" phase, give it a name (double-click the "Run Script" section header),
|
There add a new "Run Script" phase, give it the name "Build Rust library"
|
||||||
|
(double-click the "Run Script" section header),
|
||||||
and set the command to:
|
and set the command to:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -181,19 +186,26 @@ This is not yet defined, so let's do it first.
|
||||||
Go to the "Build Settings" tab and click the `+` button to add a new "User-Defined Setting".
|
Go to the "Build Settings" tab and click the `+` button to add a new "User-Defined Setting".
|
||||||
Give it the name `buildvariant` and choose a value based on the build variant: `debug` for `Debug` and `release` for `Release`.
|
Give it the name `buildvariant` and choose a value based on the build variant: `debug` for `Debug` and `release` for `Release`.
|
||||||
|
|
||||||
Now we're ready to write the `compile-library.sh` script.
|
Now we need the actual script to build the Rust library for the right targets.
|
||||||
|
It's a bit long to write out, but the logic is not too complex:
|
||||||
|
First we select the Cargo profile to use based on our `buildvariant` (that is whether to pass `--release` or not),
|
||||||
|
then we set up `LIBRARY_PATH` if necessary and finally compile the Rust library for the selected target.
|
||||||
|
Xcode passes the architectures to build in `ARCHS`.
|
||||||
|
It's either `x86_64` for simulator builds on Intel Mac hardware or `arm64`.
|
||||||
|
If it's `arm64` it can be either the simulator or an actual hardware target.
|
||||||
|
Those differ, but we can know which is which from what's in `LLVM_TARGET_TRIPLE_SUFFIX` and select the right Rust target.
|
||||||
|
|
||||||
|
Let's put all of that into a `compile-library.sh` script.
|
||||||
Create a new directory `bin` in your project directory.
|
Create a new directory `bin` in your project directory.
|
||||||
In there create the file with the following content:
|
In there create the file with the following content:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# This should be invoked from inside xcode, not manually
|
|
||||||
if [ "$#" -ne 2 ]
|
if [ "$#" -ne 2 ]
|
||||||
then
|
then
|
||||||
echo "Usage (note: only call inside xcode!):"
|
echo "Usage (note: only call inside xcode!):"
|
||||||
echo "Args: $*"
|
echo "compile-library.sh <FFI_TARGET> <buildvariant>"
|
||||||
echo "path/to/build-scripts/xc-universal-binary.sh <FFI_TARGET> <buildvariant>"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -204,7 +216,7 @@ BUILDVARIANT=$2
|
||||||
|
|
||||||
RELFLAG=
|
RELFLAG=
|
||||||
if [[ "$BUILDVARIANT" != "debug" ]]; then
|
if [[ "$BUILDVARIANT" != "debug" ]]; then
|
||||||
RELFLAG=--release
|
RELFLAG=--release
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -euvx
|
set -euvx
|
||||||
|
@ -246,13 +258,7 @@ for arch in $ARCHS; do
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
It's a bit long, but not too complex:
|
And now we're done with the setup for compiling the Rust library automatically as part of the Xcode project build.
|
||||||
It selects the cargo profile to use (that is whether to pass `--release` or not),
|
|
||||||
sets up the `LIBRARY_PATH` if necessary and finally compiles the Rust library for the selected target.
|
|
||||||
Xcode passes the architectures to build in `ARCHS`.
|
|
||||||
It's either `x86_64` for simulator builds on Intel Mac hardware or `arm64`.
|
|
||||||
If it's `arm64` it can be either the simulator or an actual hardware target.
|
|
||||||
Those differ, but we can know which is which from what's in `LLVM_TARGET_TRIPLE_SUFFIX` and select the right Rust target.
|
|
||||||
|
|
||||||
## The code parts
|
## The code parts
|
||||||
|
|
||||||
|
@ -307,7 +313,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
We simply interpolate the result of `shipping_rust_addition(30, 1)` into the string displayed.
|
We simply interpolate the result of `shipping_rust_addition(30, 1)` into the string displayed.
|
||||||
|
|
||||||
Once we compile and run it in the simulator we succeeded:
|
Once we compile and run it in the simulator we see we've succeeded at satisfying our premise:
|
||||||
|
|
||||||
> Show a non-interactive message to the user with data from a Rust library.
|
> Show a non-interactive message to the user with data from a Rust library.
|
||||||
|
|
||||||
|
@ -321,18 +327,27 @@ This was a lot of setup for calling one simple function.
|
||||||
Luckily this is a one-time setup. From here on you can extend your Rust library, define them in the header file and call them from Swift.
|
Luckily this is a one-time setup. From here on you can extend your Rust library, define them in the header file and call them from Swift.
|
||||||
If you go that route you should really start using [cbindgen] to generate that header file automatically for you.
|
If you go that route you should really start using [cbindgen] to generate that header file automatically for you.
|
||||||
|
|
||||||
|
This time we looked at building an iOS application directly calling a Rust library.
|
||||||
|
That's not actually how Glean works. The Glean Swift SDK itself wraps the Rust library and exposes a Swift library.
|
||||||
|
In a next blog post I'll showcase how we ship that as a Swift package.
|
||||||
|
|
||||||
For Glean we're stepping away from manually writing our FFI functions.
|
For Glean we're stepping away from manually writing our FFI functions.
|
||||||
We're instead migrating our code base to use [UniFFI].
|
We're instead migrating our code base to use [UniFFI].
|
||||||
UniFFI will generate the C API from an API definitions file and also comes with a bit of runtime code to handle conversion between Rust, C and Swift data types for us.
|
UniFFI will generate the C API from an API definitions file and also comes with a bit of runtime code to handle conversion between Rust, C and Swift data types for us.
|
||||||
|
We're not there yet for Glean, but you can try it on your own. [Read the UniFFI documentation][uniffi-docs] and integrate it into your project.
|
||||||
|
It should be possible to extent the setup we done her to also run the necessary steps for UniFFI.
|
||||||
|
Eventually I'll document how we did it as well.
|
||||||
|
|
||||||
[cbindgen]: https://github.com/eqrion/cbindgen
|
[cbindgen]: https://github.com/eqrion/cbindgen
|
||||||
[uniffi]: https://github.com/mozilla/uniffi-rs/
|
[uniffi]: https://github.com/mozilla/uniffi-rs/
|
||||||
|
[uniffi-docs]: https://mozilla.github.io/uniffi-rs/
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Footnotes:_
|
_Footnotes:_
|
||||||
|
|
||||||
[^1]: Click your project name in the tree view on the left. This gets you to the project configuration (backed by the `ShippingRust.xcodeproj/project.pbxproj` file). You should then see the Targets, including your `ShippingRust` target and probably `ShippingRustTests` as well. We need the former.
|
[^1]: And most of these steps are user-interface-dependent and might be different in future Xcode version. :(
|
||||||
[^2]: Previously we would have build a universal library containing the library of multiple targets. That doesn't work anymore now that `arm64` can stand for both the simulator and hardware targets. Thus linking to the individual libraries is the way to go, as the now-deprecated [`cargo-lipo`][cargo-lipo] also points out.
|
[^2]: Click your project name in the tree view on the left. This gets you to the project configuration (backed by the `ShippingRust.xcodeproj/project.pbxproj` file). You should then see the Targets, including your `ShippingRust` target and probably `ShippingRustTests` as well. We need the former.
|
||||||
|
[^3]: Previously we would have built a universal library containing the library of multiple targets. That doesn't work anymore now that `arm64` can stand for both the simulator and hardware targets. Thus linking to the individual libraries is the way to go, as the now-deprecated [`cargo-lipo`][cargo-lipo] also points out.
|
||||||
|
|
||||||
[cargo-lipo]: https://github.com/TimNN/cargo-lipo
|
[cargo-lipo]: https://github.com/TimNN/cargo-lipo
|
||||||
|
|
Loading…
Reference in a new issue