[Gradle][KT-53342] KotlinAndroidSourceSetLayoutCheckers to provide migration from V1 -> V2

This commit is contained in:
sebastian.sellmair
2022-08-03 15:50:47 +02:00
committed by Space
parent 82d92a4e82
commit 6a13c2797b
9 changed files with 242 additions and 8 deletions
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.Companion.jsCompi
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_ABI_SNAPSHOT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_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_ENABLE_CINTEROP_COMMONIZATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_OPTIMISTIC_NUMBER_COMMONIZATION
@@ -213,6 +214,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val mppAndroidSourceSetLayoutVersion: Int?
get() = property(KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION)?.toIntOrNull()
val ignoreMppAndroidSourceSetLayoutVersion: Boolean
get() = booleanProperty(KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION_1_NO_WARN) ?: false
val ignoreDisabledCInteropCommonization: Boolean
get() = booleanProperty("$KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION.nowarn") ?: false
@@ -472,6 +476,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT = "kotlin.mpp.hierarchicalStructureSupport"
const val KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN = "kotlin.mpp.androidGradlePluginCompatibility.nowarn"
const val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION = "kotlin.mpp.androidSourceSetLayoutVersion"
const val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION_1_NO_WARN = "${KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION}1.nowarn"
const val KOTLIN_NATIVE_DEPENDENCY_PROPAGATION = "kotlin.native.enableDependencyPropagation"
const val KOTLIN_MPP_ENABLE_OPTIMISTIC_NUMBER_COMMONIZATION = "kotlin.mpp.enableOptimisticNumberCommonization"
const val KOTLIN_KPM_EXPERIMENTAL_MODEL_MAPPING = "kotlin.kpm.experimentalModelMapping"
@@ -5,29 +5,32 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android
import org.jetbrains.kotlin.gradle.plugin.sources.android.checker.*
import org.jetbrains.kotlin.gradle.plugin.sources.android.configurator.*
internal data class KotlinAndroidSourceSetLayout(
val name: String,
val naming: KotlinAndroidSourceSetNaming,
val sourceSetConfigurator: KotlinAndroidSourceSetConfigurator
val sourceSetConfigurator: KotlinAndroidSourceSetConfigurator,
val checker: KotlinAndroidSourceSetLayoutChecker
) {
override fun toString(): String = "KotlinAndroidSourceSetLayout: $name"
}
internal val singleTargetAndroidSourceSetLayout = KotlinAndroidSourceSetLayout(
name = "Kotlin/Android",
name = "Kotlin/Android-SourceSetLayout",
naming = SingleTargetKotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
AndroidSourceSetConventionConfigurator,
SingleTargetSourceDirConfigurator,
)
),
checker = KotlinAndroidSourceSetLayoutChecker()
)
internal val multiplatformAndroidSourceSetLayoutV1 = KotlinAndroidSourceSetLayout(
name = "Multiplatform/Android V1",
name = "Multiplatform/Android-V1-SourceSetLayout",
naming = MultiplatformLayoutV1KotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
@@ -36,11 +39,14 @@ internal val multiplatformAndroidSourceSetLayoutV1 = KotlinAndroidSourceSetLayou
MultiplatformAndroidResourceDirConfigurator,
MultiplatformLayoutV1DependsOnConfigurator,
MultiplatformLayoutV1SourceDirConfigurator
),
checker = KotlinAndroidSourceSetLayoutChecker(
MultiplatformLayoutV1PromoteV2Checker
)
)
internal val multiplatformAndroidSourceSetLayoutV2 = KotlinAndroidSourceSetLayout(
name = "Multiplatform/Android V2",
name = "Multiplatform/Android-V2-SourceSetLayout",
naming = MultiplatformLayoutV2KotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
@@ -49,5 +55,10 @@ internal val multiplatformAndroidSourceSetLayoutV2 = KotlinAndroidSourceSetLayou
MultiplatformLayoutV2DependsOnConfigurator,
MultiplatformLayoutV2SourceDirConfigurator,
MultiplatformLayoutV2DefaultManifestLocationConfigurator
),
checker = KotlinAndroidSourceSetLayoutChecker(
MultiplatformLayoutV2AgpRequirementChecker,
MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker,
MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker
)
)
@@ -14,12 +14,13 @@ import org.jetbrains.kotlin.gradle.plugin.forEachVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.utils.androidExtension
import org.jetbrains.kotlin.gradle.utils.getOrCreate
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
internal object KotlinAndroidSourceSets {
private val logger = Logging.getLogger(this::class.java)
fun setupKotlinAndroidSourceSets(
fun applyKotlinAndroidSourceSetLayout(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout = target.project.kotlinAndroidSourceSetLayout
) {
@@ -30,6 +31,11 @@ internal object KotlinAndroidSourceSets {
val android = project.androidExtension
val naming = layout.naming
val configurator = layout.sourceSetConfigurator
val checker = layout.checker
project.runProjectConfigurationHealthCheck {
checker.checkBeforeLayoutApplied(target, layout)
}
/* Ensures that each KotlinSourceSet only invokes the 'configurator' once */
val configuredKotlinSourceSets = mutableSetOf<KotlinSourceSet>()
@@ -45,6 +51,9 @@ internal object KotlinAndroidSourceSets {
if (configuredKotlinSourceSets.add(kotlinSourceSet)) {
configurator.configure(target, kotlinSourceSet, androidSourceSet)
project.runProjectConfigurationHealthCheck {
checker.checkCreatedSourceSet(target, layout, kotlinSourceSet, androidSourceSet)
}
}
}
@@ -58,6 +67,9 @@ internal object KotlinAndroidSourceSets {
if (configuredKotlinSourceSets.add(kotlinSourceSet)) {
configurator.configure(target, kotlinSourceSet, androidSourceSet)
project.runProjectConfigurationHealthCheck {
checker.checkCreatedSourceSet(target, layout, kotlinSourceSet, androidSourceSet)
}
}
configurator.configureWithVariant(target, kotlinSourceSet, variant)
}
@@ -0,0 +1,53 @@
/*
* 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.sources.android.checker
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
internal interface KotlinAndroidSourceSetLayoutChecker {
class ProjectMisconfiguredException(message: String) : Exception(message)
fun checkBeforeLayoutApplied(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout
) = Unit
fun checkCreatedSourceSet(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) = Unit
}
internal fun KotlinAndroidSourceSetLayoutChecker(
vararg checkers: KotlinAndroidSourceSetLayoutChecker?
): KotlinAndroidSourceSetLayoutChecker {
return CompositeKotlinAndroidSourceSetLayoutChecker(checkers.filterNotNull())
}
private class CompositeKotlinAndroidSourceSetLayoutChecker(
private val checkers: List<KotlinAndroidSourceSetLayoutChecker>
) : KotlinAndroidSourceSetLayoutChecker {
override fun checkBeforeLayoutApplied(target: KotlinAndroidTarget, layout: KotlinAndroidSourceSetLayout) {
checkers.forEach { checker -> checker.checkBeforeLayoutApplied(target, layout) }
}
override fun checkCreatedSourceSet(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
checkers.forEach { checker ->
checker.checkCreatedSourceSet(target, layout, kotlinSourceSet, androidSourceSet)
}
}
}
@@ -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.sources.android.checker
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV2
/**
* Promotes MultiplatformAndroidSourceSetLayoutV2 when requirements are met
*/
internal object MultiplatformLayoutV1PromoteV2Checker : KotlinAndroidSourceSetLayoutChecker {
private val logger = Logging.getLogger(this.javaClass)
override fun checkBeforeLayoutApplied(target: KotlinAndroidTarget, layout: KotlinAndroidSourceSetLayout) {
if (target.project.kotlinPropertiesProvider.ignoreMppAndroidSourceSetLayoutVersion) return
runCatching {
if (MultiplatformLayoutV2AgpRequirementChecker.isAgpRequirementMet()) {
logger.warn(
"""
w: ${layout.name} is deprecated. Use ${multiplatformAndroidSourceSetLayoutV2.name} instead.
To enable ${multiplatformAndroidSourceSetLayoutV2.name}: put the following in your gradle.properties:
${PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION}=2
To suppress this warning: put the following in your gradle.properties:
${PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION_1_NO_WARN}=true
""".trimIndent()
)
}
}
}
}
@@ -0,0 +1,31 @@
/*
* 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.sources.android.checker
import com.android.Version
import org.jetbrains.kotlin.gradle.plugin.compareVersionNumbers
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
internal object MultiplatformLayoutV2AgpRequirementChecker : KotlinAndroidSourceSetLayoutChecker {
private const val AGP_VERSION_MIN = "7.0.0"
override fun checkBeforeLayoutApplied(target: KotlinAndroidTarget, layout: KotlinAndroidSourceSetLayout) {
if (!isAgpRequirementMet()) {
throw KotlinAndroidSourceSetLayoutChecker.ProjectMisconfiguredException(
"""
${layout.name} requires Android Gradle Plugin Version >= $AGP_VERSION_MIN.
Found ${Version.ANDROID_GRADLE_PLUGIN_VERSION}
""".trimIndent()
)
}
}
internal fun isAgpRequirementMet(): Boolean {
return compareVersionNumbers(Version.ANDROID_GRADLE_PLUGIN_VERSION, AGP_VERSION_MIN) >= 0
}
}
@@ -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.sources.android.checker
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
/**
* Will detect usage of "Android Style" source directories (like 'src/main/kotlin') and emit a warning
*/
internal object MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker : KotlinAndroidSourceSetLayoutChecker {
private val logger = Logging.getLogger(this.javaClass)
override fun checkCreatedSourceSet(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
val androidStyleSourceDir = target.project.file("src/${androidSourceSet.name}/kotlin")
if (androidStyleSourceDir in kotlinSourceSet.kotlin.srcDirs && androidStyleSourceDir.exists()) {
logger.warn(
"""
w: ${layout.name}: Usage of 'Android Style' source directory $androidStyleSourceDir is deprecated.
Use ${target.project.file("src/${kotlinSourceSet.name}/kotlin")} instead.
""".trimIndent()
)
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.sources.android.checker
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSetLayout
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV1
/**
* Detects and warns about usage of source directories from multiplatform layout version 1
*/
internal object MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker : KotlinAndroidSourceSetLayoutChecker {
private val logger = Logging.getLogger(this.javaClass)
override fun checkCreatedSourceSet(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
val v1kotlinSourceSetName = multiplatformAndroidSourceSetLayoutV1.naming.kotlinSourceSetName(
target.disambiguationClassifier, androidSourceSet.name
) ?: return
/* Layouts did agree on the name of this KotlinSourceSet -> LGTM */
if (v1kotlinSourceSetName == kotlinSourceSet.name) return
val v1KotlinSourceDir = target.project.file("src/$v1kotlinSourceSetName/kotlin")
if (v1KotlinSourceDir.exists()) {
logger.warn(
"""
w: ${layout.name}: Found used source directory $v1KotlinSourceDir
This source directory was supported by: ${multiplatformAndroidSourceSetLayoutV1.name}
Current KotlinAndroidSourceSetLayout: ${layout.name}
New source directory is: ${target.project.file("src/${kotlinSourceSet.name}/kotlin")}
""".trimIndent()
)
}
}
}
@@ -35,7 +35,7 @@ import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSets.setupKotlinAndroidSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSets.applyKotlinAndroidSourceSetLayout
import org.jetbrains.kotlin.gradle.plugin.sources.android.findKotlinSourceSet
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
@@ -61,7 +61,7 @@ internal class AndroidProjectHandler(
val project = kotlinAndroidTarget.project
val ext = project.extensions.getByName("android") as BaseExtension
setupKotlinAndroidSourceSets(kotlinAndroidTarget)
applyKotlinAndroidSourceSetLayout(kotlinAndroidTarget)
val kotlinOptions = KotlinJvmOptionsImpl()
kotlinOptions.noJdk = true