diff --git a/Cargo.toml b/Cargo.toml index 4589e47f3d..853406bd71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "examples/sprites", "examples/todolist", "examples/custom-types", + "examples/app/uniffi-bindgen-cli", "fixtures/coverall", "fixtures/callbacks", diff --git a/examples/app/ios/IOSApp.xcodeproj/project.pbxproj b/examples/app/ios/IOSApp.xcodeproj/project.pbxproj index 9a138b5f16..45b56cb596 100644 --- a/examples/app/ios/IOSApp.xcodeproj/project.pbxproj +++ b/examples/app/ios/IOSApp.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ "$(SRCROOT)/Generated/$(INPUT_FILE_BASE)FFI.h", "$(SRCROOT)/Generated/$(INPUT_FILE_BASE).swift", ); - 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"; + 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"; }; /* End PBXBuildRule section */ @@ -346,7 +346,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "bash $SRCROOT/xc-universal-binary.sh libarithmetical.a uniffi-example-arithmetic $SRCROOT/../../.. $CONFIGURATION\n"; + shellScript = "bash $SRCROOT/xc-universal-binary.sh uniffi-example-arithmetic $SRCROOT/../../.. $CONFIGURATION\n"; }; CEEB59EB25263ECE003C87D1 /* Build Universal Binary for todolist */ = { isa = PBXShellScriptBuildPhase; @@ -364,7 +364,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "bash $SRCROOT/xc-universal-binary.sh libuniffi_todolist.a uniffi-example-todolist $SRCROOT/../../.. $CONFIGURATION\n"; + shellScript = "bash $SRCROOT/xc-universal-binary.sh uniffi-example-todolist $SRCROOT/../../.. $CONFIGURATION\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -543,7 +543,9 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../../target/universal/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/debug"; PRODUCT_BUNDLE_IDENTIFIER = org.mozilla.example.uniffi.IOSApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "IOSApp-Bridging-Header.h"; @@ -567,7 +569,9 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../../target/universal/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/release"; PRODUCT_BUNDLE_IDENTIFIER = org.mozilla.example.uniffi.IOSApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "IOSApp-Bridging-Header.h"; diff --git a/examples/app/ios/README.md b/examples/app/ios/README.md index 421dee02ac..4eee831d8b 100644 --- a/examples/app/ios/README.md +++ b/examples/app/ios/README.md @@ -9,9 +9,16 @@ This will not be a complete tutorial on how to use `uniffi`, just the bits to ge ## Install Rust compiler targets with `rustup` ```sh -% rustup target add x86_64-apple-ios aarch64-apple-ios +rustup target add x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim ``` +## Creating the bindgen binary + +See [Creating the bindgen binary](https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html#foreign-language-bindings) +for a full guide on how to create a uniffi-bindgen library for your project. + +This sample project ships with one in `examples/app/uniffi-bindgen-cli`. + ## Associate the iOS project with the Rust project 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 1. In Xcode, click on the project in the Project Navigator. 2. In the main window, select the app's main target, and then select "Build Rules". 3. Add a custom rule, to process sources files with names matching `*.udl`. -4. Use `uniffi-bindgen` on each file to generate the headers and swift scaffolding. +4. Use your `uniffi-bindgen` binary on each file to generate the headers and swift scaffolding. ```sh -$HOME/.cargo/bin/uniffi-bindgen generate "$INPUT_FILE_PATH" --language swift --out-dir "$DERIVED_FILE_DIR" +$HOME/.cargo/bin/cargo run -p uniffi-bindgen -- generate "$INPUT_FILE_PATH" --language swift --out-dir "$DERIVED_FILE_DIR" ``` These will output two files Xcode is interested in. ```sh +$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)FFI.h $(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).swift -$(DERIVED_FILE_DIR)/uniffi_$(INPUT_FILE_BASE)-Bridging-Header.h ``` 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,26 +56,25 @@ The header file is a descriptor of the C API to a library that hasn't been built #ifndef IOSApp_Bridging_Header_h #define IOSApp_Bridging_Header_h -#import "todolist-Bridging-Header.h" +#import "todolistFFI.h" #endif /* IOSApp_Bridging_Header_h */ ``` -The `#import` directive points to the header file generated by `uniffi-bindgen generate` above— `$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)-Bridging-Header.h` +The `#import` directive points to the header file generated by `uniffi-bindgen generate` above— `$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE)FFI.h` ```h -#import "todolist-Bridging-Header.h" +#import "todolistFFI.h" ``` 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. ## Configure `cargo` to build the crate as a static library -1. In the `Cargo.toml` file of the Rust project, add `staticlib` to the `crate-type` list. +1. The build script automatically builds a static library (note: this requires rustc 1.64 or newer) ```toml [lib] -crate-type = ["staticlib", "cdylib"] name = "uniffi_todolist" ``` @@ -76,64 +82,63 @@ The `package` `name` and the `lib` `name` will be used below. In this case, the ## Tell Xcode how to build the Rust project. -#### TODO use a better explanation of what is going on here - 1. In Xcode, click on the project in the Project Navigator. 2. In the main window, select the app's main target, and then select "Build Phases". 3. Add a new `Run Script` build phase and move it to the top. 4. Add the script that will build a universal binary for the Rust project. -For this project, we've used a script adapted from the #mozilla/application-services project to build a universal binary with `lipo`. +`uniffi` comes with a script suitable to build a static library for all iOS targets: ```sh -bash $SRCROOT/xc-universal-binary.sh libuniffi_todolist.a uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION +bash $SRCROOT/xc-universal-binary.sh uniffi-example-todolist $SRCROOT/../../../ $CONFIGURATION ``` In this case we constructed the command: ```sh -xc-universal-binary.sh " +xc-universal-binary.sh " ``` by making: - * `STATIC_LIB_NAME` from the `lib` `name` above: `uniffi_todolist` --> `libuniffi_todolist.a` * `FFI_TARGET` from the `package` `name` above: `uniffi-example-todolist`. 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. -This script performs a few steps: - -1. Runs `cargo build` to compile the Rust project for the `x86_64-apple-ios` and `aarch64-apple-ios` targets. - * This includes using `uniffi-bindgen` to generate the Rust scaffolding so we can go from C to Rust. -2. Runs `lipo` to combine these libs in to a universal binary. -3. Puts the universal binary in to the `$WORKSPACE_PATH/target/universal` directory. +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. +This includes using `uniffi-bindgen` to generate the Rust scaffolding so we can go from C to Rust. ## Tell Xcode where the universal library is -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`. +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`. 1. In Xcode, click on the project in the Project Navigator. 2. In the main window, select the app's main target, and then select "Build Settings". 3. Search for `Library Search Paths`. -4. Add paths to where lipo constructed the universal binaries for each of `Debug` and `Release`. +4. Add paths per OS and architecture. + The OS specific part can be configured in the Xcode project editor, + but the architecture specific part has to be added by manually editing the `project.pbxproj` file. + Open `project.pbxproj` in your project direcotry and add the following in the `Debug` section: -```sh -$(SRCROOT)/../../../target/universal/debug -$(SRCROOT)/../../../target/universal/release -``` + ``` + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "../../../target/aarch64-apple-ios/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "../../../target/aarch64-apple-ios-sim/debug"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "../../../target/x86_64-apple-ios/debug"; + ``` + + The path should be a relative path pointing into the `target` directory created by `cargo`. + Add a similar block in the `Release` section and adjust the paths accordingly. ## Back to code. -By now, we should've +By now, we should've 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. 2. Configured Xcode to find the header. 3. Configured cargo to build a static library version of the crate. 4. Used `uniffi-bindgen` to generate a C API for the Rust crate. 5. Used cargo to cross-compile the crate for different targets. -6. Used lipo to combine those crates for different target into a universal binary. -7. Configured Xcode to find the universal binary. +7. Configured Xcode to find the different static libraries. By now, we should be able to hit Run in Xcode and build something. diff --git a/examples/app/ios/xc-universal-binary.sh b/examples/app/ios/xc-universal-binary.sh index a3474ddb53..b21e17d5f1 100755 --- a/examples/app/ios/xc-universal-binary.sh +++ b/examples/app/ios/xc-universal-binary.sh @@ -12,54 +12,57 @@ trap error_help ERR PATH="$(bash -l -c 'echo $PATH')" # This should be invoked from inside xcode, not manually -if [[ "${#}" -ne 4 ]] +if [[ "${#}" -ne 3 ]] then echo "Usage (note: only call inside xcode!):" - echo "path/to/build-scripts/xc-universal-binary.sh " + echo "path/to/build-scripts/xc-universal-binary.sh " exit 1 fi -# e.g. liblogins_ffi.a -STATIC_LIB_NAME=${1} # what to pass to cargo build -p, e.g. logins_ffi -FFI_TARGET=${2} -# path to app services root -SRC_ROOT=${3} -# buildvariant from our xcconfigs -BUILDVARIANT=$(echo "${4}" | tr '[:upper:]' '[:lower:]') +FFI_TARGET=${1} +# path to source code root +SRC_ROOT=${2} +# buildvariant from our xcconfigs +BUILDVARIANT=$(echo "${3}" | tr '[:upper:]' '[:lower:]') RELFLAG= -RELDIR="debug" if [[ "${BUILDVARIANT}" != "debug" ]]; then RELFLAG=--release - RELDIR=release fi -TARGETDIR=${SRC_ROOT}/target +if [[ -n "${SDK_DIR:-}" ]]; then + # Assume we're in Xcode, which means we're probably cross-compiling. + # In this case, we need to add an extra library search path for build scripts and proc-macros, + # which run on the host instead of the target. + # (macOS Big Sur does not have linkable libraries in /usr/lib/.) + export LIBRARY_PATH="${SDK_DIR}/usr/lib:${LIBRARY_PATH:-}" +fi -# We can't use cargo lipo because we can't link to universal libraries :( -# https://github.com/rust-lang/rust/issues/55235 -LIBS_ARCHS=("x86_64" "arm64") -IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios") -for i in "${!LIBS_ARCHS[@]}"; do - env -i PATH="${PATH}" \ - "${HOME}"/.cargo/bin/cargo build --locked -p "${FFI_TARGET}" --lib ${RELFLAG} --target "${IOS_TRIPLES[${i}]}" -done +IS_SIMULATOR=0 +if [ "${LLVM_TARGET_TRIPLE_SUFFIX-}" = "-simulator" ]; then + IS_SIMULATOR=1 +fi -UNIVERSAL_BINARY=${TARGETDIR}/universal/${RELDIR}/${STATIC_LIB_NAME} -NEED_LIPO= +for arch in $ARCHS; do + case "$arch" in + x86_64) + if [ $IS_SIMULATOR -eq 0 ]; then + echo "Building for x86_64, but not a simulator build. What's going on?" >&2 + exit 2 + fi -# if the universal binary doesnt exist, or if it's older than the static libs, -# we need to run `lipo` again. -if [[ ! -f "${UNIVERSAL_BINARY}" ]]; then - NEED_LIPO=1 -elif [[ "$(stat -f "%m" "${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then - NEED_LIPO=1 -elif [[ "$(stat -f "%m" "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then - NEED_LIPO=1 -fi -if [[ "${NEED_LIPO}" = "1" ]]; then - mkdir -p "${TARGETDIR}/universal/${RELDIR}" - lipo -create -output "${UNIVERSAL_BINARY}" \ - "${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \ - "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" -fi + # Intel iOS simulator + export CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios" + $HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target x86_64-apple-ios + ;; + + arm64) + if [ $IS_SIMULATOR -eq 0 ]; then + # Hardware iOS targets + $HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target aarch64-apple-ios + else + # M1 iOS simulator -- currently in Nightly only and requires to build `libstd` + $HOME/.cargo/bin/cargo rustc -p "${FFI_TARGET}" --lib --crate-type staticlib $RELFLAG --target aarch64-apple-ios-sim + fi + esac +done diff --git a/examples/app/uniffi-bindgen-cli/Cargo.toml b/examples/app/uniffi-bindgen-cli/Cargo.toml new file mode 100644 index 0000000000..4edd7fe9fe --- /dev/null +++ b/examples/app/uniffi-bindgen-cli/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "uniffi-bindgen-cli" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "uniffi-bindgen" +path = "uniffi-bindgen.rs" + +[dependencies] +uniffi = { path = "../../../uniffi", features = ["cli"] } diff --git a/examples/app/uniffi-bindgen-cli/uniffi-bindgen.rs b/examples/app/uniffi-bindgen-cli/uniffi-bindgen.rs new file mode 100644 index 0000000000..f6cff6cf1d --- /dev/null +++ b/examples/app/uniffi-bindgen-cli/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +}