Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Only one Kotlin framework can be loaded currently #2423

Closed
solivares1-zz opened this issue Nov 26, 2018 · 71 comments
Closed

Only one Kotlin framework can be loaded currently #2423

solivares1-zz opened this issue Nov 26, 2018 · 71 comments

Comments

@solivares1-zz
Copy link

So.. i managed to make two kotlin-native frameworks for Swift but when trying to add them on the project i got the following message.
(Only one Kotlin framework can be loaded currently)

Do we have any update on this?
I prefer not to merge both frameworks into one, is there another workaround?

@SvyatoslavScherbina
Copy link
Collaborator

I prefer not to merge both frameworks into one

What's the reason behind this?
Do these frameworks have something in common? Does one of these frameworks depend on the other one?

@solivares1-zz
Copy link
Author

@SvyatoslavScherbina They don't depend on each other nor have something in common, and both of those frameworks can be used in other apps.
So if i merge them when i create the .framework it will have unnecessary code from the unused one.

I think there's a workaround, creating tasks in the build.gradle to adjust for specific needs like, build A, build B or build A&B in only one .framework

But thats not optimal, we can't share several kotlin-native created libraries if we can't use more than one Kotlin framework per project

@kylejbrock
Copy link

@solivares1 That's what I've had to do to utilize multiple kotlin libraries as a single framework:

I actually support several apps, needing different features. They are both ios & android apps.

So, I've ended up with a function in gradle called: isPlatformTypeEnabled and another called isFeatureEnabled.

Then I have a block that looks similar to this:

if (isPlatformTypeEnabled("client") && isPlatformTypeEnabled("ios")) {
            iosMain {
                kotlin {
                    if (isFeatureEnabled("localization")) {
                        srcDir("$projectDir/src/localization/iosMain/kotlin")
                        srcDir("$projectDir/src/localization/mobileMain/kotlin")
                    }
                }
                dependencies {
                    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
                }
            }
            iosTest {
                dependencies {
                    implementation 'org.jetbrains.kotlin:kotlin-test'
                    implementation 'org.jetbrains.kotlin:kotlin-test-junit'
                }
            }
        }

This enables me to dynamically include different source sets based on what I pass to the gradle build.

It's definitely not ideal, but it's a sufficient work around.

This yields a single framework per application/platform. But, with the appropriate feature sets per application/platform.

@SvyatoslavScherbina
Copy link
Collaborator

@solivares1 thank you for providing the details.
I suppose this case makes sense and can be eventually supported.

@loganj
Copy link

loganj commented Feb 20, 2019

We (Square) have another use case, one we think is likely to be a factor for any large codebase (which is where multiplatform is very desirable!)

Our mobile apps are large, numerous, and highly modularized. On iOS, we ship multiple apps that are all built from different combinations of shared modules. For instance, we might have a printing module that's used by several apps (but not all).

We'd like to have our iOS modules consume our kotlin-native libraries as dependencies, but the single-framework limitation makes that difficult.

We could include all kotlin-native code for all apps in a single framework, but that would mean shipping unnecessary code, as @solivares1 pointed out. Or we could build a different framework for each app, but then we'd have to change the build for our shared printing module to use the appropriate framework depending on which app it's being included in – this is complicated and makes it impossible to build the printing module independently. Either way, we lose modularity and create unnecessary and unclear dependencies.

Ideally, our kotlin-native libraries would appear in our build dependency graph just like any other library.

@SvyatoslavScherbina
Copy link
Collaborator

@loganj

Or we could build a different framework for each app, but then we'd have to change the build for our shared printing module to use the appropriate framework depending on which app it's being included in

Do I understand correctly that you shared printing module may use different combinations of Kotlin modules depending on which application it is being included in?

@loganj
Copy link

loganj commented Feb 20, 2019

No, but the printing module itself may or may not be included in any of our applications.

If we build a single Kotlin framework that supplies all (Kotlin) dependencies for all of our applications, then the printing module will need to depend on 1) non-printing code and 2) non-printing code that may not even be needed for an app that the printing module is included in.

Imagine this scenario (not real, but an example). Dependencies flow right to left, ie application depends on iOS module, iOS module depends on kotlin-native module.

