From 5a447a823feee30dfd01ecfc27762095071a47fb Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 25 Aug 2022 11:21:11 +0200 Subject: [PATCH] [Gradle][MPP] Introduce DiagnosticReporter and Diagnostic APIs... ... for KotlinAndroidSourceSetLayoutChecker KT-53709 --- .../android/KotlinAndroidSourceSetFactory.kt | 6 ++- .../android/KotlinAndroidSourceSets.kt | 6 ++- .../KotlinAndroidSourceSetLayoutChecker.kt | 46 +++++++++++++++++-- .../MultiplatformLayoutV1PromoteV2Checker.kt | 35 +++++++------- ...tiplatformLayoutV2AgpRequirementChecker.kt | 24 +++++++--- ...youtV2AndroidStyleSourceDirUsageChecker.kt | 23 ++++++---- ...tformLayoutV1StyleSourceDirUsageChecker.kt | 31 +++++++++---- 7 files changed, 124 insertions(+), 47 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetFactory.kt index b49383bd6f7..6820290b786 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetFactory.kt @@ -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() @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt index c79f3c2a9dc..1d24fdfced9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt @@ -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) } /* diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/KotlinAndroidSourceSetLayoutChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/KotlinAndroidSourceSetLayoutChecker.kt index cd1dd2d88aa..4530ed96bf9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/KotlinAndroidSourceSetLayoutChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/KotlinAndroidSourceSetLayoutChecker.kt @@ -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 { - 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) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV1PromoteV2Checker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV1PromoteV2Checker.kt index 9e1dea78f77..1c39a23f2d9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV1PromoteV2Checker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV1PromoteV2Checker.kt @@ -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() + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AgpRequirementChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AgpRequirementChecker.kt index ed0281b9ee4..14a42b90213 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AgpRequirementChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AgpRequirementChecker.kt @@ -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() + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker.kt index a646877a801..010a87a687a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2AndroidStyleSourceDirUsageChecker.kt @@ -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() + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker.kt index 1d6097de9ff..2c80a6ba803 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/checker/MultiplatformLayoutV2MultiplatformLayoutV1StyleSourceDirUsageChecker.kt @@ -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() + } }