Hi everyone, for this new year I give to the commu...
# feed
f
Hi everyone, for this new year I give to the community : The Swift Package Manager for Kotlin multiplatform Plugin (and more), a plugin who can fully use Swift Package and your own Swift code in your KMP module. For short: this example resume the capabilities of the plugin.
kodee happy 4
🎉 20
j
So we can now finally directly use firebase swift and Apple Crypto?
f
You can import SPM to Kotlin and use it, only if it’s ObjC compatible (see @objc/@objcMembers in your swift code). we can’t import pure Swift directly to Kotlin
you need to create a bridge and it’s possible with this plugin
j
Ah ok bummer, so still not really possible. Thats the main think missing for me in KMP to be able go fully cross platform. I think the objc thing already is built in?
f
j
I mean Yes can manually do this and that, but then not feasible. Cant have 2 hours compile time in multi platform projects. Also not sustainable. But supporting SPM is huge milestone, awesome work!
f
Swift Importation is a limitation of KMP, a bridge is necessary and not easy to do without a plugin.
Using your own swift/bridge code with external dependency is now possible, it will make the things easier.
🎉 1
j
Thanks, will try it out later :)
a
Awesome, thank you!
s
If I use this plugin to develop a Kotlin Native library that wraps an SPM dependency, does the user of my library need to do anything to import that dependency themselves? Use case: I maintain a Kotlin Multiplatform library that wraps a native Android/iOS library underneath. On the Android side, I wrap a regular Android library that itself uses JNI and bundles the corresponding native library within its .aar. So users of my library on Android don’t need to do anything special; just add my library to their dependencies in gradle. But on the iOS side, my library wraps an iOS dependency via the cocoapods plugin. If someone wants to use my library, they need to also add that same dependency to their Kotlin build (usually using the cocoapods plugin in their app). So, my library’s native dependency “infects” the entire dependency tree above it for Kotlin Native / iOS users. I’ve looked into whether its possible to bundle the underlying iOS library into my Kotlin Native library artifact (with static linking or otherwise), but haven’t figured that out. Would it be the same with this SPM plugin? Or would consumers of my wrapper still need to add that native dependency to their build?
f
The plugin generate a static library that will be merged into the module. it’s basically a wrapper of the official way. As it’s the first version, it’s obviously not ready for production and need feedbacks. A case the plugin doesn’t manage is importing an .xcframework who has resources, my plugin doesn’t copy them.
s
Good to hear! I want to release a new library wrapping ZIPFoundation, so will try this out for that and send feedback. Thanks for working on this!
It’s nice to see you have support for adding my own swift code, in case I need to write
@objc
bindings for a Swift-only library
f
You can also use objcMembers, it’s faster 😄
👀 1
The plugin generate a BIG static library of the swift package, we can have some warning about duplicate symbol and has some weird behaviour, it needs to be careful.
My advice for big project with a big bridge is to work this a Local package (which will be exported by the plugin) and not directly with embedded source.
s
good to know. I think for the zip wrapper, the bridge will be small
f
It’s easier to test with a Local Package, because you can debug/test your bridge before export it.
k
I wish I had more hours in a day. Would love to really dive into this. Especially the library use case discussed here. There are multiple layers to the problem, depending on what dependencies you have. But, currently, most of the integration instructions for any KMP library with native dependencies involve adding CocoaPods to link them, and explaining to people that adding CocoaPods for linking doesn't mean iOS devs need to use CocoaPods for the rest of the project is rather difficult (although if building locally you'll need to install CocoaPods itself, which is not an easy assumption in 2024 2025).
The plugin generate a static library that will be merged into the module.
So, the "official way" is only describing how to generate cinterop. Unless I'm missing something, that doc doesn't describe actually pulling the static binary into the build. So, in a library use case, you'd have cinterop Kotlin definitions, but the actual binary wouldn't be in the klib output. If I can find some time, I want to dig into how spm4kmp actually works, but I am curious what "merged into the module" means technically. This is the part that makes building frameworks for iOS apps and building KMP libraries with native dependencies so very different.
f
I’m cheating with cinterop (kind of) and started from this lib https://github.com/ttypic/swift-klib-plugin (and went further). The steps : Generate a Swift package manifest from the Gradle declaration. Compile for each declared target (ios/macos/tvos/...) as a big static lib (.a). Create the .def file for the main product of the manifest, which is the easiest Create the .def files of each SPM dependencies set as exported, which is the hardest Call the cinterop task for each def file who make the magic. This plugin is basically a wrapper for cinterop. The particularity is that each .def file can share the same binary, with no issue. From that concern, it’s kind of easy to do the job; we need to set the correct objc bridge file inside the .def file. I even manage to export all the Firebase iOS libraries to Kotlin.
k
It's wrapping cinterop, which is essentially what the CocoaPods plugin is doing as well, because any Kotlin calling native needs to do that. But the plugin is also building static lib binaries? CocoaPods plugin needs to do that as well, so you can satisfy the linker for tests and dynamic framework builds. I guess the question is, does spm4kmp then also bundle that static lib .a file into klibs? I mean, obviously each platform klib would need to slice it's architecture from the big static lib, but I think the Kotlin compiler would do that. That's the klib library problem. You can ship Kotlin with cinterop bindings as a library, but then downstream projects still need to provide the native binary. The really tricky part is things like Firebase, where bundling Firebase itself into a KMP klib library would be quite problematic. We deal with this directly in things like https://github.com/touchlab/CrashKiOS, where the library needs to talk to Crashlytics, Bugsnag, etc, but we can't stuff the Crashlitics/Bugsnag binary into the klib itself. Technically we probably could, but that's not going to work for many users. So, running tests and building dynamic frameworks using CrashKiOS is a huge PITA (https://crashkios.touchlab.co/docs/bugsnag#step-3---setup-dynamic-linking-optional). The docs really need to be updated, as does the library itself. KMP tests on native used to be built as static kexe binaries, so the compiler would ignore Crashlytics/Bugsnag if you didn't reference them in your test code, but for a while now, tests build dynamic kexe's, so that no longer works.
It's such a pain, that the Crashlytics part of CrashKiOS no longer uses cinterop, or at least it doesn't bind to the iOS Crashlytics framework. We use ObjC dynamic calls from Kotlin, so as long as you don't actually try to call Crashlytics in your test code, no problem. That doesn't work with Bugsnag because some part of the library is straight C code, and that can't be called dynamically.
This 3rd-party non-bundled library problem is a huge issue for things like Compose Multiplatform libraries. Say you want to use Google Maps. The "easy" part is using cinterop to wrap the native map UI library for iOS. The hard part is providing the binary for the map, but not bundling it.
Anyway, that's a long discussion. Need to get back to my today stuff...
🚀 1
f
Thanks for sharing 😄 My plugin needs to be challenged and see the limitations I couldn’t have seen. I’m working on TDD style for checking my implementation with different package configurations.
k
Then 3rd party linking problem isn't something I would expect your library to solve. Just curious about what it was actually doing. "Solving" the 3rd party problem is complex because there's no simple solution, and some solutions create new issues (https://github.com/touchlab/CrashKiOS/issues/69). I don't even have a clear mental model of what a solution would actually look like, regardless of the complexity.
f
@kpgalligan I have done a lot of work on my plugin. yes, I’m doing the same work as cocoapods but in SPM style 🙂 , and less intrusive. Making the Swift dependencies available on Kotlin, it needs to be linked on the app/host side, double compilation. But, if you don’t make them available and keep them inside a bridge as I can do, I don’t need to.