Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift library mode --module-name not applied to --swift-sources #2362

Open
kriswuollett opened this issue Dec 21, 2024 · 6 comments
Open

Swift library mode --module-name not applied to --swift-sources #2362

kriswuollett opened this issue Dec 21, 2024 · 6 comments

Comments

@kriswuollett
Copy link

Shouldn't --module-name option be used for all "canImport" boilerplate when making a library mode multi-package bundle, a megazord in Mozilla terms? In other words, the module name specified on the Rust package that is used to build the library should override all transitive dependencies configuration for a module name?

I just noticed that is why my script that was trying to make a multi-package bundle that compiles was failing.

I see that Mozilla has their megazord module name in the FFI Swift Code:

// Depending on the consumer's build setup, the low-level FFI code
// might be in a separate module, or it might be compiled inline into
// this module. This is a bit of light hackery to work with both.
#if canImport(MozillaRustComponents)
import MozillaRustComponents
#endif

Because it is specified at the dependency Rust package in the uniffi.toml file (???):

[bindings.swift]
ffi_module_name = "MozillaRustComponents"
ffi_module_filename = "nimbusFFI"

But that seems like it would prevent making different "remixes", by using different bundling-Rust packages, for use in different apps?

Example

In my example I have 3 Rust packages:

  • my-corp-foo with single function my_corp_foo_greetings
  • my-corp-bar with single function my_corp_bar_greetings

where they have just one function like the following with different names to avoid global conflicts:

/// Returns a greeting worthy for `name`.
#[uniffi::export]
pub fn my_corp_foo_greetings(name: &str) -> String {
    format!("Hello, {name}, from {}!", env!("CARGO_PKG_NAME"))
}
  • my-corp-bundle which has dependencies on the two above, and has a lib.rs file like:
pub use my_corp_foo;
pub use my_corp_bar;

I then build my-corp-bundle across all of the desired apple targets, use lipo to combine the macOS and iOS-sim libraries, generate sources with:

    cargo run --bin uniffi-bindgen-swift -- --swift-sources --headers --modulemap \
        --module-name MyCorpBundleFFI \
        --modulemap-filename module.modulemap \
        ./target/aarch64-apple-darwin/release/libmy_corp_bundle.a \
        ./tmp-headers

after moving the Swift files out of tmp-headers create an xcframework inside the future Swift package directory:

    xcodebuild -create-xcframework \
        -library ./target/universal-apple-darwin/release/libmy_corp_bundle.a \
        -headers ./tmp-headers \
        -library ./target/universal-apple-ios-sim/release/libmy_corp_bundle.a \
        -headers ./tmp-headers \
        -library ./target/aarch64-apple-ios/release/libmy_corp_bundle.a \
        -headers ./tmp-headers \
        -output ./apple/UnffiSwiftFFI/MyCorpBundleRust/MyCorpBundleFFI.xcframework

And then init the Swift package that uses the xcframework as a binary dependency. Basically learned how things worked from this blog post to have something like mozilla/rust-components-swift.

The generated my_corp_foo.swift file is trying to import module my_corp_fooFFI which doesn't exist. Only module MyCorpBundleFFI exists which I specified with the --module-name command line option.

But after patching the Swift files with the updated module name and the nonisolated(unsafe) vars, I got it to compile for Swift 6 and tested the greeting function in an App that used the MyCorpBundleRust Swift package as a local dependency.

Caveats

  • It isn't exactly like Mozilla's setup because their MozillaRustComponents.xcframework has an inner MozillaRustComponents.framework for each slice:
% tree MozillaRustComponents.xcframework 
MozillaRustComponents.xcframework
├── DEPENDENCIES.md
├── Info.plist
├── ios-arm64
│   └── MozillaRustComponents.framework
│       ├── DEPENDENCIES.md
│       ├── Headers
│       │   ├── MozillaRustComponents.h
│       │   ├── RustViaductFFI.h
│       │   ├── as_ohttp_clientFFI.h
│       │   ├── autofillFFI.h
│       │   ├── crashtestFFI.h
│       │   ├── errorFFI.h
│       │   ├── fxa_clientFFI.h
│       │   ├── loginsFFI.h
│       │   ├── nimbusFFI.h
│       │   ├── placesFFI.h
│       │   ├── pushFFI.h
│       │   ├── remote_settingsFFI.h
│       │   ├── rustlogforwarderFFI.h
│       │   ├── suggestFFI.h
│       │   ├── sync15FFI.h
│       │   ├── syncmanagerFFI.h
│       │   └── tabsFFI.h
│       ├── Modules
│       │   └── module.modulemap
│       └── MozillaRustComponents
...

while mine does not:

% tree MyCorpBundleFFI.xcframework 
MyCorpBundleFFI.xcframework
├── Info.plist
├── ios-arm64
│   ├── Headers
│   │   ├── my_corp_barFFI.h
│   │   ├── my_corp_fooFFI.h
│   │   └── module.modulemap
│   └── libmy_corp_bundle.a
...
@kriswuollett
Copy link
Author

Or perhaps a submodule for each uniffi-exporting Rust package in the generated modulemap in the XCFramework works and be more semantically correct?

@mhammond
Copy link
Member

I think you're just missing configuration options - https://mozilla.github.io/uniffi-rs/latest/swift/configuration.html

eg, see how one of the crates in the megazord is configured.

@mhammond
Copy link
Member

I missed this part:

But that seems like it would prevent making different "remixes", by using different bundling-Rust packages, for use in different apps?

You can specify those options in any .toml file you like - we just look in the crate by default, but you can specify another on the command line.

@kriswuollett
Copy link
Author

I'm using the cli::swift main function, not the main one in cli::uniffi_bindgen with:

/// Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence.
#[clap(long, short)]
config: Option<Utf8PathBuf>,
.

Can they be equivalent given with the correct options? I can't recall exactly why, but I think there were a few reasons why I'm using the Swift-specific entrypoint:

  • thought I saw the Swift megazord using staticlib (which I think is required anyways to cover all Apple platforms) along with the main one mentioning dylib but not staticlib for library mode
  • was looking for library mode since thought I read an issue report regarding UDL files and that library mode solves it and is the future
  • similarly a TODO or mention somewhere suggesting maybe binaries would be split into one for each language (as an aside maybe plugin system like protoc's would work nicely, but maybe specifying lots of options would be too bothersome)

I'm not really blocked at the moment regarding this as I'm processing the output to make a Swift 6 patch(es) too in a custom cargo subcommand to make a local Swift package with the inner FFI XCFramework. I may give the config file thing and the shared entrypoint when I get a chance. Just thought it might have been an overlooked detail.

@mhammond
Copy link
Member

Can they be equivalent given with the correct options?

Not sure what "they" you are referring to? All the documented swift options obviously work with the swift cli.

thought I saw the Swift megazord using staticlib

Don't worry too much about the megazord, it's the only way to ship uniffi if you share anything between multiple crates and we have a number of tests for that in this repo. We do need better swift docs though.

was looking for library mode since thought I read an issue report regarding UDL files and that library mode solves it and is the future

Using either one is fine.

similarly a TODO or mention somewhere suggesting maybe binaries would be split into one for each language

Not sure what you mean there, but welcome PRs for docs.

@kriswuollett
Copy link
Author

Cool, thanks, will try to send some PRs for docs or other things once I get my build system figured out and finished.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants