Skip to content

Commit 1c74f74

Browse files
committed
iOS Example: Fix the build to make it work again
This includes: * Correctly calling a custom `uniffi-bindgen` through cargo. * Ditching lipo for individual static libraries per target, that way it easily works on all Mac hardware (x86_64 or arm64) as well as for iOS hardware targets. * Build a `staticlib` using cargo's new `--crate-type` option
1 parent ff3d639 commit 1c74f74

File tree

6 files changed

+100
-72
lines changed

6 files changed

+100
-72
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ members = [
1616
"examples/sprites",
1717
"examples/todolist",
1818
"examples/custom-types",
19+
"examples/app/uniffi-bindgen-cli",
1920

2021
"fixtures/coverall",
2122
"fixtures/callbacks",

examples/app/ios/IOSApp.xcodeproj/project.pbxproj

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"$(SRCROOT)/Generated/$(INPUT_FILE_BASE)FFI.h",
3535
"$(SRCROOT)/Generated/$(INPUT_FILE_BASE).swift",
3636
);
37-
script = "# Generate swift bindings for the todolist rust library.\necho \"Generating files for $INPUT_FILE_PATH\"\n\"$SRCROOT/../../../target/debug/uniffi-bindgen\" generate \"$INPUT_FILE_PATH\" --language swift --out-dir \"$SRCROOT/Generated\"\necho \"Generated files for $INPUT_FILE_BASE in $SRCROOT/Generated\"\n";
37+
script = "# Generate swift bindings for the todolist rust library.\necho \"Generating files for $INPUT_FILE_PATH\"\n$HOME/.cargo/bin/cargo run -p uniffi-bindgen-cli -- \\\n generate \"$INPUT_FILE_PATH\" \\\n --language swift \\\n --out-dir \"$SRCROOT/Generated\"\necho \"Generated files for $INPUT_FILE_BASE in $SRCROOT/Generated\"\n";
3838
};
3939
/* End PBXBuildRule section */
4040

@@ -346,7 +346,7 @@
346346
);
347347
runOnlyForDeploymentPostprocessing = 0;
348348
shellPath = /bin/sh;
349-
shellScript = "bash $SRCROOT/xc-universal-binary.sh libarithmetical.a uniffi-example-arithmetic $SRCROOT/../../.. $CONFIGURATION\n";
349+
shellScript = "bash $SRCROOT/xc-universal-binary.sh uniffi-example-arithmetic $SRCROOT/../../.. $CONFIGURATION\n";
350350
};
351351
CEEB59EB25263ECE003C87D1 /* Build Universal Binary for todolist */ = {
352352
isa = PBXShellScriptBuildPhase;
@@ -364,7 +364,7 @@
364364
);
365365
runOnlyForDeploymentPostprocessing = 0;
366366
shellPath = /bin/sh;
367-
shellScript = "bash $SRCROOT/xc-universal-binary.sh libuniffi_todolist.a uniffi-example-todolist $SRCROOT/../../.. $CONFIGURATION\n";
367+
shellScript = "bash $SRCROOT/xc-universal-binary.sh uniffi-example-todolist $SRCROOT/../../.. $CONFIGURATION\n";
368368
};
369369
/* End PBXShellScriptBuildPhase section */
370370

@@ -543,7 +543,9 @@
543543
"$(inherited)",
544544
"@executable_path/Frameworks",
545545
);
546-
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../../target/universal/debug";
546+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/debug";
547+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/debug";
548+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/debug";
547549
PRODUCT_BUNDLE_IDENTIFIER = org.mozilla.example.uniffi.IOSApp;
548550
PRODUCT_NAME = "$(TARGET_NAME)";
549551
SWIFT_OBJC_BRIDGING_HEADER = "IOSApp-Bridging-Header.h";
@@ -567,7 +569,9 @@
567569
"$(inherited)",
568570
"@executable_path/Frameworks",
569571
);
570-
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../../target/universal/release";
572+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/release";
573+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/release";
574+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/release";
571575
PRODUCT_BUNDLE_IDENTIFIER = org.mozilla.example.uniffi.IOSApp;
572576
PRODUCT_NAME = "$(TARGET_NAME)";
573577
SWIFT_OBJC_BRIDGING_HEADER = "IOSApp-Bridging-Header.h";

