[Gradle][MPP] KotlinTargetHierarchyBuilder: implement w/ include & exclude predicates

^KT-53570 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-10-06 18:26:09 +02:00
committed by Space Team
parent c5d6162578
commit a8a861e1ce
10 changed files with 321 additions and 81 deletions
@@ -52,7 +52,7 @@ interface KotlinTargetHierarchyDsl {
* +----------------------+--------------------+-----------------------+
* | | | |
*
* apple linux windows androidNative
* apple linux mingw androidNative
*
* |
* +-----------+------------+------------+
@@ -67,11 +67,10 @@ interface KotlinTargetHierarchyDsl {
* ```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")
* }
* group("native") { // <- we can re-declare already existing groups and connect children to it!
* group("unixLike") {
* anyLinux()
* anyApple()
* }
* }
* }
@@ -1,7 +1,4 @@
/*
* 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.
*/
// DO NOT EDIT MANUALLY! Generated by org.jetbrains.kotlin.generators.gradle.dsl.MppKotlinTargetHierarchyBuilderCodegenKt
package org.jetbrains.kotlin.gradle.plugin
@@ -9,24 +6,54 @@ 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
/* Declaring groups */
fun common(build: KotlinTargetHierarchyBuilder.() -> Unit) = group("common", build)
fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit = {})
/* low-level APIs */
fun includeCompilation(predicate: (KotlinCompilation<*>) -> Boolean)
fun excludeCompilation(predicate: (KotlinCompilation<*>) -> Boolean)
/* Convenient groups */
fun anyNative()
fun anyApple()
fun anyIos()
fun anyWatchos()
fun anyMacos()
fun anyTvos()
fun anyMingw()
fun anyLinux()
fun anyAndroidNative()
fun anyJs()
/* Actual targets */
fun jvm()
fun android()
fun androidNativeX64()
fun androidNativeX86()
fun androidNativeArm32()
fun androidNativeArm64()
fun iosArm32()
fun iosArm64()
fun iosX64()
fun iosSimulatorArm64()
fun watchosArm32()
fun watchosArm64()
fun watchosX86()
fun watchosX64()
fun watchosSimulatorArm64()
fun watchosDeviceArm64()
fun tvosArm64()
fun tvosX64()
fun tvosSimulatorArm64()
fun linuxX64()
fun mingwX86()
fun mingwX64()
fun macosX64()
fun macosArm64()
fun linuxArm64()
fun linuxArm32Hfp()
fun linuxMips32()
fun linuxMipsel32()
fun wasm32()
}
@@ -18,4 +18,4 @@ interface KotlinTargetsContainerWithPresets : KotlinTargetsContainer {
interface KotlinSourceSetContainer {
val sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
}
}
@@ -35,4 +35,4 @@ internal data class KotlinTargetHierarchy(
if (children.isEmpty()) return node.toString()
return node.toString() + "\n" + children.joinToString("\n").prependIndent("----")
}
}
}
@@ -29,16 +29,27 @@ private fun applyKotlinTargetHierarchy(
compilation: KotlinCompilation<*>,
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
): KotlinSourceSet? {
val sharedSourceSetName = hierarchy.node.sharedSourceSetName(compilation) ?: return null
val sharedSourceSet = sourceSets.maybeCreate(sharedSourceSetName)
val sharedSourceSet = createSharedSourceSetOrNull(sourceSets, hierarchy.node, compilation)
hierarchy.children
val childSourceSets = hierarchy.children
.mapNotNull { childHierarchy -> applyKotlinTargetHierarchy(childHierarchy, compilation, sourceSets) }
.forEach { childSourceSet -> childSourceSet.dependsOn(sharedSourceSet) }
if (hierarchy.children.isEmpty()) {
if (sharedSourceSet == null) return null
if (hierarchy.children.isNotEmpty()) {
childSourceSets.forEach { childSourceSet -> childSourceSet.dependsOn(sharedSourceSet) }
} else {
compilation.defaultSourceSet.dependsOn(sharedSourceSet)
}
return sharedSourceSet
}
private fun createSharedSourceSetOrNull(
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>,
node: KotlinTargetHierarchy.Node,
compilation: KotlinCompilation<*>,
): KotlinSourceSet? {
val sharedSourceSetName = node.sharedSourceSetName(compilation) ?: return null
return sourceSets.maybeCreate(sharedSourceSetName)
}
@@ -6,22 +6,26 @@
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.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyBuilder
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor
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.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
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 {
val context = KotlinTargetHierarchyBuilderImplContext(compilation)
this(context.getOrCreateBuilder(KotlinTargetHierarchy.Node.Root))
return context.build(KotlinTargetHierarchy.Node.Root)
}
private class KotlinTargetHierarchyBuilderImplContext(val compilation: KotlinCompilation<*>) {
private class KotlinTargetHierarchyBuilderImplContext(private val compilation: KotlinCompilation<*>) {
private val builders = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchyBuilderImpl>()
private val builtValues = hashMapOf<KotlinTargetHierarchy.Node, KotlinTargetHierarchy>()
@@ -48,7 +52,15 @@ private class KotlinTargetHierarchyBuilderImplContext(val compilation: KotlinCom
val childrenNodes = builder.children.map { it.node } -
builder.children.flatMap { child -> child.childrenClosure }.map { it.node }.toSet()
KotlinTargetHierarchy(node, childrenNodes.map(this::build).toSet())
/*
Respect include & exclude predicates!
*/
val applicableChildrenNodes = childrenNodes.filter { node ->
val childrenBuilder = getOrCreateBuilder(node)
childrenBuilder.predicate?.invoke(compilation) ?: true
}
KotlinTargetHierarchy(node, applicableChildrenNodes.map(this::build).toSet())
}
}
}
@@ -58,11 +70,163 @@ private class KotlinTargetHierarchyBuilderImpl(
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 }
val childrenClosure get() = closure { it.children }
var predicate: ((KotlinCompilation<*>) -> Boolean)? = null
override fun includeCompilation(predicate: (KotlinCompilation<*>) -> Boolean) {
val previousPredicate = this.predicate
if (previousPredicate == null) {
this.predicate = predicate
return
}
this.predicate = { previousPredicate(it) || predicate(it) }
}
override fun excludeCompilation(predicate: (KotlinCompilation<*>) -> Boolean) {
val previousPredicate = this.predicate
if (previousPredicate == null) {
this.predicate = { !predicate(it) }
return
}
this.predicate = { previousPredicate(it) && !predicate(it) }
}
private inline fun includeTarget(crossinline predicate: (KotlinTarget) -> Boolean) = includeCompilation { predicate(it.target) }
override fun anyNative() = includeTarget { it is KotlinNativeTarget }
override fun anyApple() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family.isAppleFamily }
override fun anyIos() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.IOS }
override fun anyWatchos() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.WATCHOS }
override fun anyMacos() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.OSX }
override fun anyTvos() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.TVOS }
override fun anyMingw() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.MINGW }
override fun anyLinux() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.LINUX }
override fun anyAndroidNative() = includeTarget { it is KotlinNativeTarget && it.konanTarget.family == Family.ANDROID }
override fun anyJs() = includeTarget { it is KotlinJsTargetDsl }
override fun jvm() = includeTarget { it is KotlinJvmTarget }
override fun android() = includeTarget { it is KotlinAndroidTarget }
override fun androidNativeX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.ANDROID_X64
}
override fun androidNativeX86() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.ANDROID_X86
}
override fun androidNativeArm32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.ANDROID_X86
}
override fun androidNativeArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.ANDROID_ARM64
}
override fun iosArm32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.IOS_ARM32
}
override fun iosArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.IOS_ARM64
}
override fun iosX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.IOS_X64
}
override fun iosSimulatorArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.IOS_SIMULATOR_ARM64
}
override fun watchosArm32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_ARM32
}
override fun watchosArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_ARM64
}
override fun watchosX86() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_X86
}
override fun watchosX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_X64
}
override fun watchosSimulatorArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_SIMULATOR_ARM64
}
override fun watchosDeviceArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WATCHOS_DEVICE_ARM64
}
override fun tvosArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.TVOS_ARM64
}
override fun tvosX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.TVOS_X64
}
override fun tvosSimulatorArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.TVOS_SIMULATOR_ARM64
}
override fun linuxX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.LINUX_X64
}
override fun mingwX86() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.MINGW_X86
}
override fun mingwX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.MINGW_X64
}
override fun macosX64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.MACOS_X64
}
override fun macosArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.MACOS_ARM64
}
override fun linuxArm64() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.LINUX_ARM64
}
override fun linuxArm32Hfp() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.LINUX_ARM32_HFP
}
override fun linuxMips32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.LINUX_MIPS32
}
override fun linuxMipsel32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.LINUX_MIPSEL32
}
override fun wasm32() = includeTarget {
it is KotlinNativeTarget && it.konanTarget == KonanTarget.WASM32
}
override fun group(name: String, build: KotlinTargetHierarchyBuilder.() -> Unit) {
val node = KotlinTargetHierarchy.Node.Group(name)
@@ -72,27 +236,11 @@ private class KotlinTargetHierarchyBuilderImpl(
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? {
@@ -10,26 +10,42 @@ 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")
}
group("native") {
anyNative()
/* natural hierarchy is only defined for main and test default compilations, by default */
excludeCompilation { compilation -> !(compilation.isMain() || compilation.isTest()) }
group("apple") {
anyApple()
group("ios") {
anyIos()
}
if (isLinux) group("linux")
if (isWindows) group("windows")
if (isAndroidNative) group("androidNative")
group("tvos") {
anyTvos()
}
group("watchos") {
anyWatchos()
}
group("macos") {
anyMacos()
}
}
group("linux") {
anyLinux()
}
group("mingw") {
anyMingw()
}
group("androidNative") {
anyAndroidNative()
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.KotlinTargetHierarchyBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsTargetPreset
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmWithJavaTargetPreset
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinWasmTargetPreset
import org.junit.Test
import kotlin.test.fail
class KotlinTargetHierarchyBuilderTest {
@Test
fun `test - interface offers functions for known presets`() {
val kotlinTargetHierarchyBuilderInterface = KotlinTargetHierarchyBuilder::class.java
buildProjectWithMPP().multiplatformExtension.presets
// JS targets are special and therefore are only handled manually using `anyJs()`
.filter { it !is KotlinJsTargetPreset }
.filter { it !is KotlinJsIrTargetPreset }
.filter { it !is KotlinWasmTargetPreset }
// jvmWithJava is covered by the jvm() call
.filter { it !is KotlinJvmWithJavaTargetPreset }
.forEach { preset ->
if (kotlinTargetHierarchyBuilderInterface.declaredMethods.none { it.name == preset.name })
fail("${kotlinTargetHierarchyBuilderInterface.name}: Missing ${preset.name}() function")
}
}
}
@@ -27,8 +27,8 @@ class KotlinTargetHierarchyDescriptorTest {
fun `test - simple descriptor`() {
val descriptor = KotlinTargetHierarchyDescriptor {
common {
if (target.name == "a") group("groupA")
if (target.name == "b") group("groupB")
group("groupA") { includeCompilation { it.target.name == "a" } }
group("groupB") { includeCompilation { it.target.name == "b" } }
}
}
@@ -95,12 +95,12 @@ class KotlinTargetHierarchyDslTest {
)
assertEquals(
stringSetOf("androidNativeMain", "appleMain", "linuxMain", "windowsMain"),
stringSetOf("androidNativeMain", "appleMain", "linuxMain", "mingwMain"),
kotlin.dependingSourceSetNames("nativeMain")
)
assertEquals(
stringSetOf("androidNativeTest", "appleTest", "linuxTest", "windowsTest"),
stringSetOf("androidNativeTest", "appleTest", "linuxTest", "mingwTest"),
kotlin.dependingSourceSetNames("nativeTest")
)