[Gradle] Introduce KotlinTargetHierarchy.ModuleName as public concept

KT-34662
This commit is contained in:
Sebastian Sellmair
2023-03-09 12:58:37 +01:00
committed by Space Team
parent 78673885e5
commit 78751dcb55
12 changed files with 181 additions and 125 deletions
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
interface KotlinTargetHierarchyDsl {
fun apply(
hierarchyDescriptor: KotlinTargetHierarchyDescriptor,
describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)? = null
describeExtension: (KotlinTargetHierarchyBuilder.Root.() -> Unit)? = null
)
/**
@@ -80,6 +80,6 @@ interface KotlinTargetHierarchyDsl {
* @param describeExtension: Additional groups can be described to extend the 'default'/'natural' hierarchy:
* @see KotlinTargetHierarchyDescriptor.extend
*/
fun default(describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)? = null)
fun custom(describe: KotlinTargetHierarchyBuilder.() -> Unit)
fun default(describeExtension: (KotlinTargetHierarchyBuilder.Root.() -> Unit)? = null)
fun custom(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit)
}
@@ -0,0 +1,21 @@
/*
* 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.plugin
interface KotlinTargetHierarchy {
data class ModuleName(val name: String) {
override fun toString(): String = name
companion object {
val main = ModuleName("main")
val test = ModuleName("test")
val unitTest = ModuleName("unitTest")
val integrationTest = ModuleName("integrationTest")
val instrumentedTest = ModuleName("instrumentedTest")
}
}
}
@@ -8,16 +8,27 @@ import org.jetbrains.kotlin.konan.target.DEPRECATED_TARGET_MESSAGE
@KotlinTargetsDsl
@ExperimentalKotlinGradlePluginApi
interface KotlinTargetHierarchyBuilder {
interface Root : KotlinTargetHierarchyBuilder {
fun modules(vararg module: KotlinTargetHierarchy.ModuleName)
fun withModule(vararg module: KotlinTargetHierarchy.ModuleName)
fun excludeModule(vararg module: KotlinTargetHierarchy.ModuleName)
}
/* Declaring groups */
fun common(build: KotlinTargetHierarchyBuilder.() -> Unit) = group("common", build)
fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit = {})
/* low-level APIs */
fun withCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean)
fun withoutCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean)
fun excludeCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean)
@Deprecated("Use 'excludeCompilations' instead", ReplaceWith("excludeCompilations(predicate)"))
fun withoutCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean) = excludeCompilations(predicate)
@Deprecated("Use plain 'withoutCompilations(!predicate) instead'", ReplaceWith("withoutCompilations { !predicate(it) }"))
fun filterCompilations(predicate: (KotlinCompilation<*>) -> Boolean) = withoutCompilations { !predicate(it) }
fun filterCompilations(predicate: (KotlinCompilation<*>) -> Boolean) = excludeCompilations { !predicate(it) }
/* Convenient groups */
fun withNative()
@@ -9,23 +9,23 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@ExperimentalKotlinGradlePluginApi
interface KotlinTargetHierarchyDescriptor {
fun describe(builder: KotlinTargetHierarchyBuilder)
fun extend(describe: KotlinTargetHierarchyBuilder.() -> Unit): KotlinTargetHierarchyDescriptor
fun describe(builder: KotlinTargetHierarchyBuilder.Root)
fun extend(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit): KotlinTargetHierarchyDescriptor
}
@ExperimentalKotlinGradlePluginApi
fun KotlinTargetHierarchyDescriptor(
describe: KotlinTargetHierarchyBuilder.() -> Unit
describe: KotlinTargetHierarchyBuilder.Root.() -> Unit
): KotlinTargetHierarchyDescriptor {
return KotlinTargetHierarchyDescriptorImpl(describe)
}
@ExperimentalKotlinGradlePluginApi
private class KotlinTargetHierarchyDescriptorImpl(
private val describe: KotlinTargetHierarchyBuilder.() -> Unit
private val describe: KotlinTargetHierarchyBuilder.Root.() -> Unit
) : KotlinTargetHierarchyDescriptor {
override fun extend(describe: KotlinTargetHierarchyBuilder.() -> Unit): KotlinTargetHierarchyDescriptor {
override fun extend(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit): KotlinTargetHierarchyDescriptor {
val sourceDescribe = this.describe
return KotlinTargetHierarchyDescriptor {
sourceDescribe()
@@ -33,5 +33,5 @@ private class KotlinTargetHierarchyDescriptorImpl(
}
}
override fun describe(builder: KotlinTargetHierarchyBuilder) = builder.describe()
override fun describe(builder: KotlinTargetHierarchyBuilder.Root) = builder.describe()
}
@@ -0,0 +1,37 @@
/*
* 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.plugin.mpp.targetHierarchy
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.ModuleName
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
internal suspend fun ModuleName.Companion.orNull(compilation: KotlinCompilation<*>): ModuleName? =
when (compilation) {
is KotlinJvmAndroidCompilation -> orNull(compilation.target, compilation.androidVariant.type)
else -> when (compilation.name) {
"main" -> main
"test" -> test
else -> ModuleName(compilation.name)
}
}
internal suspend fun ModuleName.Companion.orNull(
target: KotlinAndroidTarget,
variantType: AndroidVariantType
): ModuleName? = when (variantType) {
AndroidVariantType.Main ->
target.main.targetHierarchy.module.awaitFinalValue() ?: main
AndroidVariantType.UnitTest ->
target.unitTest.targetHierarchy.module.awaitFinalValue() ?: test
AndroidVariantType.InstrumentedTest ->
target.instrumentedTest.targetHierarchy.module.awaitFinalValue() ?: ModuleName("instrumentedTest")
AndroidVariantType.Unknown -> null
}
@@ -1,82 +0,0 @@
/*
* Copyright 2010-2022 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.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
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.tooling.core.withClosure
internal data class KotlinTargetHierarchy(
val node: Node, val children: Set<KotlinTargetHierarchy> = emptySet()
) {
val childrenClosure: Set<KotlinTargetHierarchy> =
children.withClosure<KotlinTargetHierarchy> { it.children }
sealed class Node {
abstract suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String?
object Root : Node() {
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? = null
}
data class Group(val name: String) : Node() {
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? {
val moduleName = Module.orNull(compilation)?.name ?: return null
return lowerCamelCaseName(name, moduleName)
}
}
final override fun toString(): String = when (this) {
is Group -> name
is Root -> "<root>"
}
}
data class Module(val name: String) {
override fun toString(): String = name
companion object {
val main = Module("main")
val test = Module("test")
private val instrumentedTest = Module("instrumentedTest")
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
}
}
}
}
override fun toString(): String {
if (children.isEmpty()) return node.toString()
return node.toString() + "\n" + children.joinToString("\n").prependIndent("----")
}
}
@@ -10,30 +10,29 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.konan.target.DEPRECATED_TARGET_MESSAGE
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.tooling.core.closure
internal suspend fun KotlinTargetHierarchyDescriptor.buildKotlinTargetHierarchy(compilation: KotlinCompilation<*>): KotlinTargetHierarchy? {
internal suspend fun KotlinTargetHierarchyDescriptor.buildKotlinTargetHierarchy(compilation: KotlinCompilation<*>): KotlinTargetHierarchyTree? {
val context = KotlinTargetHierarchyBuilderImplContext(compilation)
describe(context.getOrCreateBuilder(KotlinTargetHierarchy.Node.Root))
return context.build(KotlinTargetHierarchy.Node.Root)
describe(context.root)
return context.build(KotlinTargetHierarchyTree.Node.Root)
}
private class KotlinTargetHierarchyBuilderImplContext(private val compilation: KotlinCompilation<*>) {
private val builders = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchyBuilderImpl>()
private val builtValues = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchy?>()
val root by lazy { KotlinTargetHierarchyBuilderRootImpl(getOrCreateBuilder(KotlinTargetHierarchyTree.Node.Root)) }
fun getOrCreateBuilder(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchyBuilderImpl = builders.getOrPut(node) {
private val builders = hashMapOf<KotlinTargetHierarchyTree.Node, KotlinTargetHierarchyBuilderImpl>()
private val builtValues = hashMapOf<KotlinTargetHierarchyTree.Node, KotlinTargetHierarchyTree?>()
fun getOrCreateBuilder(node: KotlinTargetHierarchyTree.Node): KotlinTargetHierarchyBuilderImpl = builders.getOrPut(node) {
KotlinTargetHierarchyBuilderImpl(this, node)
}
suspend fun build(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchy? {
suspend fun build(node: KotlinTargetHierarchyTree.Node): KotlinTargetHierarchyTree? {
return builtValues.getOrPut(node) {
val builder = getOrCreateBuilder(node)
if (!builder.contains(compilation)) return@getOrPut null
@@ -51,14 +50,44 @@ private class KotlinTargetHierarchyBuilderImplContext(private val compilation: K
*/
val children = builder.children.mapNotNull { child -> build(child.node) }
val directChildren = children.toSet() - children.flatMap { child -> child.childrenClosure }.toSet()
KotlinTargetHierarchy(node, directChildren)
KotlinTargetHierarchyTree(node, directChildren)
}
}
}
private class KotlinTargetHierarchyBuilderRootImpl(
private val builder: KotlinTargetHierarchyBuilder
) : KotlinTargetHierarchyBuilder.Root, KotlinTargetHierarchyBuilder by builder {
private var modules: Set<KotlinTargetHierarchy.ModuleName>? = null
override fun modules(vararg module: KotlinTargetHierarchy.ModuleName) {
this.modules = module.toHashSet()
}
override fun withModule(vararg module: KotlinTargetHierarchy.ModuleName) {
this.modules = this.modules.orEmpty().plus(module)
}
override fun excludeModule(vararg module: KotlinTargetHierarchy.ModuleName) {
val modules = module.toHashSet()
if (modules.isEmpty()) return
if (this.modules == null) return
this.modules = this.modules.orEmpty() - modules
}
init {
builder.excludeCompilations exclude@{ compilation ->
val modules = this.modules ?: return@exclude false
val module = KotlinTargetHierarchy.ModuleName.orNull(compilation) ?: return@exclude false
module !in modules
}
}
}
private class KotlinTargetHierarchyBuilderImpl(
val context: KotlinTargetHierarchyBuilderImplContext,
val node: KotlinTargetHierarchy.Node,
val node: KotlinTargetHierarchyTree.Node,
) : KotlinTargetHierarchyBuilder {
val children = mutableSetOf<KotlinTargetHierarchyBuilderImpl>()
@@ -74,7 +103,7 @@ private class KotlinTargetHierarchyBuilderImpl(
this.excludePredicate = { previousExcludePredicate(it) && !predicate(it) }
}
override fun withoutCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean) {
override fun excludeCompilations(predicate: suspend (KotlinCompilation<*>) -> Boolean) {
val previousIncludePredicate = this.includePredicate
val previousExcludePredicate = this.excludePredicate
this.includePredicate = { previousIncludePredicate(it) && !predicate(it) }
@@ -95,7 +124,7 @@ private class KotlinTargetHierarchyBuilderImpl(
private inline fun withTargets(crossinline predicate: (KotlinTarget) -> Boolean) = withCompilations { predicate(it.target) }
override fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit) {
val node = KotlinTargetHierarchy.Node.Group(name)
val node = KotlinTargetHierarchyTree.Node.Group(name)
val child = context.getOrCreateBuilder(node).also(build)
children.add(child)
checkCyclicHierarchy()
@@ -273,6 +302,6 @@ private fun KotlinTargetHierarchyBuilderImpl.checkCyclicHierarchy(): Nothing? {
return null
}
internal class CyclicKotlinTargetHierarchyException(val cycle: List<KotlinTargetHierarchy.Node>) : InvalidUserCodeException(
internal class CyclicKotlinTargetHierarchyException(val cycle: List<KotlinTargetHierarchyTree.Node>) : InvalidUserCodeException(
"KotlinTargetHierarchy cycle detected: ${cycle.joinToString(" -> ")}"
)
@@ -23,21 +23,21 @@ internal class KotlinTargetHierarchyDslImpl(
override fun apply(
hierarchyDescriptor: KotlinTargetHierarchyDescriptor,
describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)?
describeExtension: (KotlinTargetHierarchyBuilder.Root.() -> Unit)?
) {
val descriptor = hierarchyDescriptor.extendIfNotNull(describeExtension)
_appliedDescriptors.add(descriptor)
applyKotlinTargetHierarchy(descriptor, targets, sourceSets)
}
override fun default(describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)?) {
override fun default(describeExtension: (KotlinTargetHierarchyBuilder.Root.() -> Unit)?) {
apply(defaultKotlinTargetHierarchy, describeExtension)
}
override fun custom(describe: KotlinTargetHierarchyBuilder.() -> Unit) {
override fun custom(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit) {
apply(KotlinTargetHierarchyDescriptor(describe))
}
}
private fun KotlinTargetHierarchyDescriptor.extendIfNotNull(describe: (KotlinTargetHierarchyBuilder.() -> Unit)?) =
private fun KotlinTargetHierarchyDescriptor.extendIfNotNull(describe: (KotlinTargetHierarchyBuilder.Root.() -> Unit)?) =
if (describe == null) this else extend(describe)
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2022 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.plugin.mpp.targetHierarchy
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.tooling.core.withClosure
internal data class KotlinTargetHierarchyTree(
val node: Node, val children: Set<KotlinTargetHierarchyTree> = emptySet()
) {
val childrenClosure: Set<KotlinTargetHierarchyTree> =
children.withClosure<KotlinTargetHierarchyTree> { it.children }
sealed class Node {
abstract suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String?
object Root : Node() {
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? = null
}
data class Group(val name: String) : Node() {
override suspend fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? {
val moduleName = KotlinTargetHierarchy.ModuleName.orNull(compilation)?.name ?: return null
return lowerCamelCaseName(name, moduleName)
}
}
final override fun toString(): String = when (this) {
is Group -> name
is Root -> "<root>"
}
}
override fun toString(): String {
if (children.isEmpty()) return node.toString()
return node.toString() + "\n" + children.joinToString("\n").prependIndent("----")
}
}
@@ -28,7 +28,7 @@ internal fun applyKotlinTargetHierarchy(
}
private suspend fun applyKotlinTargetHierarchy(
hierarchy: KotlinTargetHierarchy,
hierarchy: KotlinTargetHierarchyTree,
compilation: KotlinCompilation<*>,
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
): KotlinSourceSet? {
@@ -52,7 +52,7 @@ private suspend fun applyKotlinTargetHierarchy(
private suspend fun createSharedSourceSetOrNull(
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>,
node: KotlinTargetHierarchy.Node,
node: KotlinTargetHierarchyTree.Node,
compilation: KotlinCompilation<*>,
): KotlinSourceSet? {
val sharedSourceSetName = node.sharedSourceSetName(compilation) ?: return null
@@ -5,16 +5,12 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.ModuleName
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
private val defaultKotlinTargetHierarchyModules = hashSetOf(
KotlinTargetHierarchy.Module.main,
KotlinTargetHierarchy.Module.test
)
internal val defaultKotlinTargetHierarchy = KotlinTargetHierarchyDescriptor {
/* natural hierarchy is only applied to default 'main'/'test' compilations (by default) */
withoutCompilations { KotlinTargetHierarchy.Module.orNull(it) !in defaultKotlinTargetHierarchyModules }
withModule(ModuleName.main, ModuleName.test)
common {
/* All compilations shall receive be added to the common group by default */
@@ -10,8 +10,8 @@ package org.jetbrains.kotlin.gradle.unitTests
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.CyclicKotlinTargetHierarchyException
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchy
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchy.Node
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchyTree
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchyTree.Node
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.buildKotlinTargetHierarchy
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
@@ -149,7 +149,7 @@ class KotlinTargetHierarchyDescriptorTest {
}, hierarchy
)
fun KotlinTargetHierarchy.collectChildren(): List<KotlinTargetHierarchy> {
fun KotlinTargetHierarchyTree.collectChildren(): List<KotlinTargetHierarchyTree> {
return children.toList() + children.flatMap { it.collectChildren() }
}
@@ -249,9 +249,9 @@ private class TestHierarchyBuilder(private val node: Node) {
children.add(TestHierarchyBuilder(Node.Group(name)).also(builder))
}
fun build(): KotlinTargetHierarchy = KotlinTargetHierarchy(node, children.map { it.build() }.toSet())
fun build(): KotlinTargetHierarchyTree = KotlinTargetHierarchyTree(node, children.map { it.build() }.toSet())
}
private fun hierarchy(build: TestHierarchyBuilder.() -> Unit): KotlinTargetHierarchy {
private fun hierarchy(build: TestHierarchyBuilder.() -> Unit): KotlinTargetHierarchyTree {
return TestHierarchyBuilder(Node.Root).also(build).build()
}