[Gradle] apply KotlinHierarchyTemplate.default, by default

^KT-58676 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-06-21 11:00:01 +02:00
committed by Space Team
parent b9a8f6dce2
commit cec243ac3b
11 changed files with 293 additions and 14 deletions
@@ -9,11 +9,29 @@ Read the details here: https://kotlinlang.org/docs/multiplatform-compatibility-g
w: [CommonMainWithDependsOnDiagnostic | WARNING] commonMain can't declare dependsOn on other source sets
#diagnostic-end
w: [KotlinDefaultHierarchyFallbackDependsOnUsageDetected | WARNING] The Default Kotlin Hierarchy was not applied to 'project ':subprojectA'':
Manual .dependsOn() edges were configured for the following source sets:
[commonMain]
To suppress the 'Default Hierarchy Template' add
'kotlin.mpp.applyDefaultHierarchyTemplate=false'
to your gradle.properties
#diagnostic-end
> Configure project :subprojectB
w: [CommonMainWithDependsOnDiagnostic | WARNING] commonMain can't declare dependsOn on other source sets
#diagnostic-end
w: [KotlinDefaultHierarchyFallbackDependsOnUsageDetected | WARNING] The Default Kotlin Hierarchy was not applied to 'project ':subprojectB'':
Manual .dependsOn() edges were configured for the following source sets:
[commonMain]
To suppress the 'Default Hierarchy Template' add
'kotlin.mpp.applyDefaultHierarchyTemplate=false'
to your gradle.properties
#diagnostic-end
w: [UnusedSourceSetsWarning | WARNING] The Kotlin source set unusedCreatedInAfterEvaluate was configured but not added to any Kotlin compilation.
You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'.
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_ANDROID_STYLE_NO_WARN
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION_1_NO_WARN
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_DEPRECATED_PROPERTIES_NO_WARN
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_COMPATIBILITY_METADATA_VARIANT
@@ -233,6 +234,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val mppEnablePlatformIntegerCommonization: Boolean
get() = booleanProperty(KOTLIN_MPP_ENABLE_PLATFORM_INTEGER_COMMONIZATION) ?: false
val mppApplyDefaultHierarchyTemplate: Boolean
get() = this.booleanProperty(KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE) ?: true
val wasmStabilityNoWarn: Boolean
get() = booleanProperty("kotlin.wasm.stability.nowarn") ?: false
@@ -544,7 +548,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
private inline fun <reified T : Enum<T>> enumProperty(
propName: String,
defaultValue: T
defaultValue: T,
): T = this.property(propName)?.let { enumValueOf<T>(it.toUpperCaseAsciiOnly()) } ?: defaultValue
/**
@@ -596,6 +600,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
const val KOTLIN_MPP_IMPORT_ENABLE_KGP_DEPENDENCY_RESOLUTION = "kotlin.mpp.import.enableKgpDependencyResolution"
const val KOTLIN_MPP_IMPORT_ENABLE_SLOW_SOURCES_JAR_RESOLVER = "kotlin.mpp.import.enableSlowSourcesJarResolver"
const val KOTLIN_MPP_ENABLE_INTRANSITIVE_METADATA_CONFIGURATION = "kotlin.mpp.enableIntransitiveMetadataConfiguration"
const val KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE = "kotlin.mpp.applyDefaultHierarchyTemplate"
const val KOTLIN_NATIVE_DEPENDENCY_PROPAGATION = "kotlin.native.enableDependencyPropagation"
const val KOTLIN_NATIVE_CACHE_ORCHESTRATION = "kotlin.native.cacheOrchestration"
const val KOTLIN_NATIVE_PARALLEL_THREADS = "kotlin.native.parallelThreads"
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.gradle.plugin.diagnostics
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention.isRegisteredByKotlinSourceSetConventionAt
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.*
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV1
@@ -427,6 +429,33 @@ object KotlinToolingDiagnostics {
)
}
object KotlinDefaultHierarchyFallbackDependsOnUsageDetected : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(project: Project, sourceSetsWithDependsOnEdges: Iterable<KotlinSourceSet>) = build(
"""
The Default Kotlin Hierarchy was not applied to '${project.displayName}':
Manual .dependsOn() edges were configured for the following source sets:
${sourceSetsWithDependsOnEdges.toSet().map { it.name }}
To suppress the 'Default Hierarchy Template' add
'$KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE=false'
to your gradle.properties
""".trimIndent()
)
}
object KotlinDefaultHierarchyFallbackIllegalTargetNames : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(project: Project, illegalTargetNamesUsed: Iterable<String>) = build(
"""
The Default Kotlin Hierarchy was not applied to '${project.displayName}':
Illegal target names were found:
${illegalTargetNamesUsed.toSet()}
To suppress the 'Default Hierarchy Template' add
'$KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE=false'
to your gradle.properties
""".trimIndent()
)
}
}
@@ -0,0 +1,85 @@
/*
* 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.hierarchy
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPlugin.Companion.dynamicallyApplyWhenAndroidPluginIsApplied
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.FinaliseRefinesEdges
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics.KotlinDefaultHierarchyFallbackDependsOnUsageDetected
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics.KotlinDefaultHierarchyFallbackIllegalTargetNames
import org.jetbrains.kotlin.gradle.plugin.diagnostics.kotlinToolingDiagnosticsCollector
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal suspend fun Project.setupDefaultKotlinHierarchy() = requiredStage(FinaliseRefinesEdges) setup@{
val extension = project.multiplatformExtensionOrNull ?: return@setup
/* User configured a target hierarchy explicitly: No need for our defaults here */
if (extension.hierarchy.appliedTemplates.isNotEmpty()) return@setup
/* User explicitly disabled the default target hierarchy by Gradle property */
if (!kotlinPropertiesProvider.mppApplyDefaultHierarchyTemplate) {
setupPreMultiplatformStableDefaultDependsOnEdges()
return@setup
}
/*
User manually added a .dependsOn:
We fall back to the old behaviour and add the commonMain/commonTest default edges
*/
run check@{
val sourceSetsWithDependsOnEdges = extension.sourceSets.filter { sourceSet -> sourceSet.dependsOn.isNotEmpty() }
if (sourceSetsWithDependsOnEdges.isEmpty()) return@check
val diagnostic = KotlinDefaultHierarchyFallbackDependsOnUsageDetected(project, sourceSetsWithDependsOnEdges)
kotlinToolingDiagnosticsCollector.report(project, diagnostic)
setupPreMultiplatformStableDefaultDependsOnEdges()
return@setup
}
/*
Using a group of the 'defaultTargetHierarchy' as 'target name' will potentially lead to conflicts e.g.:
linuxX64("linux") or macosX64("native") can lead to confusion of for 'linuxMain' and 'nativeMain' SourceSets
*/
run check@{
val illegalTargetNamesUsed = illegalTargetNamesUsed()
if (illegalTargetNamesUsed.isEmpty()) return@check
val diagnostic = KotlinDefaultHierarchyFallbackIllegalTargetNames(project, illegalTargetNamesUsed)
kotlinToolingDiagnosticsCollector.report(project, diagnostic)
setupPreMultiplatformStableDefaultDependsOnEdges()
return@setup
}
extension.applyDefaultHierarchyTemplate()
}
private suspend fun Project.illegalTargetNamesUsed(): Set<String> {
val targets = multiplatformExtension.awaitTargets()
val targetNames = targets.map { it.name }.toSet()
return targets.flatMap { it.compilations }.mapNotNull { compilation ->
val hierarchy = KotlinHierarchyTemplate.default.buildHierarchy(compilation) ?: return@mapNotNull null
val nodeNames = hierarchy.childrenClosure.mapNotNull { it.node as? KotlinHierarchy.Node.Group }.map { it.name }.toSet()
nodeNames.intersect(targetNames)
}.flatten().toSet()
}
/**
* Before 1.9.20 (and without any targetHierarchy applied), we just added default dependsOn
* edges from 'main' compilations defaultSourceSets to 'commonMain' and
* edges from 'test' compilations defaultSourceSets to 'commonTest
*/
private suspend fun Project.setupPreMultiplatformStableDefaultDependsOnEdges() = multiplatformExtension.targets
.flatMap { target -> target.compilations }
.forEach { compilation ->
val sourceSetTree = KotlinSourceSetTree.orNull(compilation) ?: return@forEach
val commonSourceSetName = lowerCamelCaseName("common", sourceSetTree.name)
val commonSourceSet = multiplatformExtension.sourceSets.findByName(commonSourceSetName) ?: return@forEach
compilation.defaultSourceSet.dependsOn(commonSourceSet)
}
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.internal.customizeKotlinDependencies
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.hierarchy.orNull
import org.jetbrains.kotlin.gradle.plugin.hierarchy.setupDefaultKotlinHierarchy
import org.jetbrains.kotlin.gradle.plugin.ide.kotlinIdeMultiplatformImport
import org.jetbrains.kotlin.gradle.plugin.ide.locateOrRegisterIdeResolveDependenciesTask
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companion.sourceSetFreeCompilerArgsPropertyName
@@ -175,19 +176,8 @@ class KotlinMultiplatformPlugin : Plugin<Project> {
sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
/* Create default 'dependsOn' to commonMain/commonTest (or even common{SourceSetTree}) */
targets.all { target ->
project.launchInStage(KotlinPluginLifecycle.Stage.FinaliseRefinesEdges) {
/* Only setup default refines edges when no KotlinTargetHierarchy was applied */
if (project.multiplatformExtension.internalKotlinTargetHierarchy.appliedDescriptors.isNotEmpty()) return@launchInStage
if (project.multiplatformExtension.hierarchy.appliedTemplates.isNotEmpty()) return@launchInStage
target.compilations.forEach { compilation ->
val sourceSetTree = KotlinSourceSetTree.orNull(compilation) ?: return@forEach
val commonSourceSet = sourceSets.findByName(lowerCamelCaseName("common", sourceSetTree.name)) ?: return@forEach
compilation.defaultSourceSet.dependsOn(commonSourceSet)
}
}
project.launch {
project.setupDefaultKotlinHierarchy()
}
project.launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
@@ -0,0 +1,84 @@
/*
* 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 org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.commonMain
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.jvmMain
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE
import org.jetbrains.kotlin.gradle.plugin.hierarchy.default
import org.jetbrains.kotlin.gradle.util.assertNoDiagnostics
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.fail
class DefaultHierarchySetupTest {
@Test
fun `test - default target hierarchy is applied for simple project`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
kotlin.linuxX64()
kotlin.linuxArm64()
launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
assertEquals(
setOf(KotlinHierarchyTemplate.default), kotlin.hierarchy.appliedTemplates,
"Expected 'defaultKotlinTargetHierarchy' to be applied"
)
}
}
@Test
fun `test - default target hierarchy is disabled by flag`() = buildProjectWithMPP().runLifecycleAwareTest {
project.extraProperties.set(KOTLIN_MPP_APPLY_DEFAULT_HIERARCHY_TEMPLATE, false.toString())
val kotlin = multiplatformExtension
kotlin.jvm()
kotlin.linuxX64()
kotlin.linuxArm64()
launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
if (kotlin.hierarchy.appliedTemplates.isNotEmpty()) {
fail("Expected *no* hierarchy template to be applied")
}
project.assertNoDiagnostics()
}
}
@Test
fun `test - no hierarchy descriptor applied - when a custom dependsOn edge was configured`() =
buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
kotlin.sourceSets.jvmMain.get().dependsOn(kotlin.sourceSets.commonMain.get())
launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
if (kotlin.hierarchy.appliedTemplates.isNotEmpty()) {
fail("Expected *no* hierarchy template to be applied")
}
}
}
@Test
fun `test - no hierarchy descriptor applied - when a targetName clashes with a default group`() {
buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.linuxX64("linux") // <- group in default hierarchy is also called 'linux'
launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
if (kotlin.hierarchy.appliedTemplates.isNotEmpty()) {
fail("Expected *no* hierarchy template to be applied")
}
}
}
}
}
@@ -166,6 +166,10 @@ class KotlinNativeCompileArgumentsTest {
linuxX64SourceFile.parentFile.mkdirs()
linuxX64SourceFile.writeText("object Linux")
project.multiplatformExtension.applyHierarchyTemplate {
common { withLinux() }
}
project.evaluate()
val nativeCompilation = kotlin.linuxX64().compilations.main
@@ -0,0 +1,47 @@
/*
* 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.diagnosticsTests
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.commonMain
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.jvmMain
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.launchInStage
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.checkDiagnostics
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
import kotlin.test.Test
class DefaultHierarchySetupDiagnosticTest {
@Test
fun `test - warning KotlinTargetHierarchyFallbackDependsOnUsageDetected`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
/* Create intermediate SourceSet using custom .dependsOn calls */
val intermediateMain = kotlin.sourceSets.create("intermediateMain")
intermediateMain.dependsOn(kotlin.sourceSets.commonMain.get())
kotlin.sourceSets.jvmMain.get().dependsOn(intermediateMain)
project.launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
project.checkDiagnostics("KotlinDefaultHierarchyFallbackDependsOnUsageDetected")
}
}
@Test
fun `test - warning KotlinTargetHierarchyFallbackIllegalTargetNames`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.linuxX64("linux") // <- illegal: Will clash with 'linux' group
kotlin.linuxArm64("native") // <-- illegal: Will clash with 'native' group
kotlin.jvm()
project.launchInStage(KotlinPluginLifecycle.Stage.ReadyForExecution) {
project.checkDiagnostics("KotlinDefaultHierarchyFallbackIllegalTargetNames")
}
}
}
@@ -18,6 +18,7 @@ class MppDiagnosticsFunctionalTest {
kotlin {
jvm()
linuxX64()
applyDefaultHierarchyTemplate()
sourceSets.apply {
val myCustomCommonMain = create("myCustomCommonMain")
@@ -47,6 +48,7 @@ class MppDiagnosticsFunctionalTest {
kotlin {
jvm()
linuxX64()
applyDefaultHierarchyTemplate()
sourceSets.apply {
val unused1 = create("unused1")
@@ -86,6 +88,7 @@ class MppDiagnosticsFunctionalTest {
kotlin {
androidTarget()
linuxX64()
applyDefaultHierarchyTemplate()
sourceSets.apply {
val intermediateBetweenAndroid = create("intermediate")
@@ -0,0 +1,7 @@
[KotlinDefaultHierarchyFallbackDependsOnUsageDetected | WARNING] The Default Kotlin Hierarchy was not applied to 'root project 'test'':
Manual .dependsOn() edges were configured for the following source sets:
[intermediateMain, jvmMain]
To suppress the 'Default Hierarchy Template' add
'kotlin.mpp.applyDefaultHierarchyTemplate=false'
to your gradle.properties
@@ -0,0 +1,7 @@
[KotlinDefaultHierarchyFallbackIllegalTargetNames | WARNING] The Default Kotlin Hierarchy was not applied to 'root project 'test'':
Illegal target names were found:
[native, linux]
To suppress the 'Default Hierarchy Template' add
'kotlin.mpp.applyDefaultHierarchyTemplate=false'
to your gradle.properties