From dcb51e64dffbaa99c24e97155cc3a74ba792f6bd Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Wed, 19 Apr 2023 17:07:37 +0200 Subject: [PATCH] [Gradle] Migrate warning about missing Android-target to new infra --- ...latformProjectConfigurationHealthChecks.kt | 31 ------ .../gradle/plugin/KotlinPluginWrapper.kt | 1 - .../diagnostics/KotlinGradleProjectChecker.kt | 3 +- .../diagnostics/KotlinToolingDiagnostics.kt | 20 ++++ ...ndroidPluginWithoutAndroidTargetChecker.kt | 28 ++++++ .../KT41641AbsentAndroidTarget.kt | 99 +++++++++---------- .../kotlin/gradle/util/diagnosticUtils.kt | 21 +++- 7 files changed, 114 insertions(+), 89 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/AndroidPluginWithoutAndroidTargetChecker.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformProjectConfigurationHealthChecks.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformProjectConfigurationHealthChecks.kt index 87be7b2ae92..f6033dfdf16 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformProjectConfigurationHealthChecks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformProjectConfigurationHealthChecks.kt @@ -38,37 +38,6 @@ internal fun Project.runMissingKotlinTargetsProjectConfigurationHealthCheck() = } } -internal fun Project.runMissingAndroidTargetProjectConfigurationHealthCheck( - warningLogger: (warningMessage: String) -> Unit = project.logger::warn -) = project.runProjectConfigurationHealthCheck check@{ - if (project.kotlinPropertiesProvider.ignoreAbsentAndroidMultiplatformTarget) { - return@check - } - - val androidPluginId = findAppliedAndroidPluginIdOrNull() ?: return@check - - if (findAndroidTarget() != null) return@check - - warningLogger( - """ - Missing 'androidTarget()' Kotlin target in multiplatform project ${project.name} (${project.path})'. - The Android Gradle plugin was applied without creating a corresponding 'androidTarget()' Kotlin Target: - - ``` - plugins { - id("$androidPluginId") - kotlin("multiplatform") - } - - kotlin { - androidTarget() // <-- please register this Android target - } - ``` - - """.trimIndent() - ) -} - internal fun Project.runDisabledCInteropCommonizationOnHmppProjectConfigurationHealthCheck( warningLogger: (warningMessage: String) -> Unit = project.logger::warn ) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index c74db370638..b1fbfe5825b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -387,7 +387,6 @@ abstract class AbstractKotlinMultiplatformPluginWrapper : KotlinBasePluginWrappe get() = KotlinMultiplatformExtension::class override fun whenBuildEvaluated(project: Project) { - project.runMissingAndroidTargetProjectConfigurationHealthCheck() project.runMissingKotlinTargetsProjectConfigurationHealthCheck() project.runDisabledCInteropCommonizationOnHmppProjectConfigurationHealthCheck() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt index fb931060cfe..28752d8d70f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinGradleProjectChecker.kt @@ -99,7 +99,8 @@ internal interface KotlinGradleProjectChecker { MissingNativeStdlibChecker, UnusedSourceSetsChecker, AndroidSourceSetLayoutV1SourceSetsNotFoundChecker, - TargetsWithAmbiguousConsumableConfigurationsChecker + TargetsWithAmbiguousConsumableConfigurationsChecker, + AndroidPluginWithoutAndroidTargetChecker ) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt index 8b7e8e441de..0d24da60922 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics.kt @@ -220,4 +220,24 @@ object KotlinToolingDiagnostics { """.trimIndent() ) } + + object AndroidTargetIsMissing : ToolingDiagnosticFactory(WARNING) { + operator fun invoke(projectName: String, projectPath: String, androidPluginId: String) = build( + """ + Missing 'androidTarget()' Kotlin target in multiplatform project '$projectName ($projectPath)'. + The Android Gradle plugin was applied without creating a corresponding 'android()' Kotlin Target: + + ``` + plugins { + id("$androidPluginId") + kotlin("multiplatform") + } + + kotlin { + androidTarget() // <-- please register this Android target + } + ``` + """.trimIndent() + ) + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/AndroidPluginWithoutAndroidTargetChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/AndroidPluginWithoutAndroidTargetChecker.kt new file mode 100644 index 00000000000..d76f8840079 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/checkers/AndroidPluginWithoutAndroidTargetChecker.kt @@ -0,0 +1,28 @@ +/* + * 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.diagnostics.checkers + +import org.jetbrains.kotlin.gradle.plugin.configurationResult +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectChecker +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectCheckerContext +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnosticsCollector +import org.jetbrains.kotlin.gradle.targets.android.findAndroidTarget +import org.jetbrains.kotlin.gradle.utils.findAppliedAndroidPluginIdOrNull + +internal object AndroidPluginWithoutAndroidTargetChecker : KotlinGradleProjectChecker { + override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) { + project.configurationResult.await() + if (multiplatformExtension == null || kotlinPropertiesProvider.ignoreAbsentAndroidMultiplatformTarget) return + + val androidPluginId = project.findAppliedAndroidPluginIdOrNull() ?: return + if (project.findAndroidTarget() != null) return + collector.reportOncePerGradleProject( + project, + KotlinToolingDiagnostics.AndroidTargetIsMissing(project.name, project.path, androidPluginId) + ) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/regressionTests/KT41641AbsentAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/regressionTests/KT41641AbsentAndroidTarget.kt index 3ffaab87699..ff62b623ef3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/regressionTests/KT41641AbsentAndroidTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/regressionTests/KT41641AbsentAndroidTarget.kt @@ -8,84 +8,79 @@ package org.jetbrains.kotlin.gradle.regressionTests import com.android.build.gradle.LibraryExtension -import org.jetbrains.kotlin.gradle.util.MultiplatformExtensionTest -import org.jetbrains.kotlin.gradle.plugin.runMissingAndroidTargetProjectConfigurationHealthCheck +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.targets.android.findAndroidTarget -import org.jetbrains.kotlin.gradle.util.propertiesExtension +import org.jetbrains.kotlin.gradle.util.* +import org.jetbrains.kotlin.gradle.util.assertNoDiagnostics import org.junit.Test -import kotlin.test.* +import kotlin.test.assertNull +import kotlin.test.assertSame class KT41641AbsentAndroidTarget : MultiplatformExtensionTest() { @Test fun `test android plugin without android target`() { - project.plugins.apply("kotlin-multiplatform") - project.plugins.apply("android-library") - - /* Arbitrary minimal Android setup */ - val android = project.extensions.getByName("android") as LibraryExtension - android.compileSdk = 31 - - kotlin.jvm() - + defaultSetupWithoutAndroidTarget() /* Previously failed with 'Collection is empty.' */ assertNull(project.findAndroidTarget()) assertSame(kotlin.androidTarget(), project.findAndroidTarget()) } @Test - fun `test runMissingAndroidTargetProjectConfigurationHealthCheck`() { - project.plugins.apply("kotlin-multiplatform") - project.plugins.apply("android-library") - - /* Arbitrary minimal Android setup */ - val android = project.extensions.getByName("android") as LibraryExtension - android.compileSdk = 31 - - kotlin.jvm() + fun `test runMissingAndroidTargetProjectConfigurationHealthCheck - warning`() { + defaultSetupWithoutAndroidTarget() // Missing android target -> expect warning message - var warningMessage: String? = null - project.runMissingAndroidTargetProjectConfigurationHealthCheck(warningLogger = { warningMessage = it }) - assertNotNull(warningMessage, "Expected warning message to be logged") - - // Present android target -> expect no warning message anymore - kotlin.androidTarget() - project.runMissingAndroidTargetProjectConfigurationHealthCheck(warningLogger = { - fail("Expected no warning message to be logged. Received: $it") - }) + project.evaluate() + project.assertContainsDiagnostic(KotlinToolingDiagnostics.AndroidTargetIsMissing) } @Test - fun `test kotlin-mpp-absentAndroidTarget-nowarn flag`() { + fun `test runMissingAndroidTargetProjectConfigurationHealthCheck - no warning`() { + defaultSetupWithoutAndroidTarget() + kotlin.androidTarget() + + // Present android target -> expect no warning message anymore + project.evaluate() + project.assertNoDiagnostics() + } + + @Test + fun `test kotlin-mpp-absentAndroidTarget-nowarn flag set to true`() { + defaultSetupWithoutAndroidTarget() project.propertiesExtension["kotlin.mpp.absentAndroidTarget.nowarn"] = "true" + + project.evaluate() + project.assertNoDiagnostics() + } + + @Test + fun `test kotlin-mpp-absentAndroidTarget-nowarn flag set to false`() { + defaultSetupWithoutAndroidTarget() + project.propertiesExtension["kotlin.mpp.absentAndroidTarget.nowarn"] = null + + project.evaluate() + project.assertContainsDiagnostic(KotlinToolingDiagnostics.AndroidTargetIsMissing) + } + + @Test + fun `test kotlin-mpp-absentAndroidTarget-nowarn flag set to null`() { + defaultSetupWithoutAndroidTarget() + project.propertiesExtension["kotlin.mpp.absentAndroidTarget.nowarn"] = null + + project.evaluate() + project.assertContainsDiagnostic(KotlinToolingDiagnostics.AndroidTargetIsMissing) + } + + private fun defaultSetupWithoutAndroidTarget() { project.plugins.apply("kotlin-multiplatform") project.plugins.apply("android-library") /* Arbitrary minimal Android setup */ val android = project.extensions.getByName("android") as LibraryExtension android.compileSdk = 31 - kotlin.jvm() - project.runMissingAndroidTargetProjectConfigurationHealthCheck(warningLogger = { - fail("Expected no warning message to be logged. Received $it") - }) - - // Test when nowarn is set to false - run { - var warningMessage: String? = null - project.propertiesExtension["kotlin.mpp.absentAndroidTarget.nowarn"] = "false" - project.runMissingAndroidTargetProjectConfigurationHealthCheck(warningLogger = { warningMessage = it }) - assertNotNull(warningMessage, "Expected warning message to be logged") - } - - // Test when nowarn is set to null - run { - var warningMessage: String? = null - project.propertiesExtension["kotlin.mpp.absentAndroidTarget.nowarn"] = null - project.runMissingAndroidTargetProjectConfigurationHealthCheck(warningLogger = { warningMessage = it }) - assertNotNull(warningMessage, "Expected warning message to be logged") - } + project.setMultiplatformAndroidSourceSetLayoutVersion(2) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/diagnosticUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/diagnosticUtils.kt index 411035dc64c..0a52efd9ba6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/diagnosticUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/diagnosticUtils.kt @@ -68,15 +68,29 @@ internal fun Project.assertNoDiagnostics() { ) } +/** + * Checks that diagnostic with [factory.id] is reported. The exact parameters (if any) + * are ignored. If you need to compare the parameters, refer to the overload accepting [ToolingDiagnostic] + */ +internal fun Project.assertContainsDiagnostic(factory: ToolingDiagnosticFactory) { + kotlinToolingDiagnosticsCollector.getDiagnosticsForProject(this).assertContainsDiagnostic(factory) +} + internal fun Project.assertContainsDiagnostic(diagnostic: ToolingDiagnostic) { kotlinToolingDiagnosticsCollector.getDiagnosticsForProject(this).assertContainsDiagnostic(diagnostic) } +internal fun Collection.assertContainsDiagnostic(factory: ToolingDiagnosticFactory) { + if (!any { it.id == factory.id }) failDiagnosticNotFound("diagnostic with id ${factory.id} ", this) +} + internal fun Collection.assertContainsDiagnostic(diagnostic: ToolingDiagnostic) { + if (diagnostic !in this) failDiagnosticNotFound("diagnostic $diagnostic\n", this) +} + +private fun failDiagnosticNotFound(diagnosticDescription: String, notFoundInCollection: Collection) { fun Any.withIndent() = this.toString().prependIndent(" ") - if (diagnostic !in this) { - fail("Missing diagnostic\n${diagnostic.withIndent()} \nin:\n${this.render().withIndent()}") - } + fail("Missing ${diagnosticDescription}in:\n${notFoundInCollection.render().withIndent()}") } internal fun Project.assertNoDiagnostics(id: String) { @@ -100,4 +114,3 @@ private val expectedDiagnosticsRoot: Path get() = resourcesRoot.resolve("expectedDiagnostics") private fun expectedDiagnosticsFile(projectName: String): File = expectedDiagnosticsRoot.resolve("$projectName.txt").toFile() -