[Gradle][MPP] Implement initial KotlinTargetHierarchy APIs

^KT-53570 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-08-19 14:39:25 +02:00
committed by Space Team
parent 1fc2575052
commit c5d6162578
13 changed files with 957 additions and 4 deletions
@@ -505,6 +505,17 @@ public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinNativeXCFr
public abstract fun targets ([Lorg/jetbrains/kotlin/konan/target/KonanTarget;)V
}
public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl {
public abstract fun apply (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor;Lkotlin/jvm/functions/Function1;)V
public abstract fun custom (Lkotlin/jvm/functions/Function1;)V
public abstract fun default (Lkotlin/jvm/functions/Function1;)V
}
public final class org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl$DefaultImpls {
public static synthetic fun apply$default (Lorg/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl;Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun default$default (Lorg/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinTopLevelExtensionConfig {
public abstract fun explicitApi ()V
public abstract fun explicitApiWarning ()V
@@ -1017,6 +1028,39 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinTargetE
public abstract fun getTarget ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;
}
public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyBuilder {
public abstract fun common (Lkotlin/jvm/functions/Function1;)V
public abstract fun getCompilation ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;
public abstract fun getTarget ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;
public abstract fun group (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun isAndroidJvm ()Z
public abstract fun isAndroidNative ()Z
public abstract fun isApple ()Z
public abstract fun isIos ()Z
public abstract fun isJs ()Z
public abstract fun isJsLegacy ()Z
public abstract fun isJvm ()Z
public abstract fun isLinux ()Z
public abstract fun isMacos ()Z
public abstract fun isNative ()Z
public abstract fun isTvos ()Z
public abstract fun isWatchos ()Z
public abstract fun isWindows ()Z
}
public final class org/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyBuilder$DefaultImpls {
public static fun common (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyBuilder;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun group$default (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor : kotlin/jvm/functions/Function1 {
public abstract fun extend (Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor;
}
public final class org/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptorKt {
public static final fun KotlinTargetHierarchyDescriptor (Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor;
}
public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinTargetPreset : org/gradle/api/Named {
public abstract fun createTarget (Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;
}
@@ -0,0 +1,86 @@
/*
* 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.dsl
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyBuilder
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
@ExperimentalKotlinGradlePluginApi
interface KotlinTargetHierarchyDsl {
fun apply(
hierarchyDescriptor: KotlinTargetHierarchyDescriptor,
describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)? = null
)
/**
* Set's up a 'natural'/'default' hierarchy withing [KotlinTarget]'s in the project.
*
* #### Example 1
*
* ```kotlin
* kotlin {
* targets.hierarchy.default() // <- position of this call is not relevant!
*
* iosX64()
* iosArm64()
* linuxX64()
* linuxArm64()
* }
* ```
*
* Will create the following SourceSets:
* `[iosMain, iosTest, appleMain, appleTest, linuxMain, linuxTest, nativeMain, nativeTest]
*
*
* Hierarchy:
* ```
* common
* |
* +-----------------+-------------------+
* | |
*
* native ...
*
* |
* |
* |
* +----------------------+--------------------+-----------------------+
* | | | |
*
* apple linux windows androidNative
*
* |
* +-----------+------------+------------+
* | | | |
*
* macos ios tvos watchos
* ```
*
* #### Example 2: Adding custom groups
* Let's imagine we would additionally like to share code between linux and apple (unixLike)
*
* ```kotlin
* kotlin {
* targets.hierarchy.default { target ->
* if(target.isNative) {
* group("native") { // <- we can re-declare already existing groups and connect children to it!
* if(target.isLinux || target.isApple) {
* group("unixLike")
* }
* }
* }
* }
* }
* ```
*
* @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)
}
@@ -0,0 +1,32 @@
/*
* 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
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@ExperimentalKotlinGradlePluginApi
interface KotlinTargetHierarchyBuilder {
val target: KotlinTarget
val compilation: KotlinCompilation<*>
val isNative: Boolean
val isApple: Boolean
val isIos: Boolean
val isWatchos: Boolean
val isMacos: Boolean
val isTvos: Boolean
val isWindows: Boolean
val isLinux: Boolean
val isAndroidNative: Boolean
val isJvm: Boolean
val isAndroidJvm: Boolean
val isJsLegacy: Boolean
val isJsIr: Boolean
val isJs: Boolean
fun common(build: KotlinTargetHierarchyBuilder.() -> Unit) = group("common", build)
fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit = {})
}
@@ -0,0 +1,36 @@
/*
* 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
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@ExperimentalKotlinGradlePluginApi
interface KotlinTargetHierarchyDescriptor : (KotlinTargetHierarchyBuilder) -> Unit {
fun extend(describe: KotlinTargetHierarchyBuilder.() -> Unit): KotlinTargetHierarchyDescriptor
}
@ExperimentalKotlinGradlePluginApi
fun KotlinTargetHierarchyDescriptor(
describe: KotlinTargetHierarchyBuilder.() -> Unit
): KotlinTargetHierarchyDescriptor {
return KotlinTargetHierarchyDescriptorImpl(describe)
}
@ExperimentalKotlinGradlePluginApi
private class KotlinTargetHierarchyDescriptorImpl(
private val describe: KotlinTargetHierarchyBuilder.() -> Unit
) : KotlinTargetHierarchyDescriptor {
override fun extend(describe: KotlinTargetHierarchyBuilder.() -> Unit): KotlinTargetHierarchyDescriptor {
val sourceDescribe = this.describe
return KotlinTargetHierarchyDescriptor {
sourceDescribe()
describe()
}
}
override fun invoke(p1: KotlinTargetHierarchyBuilder) = p1.describe()
}
@@ -5,15 +5,14 @@
package org.jetbrains.kotlin.gradle.dsl
import org.gradle.api.Action
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.Project
import org.gradle.api.*
import org.gradle.api.internal.plugins.DslObject
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinTargetHierarchyDslImpl
import javax.inject.Inject
abstract class KotlinMultiplatformExtension(project: Project) :
@@ -53,6 +52,9 @@ abstract class KotlinMultiplatformExtension(project: Project) :
configure(presetExtension)
}
@ExperimentalKotlinGradlePluginApi
val targetHierarchy: KotlinTargetHierarchyDsl get() = KotlinTargetHierarchyDslImpl(targets, sourceSets)
@Suppress("unused") // DSL
val testableTargets: NamedDomainObjectCollection<KotlinTargetWithTests<*, *>>
get() = targets.withType(KotlinTargetWithTests::class.java)
@@ -180,12 +182,14 @@ internal fun <T : KotlinTarget> KotlinTargetsContainerWithPresets.configureOrCre
configure(existingTarget as T)
return existingTarget
}
existingTarget == null -> {
val newTarget = targetPreset.createTarget(targetName)
targets.add(newTarget)
configure(newTarget)
return newTarget
}
else -> {
throw InvalidUserCodeException(
"The target '$targetName' already exists, but it was not created with the '${targetPreset.name}' preset. " +
@@ -0,0 +1,38 @@
/*
* 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.utils.lowerCamelCaseName
internal data class KotlinTargetHierarchy(
val node: Node, val children: Set<KotlinTargetHierarchy> = emptySet()
) {
sealed class Node {
abstract fun sharedSourceSetName(compilation: KotlinCompilation<*>): String?
object Root : Node() {
override fun sharedSourceSetName(compilation: KotlinCompilation<*>): String? = null
}
data class Group(val name: String) : Node() {
override fun sharedSourceSetName(compilation: KotlinCompilation<*>): String {
return lowerCamelCaseName(name, compilation.name)
}
}
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("----")
}
}
@@ -0,0 +1,37 @@
/*
* 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.gradle.api.DomainObjectCollection
import org.gradle.api.NamedDomainObjectContainer
import org.jetbrains.kotlin.gradle.dsl.KotlinTargetHierarchyDsl
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyBuilder
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
internal class KotlinTargetHierarchyDslImpl(
private val targets: DomainObjectCollection<KotlinTarget>,
private val sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
) : KotlinTargetHierarchyDsl {
override fun apply(
hierarchyDescriptor: KotlinTargetHierarchyDescriptor,
describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)?
) {
applyKotlinTargetHierarchy(hierarchyDescriptor.extendIfNotNull(describeExtension), targets, sourceSets)
}
override fun default(describeExtension: (KotlinTargetHierarchyBuilder.() -> Unit)?) {
apply(naturalKotlinTargetHierarchy, describeExtension)
}
override fun custom(describe: KotlinTargetHierarchyBuilder.() -> Unit) {
apply(KotlinTargetHierarchyDescriptor(describe))
}
}
private fun KotlinTargetHierarchyDescriptor.extendIfNotNull(describe: (KotlinTargetHierarchyBuilder.() -> 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.gradle.api.DomainObjectCollection
import org.gradle.api.NamedDomainObjectContainer
import org.jetbrains.kotlin.gradle.plugin.*
internal fun applyKotlinTargetHierarchy(
hierarchyDescriptor: KotlinTargetHierarchyDescriptor,
targets: DomainObjectCollection<KotlinTarget>,
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
) {
targets
.matching { target -> target.platformType != KotlinPlatformType.common }
.all { target ->
target.compilations.all { compilation ->
val hierarchy = hierarchyDescriptor.buildKotlinTargetHierarchy(compilation)
applyKotlinTargetHierarchy(hierarchy, compilation, sourceSets)
}
}
}
private fun applyKotlinTargetHierarchy(
hierarchy: KotlinTargetHierarchy,
compilation: KotlinCompilation<*>,
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
): KotlinSourceSet? {
val sharedSourceSetName = hierarchy.node.sharedSourceSetName(compilation) ?: return null
val sharedSourceSet = sourceSets.maybeCreate(sharedSourceSetName)
hierarchy.children
.mapNotNull { childHierarchy -> applyKotlinTargetHierarchy(childHierarchy, compilation, sourceSets) }
.forEach { childSourceSet -> childSourceSet.dependsOn(sharedSourceSet) }
if (hierarchy.children.isEmpty()) {
compilation.defaultSourceSet.dependsOn(sharedSourceSet)
}
return sharedSourceSet
}
@@ -0,0 +1,116 @@
/*
* 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.gradle.api.InvalidUserCodeException
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.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.tooling.core.closure
internal fun KotlinTargetHierarchyDescriptor.buildKotlinTargetHierarchy(compilation: KotlinCompilation<*>): KotlinTargetHierarchy {
val context = KotlinTargetHierarchyBuilderImplContext(compilation)
this(context.getOrCreateBuilder(KotlinTargetHierarchy.Node.Root))
return context.build(KotlinTargetHierarchy.Node.Root)
}
private class KotlinTargetHierarchyBuilderImplContext(val compilation: KotlinCompilation<*>) {
private val builders = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchyBuilderImpl>()
private val builtValues = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchy>()
fun getOrCreateBuilder(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchyBuilderImpl = builders.getOrPut(node) {
KotlinTargetHierarchyBuilderImpl(this, node)
}
fun build(node: KotlinTargetHierarchy.Node): KotlinTargetHierarchy {
return builtValues.getOrPut(node) {
val builder = getOrCreateBuilder(node)
/*
Keep the hierarchy 'deduplicated'.
e.g.
if we have two child hierarchies:
1) a -> b -> c
2) b -> c
then we can remove the duplicated 'sub hierarchy' 2) and only return
a -> b -> c
*/
val childrenNodes = builder.children.map { it.node } -
builder.children.flatMap { child -> child.childrenClosure }.map { it.node }.toSet()
KotlinTargetHierarchy(node, childrenNodes.map(this::build).toSet())
}
}
}
private class KotlinTargetHierarchyBuilderImpl(
val context: KotlinTargetHierarchyBuilderImplContext,
val node: KotlinTargetHierarchy.Node,
) : KotlinTargetHierarchyBuilder {
override val compilation: KotlinCompilation<*> get() = context.compilation
override val target: KotlinTarget get() = compilation.target
val children = mutableSetOf<KotlinTargetHierarchyBuilderImpl>()
val childrenClosure: Set<KotlinTargetHierarchyBuilderImpl> get() = closure { it.children }
override fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit) {
val node = KotlinTargetHierarchy.Node.Group(name)
val child = context.getOrCreateBuilder(node)
children.add(child)
child.build()
checkCyclicHierarchy()
}
override val isNative: Boolean get() = target is KotlinNativeTarget
override val isApple: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family.isAppleFamily }
override val isIos: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.IOS }
override val isWatchos: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.WATCHOS }
override val isMacos: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.OSX }
override val isTvos: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.TVOS }
override val isWindows: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.MINGW }
override val isLinux: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.LINUX }
override val isAndroidNative: Boolean get() = target.let { it is KotlinNativeTarget && it.konanTarget.family == Family.ANDROID }
override val isJvm: Boolean get() = target is KotlinJvmTarget
override val isAndroidJvm: Boolean get() = target is KotlinAndroidTarget
override val isJsLegacy: Boolean get() = target is KotlinJsTarget
override val isJsIr: Boolean get() = target is KotlinJsIrTarget
override val isJs: Boolean get() = isJsIr || isJsLegacy
override fun toString(): String {
return "KotlinTargetHierarchyBuilder($node)"
}
}
/* Cycle Detection: Provide feedback for users when a KotlinTargetHierarchy cycle is declared */
private fun KotlinTargetHierarchyBuilderImpl.checkCyclicHierarchy(): Nothing? {
val stack = mutableListOf(node)
val visited = hashSetOf<KotlinTargetHierarchyBuilderImpl>()
fun checkChild(child: KotlinTargetHierarchyBuilderImpl) {
if (!visited.add(child)) return
stack += child.node
if (this == child) throw CyclicKotlinTargetHierarchyException(stack)
child.children.forEach { next -> checkChild(next) }
stack -= child.node
}
children.forEach { child -> checkChild(child) }
return null
}
internal class CyclicKotlinTargetHierarchyException(val cycle: List<KotlinTargetHierarchy.Node>) : InvalidUserCodeException(
"KotlinTargetHierarchy cycle detected: ${cycle.joinToString(" -> ")}"
)
@@ -0,0 +1,36 @@
/*
* 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.KotlinTargetHierarchyDescriptor
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.mpp.isTest
internal val naturalKotlinTargetHierarchy = KotlinTargetHierarchyDescriptor {
if (!compilation.isMain() && !compilation.isTest()) {
/* This hierarchy is only defined for default 'main' and 'test' compilations */
return@KotlinTargetHierarchyDescriptor
}
common {
if (isNative) {
group("native") {
if (isApple) {
group("apple") {
if (isIos) group("ios")
if (isTvos) group("tvos")
if (isWatchos) group("watchos")
if (isMacos) group("macos")
}
}
if (isLinux) group("linux")
if (isWindows) group("windows")
if (isAndroidNative) group("androidNative")
}
}
}
}
@@ -0,0 +1,200 @@
/*
* 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle
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.buildKotlinTargetHierarchy
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertSame
class KotlinTargetHierarchyDescriptorTest {
private val project = buildProjectWithMPP()
private val kotlin = project.multiplatformExtension
@Test
fun `test - simple descriptor`() {
val descriptor = KotlinTargetHierarchyDescriptor {
common {
if (target.name == "a") group("groupA")
if (target.name == "b") group("groupB")
}
}
val targetA = kotlin.linuxX64("a")
val targetB = kotlin.linuxArm64("b")
val targetC = kotlin.jvm()
assertEquals(
hierarchy {
group("common") {
group("groupA")
}
},
descriptor.buildKotlinTargetHierarchy(targetA.compilations.main)
)
assertEquals(
hierarchy {
group("common") {
group("groupB")
}
},
descriptor.buildKotlinTargetHierarchy(targetB.compilations.main)
)
assertEquals(
hierarchy { group("common") },
descriptor.buildKotlinTargetHierarchy(targetC.compilations.main)
)
}
@Test
fun `test - extend`() {
val descriptor = KotlinTargetHierarchyDescriptor { group("base") }.extend {
group("base") {
group("extension")
}
}
assertEquals(
hierarchy {
group("base") {
group("extension")
}
},
descriptor.buildKotlinTargetHierarchy(kotlin.linuxX64().compilations.getByName("main"))
)
}
@Test
fun `test - extend - with new root`() {
val descriptor = KotlinTargetHierarchyDescriptor { group("base") }.extend {
group("newRoot") {
group("base") {
group("extension")
}
}
}
assertEquals(
hierarchy {
group("newRoot") {
group("base") {
group("extension")
}
}
},
descriptor.buildKotlinTargetHierarchy(kotlin.linuxX64().compilations.main)
)
}
@Test
fun `test - extend - with two new roots and two extensions`() {
val descriptor = KotlinTargetHierarchyDescriptor { group("base") }
.extend {
group("newRoot1") {
group("base") {
group("extension1")
}
}
}
.extend {
group("newRoot2") {
group("base") {
group("extension2")
}
}
}
val hierarchy = descriptor.buildKotlinTargetHierarchy(kotlin.linuxX64().compilations.main)
assertEquals(
hierarchy {
group("newRoot1") {
group("base") {
group("extension1")
group("extension2")
}
}
group("newRoot2") {
group("base") {
group("extension1")
group("extension2")
}
}
}, hierarchy
)
fun KotlinTargetHierarchy.collectChildren(): List<KotlinTargetHierarchy> {
return children.toList() + children.flatMap { it.collectChildren() }
}
/* Check that all equal hierarchies are even the same instance */
val allNodes = hierarchy.collectChildren()
allNodes.forEach { node ->
val equalNodes = allNodes.filter { otherNode -> otherNode == node }
equalNodes.forEach { equalNode ->
assertSame(node, equalNode, "Expected equal nodes to be the same instance")
}
}
}
@Test
fun `test - cycle`() {
val descriptor = KotlinTargetHierarchyDescriptor {
group("x") { // decoy 1
group("a") {
group("xx") // decoy 2
group("b") {
group("xxx") // decoy 3
group("c") {
group("a") {
group("xxxx") // decoy 4
}
}
}
}
}
}
val cycleStack = assertFailsWith<CyclicKotlinTargetHierarchyException> {
descriptor.buildKotlinTargetHierarchy(kotlin.linuxX64().compilations.main)
}.cycle
assertEquals(
listOf(Node.Group("a"), Node.Group("b"), Node.Group("c"), Node.Group("a")),
cycleStack
)
}
}
private class TestHierarchyBuilder(private val node: Node) {
private val children = mutableSetOf<TestHierarchyBuilder>()
fun group(name: String, builder: TestHierarchyBuilder.() -> Unit = {}) {
children.add(TestHierarchyBuilder(Node.Group(name)).also(builder))
}
fun build(): KotlinTargetHierarchy = KotlinTargetHierarchy(node, children.map { it.build() }.toSet())
}
private fun hierarchy(build: TestHierarchyBuilder.() -> Unit): KotlinTargetHierarchy {
return TestHierarchyBuilder(Node.Root).also(build).build()
}
@@ -0,0 +1,268 @@
/*
* 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
import kotlin.test.Test
import kotlin.test.assertEquals
class KotlinTargetHierarchyDslTest {
private val project = buildProjectWithMPP()
private val kotlin = project.multiplatformExtension
@Test
fun `test - hierarchy default - targets from all families`() {
kotlin.apply {
targetHierarchy.default()
iosArm32()
iosArm64()
iosX64()
iosSimulatorArm64()
tvosArm64()
tvosX64()
watchosArm32()
watchosArm64()
macosX64()
macosArm64()
linuxX64()
linuxArm32Hfp()
mingwX64()
mingwX86()
androidNativeArm32()
androidNativeArm64()
}
assertEquals(
stringSetOf(
"androidNativeArm32Main",
"androidNativeArm64Main",
"iosArm32Main",
"iosArm64Main",
"iosSimulatorArm64Main",
"iosX64Main",
"linuxArm32HfpMain",
"linuxX64Main",
"macosArm64Main",
"macosX64Main",
"mingwX64Main",
"mingwX86Main",
"nativeMain",
"tvosArm64Main",
"tvosX64Main",
"watchosArm32Main",
"watchosArm64Main"
),
kotlin.dependingSourceSetNames("commonMain")
)
assertEquals(
stringSetOf(
"androidNativeArm32Test",
"androidNativeArm64Test",
"iosArm32Test",
"iosArm64Test",
"iosSimulatorArm64Test",
"iosX64Test",
"linuxArm32HfpTest",
"linuxX64Test",
"macosArm64Test",
"macosX64Test",
"mingwX64Test",
"mingwX86Test",
"nativeTest",
"tvosArm64Test",
"tvosX64Test",
"watchosArm32Test",
"watchosArm64Test"
), kotlin.dependingSourceSetNames("commonTest")
)
assertEquals(
stringSetOf("androidNativeMain", "appleMain", "linuxMain", "windowsMain"),
kotlin.dependingSourceSetNames("nativeMain")
)
assertEquals(
stringSetOf("androidNativeTest", "appleTest", "linuxTest", "windowsTest"),
kotlin.dependingSourceSetNames("nativeTest")
)
assertEquals(
stringSetOf("iosMain", "macosMain", "tvosMain", "watchosMain"),
kotlin.dependingSourceSetNames("appleMain")
)
assertEquals(
stringSetOf("iosTest", "macosTest", "tvosTest", "watchosTest"),
kotlin.dependingSourceSetNames("appleTest")
)
assertEquals(
stringSetOf("iosArm32Main", "iosArm64Main", "iosSimulatorArm64Main", "iosX64Main"),
kotlin.dependingSourceSetNames("iosMain")
)
assertEquals(
stringSetOf("iosArm32Test", "iosArm64Test", "iosSimulatorArm64Test", "iosX64Test"),
kotlin.dependingSourceSetNames("iosTest")
)
assertEquals(
stringSetOf("tvosArm64Main", "tvosX64Main"),
kotlin.dependingSourceSetNames("tvosMain")
)
assertEquals(
stringSetOf("tvosArm64Test", "tvosX64Test"),
kotlin.dependingSourceSetNames("tvosTest")
)
assertEquals(
stringSetOf("watchosArm32Main", "watchosArm64Main"),
kotlin.dependingSourceSetNames("watchosMain")
)
assertEquals(
stringSetOf("watchosArm32Test", "watchosArm64Test"),
kotlin.dependingSourceSetNames("watchosTest")
)
assertEquals(
stringSetOf("linuxArm32HfpMain", "linuxX64Main"),
kotlin.dependingSourceSetNames("linuxMain")
)
assertEquals(
stringSetOf("linuxArm32HfpTest", "linuxX64Test"),
kotlin.dependingSourceSetNames("linuxTest")
)
}
@Test
fun `test - hierarchy default - only linuxX64`() {
kotlin.apply {
targetHierarchy.default()
kotlin.linuxX64()
}
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val nativeMain = kotlin.sourceSets.getByName("nativeMain")
val nativeTest = kotlin.sourceSets.getByName("nativeTest")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val linuxX64Main = kotlin.sourceSets.getByName("linuxX64Main")
val linuxX64Test = kotlin.sourceSets.getByName("linuxX64Test")
assertEquals(
setOf(commonMain, commonTest, nativeMain, nativeTest, linuxMain, linuxTest, linuxX64Main, linuxX64Test),
kotlin.sourceSets.toSet()
)
}
@Test
fun `test - hierarchy apply - extend`() {
val descriptor = KotlinTargetHierarchyDescriptor {
group("common") {
group("base")
}
}
kotlin.apply {
targetHierarchy.apply(descriptor) { group("base") { group("extension") } }
linuxX64()
}
assertEquals(
stringSetOf("baseMain", "linuxX64Main"), kotlin.dependingSourceSetNames("commonMain")
)
assertEquals(
stringSetOf("extensionMain"), kotlin.dependingSourceSetNames("baseMain")
)
assertEquals(
stringSetOf("linuxX64Main"), kotlin.dependingSourceSetNames("extensionMain")
)
assertEquals(
stringSetOf(), kotlin.dependingSourceSetNames("linuxX64Main")
)
}
@Test
fun `test - hierarchy set - extend - with new root`() {
val descriptor = KotlinTargetHierarchyDescriptor {
group("common") {
group("base")
}
}
kotlin.apply {
targetHierarchy.apply(descriptor) {
group("newRoot") {
group("base") { group("extension") }
}
}
linuxX64()
}
assertEquals(
stringSetOf("baseMain"), kotlin.dependingSourceSetNames("newRootMain")
)
assertEquals(
stringSetOf("baseMain", "linuxX64Main"), kotlin.dependingSourceSetNames("commonMain")
)
assertEquals(
stringSetOf("extensionMain"), kotlin.dependingSourceSetNames("baseMain")
)
assertEquals(
stringSetOf("linuxX64Main"), kotlin.dependingSourceSetNames("extensionMain")
)
assertEquals(
stringSetOf(), kotlin.dependingSourceSetNames("linuxX64Main")
)
}
}
private fun KotlinMultiplatformExtension.dependingSourceSetNames(sourceSetName: String) =
dependingSourceSetNames(sourceSets.getByName(sourceSetName))
private fun KotlinMultiplatformExtension.dependingSourceSetNames(sourceSet: KotlinSourceSet) =
sourceSets.filter { sourceSet in it.dependsOn }.map { it.name }.toStringSet()
/* StringSet: Special Set implementation, which makes it easy to copy and paste after assertions fail */
private fun stringSetOf(vararg values: String) = StringSet(values.toSet())
private fun Iterable<String>.toStringSet() = StringSet(this.toSet())
private data class StringSet(private val set: Set<String>) : Set<String> by set {
override fun toString(): String {
return "stringSetOf(" + set.joinToString(", ") { "\"$it\"" } + ")"
}
}
@@ -0,0 +1,12 @@
/*
* 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
import org.gradle.api.NamedDomainObjectCollection
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
val NamedDomainObjectCollection<out KotlinCompilation<*>>.main: KotlinCompilation<*> get() = getByName("main")
val NamedDomainObjectCollection<out KotlinCompilation<*>>.test: KotlinCompilation<*> get() = getByName("test")