Kotlin-Native module iOS module application
kotlin-print printing retail
kotlin-rtl retail retail
kotlin-rst restaurants restaurants

A single kotlin framework means that 1) printing cannot depend directly on kotlin-print, but instead will depend on a framework that also includes unrelated application-level code (kotlin-rtl, kotlin-rst) and 2) the retail application will end up including kotlin-rst, which is application-level code for restaurants.

@SvyatoslavScherbina
Copy link
Collaborator

Thank you for detailed explanation!
I have one question left: does one of Kotlin/Native modules depend on another one?

@loganj
Copy link

loganj commented Feb 21, 2019

Yes, certainly. There would be dependencies among the kotlin-native modules. The example above is not complex enough to illustrate it well, but you could imagine both direct and transitive dependencies from iOS modules to kotlin-native modules, and we'd like for those to resolve normally.

@SvyatoslavScherbina
Copy link
Collaborator

SvyatoslavScherbina commented Feb 22, 2019

Unfortunately, this kinda conflicts with our current vision and efforts.
Kotlin/Native follows "closed world" compilation model. This means that the entire Kotlin world is compiled to the single native binary, e.g. framework. Two Kotlin/Native frameworks would mean two different Kotlin "worlds", particularly they wouldn't have same Objective-C/Swift custom classes in their APIs.
Some combination of too approaches may still be possible, but we are not working on this currently.

@loganj
Copy link

loganj commented Feb 22, 2019

Any suggestions for how we might be able to adopt?

Is there any known way to resolve this at build time? e.g. specify dependencies from iOS modules onto kotlin libraries/modules, and from kotlin modules onto each other, and resolve to a single framework when building?

We have many, many places where shared code makes a great deal of sense, so we're motivated to solve this issue – but we've been working hard to modularize (for obvious reasons) and need to maintain that progress.

Edit: It would be helpful to better understand the "worlds" issue. Can you explain (or point to) a bit more about the ObjC/Swift custom class problem?

@SvyatoslavScherbina
Copy link
Collaborator

Are your iOS modules static or dynamic libraries?

Edit: It would be helpful to better understand the "worlds" issue. Can you explain (or point to) a bit more about the ObjC/Swift custom class problem?

Consider Kotlin module1, containing class A, and Kotlin module2, containing class B subclassing A. In our closed world compilation model all Kotlin code is supposed to be compiled into a single native binary, e.g. framework. So if it was possible to have framework1 with A and framework2 with B, then framework2 would contain its own version of A too, so there would be two A classes.
This doesn't apply to some standard types like strings and collections (that's what I meant by non-custom), because these are mapped to standard Swift/Objective-C types.

@loganj
Copy link

loganj commented Feb 25, 2019

Our modules are all frameworks (mostly defined in the same repo w/ cocoapods) which are statically linked into our executables.

Thanks for the (very clear) single-world explanation.

@beobyte
Copy link

beobyte commented Mar 29, 2019

The ability to include several frameworks in projects (in iOS, for example) that were created using Kotlin/Native will be very useful for developers who provide their solutions in the form of frameworks or libraries. In this case, consumers of such frameworks can use Kotlin/Native for their own code, as well as connect third-party solutions.

@xsveda
Copy link

xsveda commented May 8, 2019

I believe that the option to load multiple Kotlin frameworks would help the adoption of multiplatform. Our company is an agency that makes different apps for various customers. But some, mainly infrastructure parts, of the apps are the same so both Android and iOS teams have built many libraries to address that.

Android libraries are published and distributed through our company’s Maven Nexus. iOS libraries are distributed through CocoaPods.

We started to re-write some of these libraries to Kotlin Multiplatform. For Android it works still the same and projects, that depend on them do not need to change anything, they don’t even notice that the library is now shipped as multiplatform. We’d like to have similar experience for iOS projects as it is possible to build the multiplatform libraries to frameworks and also distribute through CocoaPods.

