Export implementation dependencies of Native-shared source sets as API
As the Kotlin/Native compiler always requires transitive dependencies to be present among the libraries during compilation, it is necessary to export the implementation dependencies of Native-shared source sets as if they were API dependencies. To do that, add the extendsFrom-relationship between the apiElements configuration of the metadata target (so that Gradle adds the dependencies to the transitive dependencies graph) and also add the dependencies to the project structure metadata (so that the consumer can find out that it's only their corresponding Native-shared source sets that need these particular transitive dependencies)
This commit is contained in:
+58
-8
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle
|
||||
import org.jetbrains.kotlin.gradle.util.modify
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.junit.Assume
|
||||
import org.junit.Ignore
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
@@ -20,18 +19,22 @@ import kotlin.test.assertFalse
|
||||
class KlibBasedMppIT : BaseGradleIT() {
|
||||
override val defaultGradleVersion = GradleVersionRequired.AtLeast("6.0")
|
||||
|
||||
companion object {
|
||||
private const val MODULE_GROUP = "com.example"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildWithProjectDependency() = testBuildWithDependency {
|
||||
gradleBuildScript().appendText("\n" + """
|
||||
dependencies {
|
||||
commonMainImplementation(project("$embeddedModuleName"))
|
||||
commonMainImplementation(project("$dependencyModuleName"))
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildWithPublishedDependency() = testBuildWithDependency {
|
||||
build(":$embeddedModuleName:publish") {
|
||||
build(":$dependencyModuleName:publish") {
|
||||
assertSuccessful()
|
||||
}
|
||||
|
||||
@@ -40,23 +43,23 @@ class KlibBasedMppIT : BaseGradleIT() {
|
||||
maven("${'$'}rootDir/repo")
|
||||
}
|
||||
dependencies {
|
||||
commonMainImplementation("com.example:$embeddedModuleName:1.0")
|
||||
commonMainImplementation("$MODULE_GROUP:$dependencyModuleName:1.0")
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
// prevent Gradle from linking the above dependency to the project:
|
||||
gradleBuildScript(embeddedModuleName).appendText("\ngroup = \"some.other.group\"")
|
||||
gradleBuildScript(dependencyModuleName).appendText("\ngroup = \"some.other.group\"")
|
||||
}
|
||||
|
||||
private val embeddedModuleName = "project-dep"
|
||||
private val dependencyModuleName = "project-dep"
|
||||
|
||||
private fun testBuildWithDependency(configureDependency: Project.() -> Unit) = with(Project("common-klib-lib-and-app")) {
|
||||
Assume.assumeTrue(HostManager.hostIsMac)
|
||||
|
||||
embedProject(Project("common-klib-lib-and-app"), renameTo = embeddedModuleName)
|
||||
embedProject(Project("common-klib-lib-and-app"), renameTo = dependencyModuleName)
|
||||
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
|
||||
|
||||
projectDir.resolve(embeddedModuleName + "/src").walkTopDown().filter { it.extension == "kt" }.forEach { file ->
|
||||
projectDir.resolve(dependencyModuleName + "/src").walkTopDown().filter { it.extension == "kt" }.forEach { file ->
|
||||
file.modify { it.replace("package com.h0tk3y.hmpp.klib.demo", "package com.projectdep") }
|
||||
}
|
||||
|
||||
@@ -110,4 +113,51 @@ class KlibBasedMppIT : BaseGradleIT() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val transitiveDepModuleName = "transitive-dep"
|
||||
|
||||
@Test
|
||||
fun testKotlinNativeImplPublishedDeps() =
|
||||
testKotlinNativeImplementationDependencies {
|
||||
build(":$transitiveDepModuleName:publish", ":$dependencyModuleName:publish") {
|
||||
assertSuccessful()
|
||||
}
|
||||
|
||||
gradleBuildScript().appendText("\n" + """
|
||||
repositories {
|
||||
maven("${'$'}rootDir/repo")
|
||||
}
|
||||
dependencies {
|
||||
commonMainImplementation("$MODULE_GROUP:$dependencyModuleName:1.0")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
listOf(transitiveDepModuleName, dependencyModuleName).forEach {
|
||||
// prevent Gradle from linking the above dependency to the project:
|
||||
gradleBuildScript(it).appendText("\ngroup = \"com.some.other.group\"")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKotlinNativeImplProjectDeps() =
|
||||
testKotlinNativeImplementationDependencies {
|
||||
gradleBuildScript().appendText("\ndependencies { \"commonMainImplementation\"(project(\":$dependencyModuleName\")) }")
|
||||
}
|
||||
|
||||
private fun testKotlinNativeImplementationDependencies(
|
||||
setupDependencies: Project.() -> Unit
|
||||
) = with(Project("common-klib-lib-and-app")) {
|
||||
embedProject(Project("common-klib-lib-and-app"), renameTo = transitiveDepModuleName)
|
||||
embedProject(Project("common-klib-lib-and-app"), renameTo = dependencyModuleName)
|
||||
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
|
||||
gradleBuildScript(dependencyModuleName).appendText("\ndependencies { \"commonMainImplementation\"(project(\":$transitiveDepModuleName\")) }")
|
||||
|
||||
setupDependencies(this@with)
|
||||
|
||||
val compileNativeMetadataTaskName = "compileIosMainKotlinMetadata"
|
||||
build(":$compileNativeMetadataTaskName") {
|
||||
assertSuccessful()
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -11,6 +11,9 @@ import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
|
||||
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
@@ -88,7 +91,21 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec
|
||||
sourceSet.name to sourceSet.dependsOn.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }.toSet()
|
||||
},
|
||||
sourceSetModuleDependencies = sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
|
||||
sourceSet.name to project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies.map {
|
||||
/**
|
||||
* Currently, Kotlin/Native dependencies must include the implementation dependencies, too. These dependencies must also be
|
||||
* published as API dependencies of the metadata module to get into the resolution result, see
|
||||
* [KotlinMetadataTargetConfigurator.exportDependenciesForPublishing].
|
||||
*/
|
||||
val isNativeSharedSourceSet = sourceSetsWithMetadataCompilations[sourceSet] is KotlinSharedNativeCompilation
|
||||
val sourceSetExportedDependencies = when {
|
||||
isNativeSharedSourceSet -> sourceSet.getSourceSetHierarchy().flatMap { hierarchySourceSet ->
|
||||
listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE).flatMap { scope ->
|
||||
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope).allDependencies.toList()
|
||||
}
|
||||
}.distinct()
|
||||
else -> project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies
|
||||
}
|
||||
sourceSet.name to sourceSetExportedDependencies.map {
|
||||
ModuleDependencyIdentifier(it.group.orEmpty(), it.name)
|
||||
}.toSet()
|
||||
},
|
||||
|
||||
+33
@@ -177,6 +177,10 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
)
|
||||
}
|
||||
|
||||
sourceSetsWithMetadataCompilations.values.forEach { compilation ->
|
||||
exportDependenciesForPublishing(compilation)
|
||||
}
|
||||
|
||||
val generateMetadata = createGenerateProjectStructureMetadataTask()
|
||||
|
||||
allMetadataJar.from(project.files(Callable {
|
||||
@@ -186,6 +190,35 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportDependenciesForPublishing(
|
||||
compilation: AbstractKotlinCompilation<*>
|
||||
) {
|
||||
val sourceSet = compilation.defaultSourceSet
|
||||
val isSharedNativeCompilation = compilation is KotlinSharedNativeCompilation
|
||||
val target = compilation.target
|
||||
with(target.project) {
|
||||
val apiElementsConfiguration = configurations.getByName(target.apiElementsConfigurationName)
|
||||
|
||||
// With the metadata target, we publish all API dependencies of all the published source sets together:
|
||||
apiElementsConfiguration.extendsFrom(sourceSetDependencyConfigurationByScope(sourceSet, KotlinDependencyScope.API_SCOPE))
|
||||
|
||||
/** For Kotlin/Native-shared source sets, we also add the implementation dependencies to apiElements, because Kotlin/Native
|
||||
* can't have any implementation dependencies, all dependencies used for compilation must be shipped along with the klib.
|
||||
* It's OK that they are exposed as API dependencies here, because at the consumer side, they are dispatched to the
|
||||
* consumer's source sets in a granular way, so they will only be visible in the source sets that see the sharedn-Native
|
||||
* source set.
|
||||
* See also: [buildKotlinProjectStructureMetadata], where these dependencies must be included into the source set exported deps.
|
||||
*/
|
||||
if (isSharedNativeCompilation) {
|
||||
sourceSet.getSourceSetHierarchy().forEach { hierarchySourceSet ->
|
||||
apiElementsConfiguration.extendsFrom(
|
||||
sourceSetDependencyConfigurationByScope(hierarchySourceSet, KotlinDependencyScope.IMPLEMENTATION_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMergedAllSourceSetsConfigurations(target: KotlinMetadataTarget): Unit = with(target.project) {
|
||||
listOf(ALL_COMPILE_METADATA_CONFIGURATION_NAME, ALL_RUNTIME_METADATA_CONFIGURATION_NAME).forEach { configurationName ->
|
||||
project.configurations.create(configurationName).apply {
|
||||
|
||||
Reference in New Issue
Block a user