Gradle Multiproject 和 Kotlin Multiplatform – 具有对兄弟项目的 API 依赖的 KMM 框架。

huangapple go评论56阅读模式
英文:

Gradle Multiproject and Kotlin Multiplatform - KMM Framework with API dependency on sibling project

问题

Context

我正在一个项目上工作,在这个项目中,Web服务和移动客户端尽可能共享Kotlin代码。"api"和"client"子项目依赖于"models"子项目。

API子项目是Web服务。据我了解,它使用来自Models子项目的JVM输出。

客户端子项目用于iOS、macOS和Android应用程序的消费。再次根据我有限的了解,移动应用程序使用Kotlin/Native和Kotlin/JVM输出。

include("models", "api", "client")

"client"的缩写gradle.build.kts示例

plugins {
    alias(libs.plugins.kotlin.kmp)
    ...
}

kotlin {

    android()

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeFramework"
        }
    }

    listOf(
        macosArm64(),
        macosX64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeFramework"
        }
    }

    sourceSets {

        all {
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCRefinement")
        }

        val commonMain by getting {
            dependencies {
                api(project(":models"))
                ....
            }
        }
        val commonTest by getting {
            dependencies { ... }
        }
        val androidMain by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }

        val macosX64Main by getting
        val macosArm64Main by getting
        val macMain by creating {
            dependsOn(commonMain)
            macosX64Main.dependsOn(this)
            macosArm64Main.dependsOn(this)
        }
    }
}

android {
    ...
}

"models"的缩写gradle.build.kts示例

plugins {
    alias(libs.plugins.kotlin.kmp)
    ...
}

kotlin {

    jvm()

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeModels"
        }
    }

    listOf(
        macosArm64(),
        macosX64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeModels"
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies { ... }
        }
        val commonTest by getting {
            dependencies { ... }
        }

        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }

        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }

        val macosX64Main by getting
        val macosArm64Main by getting
        val macMain by creating {
            dependsOn(commonMain)
            macosX64Main.dependsOn(this)
            macosArm64Main.dependsOn(this)
        }
    }
}

我认为以这种方式设置项目可以使Models子项目保持相对稳定,不需要重建。如果我通过这种配置让事情变得更加复杂,欢迎提出建议。

The Problem

我不理解在KMM中 dependencies { api(project(":models")) } 是如何工作的。

在Models项目中,我在iosMain源集中有特定于iOS的代码。当我构建iOS应用程序(在Xcode中)时,我看到Client使用的类型和Model类型。这些类型以"Models"为前缀(例如,ModelsUser)。

然而,我看不到Client项目不使用的方法、函数和类型。此外,Model子项目中的特定于iOS的代码不可用。我认为这有点有道理 - 将依赖设置为api(...) 可能会导出尽可能少的符号。

那么,也许我应该在我的iOS应用程序中同时消费Model项目?换句话说...

import AwesomeFramework
import AwesomeModels

然而,问题是没有任何东西可以提供两个框架之间的链接。从Client生成的ModelsUser类与从Model子项目生成的User类是独立且不同的。🤷‍♂️

我可以将Models和Client项目合并在一起,但我想了解为什么这是最"正确"的解决方案。

我已经达到了我(非常有限的)Gradle和Kotlin Multiplatform知识的尽头。寻找可以填补空白的解决方案或资源。

英文:

Context

I'm working on a project where the web service and mobile clients share as much Kotlin code as possible. The "api" and "client" subprojects have a dependency on the "models" subproject.

The API subproject is the web service. As far as I understand it, it consumes the JVM output from the Model subproject.

The Client subproject is for consumption by the iOS, macOS, and Android apps. Again, according to my kindergarten-level understanding, the mobile apps consume Kotlin/Native and Kotlin/JVM output.

include("models", "api", "client")

Abbreviated gradle.build.kts for "client"

plugins {
    alias(libs.plugins.kotlin.kmp)
    ...
}

kotlin {

    android()

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeFramework"
        }
    }

    listOf(
        macosArm64(),
        macosX64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeFramework"
        }
    }

    sourceSets {

        all {
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCRefinement")
        }

        val commonMain by getting {
            dependencies {
                api(project(":models"))
                ....
            }
        }
        val commonTest by getting {
            dependencies { ... }
        }
        val androidMain by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }

        val macosX64Main by getting
        val macosArm64Main by getting
        val macMain by creating {
            dependsOn(commonMain)
            macosX64Main.dependsOn(this)
            macosArm64Main.dependsOn(this)
        }
    }
}

android {
    ...
}

Abbreviated gradle.build.kts for "models"

plugins {
    alias(libs.plugins.kotlin.kmp)
    ...
}

kotlin {

    jvm()

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeModels"
        }
    }

    listOf(
        macosArm64(),
        macosX64()
    ).forEach {
        it.binaries.framework {
            baseName = "AwesomeModels"
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies { ... }
        }
        val commonTest by getting {
            dependencies { ... }
        }

        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }

        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }

        val macosX64Main by getting
        val macosArm64Main by getting
        val macMain by creating {
            dependsOn(commonMain)
            macosX64Main.dependsOn(this)
            macosArm64Main.dependsOn(this)
        }
    }
}

I thought setting my project up this way would allow for the Models subproject to remain fairly stable and not require rebuilds. Open to suggestions if I've made things harder on myself with this configuration.

The Problem

I do not understand how dependencies { api(project(":models")) } works for KMM.

In the Models project, I have iOS specific code in the iosMain source set. When I build the iOS app (in Xcode), I see the Client types and the Model types that Client uses. These types are preceded with Models (e.g., ModelsUser).

However, I don't see methods, functions, and types the Client project doesn't use. Furthermore, the iOS specific code in the Model subproject isn't available. I think this kinda makes sense - setting the dependency to api(...) is probably going to export the smallest number of symbols possible.

So, maybe I should also consume the Model project inside of my iOS app? In other words...

import AwesomeFramework
import AwesomeModels

However, the problem is there's nothing to provide a linkage between the two frameworks. The ModelsUser class emitted from Client is separate and distinct from the User class emitted from the Models subproject. 🤷🏼‍♂️

I could smash the Models and Client projects together, but I would like to understand why that's the most "correct" solution.

I've reached the end of my (very limited) Gradle and Kotlin Multiplatform knowledge. Looking for solutions or resources that would help fill in the gaps.

答案1

得分: 1

你对项目设置的理解是正确的。
为了完全将models导出到ObjC/Swift,你需要导出依赖项:

it.binaries.framework {
    baseName = "AwesomeFramework"
    export(project(":models"))
}

来源:https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#export-dependencies-to-binaries

目前,由Kotlin生成的框架确实被认为包含唯一的声明。这就是为什么AwesomeFramework中的User模型与AwesomeModels中的模型不同的原因。
请关注KT-42250以获取更新。

英文:

Your understanding of the project setup is correct.
In order to fully export the models to ObjC/Swift you'll need to export the dependency:

it.binaries.framework {
    baseName = "AwesomeFramework"
    export(project(":models"))
}

Source: https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#export-dependencies-to-binaries

At the moment frameworks generated by Kotlin are indeed considered to contain unique declarations. Which is why your User model from AwesomeFramework isn't the same as the one from AwesomeModels.
Please follow KT-42250 for updates.

huangapple
  • 本文由 发表于 2023年5月22日 04:31:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301816.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定