[Gradle] KotlinTargetHierarchy.Module: Respect configuration from Android

KT-34662
This commit is contained in:
Sebastian Sellmair
2023-03-08 16:48:22 +01:00
committed by Space Team
parent 7ab6349ad5
commit 5fe6e3edbb
11 changed files with 125 additions and 43 deletions
@@ -13,8 +13,9 @@ interface KotlinTargetHierarchyBuilder {
fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit = {})
/* low-level APIs */
fun withCompilations(predicate: (KotlinCompilation<*>) -> Boolean)
fun withoutCompilations(predicate: (KotlinCompilation<*>) -> Boolean)
fun withCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean)
fun withoutCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean)
@Deprecated("Use plain 'withoutCompilations(!predicate) instead'", ReplaceWith("withoutCompilations { !predicate(it) }"))
fun filterCompilations(predicate: (KotlinCompilation<*>) -> Boolean) = withoutCompilations { !predicate(it) }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType
import org.jetbrains.kotlin.gradle.plugin.sources.android.type
@@ -20,14 +22,14 @@ internal data class KotlinTargetHierarchy(
children.withClosure<KotlinTargetHierarchy> { it.children }
sealed class Node {
abstract fun sharedSourceSetName(compilation: KotlinCompilation<*>): String?
abstract suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String?
object Root : Node() {
override fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? = null
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? = null
}
data class Group(val name: String) : Node() {
override fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? {
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? {
val moduleName = Module.orNull(compilation)?.name ?: return null
return lowerCamelCaseName(name, moduleName)
}
@@ -47,19 +49,29 @@ internal data class KotlinTargetHierarchy(
val test = Module("test")
private val instrumentedTest = Module("instrumentedTest")
fun orNull(compilation: KotlinCompilation<*>): Module? = when (compilation) {
is KotlinJvmAndroidCompilation -> when (compilation.androidVariant.type) {
AndroidVariantType.Main -> main
AndroidVariantType.UnitTest -> test
AndroidVariantType.InstrumentedTest -> instrumentedTest
AndroidVariantType.Unknown -> null
}
suspend fun orNull(compilation: KotlinCompilation<*>): Module? = when (compilation) {
is KotlinJvmAndroidCompilation -> orNull(compilation.target, compilation.androidVariant.type)
else -> when (compilation.name) {
"main" -> main
"test" -> test
else -> Module(compilation.name)
}
}
suspend fun orNull(target: KotlinAndroidTarget, variantType: AndroidVariantType): Module? {
return when (variantType) {
AndroidVariantType.Main ->
Module(target.main.kotlinModuleName.awaitFinalValue() ?: return main)
AndroidVariantType.UnitTest ->
Module(target.unitTest.kotlinModuleName.awaitFinalValue() ?: return test)
AndroidVariantType.InstrumentedTest ->
Module(target.instrumentedTest.kotlinModuleName.awaitFinalValue() ?: return instrumentedTest)
AndroidVariantType.Unknown -> null
}
}
}
}
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.tooling.core.closure
internal fun KotlinTargetHierarchyDescriptor.buildKotlinTargetHierarchy(compilation: KotlinCompilation<*>): KotlinTargetHierarchy? {
internal suspend fun KotlinTargetHierarchyDescriptor.buildKotlinTargetHierarchy(compilation: KotlinCompilation<*>): KotlinTargetHierarchy? {
val context = KotlinTargetHierarchyBuilderImplContext(compilation)
describe(context.getOrCreateBuilder(KotlinTargetHierarchy.Node.Root))
return context.build(KotlinTargetHierarchy.Node.Root)
@@ -33,10 +33,10 @@ private class KotlinTargetHierarchyBuilderImplContext(private val compilation: K
KotlinTargetHierarchyBuilderImpl(this, node)
}
fun build(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchy? {
suspend fun build(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchy? {
return builtValues.getOrPut(node) {
val builder = getOrCreateBuilder(node)
if (compilation !in builder) return@getOrPut null
if (!builder.contains(compilation)) return@getOrPut null
/*
Keep the hierarchy 'deduplicated'.
@@ -64,24 +64,24 @@ private class KotlinTargetHierarchyBuilderImpl(
val children = mutableSetOf<KotlinTargetHierarchyBuilderImpl>()
val childrenClosure get() = closure { it.children }
private var includePredicate: ((KotlinCompilation<*>) -> Boolean) = { false }
private var excludePredicate: ((KotlinCompilation<*>) -> Boolean) = { false }
private var includePredicate: (suspend (KotlinCompilation<*>) -> Boolean) = { false }
private var excludePredicate: (suspend (KotlinCompilation<*>) -> Boolean) = { false }
override fun withCompilations(predicate: (KotlinCompilation<*>) -> Boolean) {
override fun withCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean) {
val previousIncludePredicate = this.includePredicate
val previousExcludePredicate = this.excludePredicate
this.includePredicate = { previousIncludePredicate(it) || predicate(it) }
this.excludePredicate = { previousExcludePredicate(it) && !predicate(it) }
}
override fun withoutCompilations(predicate: (KotlinCompilation<*>) -> Boolean) {
override fun withoutCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean) {
val previousIncludePredicate = this.includePredicate
val previousExcludePredicate = this.excludePredicate
this.includePredicate = { previousIncludePredicate(it) && !predicate(it) }
this.excludePredicate = { previousExcludePredicate(it) || predicate(it) }
}
operator fun contains(compilation: KotlinCompilation<*>): Boolean {
suspend fun contains(compilation: KotlinCompilation<*>): Boolean {
/* Return eagerly, when compilation is explicitly excluded */
if (excludePredicate(compilation)) return false
@@ -89,7 +89,7 @@ private class KotlinTargetHierarchyBuilderImpl(
if (includePredicate(compilation)) return true
/* Find any child that includes this compilation */
return childrenClosure.any { child -> compilation in child }
return childrenClosure.any { child -> child.contains(compilation) }
}
private inline fun withTargets(crossinline predicate: (KotlinTarget) -> Boolean) = withCompilations { predicate(it.target) }
@@ -19,13 +19,15 @@ internal fun applyKotlinTargetHierarchy(
.matching { target -> target.platformType != KotlinPlatformType.common }
.all { target ->
target.compilations.all forCompilation@{ compilation ->
val hierarchy = hierarchyDescriptor.buildKotlinTargetHierarchy(compilation) ?: return@forCompilation
applyKotlinTargetHierarchy(hierarchy, compilation, sourceSets)
target.project.kotlinMultiplatformPluginLifecycle.launch {
val hierarchy = hierarchyDescriptor.buildKotlinTargetHierarchy(compilation) ?: return@launch
applyKotlinTargetHierarchy(hierarchy, compilation, sourceSets)
}
}
}
}
private fun applyKotlinTargetHierarchy(
private suspend fun applyKotlinTargetHierarchy(
hierarchy: KotlinTargetHierarchy,
compilation: KotlinCompilation<*>,
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
@@ -48,7 +50,7 @@ private fun applyKotlinTargetHierarchy(
return sharedSourceSet
}
private fun createSharedSourceSetOrNull(
private suspend fun createSharedSourceSetOrNull(
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>,
node: KotlinTargetHierarchy.Node,
compilation: KotlinCompilation<*>,
@@ -11,14 +11,15 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.kotlinMultiplatformPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.launch
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchy
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidBaseSourceSetName
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType
import org.jetbrains.kotlin.gradle.plugin.sources.android.type
import org.jetbrains.kotlin.gradle.plugin.sources.android.variantType
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal object MultiplatformLayoutV2DependsOnConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
@@ -31,22 +32,16 @@ internal object MultiplatformLayoutV2DependsOnConfigurator : KotlinAndroidSource
}
private fun setDefaultDependsOn(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variantType: AndroidVariantType) {
target.project.kotlinMultiplatformPluginLifecycle.launch {
await(KotlinMultiplatformPluginLifecycle.Stage.FinaliseRefinesEdges)
target.project.launch(KotlinMultiplatformPluginLifecycle.Stage.FinaliseRefinesEdges) {
/* Only setup default if not KotlinTargetHierarchy was applied */
if (target.project.multiplatformExtensionOrNull?.internalKotlinTargetHierarchy?.appliedDescriptors.orEmpty().isNotEmpty()) {
return@launch
}
/* Add default dependency on 'commonMain' or 'commonTest' */
val commonMain = target.project.kotlinExtension.sourceSets.getByName(COMMON_MAIN_SOURCE_SET_NAME)
val commonTest = target.project.kotlinExtension.sourceSets.getByName(COMMON_TEST_SOURCE_SET_NAME)
when (variantType) {
AndroidVariantType.Main -> kotlinSourceSet.dependsOn(commonMain)
AndroidVariantType.UnitTest -> kotlinSourceSet.dependsOn(commonTest)
AndroidVariantType.InstrumentedTest, AndroidVariantType.Unknown -> Unit
}
val module = KotlinTargetHierarchy.Module.orNull(target, variantType) ?: return@launch
val commonSourceSetName = lowerCamelCaseName("common", module.name)
val commonSourceSet = target.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return@launch
kotlinSourceSet.dependsOn(commonSourceSet)
}
}
}
@@ -12,9 +12,13 @@ import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.*
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes
import org.jetbrains.kotlin.gradle.targets.android.InternalKotlinAndroidTargetVariantTypeDsl
import org.jetbrains.kotlin.gradle.targets.android.KotlinAndroidTargetVariantTypeDsl
import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
import org.jetbrains.kotlin.gradle.utils.forAllAndroidVariants
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -36,6 +40,15 @@ abstract class KotlinAndroidTarget @Inject constructor(
override val compilations: NamedDomainObjectContainer<out KotlinJvmAndroidCompilation> =
project.container(KotlinJvmAndroidCompilation::class.java)
@ExperimentalKotlinGradlePluginApi
val main: KotlinAndroidTargetVariantTypeDsl = InternalKotlinAndroidTargetVariantTypeDsl(project)
@ExperimentalKotlinGradlePluginApi
val unitTest: KotlinAndroidTargetVariantTypeDsl = InternalKotlinAndroidTargetVariantTypeDsl(project)
@ExperimentalKotlinGradlePluginApi
val instrumentedTest: KotlinAndroidTargetVariantTypeDsl = InternalKotlinAndroidTargetVariantTypeDsl(project)
/** Names of the Android library variants that should be published from the target's project within the default publications which are
* set up if the `maven-publish` Gradle plugin is applied.
*
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.targets.android
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle.Stage
import org.jetbrains.kotlin.gradle.plugin.newLifecycleAwareProperty
sealed class KotlinAndroidTargetVariantTypeDsl {
abstract val kotlinModuleName: Property<String>
}
internal val KotlinAndroidTargetVariantTypeDsl.internal: InternalKotlinAndroidTargetVariantTypeDsl
get() = when (this) {
is InternalKotlinAndroidTargetVariantTypeDsl -> this
}
internal class InternalKotlinAndroidTargetVariantTypeDsl(
val project: Project,
) : KotlinAndroidTargetVariantTypeDsl() {
override val kotlinModuleName: Property<String> by project.newLifecycleAwareProperty<String>(
finaliseIn = Stage.BeforeFinaliseRefinesEdges
)
}
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
import org.jetbrains.kotlin.gradle.tasks.KotlinTest
import org.jetbrains.kotlin.gradle.testing.internal.reportsDir
import org.jetbrains.kotlin.gradle.utils.appendLine
import org.jetbrains.kotlin.gradle.utils.isParentOf
import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import org.slf4j.Logger
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import junit.framework.TestCase.assertNull
import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue
import org.jetbrains.kotlin.gradle.plugin.kotlinMultiplatformPluginLifecycle
import org.jetbrains.kotlin.gradle.targets.android.InternalKotlinAndroidTargetVariantTypeDsl
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.junit.Test
class KotlinAndroidTargetVariantTypeDslTest {
private val project = buildProjectWithMPP()
@Test
fun `test - targetHierarchyModuleName - not set`() {
val dsl = InternalKotlinAndroidTargetVariantTypeDsl(project)
project.kotlinMultiplatformPluginLifecycle.launch {
assertNull(dsl.kotlinModuleName.orNull)
assertNull(dsl.kotlinModuleName.awaitFinalValue())
}
project.evaluate()
}
}
@@ -201,7 +201,7 @@ class KotlinTargetHierarchyDslTest {
)
assertEquals(
stringSetOf("jvmTest", "androidUnitTest", "androidUnitTest", "androidUnitTestDebug", "androidUnitTestRelease"),
stringSetOf("androidUnitTest", "androidUnitTestDebug", "androidUnitTestRelease", "jvmTest"),
kotlin.dependingSourceSetNames("jvmAndAndroidTest")
)
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.enqueue
import org.jetbrains.kotlin.gradle.plugin.launch
import org.jetbrains.kotlin.gradle.plugin.mpp.getHostSpecificMainSharedSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.InternalKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.internal
@@ -70,7 +70,7 @@ class InternalKotlinSourceSetTest {
nativeMain.internal.compilations
)
project.enqueue(KotlinMultiplatformPluginLifecycle.Stage.Finalised) {
project.launch(KotlinMultiplatformPluginLifecycle.Stage.Finalised) {
assertEquals<Set<KotlinCompilation<*>>>(
setOf(
metadataCompilation,