From eb1f2686547ac2bd8b1e107ab6015f557ba1d48c Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Wed, 18 Sep 2024 10:22:53 -0400 Subject: [PATCH] Build improvements with UniFFI library mode Bumped UniFFI to 0.28.2 Added a tool to run uniffi-bindgen in library mode. It can input either a specific library path or the megazord crate name. Use that simplify several build scripts -- especially the generate docs ones. The best part of this is that we no longer have to maintain hand-written modulemaps, which makes adding a new component harder than it needs to be. Split out the uniffi-bindgen commands from `build-xcframework.sh`. This way you can run them standalone and see the results, even if you don't have XCode setup. One change is that automation/swift-components-docs/generate-swift-project.sh now uses `megazord_ios` rather than `megazord`. I think this should result in slightly more accurate docs, since historically some components in the Android megazord aren't in the iOS one (Although, I think they match at the present). --- .cargo/config.toml | 1 + Cargo.lock | 51 +++-- Cargo.toml | 5 +- .../kotlin-components-docs/generate_docs.sh | 16 +- .../generate-modulemap.sh | 21 -- .../generate-swift-project.sh | 38 +--- components/autofill/uniffi.toml | 1 - components/crashtest/uniffi.toml | 1 - components/fxa-client/uniffi.toml | 1 - components/logins/uniffi.toml | 1 - components/nimbus/uniffi.toml | 1 - components/places/uniffi.toml | 1 - components/remote_settings/uniffi.toml | 1 - components/support/error/uniffi.toml | 1 - .../support/rust-log-forwarder/uniffi.toml | 1 - components/sync15/uniffi.toml | 1 - components/sync_manager/uniffi.toml | 1 - components/tabs/uniffi.toml | 1 - megazords/ios-rust/build-xcframework.sh | 14 +- megazords/ios-rust/focus/module.modulemap | 6 - megazords/ios-rust/generate-files.sh | 24 +++ megazords/ios-rust/module.modulemap | 6 - taskcluster/scripts/build-and-test-swift.py | 25 +-- taskcluster/scripts/server-megazord-build.py | 13 +- tools/uniffi-bindgen-library-mode/Cargo.toml | 14 ++ tools/uniffi-bindgen-library-mode/src/main.rs | 202 ++++++++++++++++++ 26 files changed, 303 insertions(+), 145 deletions(-) delete mode 100755 automation/swift-components-docs/generate-modulemap.sh delete mode 100644 megazords/ios-rust/focus/module.modulemap create mode 100755 megazords/ios-rust/generate-files.sh delete mode 100644 megazords/ios-rust/module.modulemap create mode 100644 tools/uniffi-bindgen-library-mode/Cargo.toml create mode 100644 tools/uniffi-bindgen-library-mode/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 15a86008e..56b2f2cdf 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,7 @@ [alias] regen-protobufs = ["run", "--bin", "protobuf-gen", "tools/protobuf_files.toml"] uniffi-bindgen = ["run", "--package", "embedded-uniffi-bindgen", "--"] +uniffi-bindgen-library-mode = ["run", "--package", "uniffi-bindgen-library-mode", "--"] dev-install = ["install", "asdev"] verify_env = ["asdev", "verify_env"] fxa = ["run", "-p", "examples-fxa-client", "--"] diff --git a/Cargo.lock b/Cargo.lock index c40789f1d..37f6171d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4805,12 +4805,13 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "uniffi" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31bff6daf87277a9014bcdefbc2842b0553392919d1096843c5aad899ca4588" +checksum = "51ce6280c581045879e11b400bae14686a819df22b97171215d15549efa04ddb" dependencies = [ "anyhow", "camino", + "cargo_metadata", "clap 4.2.2", "uniffi_bindgen", "uniffi_build", @@ -4818,11 +4819,23 @@ dependencies = [ "uniffi_macros", ] +[[package]] +name = "uniffi-bindgen-library-mode" +version = "0.1.0" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "clap 4.2.2", + "uniffi", + "uniffi_bindgen", +] + [[package]] name = "uniffi_bindgen" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96061d7e01b185aa405f7c9b134741ab3e50cc6796a47d6fd8ab9a5364b5feed" +checksum = "5e9f25730c9db2e878521d606f54e921edb719cdd94d735e7f97705d6796d024" dependencies = [ "anyhow", "askama", @@ -4838,15 +4851,14 @@ dependencies = [ "textwrap 0.16.1", "toml", "uniffi_meta", - "uniffi_testing", "uniffi_udl", ] [[package]] name = "uniffi_build" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6b86f9b221046af0c533eafe09ece04e2f1ded04ccdc9bba0ec09aec1c52bd" +checksum = "88dba57ac699bd8ec53d6a352c8dd0e479b33f698c5659831bb1e4ce468c07bd" dependencies = [ "anyhow", "camino", @@ -4855,9 +4867,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcfa22f55829d3aaa7acfb1c5150224188fe0f27c59a8a3eddcaa24d1ffbe58" +checksum = "d2c801f0f05b06df456a2da4c41b9c2c4fdccc6b9916643c6c67275c4c9e4d07" dependencies = [ "quote", "syn 2.0.72", @@ -4865,13 +4877,12 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3210d57d6ab6065ab47a2898dacdb7c606fd6a4156196831fa3bf82e34ac58a6" +checksum = "61049e4db6212d0ede80982adf0e1d6fa224e6118387324c5cfbe3083dfb2252" dependencies = [ "anyhow", "bytes", - "camino", "log", "once_cell", "paste", @@ -4880,9 +4891,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58691741080935437dc862122e68d7414432a11824ac1137868de46181a0bd2" +checksum = "b40fd2249e0c5dcbd2bfa3c263db1ec981f7273dca7f4132bf06a272359a586c" dependencies = [ "bincode", "camino", @@ -4898,9 +4909,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7663eacdbd9fbf4a88907ddcfe2e6fa85838eb6dc2418a7d91eebb3786f8e20b" +checksum = "c9ad57039b4fafdbf77428d74fff40e0908e5a1731e023c19cfe538f6d4a8ed6" dependencies = [ "anyhow", "bytes", @@ -4910,9 +4921,9 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f922465f7566f25f8fe766920205fdfa9a3fcdc209c6bfb7557f0b5bf45b04dd" +checksum = "21fa171d4d258dc51bbd01893cc9608c1b62273d2f9ea55fb64f639e77824567" dependencies = [ "anyhow", "camino", @@ -4923,9 +4934,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef408229a3a407fafa4c36dc4f6ece78a6fb258ab28d2b64bddd49c8cb680f6" +checksum = "f52299e247419e7e2934bef2f94d7cccb0e6566f3248b1d48b160d8f369a2668" dependencies = [ "anyhow", "textwrap 0.16.1", diff --git a/Cargo.toml b/Cargo.toml index 2cbf2cc97..3dfefc867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "tools/protobuf-gen", "tools/embedded-uniffi-bindgen", "tools/start-bindings", + "tools/uniffi-bindgen-library-mode", "automation/swift-components-docs", "examples/*/", @@ -122,6 +123,7 @@ default-members = [ # "testing/sync-test", "tools/protobuf-gen", "tools/embedded-uniffi-bindgen", + "tools/uniffi-bindgen-library-mode", "examples/*/", "testing/separated/*/", ] @@ -129,7 +131,8 @@ default-members = [ [workspace.dependencies] rusqlite = "0.31.0" libsqlite3-sys = "0.28.0" -uniffi = "0.28.0" +uniffi = "0.28.2" +uniffi_bindgen = "0.28.2" [profile.release] opt-level = "s" diff --git a/automation/kotlin-components-docs/generate_docs.sh b/automation/kotlin-components-docs/generate_docs.sh index 884713a0c..736b8e850 100755 --- a/automation/kotlin-components-docs/generate_docs.sh +++ b/automation/kotlin-components-docs/generate_docs.sh @@ -14,20 +14,10 @@ CARGO="$HOME/.cargo/bin/cargo" # Set the documentation directory KOTLIN_DIR="$WORKING_DIR/src/main/kotlin" -# Build the Rust crate using Cargo -echo "Building the Rust crate..." -$CARGO build -p megazord --release - -# Define the path to the generated Rust library -LIBRARY_FILE="$REPO_ROOT/target/release/libmegazord.so" -if [[ ! -f "$LIBRARY_FILE" ]]; then - echo "Error: Rust library not found at $LIBRARY_FILE" - exit 1 -fi - -# Generate Kotlin bindings, headers, and module map using uniffi-bindgen +# Generate Kotlin bindings to use as the documentation source echo "Generating Kotlin bindings with uniffi-bindgen..." -$CARGO uniffi-bindgen generate --library "$LIBRARY_FILE" --language kotlin --out-dir "$KOTLIN_DIR" + +$CARGO uniffi-bindgen-library-mode -m megazord kotlin "$KOTLIN_DIR" # Generate documentation with increased memory (cd "$WORKING_DIR" && "$REPO_ROOT/gradlew" --max-workers=2 dokkaHtml -Dorg.gradle.vfs.watch=false) diff --git a/automation/swift-components-docs/generate-modulemap.sh b/automation/swift-components-docs/generate-modulemap.sh deleted file mode 100755 index 41b327112..000000000 --- a/automation/swift-components-docs/generate-modulemap.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -# Define the module name and path to the include directory -MODULE_NAME="MozillaRustComponents" -INCLUDE_DIR="Sources/SwiftComponents/include" -MODULEMAP_FILE="$INCLUDE_DIR/module.modulemap" - -# Start creating the module map -echo "module $MODULE_NAME {" > "$MODULEMAP_FILE" - -# Find all .h files in the include directory and add them to the module map -for header in "$INCLUDE_DIR"/*.h; do - echo " header \"$(basename "$header")\"" >> "$MODULEMAP_FILE" -done - -# Add export statement to the end of the module map -echo " export *" >> "$MODULEMAP_FILE" -echo "}" >> "$MODULEMAP_FILE" - -# Confirm module.modulemap was created -echo "module.modulemap generated at: $MODULEMAP_FILE" diff --git a/automation/swift-components-docs/generate-swift-project.sh b/automation/swift-components-docs/generate-swift-project.sh index 6e4d357e9..97cf9d616 100755 --- a/automation/swift-components-docs/generate-swift-project.sh +++ b/automation/swift-components-docs/generate-swift-project.sh @@ -8,7 +8,6 @@ set -euo pipefail FRAMEWORK_NAME="SwiftComponents" THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -REPO_ROOT="$( dirname "$( dirname "$THIS_DIR" )" )" WORKING_DIR="$THIS_DIR" CARGO="$HOME/.cargo/bin/cargo" @@ -17,39 +16,10 @@ INCLUDE_DIR="$WORKING_DIR/Sources/$FRAMEWORK_NAME/include" SWIFT_DIR="$WORKING_DIR/Sources/$FRAMEWORK_NAME" mkdir -p "$INCLUDE_DIR" -# Build the Rust crate using Cargo -echo "Building the Rust crate..." -$CARGO build -p megazord_ios --release - -# Define the path to the generated Rust library -LIBRARY_FILE="$REPO_ROOT/target/release/libmegazord_ios.a" -if [[ ! -f "$LIBRARY_FILE" ]]; then - echo "Error: Rust library not found at $LIBRARY_FILE" - exit 1 -fi - -# Generate Swift bindings, headers, and module map using uniffi-bindgen -echo "Generating Swift bindings with uniffi-bindgen..." -$CARGO uniffi-bindgen generate --library "$LIBRARY_FILE" --language swift --out-dir "$SWIFT_DIR" - -# Move generated header files to the include directory -echo "Moving header files to include directory..." -mv "$SWIFT_DIR"/*.h "$INCLUDE_DIR" || { - echo "Error: Failed to move header files." - exit 1 -} - -# Remove any old modulemaps -echo "Cleaning up old module maps..." -rm -f "$SWIFT_DIR"/*.modulemap - -# Generate a new module map -echo "Generating module map..." -if [[ ! -f "$WORKING_DIR/generate-modulemap.sh" ]]; then - echo "Error: generate-modulemap.sh script not found." - exit 1 -fi -"$WORKING_DIR/generate-modulemap.sh" +# Generate Swift bindings, headers, and module map to use as the documentation source +echo "Generating Swift bindings" +$CARGO uniffi-bindgen-library-mode -m megazord_ios swift --swift-sources "$SWIFT_DIR" +$CARGO uniffi-bindgen-library-mode -m megazord_ios swift --headers --modulemap --modulemap-filename module.modulemap "$INCLUDE_DIR" # Success message echo "Successfully generated Swift bindings, headers, and module map." diff --git a/components/autofill/uniffi.toml b/components/autofill/uniffi.toml index 6110d33e8..6131c9449 100644 --- a/components/autofill/uniffi.toml +++ b/components/autofill/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.autofill" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "autofillFFI" -generate_module_map = false diff --git a/components/crashtest/uniffi.toml b/components/crashtest/uniffi.toml index 334f7928a..33e9bade5 100644 --- a/components/crashtest/uniffi.toml +++ b/components/crashtest/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.crashtest" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "crashtestFFI" -generate_module_map = false diff --git a/components/fxa-client/uniffi.toml b/components/fxa-client/uniffi.toml index 8711ad1fe..3994ff759 100644 --- a/components/fxa-client/uniffi.toml +++ b/components/fxa-client/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.fxaclient" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "fxa_clientFFI" -generate_module_map = false diff --git a/components/logins/uniffi.toml b/components/logins/uniffi.toml index 8644c94ee..ff077a29c 100644 --- a/components/logins/uniffi.toml +++ b/components/logins/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.logins" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "loginsFFI" -generate_module_map = false diff --git a/components/nimbus/uniffi.toml b/components/nimbus/uniffi.toml index 67ffba375..9145b261c 100644 --- a/components/nimbus/uniffi.toml +++ b/components/nimbus/uniffi.toml @@ -13,7 +13,6 @@ from_custom = "{}.toString()" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "nimbusFFI" -generate_module_map = false [bindings.python] # This can be commented out, and the `--library` argument of `bindgen-uniffi` should be used instead. diff --git a/components/places/uniffi.toml b/components/places/uniffi.toml index 63a81b608..54781f113 100644 --- a/components/places/uniffi.toml +++ b/components/places/uniffi.toml @@ -6,4 +6,3 @@ package_name = "mozilla.appservices.places.uniffi" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "placesFFI" -generate_module_map = false diff --git a/components/remote_settings/uniffi.toml b/components/remote_settings/uniffi.toml index 49eb220f5..c88adcd4d 100644 --- a/components/remote_settings/uniffi.toml +++ b/components/remote_settings/uniffi.toml @@ -13,5 +13,4 @@ from_custom = "{}.toString()" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "remote_settingsFFI" -generate_module_map = false diff --git a/components/support/error/uniffi.toml b/components/support/error/uniffi.toml index 4678b5c28..517f14922 100644 --- a/components/support/error/uniffi.toml +++ b/components/support/error/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.errorsupport" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "errorFFI" -generate_module_map = false diff --git a/components/support/rust-log-forwarder/uniffi.toml b/components/support/rust-log-forwarder/uniffi.toml index cfe8b53e9..0284b0d26 100644 --- a/components/support/rust-log-forwarder/uniffi.toml +++ b/components/support/rust-log-forwarder/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.rust_log_forwarder" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "rustlogforwarderFFI" -generate_module_map = false diff --git a/components/sync15/uniffi.toml b/components/sync15/uniffi.toml index b04c2b2aa..de8b20d31 100644 --- a/components/sync15/uniffi.toml +++ b/components/sync15/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.sync15" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "sync15FFI" -generate_module_map = false diff --git a/components/sync_manager/uniffi.toml b/components/sync_manager/uniffi.toml index 12d043d4f..9b0ad6a45 100644 --- a/components/sync_manager/uniffi.toml +++ b/components/sync_manager/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.syncmanager" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "syncmanagerFFI" -generate_module_map = false diff --git a/components/tabs/uniffi.toml b/components/tabs/uniffi.toml index 2887a857f..da413b2c4 100644 --- a/components/tabs/uniffi.toml +++ b/components/tabs/uniffi.toml @@ -4,4 +4,3 @@ package_name = "mozilla.appservices.remotetabs" [bindings.swift] ffi_module_name = "MozillaRustComponents" ffi_module_filename = "tabsFFI" -generate_module_map = false diff --git a/megazords/ios-rust/build-xcframework.sh b/megazords/ios-rust/build-xcframework.sh index 706b28e59..8457d66a1 100755 --- a/megazords/ios-rust/build-xcframework.sh +++ b/megazords/ios-rust/build-xcframework.sh @@ -133,7 +133,6 @@ rm -rf "$XCFRAMEWORK_ROOT" COMMON="$XCFRAMEWORK_ROOT/common/$FRAMEWORK_NAME.framework" mkdir -p "$COMMON/Modules" -cp "$WORKING_DIR/module.modulemap" "$COMMON/Modules/" cp "$WORKING_DIR/DEPENDENCIES.md" "$COMMON/DEPENDENCIES.md" @@ -147,17 +146,8 @@ UNIFFI_BINDGEN_LIBRARY="$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$LIB_NAME" cp "$WORKING_DIR/$FRAMEWORK_NAME.h" "$COMMON/Headers" cp "$REPO_ROOT/components/viaduct/ios/RustViaductFFI.h" "$COMMON/Headers" -# Next, move the generated headers. -# -# TODO: https://github.com/mozilla/uniffi-rs/issues/1060 -# -# It would be neat if there was a single UniFFI command that would generate headers-only, but for -# now we generate both the `.h` and `.swift` files, then delete the `.swift`. Bleh. - -$CARGO uniffi-bindgen generate --library "$UNIFFI_BINDGEN_LIBRARY" -l swift -o "$COMMON/Headers" -rm -rf "$COMMON"/Headers/*.swift - - +# Next, generate files with uniffi-bindgen +"$THIS_DIR/generate-files.sh" "$UNIFFI_BINDGEN_LIBRARY" "$COMMON" # Flesh out the framework for each architecture based on the common files. # It's a little fiddly, because we apparently need to put all the simulator targets diff --git a/megazords/ios-rust/focus/module.modulemap b/megazords/ios-rust/focus/module.modulemap deleted file mode 100644 index 31076867b..000000000 --- a/megazords/ios-rust/focus/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module MozillaRustComponents { - umbrella header "MozillaRustComponents.h" - - export * - module * { export * } -} diff --git a/megazords/ios-rust/generate-files.sh b/megazords/ios-rust/generate-files.sh new file mode 100755 index 000000000..0bb6a654e --- /dev/null +++ b/megazords/ios-rust/generate-files.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -ex + +if [[ $# -ne 2 ]] ; then + echo "USAGE megazords/ios-rust/generate-files.sh [UNIFFI_BINDGEN_LIBRARY] [COMMON]" + exit 1 +fi + +UNIFFI_BINDGEN_LIBRARY=$1 +COMMON=$2 + +# Helper to run the cargo build command in a controlled environment. +# It's important that we don't let environment variables from the user's default +# desktop build environment leak into the iOS build, otherwise it might e.g. +# link against the desktop build of NSS. +CARGO="$HOME/.cargo/bin/cargo" + +# Run uniffi-bindgen-library-mode to generate the files. +# +# We can't use the `-m` flag because UNIFFI_BINDGEN_LIBRARY is cross-compiled, which our +# uniffi-bindgen-library-mode tool can't handle yet. +"$CARGO" uniffi-bindgen-library-mode -l "$UNIFFI_BINDGEN_LIBRARY" swift --headers "$COMMON/Headers" +"$CARGO" uniffi-bindgen-library-mode -l "$UNIFFI_BINDGEN_LIBRARY" swift --modulemap "$COMMON/Modules" --xcframework --modulemap-filename module.modulemap diff --git a/megazords/ios-rust/module.modulemap b/megazords/ios-rust/module.modulemap deleted file mode 100644 index 31076867b..000000000 --- a/megazords/ios-rust/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module MozillaRustComponents { - umbrella header "MozillaRustComponents.h" - - export * - module * { export * } -} diff --git a/taskcluster/scripts/build-and-test-swift.py b/taskcluster/scripts/build-and-test-swift.py index 2017ceb0a..762bb0d79 100755 --- a/taskcluster/scripts/build-and-test-swift.py +++ b/taskcluster/scripts/build-and-test-swift.py @@ -143,21 +143,16 @@ def generate_uniffi_bindings(args): # Generate sources for Focus generate_uniffi_bindings_for_target(focus_out_dir, "megazord_focus") -def generate_uniffi_bindings_for_target(out_dir, library_name): - log(f"generating sources for {library_name}") - # Use a megazord library that was built for the XCFramework. Pick an arbitrary target, since - # the target doesn't affect the UniFFI bindings. - lib_path = f'target/aarch64-apple-ios/release/lib{library_name}.a' - - cmdline = ['generate', '--library', lib_path, '-l', 'swift', '-o', out_dir] - run_uniffi_bindgen(cmdline) - -def run_uniffi_bindgen(bindgen_args): - all_args = [ - 'cargo', 'run', '-p', 'embedded-uniffi-bindgen', - ] - all_args.extend(bindgen_args) - subprocess.check_call(all_args, cwd=ROOT_DIR) +def generate_uniffi_bindings_for_target(out_dir, megazord): + log(f"generating sources for {megazord}") + # We can't use the `-m` flag here because the megazord library was cross-compiled and the + # `uniffi-bindgen-library-mode` tool can't handle that yet. Instead, send one of the library + # paths using the `-l` flag. Pick an arbitrary target, since the it doesn't affect the UniFFI + # bindings. + lib_path = f'target/aarch64-apple-ios/release/lib{megazord}.a' + subprocess.check_call([ + 'cargo', 'uniffi-bindgen-library-mode', '-l', lib_path, "swift", out_dir + ]) def copy_source_dirs(args): out_dir = args.out_dir / 'all' diff --git a/taskcluster/scripts/server-megazord-build.py b/taskcluster/scripts/server-megazord-build.py index 23effc0f0..99e1ea379 100755 --- a/taskcluster/scripts/server-megazord-build.py +++ b/taskcluster/scripts/server-megazord-build.py @@ -89,17 +89,20 @@ def _build_shared_library(megazord, target, dist_dir): 'cargo', 'build', '--manifest-path', f'{SRC_ROOT}/megazords/{megazord}/Cargo.toml', '--release', '--target', target, ], env=env, cwd=SRC_ROOT) - # Move the .so file to the dist_directory - shutil.move(SRC_ROOT / 'target' / target / 'release' / filename, dist_dir / filename) - # This is only temporary, until cirrus uses pre-built binaries. _patch_uniffi_tomls() - # Generate the Python FFI. We do this with `--library` so we don't have to specify the UDL or the uniffi.toml file. + library_path = SRC_ROOT / 'target' / target / 'release' / filename + + # Generate the Python FFI. We do this with `uniffi-bindgen-library-mode` so we don't have to specify the UDL or the uniffi.toml file. + # Use the `-l` flag rather than `-m` since we want to specify a particular target. subprocess.check_call([ - 'cargo', 'uniffi-bindgen', 'generate', '--library', dist_dir / filename, '--language', 'python', '--out-dir', dist_dir, + 'cargo', 'uniffi-bindgen-library-mode', '-l', library_path.as_posix(), 'python', dist_dir ], env=env, cwd=SRC_ROOT) + # Move the .so file to the dist_directory + shutil.move(SRC_ROOT / 'target' / target / 'release' / filename, dist_dir / filename) + return filename def _patch_uniffi_tomls(): diff --git a/tools/uniffi-bindgen-library-mode/Cargo.toml b/tools/uniffi-bindgen-library-mode/Cargo.toml new file mode 100644 index 000000000..fb3c4ed1b --- /dev/null +++ b/tools/uniffi-bindgen-library-mode/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "uniffi-bindgen-library-mode" +version = "0.1.0" +authors = ["The Firefox Sync Developers "] +edition = "2021" +license = "MPL-2.0" + +[dependencies] +uniffi = { workspace = true, features = ["cli"] } +uniffi_bindgen = { workspace = true } +clap = "4" +cargo_metadata = "0.15" +camino = "1" +anyhow = "1" diff --git a/tools/uniffi-bindgen-library-mode/src/main.rs b/tools/uniffi-bindgen-library-mode/src/main.rs new file mode 100644 index 000000000..4bbf81674 --- /dev/null +++ b/tools/uniffi-bindgen-library-mode/src/main.rs @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{ + env::consts::{DLL_PREFIX, DLL_SUFFIX}, + fmt, process, +}; + +use anyhow::{bail, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use clap::{Args, Parser, Subcommand}; +use uniffi_bindgen::bindings::{generate_swift_bindings, SwiftBindingsOptions}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Cli { + #[command(flatten)] + megazord: MegazordArg, + #[command(subcommand)] + command: Command, +} + +#[derive(Args)] +#[group(required = true, multiple = false)] +struct MegazordArg { + /// Name of the megazord to use + #[arg(short, long, value_parser=["megazord", "megazord_ios", "megazord_focus", "cirrus", "nimbus-experimenter"])] + megazord: Option, + + /// Path to a library file + #[arg(short, long)] + library: Option, +} + +#[derive(Subcommand)] +enum Command { + Kotlin { + out_dir: Utf8PathBuf, + }, + Swift { + out_dir: Utf8PathBuf, + /// Generate swift files + #[arg(long)] + swift_sources: bool, + /// Generate header files + #[arg(long)] + headers: bool, + /// Generate modulemap + #[arg(long)] + modulemap: bool, + // Generate an xcframework-compatible modulemap + #[arg(long)] + xcframework: bool, + /// module name for the generated modulemap + #[arg(long)] + module_name: Option, + /// filename for the generate modulemap + #[arg(long)] + modulemap_filename: Option, + }, + Python { + out_dir: Utf8PathBuf, + }, +} + +enum Language { + Kotlin, + Swift, + Python, +} + +fn main() { + if let Err(e) = run_uniffi_bindgen(Cli::parse()) { + eprintln!("{e}"); + std::process::exit(1); + } +} + +fn run_uniffi_bindgen(cli: Cli) -> Result<()> { + let metadata = cargo_metadata::MetadataCommand::new() + .exec() + .expect("error running cargo metadata"); + let megazord = Megazord::new( + &cli.megazord, + cli.command.language(), + &metadata.workspace_root, + )?; + let config_supplier = uniffi::CargoMetadataConfigSupplier::from(metadata); + + match cli.command { + Command::Kotlin { out_dir } => { + uniffi::generate_bindings_library_mode( + &megazord.library_path, + None, + &uniffi::KotlinBindingGenerator, + &config_supplier, + None, + &out_dir, + false, + )?; + } + Command::Swift { + out_dir, + mut swift_sources, + mut headers, + mut modulemap, + xcframework, + module_name, + modulemap_filename, + } => { + let module_name = module_name.unwrap_or_else(|| "MozillaRustComponents".to_owned()); + // If no generate kinds were specified, generate them all + if !(swift_sources || headers || modulemap) { + swift_sources = true; + headers = true; + modulemap = true; + } + + generate_swift_bindings(SwiftBindingsOptions { + out_dir, + generate_swift_sources: swift_sources, + generate_headers: headers, + generate_modulemap: modulemap, + library_path: megazord.library_path, + xcframework, + module_name: Some(module_name), + modulemap_filename, + metadata_no_deps: false, + })?; + } + Command::Python { out_dir } => { + uniffi::generate_bindings_library_mode( + &megazord.library_path, + None, + &uniffi::PythonBindingGenerator, + &config_supplier, + None, + &out_dir, + false, + )?; + } + }; + Ok(()) +} + +struct Megazord { + library_path: Utf8PathBuf, +} + +impl Megazord { + fn new(arg: &MegazordArg, language: Language, workspace_root: &Utf8Path) -> Result { + if let Some(crate_name) = &arg.megazord { + // Build the megazord + process::Command::new("cargo") + .args(["build", "--release", "-p", crate_name]) + .spawn()? + .wait()?; + + let filename = match language { + // Swift uses static libs + Language::Swift => format!("lib{}.a", crate_name.replace('-', "_")), + // Everything else uses dynamic libraries + _ => format!( + "{}{}{}", + DLL_PREFIX, + crate_name.replace('-', "_"), + DLL_SUFFIX + ), + }; + let library_path = workspace_root.join("target").join("release").join(filename); + Ok(Self { library_path }) + } else if let Some(library_path) = &arg.library { + Ok(Self { + library_path: library_path.clone(), + }) + } else { + bail!("Neither megazord nor library specified") + } + } +} + +impl fmt::Display for Language { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = match self { + Self::Swift => "swift", + Self::Kotlin => "kotlin", + Self::Python => "python", + }; + write!(f, "{}", name) + } +} + +impl Command { + fn language(&self) -> Language { + match self { + Self::Kotlin { .. } => Language::Kotlin, + Self::Swift { .. } => Language::Swift, + Self::Python { .. } => Language::Python, + } + } +}