Skip to content

Commit 7afd574

Browse files
committed
iOS Example: Fix the build to make it work again
This includes: * Correctly calling `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 d5d88ed commit 7afd574

File tree

3 files changed

+81
-70
lines changed

3 files changed

+81
-70
lines changed

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 -- \\\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: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ 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
13+
```
14+
15+
## Install `uniffi-bindgen`
16+
17+
```sh
18+
cargo install uniffi_bindgen
1319
```
1420

1521
## Associate the iOS project with the Rust project
@@ -32,8 +38,8 @@ $HOME/.cargo/bin/uniffi-bindgen generate "$INPUT_FILE_PATH" --language swift --o
3238
These will output two files Xcode is interested in.
3339

3440
```sh
41+
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)FFI.h
3542
$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).swift
36-
$(DERIVED_FILE_DIR)/uniffi_$(INPUT_FILE_BASE)-Bridging-Header.h
3743
```
3844

3945
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 +55,89 @@ The header file is a descriptor of the C API to a library that hasn't been built
4955
#ifndef IOSApp_Bridging_Header_h
5056
#define IOSApp_Bridging_Header_h
5157

52-
#import "todolist-Bridging-Header.h"
58+
#import "todolistFFI.h"
5359

5460
#endif /* IOSApp_Bridging_Header_h */
5561
```
5662

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

5965
```h
60-
#import "todolist-Bridging-Header.h"
66+
#import "todolistFFI.h"
6167
```
6268

6369
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.
6470

6571
## Configure `cargo` to build the crate as a static library
6672

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

6975
```toml
7076
[lib]
71-
crate-type = ["staticlib", "cdylib"]
7277
name = "uniffi_todolist"
7378
```
7479

7580
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`.
7681

7782
## Tell Xcode how to build the Rust project.
7883

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

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

8891
```sh
89-
bash $SRCROOT/xc-universal-binary.sh libuniffi_todolist.a uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION
92+
bash $SRCROOT/xc-universal-binary.sh uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION
9093
```
9194

9295
In this case we constructed the command:
9396

9497
```sh
95-
xc-universal-binary.sh <STATIC_LIB_NAME> <FFI_TARGET> <WORKSPACE_PATH> <BUILD_CONFIGURATION>"
98+
xc-universal-binary.sh <FFI_TARGET> <WORKSPACE_PATH> <BUILD_CONFIGURATION>"
9699
```
97100
98101
by making:
99102
100-
* `STATIC_LIB_NAME` from the `lib` `name` above: `uniffi_todolist` --> `libuniffi_todolist.a`
101103
* `FFI_TARGET` from the `package` `name` above: `uniffi-example-todolist`.
102104
103105
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.
104106
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.
107+
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.
108+
This includes using `uniffi-bindgen` to generate the Rust scaffolding so we can go from C to Rust.
111109
112110
## Tell Xcode where the universal library is
113111
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`.
112+
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`.
115113
116114
1. In Xcode, click on the project in the Project Navigator.
117115
2. In the main window, select the app's main target, and then select "Build Settings".
118116
3. Search for `Library Search Paths`.
119-
4. Add paths to where lipo constructed the universal binaries for each of `Debug` and `Release`.
117+
4. Add paths per OS and architecture.
118+
The OS specific part can be configured in the Xcode project editor,
119+
but the architecture specific part has to be added by manually editing the `project.pbxproj` file.
120+
Open `project.pbxproj` in your project direcotry and add the following in the `Debug` section:
120121
121-
```sh
122-
$(SRCROOT)/../../../target/universal/debug
123-
$(SRCROOT)/../../../target/universal/release
124-
```
122+
```
123+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/debug";
124+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/debug";
125+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/debug";
126+
```
127+
128+
The path should be a relative path pointing into the `target` directory created by `cargo`.
129+
Add a similar block in the `Release` section and adjust the paths accordingly.
125130
126131
## Back to code.
127132
128-
By now, we should've
133+
By now, we should've
129134
130135
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.
131136
2. Configured Xcode to find the header.
132137
3. Configured cargo to build a static library version of the crate.
133138
4. Used `uniffi-bindgen` to generate a C API for the Rust crate.
134139
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.
140+
7. Configured Xcode to find the different static libraries.
137141
138142
By now, we should be able to hit Run in Xcode and build something.
139143

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

0 commit comments

Comments
 (0)