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

Generate Kotlin and Swift at the same time as generating TS/C++ #149

Closed
jhugman opened this issue Oct 30, 2024 · 10 comments
Closed

Generate Kotlin and Swift at the same time as generating TS/C++ #149

jhugman opened this issue Oct 30, 2024 · 10 comments

Comments

@jhugman
Copy link
Owner

jhugman commented Oct 30, 2024

@zzorba has been asking for this for a lilttle while.

While the main React Native app requires typescript and C++, iOS extensions still require Swift.

Can we generate the Swift from the Rust at the same time as generating the Typescript and C++?

This issue just covers the generating and building of the Kotlin and Swift generation.

@almost adds this question: can we run the Kotlin and Swift bindings at the same time as the Typescript? This enables a host of new capabilities.

@zzorba
Copy link
Collaborator

zzorba commented Oct 30, 2024

I believe you can run the bindings at the same time, though I do not know if they share the process state or not (I would assume they would, but I'm just not sure since it hasn't been our need).

Our app uses the Swift bindings in our NotificationExtension and the typescript bindings in the foreground app. They both share the same data store (sqlite) for coordination.

@jhugman
Copy link
Owner Author

jhugman commented Oct 30, 2024

I believe you can run the bindings at the same time, though I do not know if they share the process state or not (I would assume they would, but I'm just not sure since it hasn't been our need).

Interesting.

Eyeballing the code, I was under the impression that just generating the extra bindings was necessary but not sufficient for running in the same process. I am expecting that this would require changes to how callback interfaces work.

Still, that is for another issue :)

@Johennes
Copy link
Collaborator

Johennes commented Nov 4, 2024

Don't app extensions on iOS run in their own process (and with limited system resources) anyway? From what I know they can only share certain storage locations with the main app but not the same system process.

@zzorba
Copy link
Collaborator

zzorba commented Nov 4, 2024

Yes that's correct about extensions, they are isolated and only share storage and keychain access.

But theoretically you could have swift code and react native code in the library both talking to the same binary.

Not sure if it's a large use case though?

@Johennes
Copy link
Collaborator

Johennes commented Nov 4, 2024

Oh, I see what you mean now. Hm, yeah I suppose there could be a use case when you want to combine the bindings with some native APIs without having to poke out to TS and back in to another library.

@Johennes
Copy link
Collaborator

I recently got to the point where I needed to use matrix-rust-sdk in an iOS Notification Service Extension (NSE). Generating the Swift bindings with slight adaptions to https://github.com/matrix-org/matrix-rust-sdk/blob/main/xtask/src/swift.rs#L212 and some help from @zzorba wasn't complicated. The headers and (consolidated) module map need to be included in the xcframework that UBRN created. The Swift files and the xcframework can then be included in the NSE. The only other thing I had to do was

target 'nse_target' do
  inherit! :search_paths
end

To get this into UBRN, we'd have to make it call uniffi-rs to generate the headers, module map and Swift files and then move these files around accordingly. For a library created with UBRN, I don't think we'd want the Swift files to be included in its pod. The latter depends on further RN libs which would be inconvenient to include in an app extension because it is subject to resource limits.

Maybe we could generate a second pod for holding just the Swift files? Consumers would then link the main pod into their main app and the other pod (plus the xcframework from the main pod) into their extension.

Alternatively it might be possible to consume the Swift files from node_modules. This might break on updates of the library though as files are added or removed.

@Johennes
Copy link
Collaborator

@jhugman I'm curious about your thoughts on how to best integrate this into ubrn, starting with the Swift side. We could just call generate_bindings with uniffi_bindgen::bindings::SwiftBindingGenerator in cli.rs. The Swift bindings generator produces headers and module maps that need to go into the .xcframework, however, and that has already been assembled at this point as part of the build ios command.

The build command currently has a no_xcodebuild to allow constructing the framework externally. I'm wondering if we could lift this and instead move the framework assembly into the generate command?

Alternatively we could also reassemble the framework during generate or just push the files around ourselves (the framework is just a folder). That might be a little backwards though.

@jhugman
Copy link
Owner Author

jhugman commented Jan 29, 2025

Hmm. I hadn't thought much about when to generate this.

I had thought that we should have a native-src (or something) in android and ios in ubrn.config.yaml: if it is present, then we should generate swift and/or kotlin with the vendored BindingsGenerator.

If we need to generate Swift before the xframework is made— are we going to have to invoke a swift compiler?

I think my preference would be to generate the swift as part of the build, rather than moving the lipo/xcodebuild part to the generate step.

If not that, then adding another generate subcommand (say generate xcframework/generate native-bundle?) which does the lipo/xcodebuild and runs the kotlin/swift generation.

This new task would be run as part of build --and-generate and generate all.

What do you think?

@Johennes
Copy link
Collaborator

If we need to generate Swift before the xframework is made— are we going to have to invoke a swift compiler?

No, I don't think so. It's basically all done by SwiftBindingGenerator. It takes a library file from within the xcframework as its input. The Swift itself lives outside the framework. However, the bindings generator also produces some headers and these need to go into the framework for the Swift to work. That's what's causing the ordering hassle.

I think my preference would be to generate the swift as part of the build, rather than moving the lipo/xcodebuild part to the generate step.

Ok, let me try it like this because I think it'll also be less changes than the other option. If needed, we can always consider rearranging the steps later.

@Johennes
Copy link
Collaborator

This has been addressed now with #214 and #218.

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

No branches or pull requests

3 participants