[Gradle][MPP] Introduce DiagnosticReporter and Diagnostic APIs...

... for KotlinAndroidSourceSetLayoutChecker

KT-53709
This commit is contained in:
Sebastian Sellmair
2022-08-25 11:21:11 +02:00
committed by Space
parent 776b68f2fe
commit 5a447a823f
7 changed files with 124 additions and 47 deletions
@@ -11,13 +11,15 @@ import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.checker.KotlinAndroidSourceSetLayoutChecker
import org.jetbrains.kotlin.gradle.utils.getOrCreate
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
internal class KotlinAndroidSourceSetFactory(
private val target: KotlinAndroidTarget,
private val kotlin: KotlinProjectExtension,
private val layout: KotlinAndroidSourceSetLayout
private val layout: KotlinAndroidSourceSetLayout,
private val diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
) {
private val configuredKotlinSourceSets = mutableSetOf<KotlinSourceSet>()
@@ -27,7 +29,7 @@ internal class KotlinAndroidSourceSetFactory(
layout.sourceSetConfigurator.configure(target, kotlinSourceSet, androidSourceSet)
target.project.runProjectConfigurationHealthCheck {
val layout = this@KotlinAndroidSourceSetFactory.layout
layout.checker.checkCreatedSourceSet(target, layout, kotlinSourceSet, androidSourceSet)
layout.checker.checkCreatedSourceSet(diagnosticReporter, target, layout, kotlinSourceSet, androidSourceSet)
}
}
return kotlinSourceSet
@@ -9,6 +9,7 @@ import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.forEachVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.checker.KotlinAndroidSourceSetLayoutChecker
import org.jetbrains.kotlin.gradle.utils.androidExtension
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
@@ -23,10 +24,11 @@ internal object KotlinAndroidSourceSets {
logger.debug("Applying ${KotlinAndroidSourceSetLayout::class.java.simpleName}: ${layout.name}")
val android = target.project.androidExtension
val factory = KotlinAndroidSourceSetFactory(target, target.project.kotlinExtension, layout)
val diagnosticReporter = KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter.create(target.project, logger, layout)
val factory = KotlinAndroidSourceSetFactory(target, target.project.kotlinExtension, layout, diagnosticReporter)
target.project.runProjectConfigurationHealthCheck {
layout.checker.checkBeforeLayoutApplied(target, layout)
layout.checker.checkBeforeLayoutApplied(diagnosticReporter, target, layout)
}
/*
@@ -6,19 +6,38 @@
package org.jetbrains.kotlin.gradle.plugin.sources.android.checker
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.Project
import org.gradle.api.logging.Logger
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.utils.SingleWarningPerBuild
internal interface KotlinAndroidSourceSetLayoutChecker {
class ProjectMisconfiguredException(message: String) : Exception(message)
interface DiagnosticReporter {
fun error(diagnostic: Diagnostic): Nothing
fun warning(diagnostic: Diagnostic)
companion object {
fun create(project: Project, logger: Logger, layout: KotlinAndroidSourceSetLayout): DiagnosticReporter =
DiagnosticReporterImpl(project, logger, layout)
}
}
interface Diagnostic {
val message: String
}
fun checkBeforeLayoutApplied(
diagnosticReporter: DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout
) = Unit
fun checkCreatedSourceSet(
diagnosticReporter: DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
@@ -26,6 +45,22 @@ internal interface KotlinAndroidSourceSetLayoutChecker {
) = Unit
}
private class DiagnosticReporterImpl(
private val project: Project,
private val logger: Logger,
private val layout: KotlinAndroidSourceSetLayout
) : KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter {
override fun error(diagnostic: KotlinAndroidSourceSetLayoutChecker.Diagnostic): Nothing {
throw KotlinAndroidSourceSetLayoutChecker.ProjectMisconfiguredException("${layout.name}: ${diagnostic.message}")
}
override fun warning(diagnostic: KotlinAndroidSourceSetLayoutChecker.Diagnostic) {
SingleWarningPerBuild.show(project, logger, "w: ${layout.name}: ${diagnostic.message}\n")
}
}
/* Composite Implementation */
internal fun KotlinAndroidSourceSetLayoutChecker(
vararg checkers: KotlinAndroidSourceSetLayoutChecker?
): KotlinAndroidSourceSetLayoutChecker {
@@ -36,18 +71,23 @@ 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 checkBeforeLayoutApplied(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout
) {
checkers.forEach { checker -> checker.checkBeforeLayoutApplied(diagnosticReporter, target, layout) }
}
override fun checkCreatedSourceSet(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
checkers.forEach { checker ->
checker.checkCreatedSourceSet(target, layout, kotlinSourceSet, androidSourceSet)
checker.checkCreatedSourceSet(diagnosticReporter, target, layout, kotlinSourceSet, androidSourceSet)
}
}
}
@@ -5,36 +5,39 @@
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.multiplatformAndroidSourceSetLayoutV1
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV2
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
/**
* Promotes MultiplatformAndroidSourceSetLayoutV2 when requirements are met
*/
internal object MultiplatformLayoutV1PromoteV2Checker : KotlinAndroidSourceSetLayoutChecker {
private val logger = Logging.getLogger(this.javaClass)
override fun checkBeforeLayoutApplied(target: KotlinAndroidTarget, layout: KotlinAndroidSourceSetLayout) {
override fun checkBeforeLayoutApplied(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout
) {
if (target.project.kotlinPropertiesProvider.ignoreMppAndroidSourceSetLayoutVersion) return
runCatching {
if (MultiplatformLayoutV2AgpRequirementChecker.isAgpRequirementMet()) {
SingleWarningPerBuild.show(
target.project, logger,
"""
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()
)
diagnosticReporter.warning(PromoteV2LayoutDiagnostic())
}
}
}
class PromoteV2LayoutDiagnostic : KotlinAndroidSourceSetLayoutChecker.Diagnostic {
override val message: String
get() = """
${multiplatformAndroidSourceSetLayoutV1.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()
}
}
@@ -14,18 +14,28 @@ internal object MultiplatformLayoutV2AgpRequirementChecker : KotlinAndroidSource
private const val AGP_VERSION_MIN = "7.0.0"
override fun checkBeforeLayoutApplied(target: KotlinAndroidTarget, layout: KotlinAndroidSourceSetLayout) {
override fun checkBeforeLayoutApplied(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
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()
)
diagnosticReporter.error(AgpRequirementNotMetDiagnostic(AGP_VERSION_MIN, Version.ANDROID_GRADLE_PLUGIN_VERSION))
}
}
internal fun isAgpRequirementMet(): Boolean {
return compareVersionNumbers(Version.ANDROID_GRADLE_PLUGIN_VERSION, AGP_VERSION_MIN) >= 0
}
internal class AgpRequirementNotMetDiagnostic(
val requiredMinAgpVersion: String,
val currentAgpVersion: String
) : KotlinAndroidSourceSetLayoutChecker.Diagnostic {
override val message: String
get() = """
requires Android Gradle Plugin Version >= $requiredMinAgpVersion.
Found $currentAgpVersion
""".trimIndent()
}
}
@@ -10,15 +10,15 @@ 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 java.io.File
/**
* 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(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
@@ -26,12 +26,19 @@ internal object MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker : KotlinA
) {
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()
)
val kotlinStyleSourceDirToUse = target.project.file("src/${kotlinSourceSet.name}/kotlin")
diagnosticReporter.warning(AndroidStyleSourceDirUsageDiagnostic(androidStyleSourceDir, kotlinStyleSourceDirToUse))
}
}
class AndroidStyleSourceDirUsageDiagnostic(
private val androidStyleSourceDirInUse: File,
private val kotlinStyleSourceDirToUse: File
) : KotlinAndroidSourceSetLayoutChecker.Diagnostic {
override val message: String
get() = """
Usage of 'Android Style' source directory $androidStyleSourceDirInUse is deprecated.
Use $kotlinStyleSourceDirToUse instead.
""".trimIndent()
}
}
@@ -12,15 +12,15 @@ 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.androidSourceSetInfo
import org.jetbrains.kotlin.gradle.plugin.sources.android.multiplatformAndroidSourceSetLayoutV1
import java.io.File
/**
* 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(
diagnosticReporter: KotlinAndroidSourceSetLayoutChecker.DiagnosticReporter,
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout,
kotlinSourceSet: KotlinSourceSet,
@@ -35,14 +35,27 @@ internal object MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChe
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()
diagnosticReporter.warning(
V1StyleSourceDirUsageDiagnostic(
layout = layout,
v1StyleSourceDirInUse = v1KotlinSourceDir,
v2StyleSourceDirToUse = target.project.file("src/${kotlinSourceSet.name}/kotlin")
)
)
}
}
class V1StyleSourceDirUsageDiagnostic(
private val layout: KotlinAndroidSourceSetLayout,
private val v1StyleSourceDirInUse: File,
private val v2StyleSourceDirToUse: File
) : KotlinAndroidSourceSetLayoutChecker.Diagnostic {
override val message: String
get() = """
Found used source directory $v1StyleSourceDirInUse
This source directory was supported by: ${multiplatformAndroidSourceSetLayoutV1.name}
Current KotlinAndroidSourceSetLayout: ${layout.name}
New source directory is: $v2StyleSourceDirToUse
""".trimIndent()
}
}