Due to the one Kotlin Framework limitation we can‘t just swap the libraries when the project uses more than one of them. Our current workaround is to publish the native variants of multiplatform libraries as .klib artifacts to Maven Nexus and each customer project has to create its own „integration“ Kotlin Multiplatform module for iOS Kotlin Multiplatform libraries, that depends on all libraries the project needs, export them and build framework that contains all Kotlin Multiplatform dependencies the project needs. It works but it requires a non-trivial change to the project structure that need to be maintained and this barrier don’t allow us to use this approach on more (all) projects we are currently working on.

@SvyatoslavScherbina
Copy link
Collaborator

@xsveda could you provide more details on your setup?

Are your libraries independent? E.g. do their APIs depend on each other or have common API dependencies?

@xsveda
Copy link

xsveda commented May 10, 2019

Are your libraries independent? E.g. do their APIs depend on each other or have common API dependencies?

The libraries are not independent and their APIs may depend on each other.

Our applications are well modularized (some have around 60 modules) and we use three layers of module types:

  • library modules i.e. :logging, :printing
    • These modules are shared across various projects and might be extracted to public library
  • generic modules i.e. :networking, :storage :locale
    • These modules are project specific but are shared across various :core modules
  • core modules i.e. :products, :order, :payment
    • These modules are project specific and have the logic of single business use cases

Most of these modules are candidates to be re-done as multiplatform. They form a tree of dependencies where some of them are transitive. The root of the tree is an Android or iOS app that picks all the :core modules it needs.

@SvyatoslavScherbina
Copy link
Collaborator