examples/app/ios/README.md

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ This will not be a complete tutorial on how to use `uniffi`, just the bits to ge
99
## Install Rust compiler targets with `rustup`
1010

1111
```sh
12-
% rustup target add x86_64-apple-ios aarch64-apple-ios
12+
rustup target add x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim
1313
```
1414

15+
## Creating the bindgen binary
16+
17+
See [Creating the bindgen binary](https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html#foreign-language-bindings)
18+
for a full guide on how to create a uniffi-bindgen library for your project.
19+
20+
This sample project ships with one in `examples/app/uniffi-bindgen-cli`.
21+
1522
## Associate the iOS project with the Rust project
1623

1724
1. In Xcode, right click on the project in the Project Navigator.
@@ -23,17 +30,17 @@ This will not be a complete tutorial on how to use `uniffi`, just the bits to ge
2330
1. In Xcode, click on the project in the Project Navigator.
2431
2. In the main window, select the app's main target, and then select "Build Rules".
2532
3. Add a custom rule, to process sources files with names matching `*.udl`.
26-
4. Use `uniffi-bindgen` on each file to generate the headers and swift scaffolding.
33+
4. Use your `uniffi-bindgen` binary on each file to generate the headers and swift scaffolding.
2734

2835
```sh
29-
$HOME/.cargo/bin/uniffi-bindgen generate "$INPUT_FILE_PATH" --language swift --out-dir "$DERIVED_FILE_DIR"
36+
$HOME/.cargo/bin/cargo run -p uniffi-bindgen -- generate "$INPUT_FILE_PATH" --language swift --out-dir "$DERIVED_FILE_DIR"
3037
```
3138

3239
These will output two files Xcode is interested in.
3340

3441
```sh
42+
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)FFI.h
3543
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).swift
36-
$(DERIVED_FILE_DIR)/uniffi_$(INPUT_FILE_BASE)-Bridging-Header.h
3744
```
3845

3946
The header file is a descriptor of the C API to a library that hasn't been built yet. The Swift file is a Swift facade that calls that C API.
@@ -49,91 +56,89 @@ The header file is a descriptor of the C API to a library that hasn't been built
4956
#ifndef IOSApp_Bridging_Header_h
5057
#define IOSApp_Bridging_Header_h
5158

52-
#import "todolist-Bridging-Header.h"
59+
#import "todolistFFI.h"
5360

5461
#endif /* IOSApp_Bridging_Header_h */
5562
```
5663

57-
The `#import` directive points to the header file generated by `uniffi-bindgen generate` above— `$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)-Bridging-Header.h`
64+
The `#import` directive points to the header file generated by `uniffi-bindgen generate` above— `$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)FFI.h`
5865

5966
```h
60-
#import "todolist-Bridging-Header.h"
67+
#import "todolistFFI.h"
6168
```
6269

6370
This `IOSApp-Bridging-Header.h` tells Xcode to look for the header file; because it's in `DERIVED_FILE_DIR`, Xcode should know where to look.
6471

6572
## Configure `cargo` to build the crate as a static library
6673

67-
1. In the `Cargo.toml` file of the Rust project, add `staticlib` to the `crate-type` list.
74+
1. The build script automatically builds a static library (note: this requires rustc 1.64 or newer)
6875

6976
```toml
7077
[lib]
71-
crate-type = ["staticlib", "cdylib"]
7278
name = "uniffi_todolist"
7379
```
7480

7581
The `package` `name` and the `lib` `name` will be used below. In this case, the package name is `uniffi-example-todolist` and the lib name is `uniffi_todolist`.
7682

7783
## Tell Xcode how to build the Rust project.
7884

79-
#### TODO use a better explanation of what is going on here
80-
8185
1. In Xcode, click on the project in the Project Navigator.
8286
2. In the main window, select the app's main target, and then select "Build Phases".
8387
3. Add a new `Run Script` build phase and move it to the top.
8488
4. Add the script that will build a universal binary for the Rust project.
8589

86-
For this project, we've used a script adapted from the #mozilla/application-services project to build a universal binary with `lipo`.
90+
`uniffi` comes with a script suitable to build a static library for all iOS targets:
8791

8892
```sh
89-
bash $SRCROOT/xc-universal-binary.sh libuniffi_todolist.a uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION
93+
bash $SRCROOT/xc-universal-binary.sh uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION
9094
```
9195

9296
In this case we constructed the command:
9397

9498
```sh
95-
xc-universal-binary.sh <STATIC_LIB_NAME> <FFI_TARGET> <WORKSPACE_PATH> <BUILD_CONFIGURATION>"
99+
xc-universal-binary.sh <FFI_TARGET> <WORKSPACE_PATH> <BUILD_CONFIGURATION>"
96100
```
97101
98102
by making:
99103
100-
* `STATIC_LIB_NAME` from the `lib` `name` above: `uniffi_todolist` --> `libuniffi_todolist.a`
101104
* `FFI_TARGET` from the `package` `name` above: `uniffi-example-todolist`.
102105
103106
The workspace path is where the `Cargo.toml` will resolve the Rust project, and also determine the target directory that `cargo build` and `lipo` will put its artifacts.
104107
105-
This script performs a few steps:
106-
107-
1. Runs `cargo build` to compile the Rust project for the `x86_64-apple-ios` and `aarch64-apple-ios` targets.
108-
* This includes using `uniffi-bindgen` to generate the Rust scaffolding so we can go from C to Rust.
109-
2. Runs `lipo` to combine these libs in to a universal binary.
110-
3. Puts the universal binary in to the `$WORKSPACE_PATH/target/universal` directory.
108+
This script runs `cargo build` to compile the Rust project for the `x86_64-apple-ios`, `aarch64-apple-ios` and `aarch64-apple-ios-sim` targets.
109+
This includes using `uniffi-bindgen` to generate the Rust scaffolding so we can go from C to Rust.
111110
112111
## Tell Xcode where the universal library is
113112
114-
Finally, we need to tell Xcode to look for the universal binary `libuniffi_todolist.a` is, so it can tie it together with the header file `todolist-Bridging-Header.h`.
113+
Finally, we need to tell Xcode to look for the libraries based on the target, so it can tie it together with the header file `todolistFFI.h`.
115114
116115
1. In Xcode, click on the project in the Project Navigator.
117116
2. In the main window, select the app's main target, and then select "Build Settings".
118117
3. Search for `Library Search Paths`.
119-
4. Add paths to where lipo constructed the universal binaries for each of `Debug` and `Release`.
118+
4. Add paths per OS and architecture.
119+
The OS specific part can be configured in the Xcode project editor,
120+
but the architecture specific part has to be added by manually editing the `project.pbxproj` file.
121+
Open `project.pbxproj` in your project direcotry and add the following in the `Debug` section:
120122
121-
```sh
122-
$(SRCROOT)/../../../target/universal/debug
123-
$(SRCROOT)/../../../target/universal/release
124-
```
123+
```
124+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/debug";
125+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/debug";
126+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/debug";
127+
```
128+
129+
The path should be a relative path pointing into the `target` directory created by `cargo`.
130+
Add a similar block in the `Release` section and adjust the paths accordingly.
125131
126132
## Back to code.
127133
128-
By now, we should've
134+
By now, we should've
129135
130136
1. Used `uniffi-bindgen` to generate a Swift file to call a C API header and put them in a place Xcode can find them.
131137
2. Configured Xcode to find the header.
132138
3. Configured cargo to build a static library version of the crate.
133139
4. Used `uniffi-bindgen` to generate a C API for the Rust crate.
134140
5. Used cargo to cross-compile the crate for different targets.
135-
6. Used lipo to combine those crates for different target into a universal binary.
136-
7. Configured Xcode to find the universal binary.
141+
7. Configured Xcode to find the different static libraries.
137142
138143
By now, we should be able to hit Run in Xcode and build something.
139144

examples/app/ios/xc-universal-binary.sh

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,54 +12,57 @@ trap error_help ERR
1212
PATH="$(bash -l -c 'echo $PATH')"
1313

1414
# This should be invoked from inside xcode, not manually
15-
if [[ "${#}" -ne 4 ]]
15+
if [[ "${#}" -ne 3 ]]
1616
then
1717
echo "Usage (note: only call inside xcode!):"
18-
echo "path/to/build-scripts/xc-universal-binary.sh <STATIC_LIB_NAME> <FFI_TARGET> <SRC_ROOT_PATH> <buildvariant>"
18+
echo "path/to/build-scripts/xc-universal-binary.sh <FFI_TARGET> <SRC_ROOT_PATH> <buildvariant>"
1919
exit 1
2020
fi
21-
# e.g. liblogins_ffi.a
22-
STATIC_LIB_NAME=${1}
2321
# what to pass to cargo build -p, e.g. logins_ffi
24-
FFI_TARGET=${2}
25-
# path to app services root
26-
SRC_ROOT=${3}
27-
# buildvariant from our xcconfigs
28-
BUILDVARIANT=$(echo "${4}" | tr '[:upper:]' '[:lower:]')
22+
FFI_TARGET=${1}
23+
# path to source code root
24+
SRC_ROOT=${2}
25+
# buildvariant from our xcconfigs
26+
BUILDVARIANT=$(echo "${3}" | tr '[:upper:]' '[:lower:]')
2927

3028
RELFLAG=
31-
RELDIR="debug"
3229
if [[ "${BUILDVARIANT}" != "debug" ]]; then
3330
RELFLAG=--release
34-
RELDIR=release
3531
fi
3632

37-
TARGETDIR=${SRC_ROOT}/target
33+
if [[ -n "${SDK_DIR:-}" ]]; then
34+
# Assume we're in Xcode, which means we're probably cross-compiling.
35+
# In this case, we need to add an extra library search path for build scripts and proc-macros,
36+
# which run on the host instead of the target.
37+
# (macOS Big Sur does not have linkable libraries in /usr/lib/.)
38+
export LIBRARY_PATH="${SDK_DIR}/usr/lib:${LIBRARY_PATH:-}"
39+
fi
3840

39-
# We can't use cargo lipo because we can't link to universal libraries :(
40-
# https://github.com/rust-lang/rust/issues/55235
41-
LIBS_ARCHS=("x86_64" "arm64")
42-
IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios")
43-
for i in "${!LIBS_ARCHS[@]}"; do
44-
env -i PATH="${PATH}" \
45-
"${HOME}"/.cargo/bin/cargo build --locked -p "${FFI_TARGET}" --lib ${RELFLAG} --target "${IOS_TRIPLES[${i}]}"
46-
done
41+
IS_SIMULATOR=0
42+
if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then
43+
IS_SIMULATOR=1
44+
fi
4745

48-
UNIVERSAL_BINARY=${TARGETDIR}/universal/${RELDIR}/${STATIC_LIB_NAME}
49-
NEED_LIPO=
46+
for arch in $ARCHS; do
47+
case "$arch" in
48+
x86_64)
49+
if [ $IS_SIMULATOR -eq 0 ]; then
50+
echo "Building for x86_64, but not a simulator build. What's going on?" >&2
51+
exit 2
52+
fi
5053

51-
# if the universal binary doesnt exist, or if it's older than the static libs,
52-
# we need to run `lipo` again.
53-
if [[ ! -f "${UNIVERSAL_BINARY}" ]]; then
54-
NEED_LIPO=1
55-
elif [[ "$(stat -f "%m" "${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then
56-
NEED_LIPO=1
57-
elif [[ "$(stat -f "%m" "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then
58-
NEED_LIPO=1
59-
fi
60-
if [[ "${NEED_LIPO}" = "1" ]]; then
61-
mkdir -p "${TARGETDIR}/universal/${RELDIR}"
62-
lipo -create -output "${UNIVERSAL_BINARY}" \
63-
"${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \
64-
"${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}"
65-
fi
54+
# Intel iOS simulator
55+
export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios"
56+
$HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target x86_64-apple-ios
57+
;;
58+
59+
arm64)
60+
if [ $IS_SIMULATOR -eq 0 ]; then
61+
# Hardware iOS targets
62+
$HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target aarch64-apple-ios
63+
else
64+
# M1 iOS simulator -- currently in Nightly only and requires to build `libstd`
65+
$HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target aarch64-apple-ios-sim
66+
fi
67+
esac
68+
done
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "uniffi-bindgen-cli"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[[bin]]
8+
name = "uniffi-bindgen"
9+
path = "uniffi-bindgen.rs"
10+
11+
[dependencies]
12+
uniffi = { path = "../../../uniffi", features = ["cli"] }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
uniffi::uniffi_bindgen_main()
3+
}

0 commit comments

Comments
 (0)