Thanks!
As I've explained above, Kotlin/Native follows closed world compilation model. However there is a way such a case may be eventually supported under this model: the entire "world" (meaning your collection of dependent modules) to be built from .klibs into a bunch of frameworks at the same time. Then one would be able to select consistent subset of these frameworks for an app, but frameworks from that single compilation would be compatible only with frameworks from the same compilation. I.e. different compilations wouldn't be mixable. (This doesn't exclude possibility of having different "worlds" independently).
Does this sound suitable for your case?

@SvyatoslavScherbina
Copy link
Collaborator

Btw, do you distribute these libraries as different pods or as subspecs of a single pod? And could you clarify what do you mean by "requires a non-trivial change to the project structure that need to be maintained" here?

@xsveda
Copy link

xsveda commented May 13, 2019

Do you distribute these libraries as different pods or as subspecs of a single pod?

We distribute them as standalone pods and maintain them in separate repositories with separate versioning.

Could you clarify what do you mean by “requires a non-trivial change to the project structure that need to be maintained” here?

Our current aim is to start building libraries shared between different apps using Kotlin Multiplatform without any change to those projects. Current version of a library is being built from Swift codebase and distributed via Cocoapods - we want to build the next library version from Kotlin Multiplatform codebase.
By non-trivial change I mean introducing the “integration” Kotlin Multiplatform module that puts all required Kotlin Multiplatform libraries into one framework for that particular iOS application.

Your suggestion to build all .klibs into frameworks at the same time is very interesting and might actually help us here. If I understand it correctly, we might set it up in the way where single “integration” project would fetch all the .klibs of all the libraries in our organisation’s projects & for each one of them would build a separate framework which could be integrated to the Swift project via Cocoapods.
From the application project point of view nothing will change.

Am I understanding it correctly?

If so, few more details come to my mind:

  1. How could we do the versioning of build set of frameworks? Let’s say we have lib-A v1.0 & v1.1, lib-B v1.0 & lib-B v1.1. In order to support all the combinations, we would need to maintain 4 build sets (A1.0 + B1.0, A1.1 + B1.1, A1.0 + B1.1, A1.1 + B1.1), which might quickly become unfeasible.

  2. Let’s say we have 3 totally independent libraries. The only thing that is included in all of them is kotlin-stdlib. How many frameworks in a set built from them would be? One for every library and one common? Or a set of commons that would contain shared code just for libraries A+B, B+C, A+C and shared code for all A+B+C?

  3. With this consistent set of frameworks build, do you see any option how to generate similar set of CocoaPods for delivery?

@epool
Copy link

epool commented May 13, 2019

could we use a Kotlin/Native module(let's name it A) as a kind of bridge and include in there all the other Kotlin/Native modules(let's name them B, C, etc.) and then only expose the A framework to iOS and cocopods?

@xsveda
Copy link

xsveda commented May 13, 2019

@epool yes, this works as described here

@SvyatoslavScherbina
Copy link
Collaborator

@xsveda Unfortunately, the idea described above doesn't look feasible with separate versioning.
Do I understand correctly that you use different release cycles for your libraries?

@xsveda
Copy link

xsveda commented May 14, 2019

@SvyatoslavScherbina Yes, the ones that are shared across different applications have separate versioning and release cycle.

@sav007
Copy link

sav007 commented Jun 3, 2019

Hey folks, is there any updates? We (Shopify) sharing the same concern, single framework limitation destroys high modularization of shareable code. As for now we have only 2 KN modules that independent from each other but that for sure not scalable. Having a bridge module sounds like a hack solution and won't scale at large organization as well.

@SvyatoslavScherbina
Copy link
Collaborator

@sav007

As for now we have only 2 KN modules that independent from each other

Do you expect your modules to be independent further? Or form independent groups? Would support for multiple framework help you if it had the restriction described above?

but that for sure not scalable

Could you clarify?

Having a bridge module sounds like a hack solution and won't scale at large organization as well.

Is your Swift code (that consumes Kotlin modules) modularized too? If so, how do you manage dependencies between your Swift modules?

@sav007
Copy link

sav007 commented Jun 4, 2019

Do you expect your modules to be independent further? Or form independent groups? Would support for multiple framework help you if it had the restriction described above?

It's hard to say from our current state, as we still experimenting, but they might. For now we see couple independent KN modules that produces 2 iOS frameworks that will be consumed by iOS app. But with the current limitation we must generate one framework that will aggregate (depend on) all KN modules and produce one framework, right? That means we can't have them as independent modularized features.

Is your Swift code (that consumes Kotlin modules) modularized too? If so, how do you manage dependencies between your Swift modules?

All our swift code lives in monorepo, so basically we don't need to have package manager as any feature is subproject of the monorepo.
We haven't decided how we will distribute frameworks produced by KN though, for now it might be "drag and drop".

@SvyatoslavScherbina
Copy link
Collaborator

Ok, thanks for the details.
Supporting multiple independent frameworks doesn't conflict with any fundamental property of Kotlin/Native and is blocked mostly by certain technical issues (requiring some time to fix though).

@rharter
Copy link

rharter commented Jul 6, 2019

Another use case that this issue prevents is the use of Kotlin multiplatform to develop SDKs.

Purely as an example, consider Firebase, which has client libraries for Android, iOS, Javascript and C++. It would be a huge win for Firebase, or others developing similar cross platform services, to be able to develop a single SDK in Kotlin, and distribute it to users on any of their platforms without the user having to know or care that it's a kotlin-mpp project.

An ideal scenario that I was looking into for a recent internal project of mine was to have something akin to a firebase-core modules (sticking with Firebase to avoid Foo). This would contain the functionality of the entire SDK and have lightweight interfaces to serve as the native api.

The android-sdk would simply expose the core api, while ios-sdk would be a swift project that depends on core, but exposes a more idiomatic Swift API that allows internal handling of deficiencies in Kotlin/Native, like the lack of full generic support. The js-sdk and cpp-sdk would be the same, simple pass-through if possible, or a thin API layer where needed.

Ideally one would be able to create such a project, and serve the ios-sdk as a Cocoapod, without the user having to know or care what technology it's built on. The fact that, if you attempt this currently, the build will fail if the user includes more than one Kotlin/Native based dependency blocks this type of use case.

In an ideal world, which I realize probably isn't feasibly at this point due the need for all K/N libraries to be compiled with the same version of K/N, the resulting assets, an aar for Android, a Cocoapod for iOS, a .so for C/C++, or an npm package for JS, would not contain the entire Kotlin world, but would depend on a stdlib style artifact that JetBrains maintains which provides that functionality. Then the native build systems could manage that dependency themselves.

Short of that, for the native side, at least, would it not be feasible to add to the generated header files a standard #ifndef KOTLIN_STD style declaration around the generated code for each exported module? This would be great for the Kotlin stdlib and types, but also for any exported dependencies so that if multiple Kotlin libraries depend on the same library (like SQLDelight, or ReactiveStreams), they won't conflict.

@SvyatoslavScherbina
Copy link
Collaborator

If the two Kotlin libraries depend on a same Cocoapod, and these two libraries end up in a "Kotlin-less" iOS project, will the end app have two copies of the Cocapod used by the 2 Kotlin libraries?

With proper configuration, the end app will have two copies of the pod only if the pod is static and at least one of the frameworks is dynamic, just like with Swift or Objective-C.

@4ntoine
Copy link

4ntoine commented Dec 28, 2019

Seems to be my case too: 2 directly independent modules (though both depend on the same 3rd module) cause:

...
/Users/asmirnov/Documents/dev/src/Notes/NotesClientApp/app-mvp/build/cocoapods/framework/app_mvp.framework/app_mvp(result.o)
duplicate symbol '_Konan_DebugGetFieldName' in:
    /Users/asmirnov/Documents/dev/src/Notes/NotesClientApp/app-infra-rest-ktor/build/cocoapods/framework/app_infra_rest_ktor.framework/app_infra_rest_ktor(result.o)
...

https://github.com/4ntoine/NotesClientApp/blob/d5441806b88322f8f791a27d615c0527d1750ba5/app-ios/Podfile#L7

Not sure how it will be resolved (taking into account the comment), waiting for 1.3.70 ...

4ntoine added a commit to 4ntoine/NotesClientApp that referenced this issue Dec 29, 2019
Create a fake `app-ios-lib` module as a workaround for JetBrains/kotlin-native#2423.
Update `app-ios` project due to changed framework name.
Still not using Kotlin classes for REST + JSON though (for now).
4ntoine added a commit to 4ntoine/NotesClientApp that referenced this issue Dec 29, 2019
Create a fake `app-ios-lib` module as a workaround for JetBrains/kotlin-native#2423.
Update `app-ios` project due to changed framework name.
Still not using Kotlin classes for REST + JSON though (for now).
4ntoine added a commit to 4ntoine/NotesClientApp that referenced this issue Jan 12, 2020
Move to coroutines.
Create a fake `app-ios-lib` module as a workaround for JetBrains/kotlin-native#2423.
Update `app-ios` project due to changed framework name.
Add Ktor-client.
Add tests.
Use server version "2.0".
Bump self version to "1.0.0".
4ntoine added a commit to 4ntoine/NotesClientApp that referenced this issue Jan 12, 2020
Move to coroutines.
Create a fake `app-ios-lib` module as a workaround for JetBrains/kotlin-native#2423.
Update `app-ios` project due to changed framework name.
Add Ktor-client.
Add tests.
Use server version "2.0".
Bump self version to "1.0.0".
4ntoine added a commit to 4ntoine/NotesClientApp that referenced this issue Jan 12, 2020
Move to coroutines.
Create a fake `app-ios-lib` module as a workaround for JetBrains/kotlin-native#2423.
Update `app-ios` project due to changed framework name.
Add Ktor-client.
Add tests.
Use server version "2.0".
Bump self version to "1.0.0".
@Coneys
Copy link

Coneys commented Jan 14, 2020

We have kotlin 1.3.70 EAP now, but I couldn't find any info about this issue. What is the status? Will it also work with Cocoapods? (So we could use 2 or more cocoapods created with kotlin native)

@SvyatoslavScherbina
Copy link
Collaborator

We plan to fix multiple debug cocoapods-plugin-produced frameworks in 1.4.
Until then switching frameworks from static to dynamic would help (however may bring other issues).

To make frameworks dynamic you can add the following fragment to the end of your build.gradle.kts:

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
            isStatic = false
        }
    }
}

@SvyatoslavScherbina
Copy link
Collaborator

We plan to fix multiple debug cocoapods-plugin-produced frameworks in 1.4.

Should already be fixed in master, btw (#3853 and #3818).

@zalewskise
Copy link

Was anyone able to use multiple kmp modules with the kotlin 1.3.70 in iOS project (without using a bridge module solution)? I was trying to use 2 different modules and create as podspec with cocoapods but no luck.

@SvyatoslavScherbina
Copy link
Collaborator

Please see my comments above.

@werner77
Copy link

Does anybody have an example of a build.gradle that wraps multiple kotlin native frameworks for cocoapods?

@par128
Copy link

par128 commented Apr 23, 2020

While using "Umbrella" module technique, the class names are prefixed with module names.
Here's an example.
Let's have a couple of modules: Factory and Winery.
An Umbrella module depends on Factory and Winery is built into an Umbrella.framework.
On iOS side classes become named as FactoryCar and WineryBottle.

Ultimately iOS code is infested with "Factory" and "Winery" prefixes.
What was the reason for adding the module name prefix?
Is there a way to avoid auto-prefixing of class names?

Thanks for your efforts, guys!

@SvyatoslavScherbina
Copy link
Collaborator

Is there a way to avoid auto-prefixing of class names?

For Swift it should be enough to export these Kotlin modules explicitly. See the documentation for more details on exporting: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#exporting-dependencies-to-binaries

@par128
Copy link

par128 commented Apr 24, 2020

@SvyatoslavScherbina Works like charm! Thank you so much, now the code looks neat!

@BryanJBryce
Copy link

@SvyatoslavScherbina Should this issue be closed or is there still functionality coming?

@SvyatoslavScherbina
Copy link
Collaborator

or is there still functionality coming?

Regardless of this, there are still cases discussed above that aren't supported currently.
So the next step should be to outline these cases and create new refined issues. After that this issue should be closed.

@kpgalligan
Copy link
Contributor

We plan to fix multiple debug cocoapods-plugin-produced frameworks in 1.4.

@SvyatoslavScherbina Should static frameworks work in 1.4.0? I'm getting duplicate symbols for architecture x86_64 from the linker. Not sure if there's extra config needed.

@SvyatoslavScherbina
Copy link
Collaborator

@kpgalligan no, this problem is not fixed yet. Please disable compiler caches to workaround: add

kotlin.native.cacheKind=none

to gradle.properties.

What is your case, why do you need multiple static debug frameworks? (Release ones work ok).
Are these frameworks defined in a single Gradle multi-project? If yes, why not define an "umbrella" framework?

@kpgalligan
Copy link
Contributor

Client would like to know. Personally, I want to understand bounds and implications. For example, if you publish a library from KMP for iOS use, you need to at least publish debug as dynamic or run the risk that a user of your library will also want to use KMP and run into linker errors. Can explain that in better detail later, but I want to understand the caching and how things work with release first.

Individual client users can use an umbrella, but I've found that they don't all like the idea that they need to, depending on the size of the org. If two teams work on truly different parts of the code, they'd prefer to not comingle dev and release timelines, let's say. In the case of this particular team, they're sticking with dynamic frameworks purely because of this issue. If the plan is to eventually make this work, I don't think that would be a factor, as this is only an issue as they scale, but if this is something deeper and won't be fixed, it's good to know.

@SvyatoslavScherbina
Copy link
Collaborator

Thank you for the details!

For example, if you publish a library from KMP for iOS use, you need to at least publish debug as dynamic or run the risk that a user of your library will also want to use KMP and run into linker errors.

We don't use compiler caches for release binaries. If you publish a debug binary, disabling compiler caches is generally a good idea. For example, it would reduce the size of the binary.

I want to understand the caching

When using compiler caches for static binaries, precompiled .o files for external dependencies are added to the static binary (which is actually an archive). And when Xcode links two such binaries into the executable, identical .o files clash.

If the plan is to eventually make this work

As I've already mentioned, this already works, you just need to disable compiler caches.

@kpgalligan
Copy link
Contributor

Thank you, that actually clears the situation up quite a bit.

@SvyatoslavScherbina
Copy link
Collaborator

Created new (meta) issues for the remaining problems:
https://youtrack.jetbrains.com/issue/KT-42247: covers "umbrella" framework technique problems
https://youtrack.jetbrains.com/issue/KT-42250: covers compilation of Kotlin module to framework that doesn't contains all dependencies

Feel free to report more issues: https://kotl.in/issue.

@SvyatoslavScherbina
Copy link
Collaborator

Also this one: https://youtrack.jetbrains.com/issue/KT-42254
(for using multiple debug static frameworks or pods in single application)

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

No branches or pull requests