From 4c120e9190a96a707c2a1cb03f7affe39936b798 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Wed, 3 Jun 2026 11:32:13 -0700 Subject: [PATCH 1/8] support enabled built-in kotlin --- .../src/main/kotlin/FlutterPluginUtils.kt | 43 +++++++ .../src/test/kotlin/FlutterPluginUtilsTest.kt | 114 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index d5b8a42195f12..36552114524db 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -533,6 +533,49 @@ object FlutterPluginUtils { @JvmStatic @JvmName("detectApplyingKotlinGradlePlugin") internal fun detectApplyingKotlinGradlePlugin(project: Project) { + val gradlePropertiesFile = project.rootProject.file("gradle.properties") + val properties = readPropertiesIfExist(gradlePropertiesFile) + val isBuiltInKotlinEnabled = properties.getProperty("android.builtInKotlin")?.toBoolean() ?: false + + if (isBuiltInKotlinEnabled) { + val allSubprojectsDoNotApplyKgp = project.rootProject.subprojects.all { subproject -> + if (!subproject.buildFile.exists() || subproject.buildFile.absolutePath.contains(".android")) { + true + } else { + val scriptText: String = + if (subproject.buildFile.absolutePath.contains("app/build.gradle")) { + getBuildGradleFileFromProjectDir(subproject.projectDir, subproject.logger).readText() + } else { + subproject.buildFile.readText() + } + + val (hasKgpPlugin, hasAppPlugin, hasLibPlugin) = + if (subproject.buildFile.extension == "kts") { + Triple( + kgpRegexKotlin.containsMatchIn(scriptText), + appPluginRegexKotlin.containsMatchIn(scriptText), + libPluginRegexKotlin.containsMatchIn(scriptText) + ) + } else { + Triple( + kgpRegexGroovy.containsMatchIn(scriptText), + appPluginRegexGroovy.containsMatchIn(scriptText), + libPluginRegexGroovy.containsMatchIn(scriptText) + ) + } + + if (!hasAppPlugin && !hasLibPlugin) { + true + } else { + !hasKgpPlugin + } + } + } + if (allSubprojectsDoNotApplyKgp) { + return + } + } + val pluginsWithKGPAppliedList = mutableListOf() var shouldLogForApp = false diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index ab51a4f5de016..c37899fd24be5 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -924,6 +924,120 @@ class FlutterPluginUtilsTest { @Nested inner class DetectApplyingKotlinGradlePluginTests { + @Test + fun `exits early when built-in Kotlin is enabled and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + val gradlePropertiesFile = File(rootDir, "gradle.properties").apply { + createNewFile() + writeText("android.builtInKotlin=true\n") + } + + val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } + val appBuildGradleFile = + File(appDir, "build.gradle").apply { + createNewFile() + writeText( + """ + plugins { + id("com.android.application") + } + """.trimIndent() + ) + } + + val rootProject = mockk() + val mockGradle = mockk() + val mockLogger = mockk(relaxed = true) + val appProjectPluginManager = mockk(relaxed = true) + + every { rootProject.file("gradle.properties") } returns gradlePropertiesFile + every { rootProject.projectDir } returns rootDir + + val appProject = + createMockSubproject( + tempDir = tempDir, + buildFile = appBuildGradleFile, + projectName = "app", + mockLogger = mockLogger, + rootProjectMock = rootProject, + gradleMock = mockGradle, + pluginManager = appProjectPluginManager + ) + + every { rootProject.subprojects } returns setOf(appProject) + + detectApplyingKotlinGradlePlugin(appProject) + + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { appProjectPluginManager.apply(any()) } + } + + @Test + fun `does not exit early when built-in Kotlin is enabled but a subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + val gradlePropertiesFile = File(rootDir, "gradle.properties").apply { + createNewFile() + writeText("android.builtInKotlin=true\n") + } + + val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } + val appBuildGradleFile = + File(appDir, "build.gradle").apply { + createNewFile() + writeText( + """ + plugins { + id("com.android.application") + id("kotlin-android") + } + """.trimIndent() + ) + } + + val rootProject = mockk() + val mockGradle = mockk() + val mockLogger = mockk(relaxed = true) + val appProjectPluginManager = mockk(relaxed = true) + + every { rootProject.file("gradle.properties") } returns gradlePropertiesFile + every { rootProject.projectDir } returns rootDir + + val appProject = + createMockSubproject( + tempDir = tempDir, + buildFile = appBuildGradleFile, + projectName = "app", + mockLogger = mockLogger, + rootProjectMock = rootProject, + gradleMock = mockGradle, + pluginManager = appProjectPluginManager + ) + + every { rootProject.subprojects } returns setOf(appProject) + + val subprojectsActionSlot = slot>() + val projectsEvaluatedActionSlot = slot>() + + every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit + every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + + detectApplyingKotlinGradlePlugin(appProject) + + verify { rootProject.subprojects(capture(subprojectsActionSlot)) } + subprojectsActionSlot.captured.execute(appProject) + + verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } + projectsEvaluatedActionSlot.captured.execute(mockGradle) + + verify { + mockLogger.error(match { it.contains("applies the Kotlin Gradle Plugin, which will cause build failures") }) + } + } + @Test fun `logs app warning when KGP is only applied in app`( @TempDir tempDir: Path From 15c34f6441b17ef5a08553fb69df1b00ff41dfae Mon Sep 17 00:00:00 2001 From: jesswrd Date: Wed, 3 Jun 2026 13:17:39 -0700 Subject: [PATCH 2/8] abstract repetitive logic --- .../src/main/kotlin/FlutterPluginUtils.kt | 111 +++++++++--------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 36552114524db..4452479fb30e7 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -535,42 +535,19 @@ object FlutterPluginUtils { internal fun detectApplyingKotlinGradlePlugin(project: Project) { val gradlePropertiesFile = project.rootProject.file("gradle.properties") val properties = readPropertiesIfExist(gradlePropertiesFile) - val isBuiltInKotlinEnabled = properties.getProperty("android.builtInKotlin")?.toBoolean() ?: false + val isBuiltInKotlinEnabled = + properties.getProperty("android.builtInKotlin")?.toBoolean() ?: false if (isBuiltInKotlinEnabled) { - val allSubprojectsDoNotApplyKgp = project.rootProject.subprojects.all { subproject -> - if (!subproject.buildFile.exists() || subproject.buildFile.absolutePath.contains(".android")) { - true - } else { - val scriptText: String = - if (subproject.buildFile.absolutePath.contains("app/build.gradle")) { - getBuildGradleFileFromProjectDir(subproject.projectDir, subproject.logger).readText() - } else { - subproject.buildFile.readText() - } - - val (hasKgpPlugin, hasAppPlugin, hasLibPlugin) = - if (subproject.buildFile.extension == "kts") { - Triple( - kgpRegexKotlin.containsMatchIn(scriptText), - appPluginRegexKotlin.containsMatchIn(scriptText), - libPluginRegexKotlin.containsMatchIn(scriptText) - ) - } else { - Triple( - kgpRegexGroovy.containsMatchIn(scriptText), - appPluginRegexGroovy.containsMatchIn(scriptText), - libPluginRegexGroovy.containsMatchIn(scriptText) - ) - } - - if (!hasAppPlugin && !hasLibPlugin) { + val allSubprojectsDoNotApplyKgp = + project.rootProject.subprojects.all { subproject -> + val pluginState = getSubprojectPluginState(subproject) + if (pluginState == null || (!pluginState.hasAppPlugin && !pluginState.hasLibPlugin)) { true } else { - !hasKgpPlugin + !pluginState.hasKgpPlugin } } - } if (allSubprojectsDoNotApplyKgp) { return } @@ -580,37 +557,15 @@ object FlutterPluginUtils { var shouldLogForApp = false project.rootProject.subprojects { - // Accounts for Add-to-app scenarios where the Flutter Module ephemeral .android/ directory should not be adjusted and by default does not apply KGP - if (!buildFile.exists() || buildFile.absolutePath.contains(".android")) return@subprojects - - val scriptText: String = - if (buildFile.absolutePath.contains("app/build.gradle")) { - getBuildGradleFileFromProjectDir(this.projectDir, this.logger).readText() - } else { - buildFile.readText() - } - - val (hasKgpPlugin, hasAppPlugin, hasLibPlugin) = - if (buildFile.extension == "kts") { - Triple( - kgpRegexKotlin.containsMatchIn(scriptText), - appPluginRegexKotlin.containsMatchIn(scriptText), - libPluginRegexKotlin.containsMatchIn(scriptText) - ) - } else { - Triple( - kgpRegexGroovy.containsMatchIn(scriptText), - appPluginRegexGroovy.containsMatchIn(scriptText), - libPluginRegexGroovy.containsMatchIn(scriptText) - ) - } + val pluginState = getSubprojectPluginState(this) ?: return@subprojects // Ensures applying AGP exists in the build file configuration. - if (!hasAppPlugin && !hasLibPlugin) return@subprojects + if (!pluginState.hasAppPlugin && !pluginState.hasLibPlugin) return@subprojects - if (!hasKgpPlugin) { + if (!pluginState.hasKgpPlugin) { try { pluginManager.apply("kotlin-android") + println("applied KGP in FGP") } catch (_: Exception) { logger.quiet( """ @@ -624,11 +579,11 @@ object FlutterPluginUtils { } // Apply AGP exists and Apply KGP also exists in build.gradle - if (hasAppPlugin) { + if (pluginState.hasAppPlugin) { shouldLogForApp = true } - if (hasLibPlugin) { + if (pluginState.hasLibPlugin) { pluginsWithKGPAppliedList.add(name) } } @@ -660,6 +615,46 @@ object FlutterPluginUtils { } } + private data class SubprojectPluginState( + val hasKgpPlugin: Boolean, + val hasAppPlugin: Boolean, + val hasLibPlugin: Boolean + ) + + private fun getSubprojectPluginState(subproject: Project): SubprojectPluginState? { + val buildFile = subproject.buildFile + if (!buildFile.exists() || buildFile.absolutePath.contains(".android")) { + return null + } + + val scriptText: String = + if (buildFile.absolutePath.contains("app/build.gradle")) { + getBuildGradleFileFromProjectDir( + subproject.projectDir, + subproject.logger + ).readText() + } else { + buildFile.readText() + } + + val (hasKgpPlugin, hasAppPlugin, hasLibPlugin) = + if (buildFile.extension == "kts") { + Triple( + kgpRegexKotlin.containsMatchIn(scriptText), + appPluginRegexKotlin.containsMatchIn(scriptText), + libPluginRegexKotlin.containsMatchIn(scriptText) + ) + } else { + Triple( + kgpRegexGroovy.containsMatchIn(scriptText), + appPluginRegexGroovy.containsMatchIn(scriptText), + libPluginRegexGroovy.containsMatchIn(scriptText) + ) + } + + return SubprojectPluginState(hasKgpPlugin, hasAppPlugin, hasLibPlugin) + } + /** Prints error message and fix for any plugin compileSdkVersion or ndkVersion that are higher than the project. */ @JvmStatic @JvmName("detectLowCompileSdkVersionOrNdkVersion") From d24e96b6510dcc6d0a7ca5b5813b087dc26177b5 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 4 Jun 2026 10:39:10 -0700 Subject: [PATCH 3/8] fixed error and added pluginProject as subproject --- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 81 ++++++++++++++++--- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index c37899fd24be5..dd41830012f84 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -929,10 +929,11 @@ class FlutterPluginUtilsTest { @TempDir tempDir: Path ) { val rootDir = tempDir.toFile() - val gradlePropertiesFile = File(rootDir, "gradle.properties").apply { - createNewFile() - writeText("android.builtInKotlin=true\n") - } + val gradlePropertiesFile = File(rootDir, "gradle.properties") + .apply { + createNewFile() + writeText("android.builtInKotlin=true\n") + } val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } val appBuildGradleFile = @@ -947,10 +948,24 @@ class FlutterPluginUtilsTest { ) } + val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } + val pluginBuildGradleFile = + File(pluginDir, "build.gradle").apply { + createNewFile() + writeText( + """ + plugins { + id("com.android.library") + } + """.trimIndent() + ) + } + val rootProject = mockk() val mockGradle = mockk() val mockLogger = mockk(relaxed = true) val appProjectPluginManager = mockk(relaxed = true) + val pluginProjectPluginManager = mockk(relaxed = true) every { rootProject.file("gradle.properties") } returns gradlePropertiesFile every { rootProject.projectDir } returns rootDir @@ -966,12 +981,24 @@ class FlutterPluginUtilsTest { pluginManager = appProjectPluginManager ) - every { rootProject.subprojects } returns setOf(appProject) + val pluginProject = + createMockSubproject( + tempDir = tempDir, + buildFile = pluginBuildGradleFile, + projectName = "plugin", + mockLogger = mockLogger, + rootProjectMock = rootProject, + gradleMock = mockGradle, + pluginManager = pluginProjectPluginManager + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) detectApplyingKotlinGradlePlugin(appProject) verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appProjectPluginManager.apply(any()) } + verify(exactly = 0) { appProjectPluginManager.apply(any()) } + verify(exactly = 0) { pluginProjectPluginManager.apply(any()) } } @Test @@ -979,10 +1006,11 @@ class FlutterPluginUtilsTest { @TempDir tempDir: Path ) { val rootDir = tempDir.toFile() - val gradlePropertiesFile = File(rootDir, "gradle.properties").apply { - createNewFile() - writeText("android.builtInKotlin=true\n") - } + val gradlePropertiesFile = File(rootDir, "gradle.properties") + .apply { + createNewFile() + writeText("android.builtInKotlin=true\n") + } val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } val appBuildGradleFile = @@ -992,6 +1020,19 @@ class FlutterPluginUtilsTest { """ plugins { id("com.android.application") + } + """.trimIndent() + ) + } + + val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } + val pluginBuildGradleFile = + File(pluginDir, "build.gradle").apply { + createNewFile() + writeText( + """ + plugins { + id("com.android.library") id("kotlin-android") } """.trimIndent() @@ -1002,6 +1043,7 @@ class FlutterPluginUtilsTest { val mockGradle = mockk() val mockLogger = mockk(relaxed = true) val appProjectPluginManager = mockk(relaxed = true) + val pluginProjectPluginManager = mockk(relaxed = true) every { rootProject.file("gradle.properties") } returns gradlePropertiesFile every { rootProject.projectDir } returns rootDir @@ -1017,7 +1059,18 @@ class FlutterPluginUtilsTest { pluginManager = appProjectPluginManager ) - every { rootProject.subprojects } returns setOf(appProject) + val pluginProject = + createMockSubproject( + tempDir = tempDir, + buildFile = pluginBuildGradleFile, + projectName = "plugin", + mockLogger = mockLogger, + rootProjectMock = rootProject, + gradleMock = mockGradle, + pluginManager = pluginProjectPluginManager + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1029,12 +1082,16 @@ class FlutterPluginUtilsTest { verify { rootProject.subprojects(capture(subprojectsActionSlot)) } subprojectsActionSlot.captured.execute(appProject) + subprojectsActionSlot.captured.execute(pluginProject) verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } projectsEvaluatedActionSlot.captured.execute(mockGradle) + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } verify { - mockLogger.error(match { it.contains("applies the Kotlin Gradle Plugin, which will cause build failures") }) + mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) } } From 3d340ba16923d3d18a48e5d18a021a840a5be78d Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 4 Jun 2026 13:50:08 -0700 Subject: [PATCH 4/8] fix error and refactored for readability --- .../gradle/src/test/kotlin/DeeplinkTest.kt | 4 +- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 666 +++++------------- 2 files changed, 172 insertions(+), 498 deletions(-) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/DeeplinkTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/DeeplinkTest.kt index 1b779b21fae50..4fce003030fcd 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/DeeplinkTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/DeeplinkTest.kt @@ -4,7 +4,7 @@ package com.flutter.gradle -import org.gradle.internal.impldep.org.junit.Assert.assertThrows +import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertFalse @@ -41,7 +41,7 @@ class DeeplinkTest { val deeplink1 = Deeplink("scheme1", "host1", "path1", IntentFilterCheck()) val deeplink2 = null - assertThrows(NullPointerException::class.java) { deeplink1.equals(deeplink2) } + assertThrows { deeplink1.equals(deeplink2) } } @Test diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index dd41830012f84..0af7edce74a68 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -34,8 +34,8 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.invocation.Gradle import org.gradle.api.logging.Logger import org.gradle.api.plugins.PluginManager -import org.gradle.internal.impldep.junit.framework.TestCase.assertFalse -import org.gradle.internal.impldep.junit.framework.TestCase.assertTrue +import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.jetbrains.kotlin.gradle.plugin.extraProperties import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows @@ -924,81 +924,76 @@ class FlutterPluginUtilsTest { @Nested inner class DetectApplyingKotlinGradlePluginTests { + private val rootProject = mockk() + private val mockGradle = mockk() + private val mockLogger = mockk(relaxed = true) + + private fun writeGradleProperties(rootDir: File, content: String) { + File(rootDir, "gradle.properties").apply { + createNewFile() + writeText(content) + } + } + + private fun createSubproject( + tempDir: Path, + projectName: String, + plugins: List = emptyList(), + legacyPlugins: List = emptyList() + ): Pair { + val rootDir = tempDir.toFile() + every { rootProject.file("gradle.properties") } returns File(rootDir, "gradle.properties") + every { rootProject.projectDir } returns rootDir + + val projectDir = tempDir.resolve(projectName).toFile().apply { mkdirs() } + val buildGradleFile = File(projectDir, "build.gradle").apply { + createNewFile() + val pluginsBlock = if (plugins.isNotEmpty()) { + "plugins {\n" + plugins.joinToString("\n") { " id(\"$it\")" } + "\n}\n" + } else "" + val legacyBlock = if (legacyPlugins.isNotEmpty()) { + legacyPlugins.joinToString("\n") { "apply plugin: '$it'" } + "\n" + } else "" + writeText(pluginsBlock + legacyBlock) + } + val pluginManager = mockk(relaxed = true) + val project = createMockSubproject( + tempDir = tempDir, + buildFile = buildGradleFile, + projectName = projectName, + mockLogger = mockLogger, + rootProjectMock = rootProject, + gradleMock = mockGradle, + pluginManager = pluginManager + ) + return Pair(project, pluginManager) + } + @Test fun `exits early when built-in Kotlin is enabled and no subproject applies KGP`( @TempDir tempDir: Path ) { val rootDir = tempDir.toFile() - val gradlePropertiesFile = File(rootDir, "gradle.properties") - .apply { - createNewFile() - writeText("android.builtInKotlin=true\n") - } + writeGradleProperties(rootDir, "android.builtInKotlin=true\n") - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - } - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - every { rootProject.file("gradle.properties") } returns gradlePropertiesFile - every { rootProject.projectDir } returns rootDir - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) every { rootProject.subprojects } returns setOf(appProject, pluginProject) detectApplyingKotlinGradlePlugin(appProject) verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appProjectPluginManager.apply(any()) } - verify(exactly = 0) { pluginProjectPluginManager.apply(any()) } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } } @Test @@ -1006,69 +1001,18 @@ class FlutterPluginUtilsTest { @TempDir tempDir: Path ) { val rootDir = tempDir.toFile() - val gradlePropertiesFile = File(rootDir, "gradle.properties") - .apply { - createNewFile() - writeText("android.builtInKotlin=true\n") - } - - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - id("kotlin-android") - } - """.trimIndent() - ) - } + writeGradleProperties(rootDir, "android.builtInKotlin=true\n") - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - every { rootProject.file("gradle.properties") } returns gradlePropertiesFile - every { rootProject.projectDir } returns rootDir - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) every { rootProject.subprojects } returns setOf(appProject, pluginProject) @@ -1093,67 +1037,24 @@ class FlutterPluginUtilsTest { verify { mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } } @Test fun `logs app warning when KGP is only applied in app`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - id("kotlin-android") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - } - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application", "kotlin-android") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1170,83 +1071,37 @@ class FlutterPluginUtilsTest { verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } projectsEvaluatedActionSlot.captured.execute(mockGradle) - verify { - mockLogger.error( - """ - WARNING: Your Android app project: app located at: ${appBuildGradleFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - """.trimIndent() - ) + """.trimIndent() + verify { + mockLogger.error(expectedAppWarning) } verify(exactly = 0) { mockLogger.error(match { it.contains("Your app uses the following plugins") }) } - verify(exactly = 0) { appProjectPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginProjectPluginManager.apply("kotlin-android") } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } } @Test fun `logs plugin warning when KGP is only applied in one plugin`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - id("kotlin-android") - } - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1281,70 +1136,24 @@ class FlutterPluginUtilsTest { verify(exactly = 0) { mockLogger.error(match { it.contains("Your Android app project") }) } - verify(exactly = 1) { appProjectPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginProjectPluginManager.apply("kotlin-android") } + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } } @Test fun `logs app and plugin warning when KGP is applied in both app and plugins`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - id("kotlin-android") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - id("kotlin-android") - } - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application", "kotlin-android") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1361,15 +1170,14 @@ class FlutterPluginUtilsTest { verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } projectsEvaluatedActionSlot.captured.execute(mockGradle) - verify { - mockLogger.error( - """ - WARNING: Your Android app project: app located at: ${appBuildGradleFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - """.trimIndent() - ) + """.trimIndent() + verify { + mockLogger.error(expectedAppWarning) } verify { @@ -1387,78 +1195,29 @@ class FlutterPluginUtilsTest { ) } - verify(exactly = 0) { appProjectPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginProjectPluginManager.apply("kotlin-android") } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } } @Test fun `logs app and plugin warning when legacy KGP configuration is applied in both app and plugins`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - apply plugin: 'com.android.application' - apply plugin: 'kotlin-android' - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - apply plugin: 'com.android.library' - apply plugin: 'kotlin-android' - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectOnePluginManager = mockk(relaxed = true) - val pluginProjectTwoPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProjectOne = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin1", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectOnePluginManager - ) - - val pluginProjectTwo = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin2", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectTwoPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + legacyPlugins = listOf("com.android.application", "kotlin-android") + ) + val (pluginProjectOne, pluginProjectOnePluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin1", + legacyPlugins = listOf("com.android.library", "kotlin-android") + ) + val (pluginProjectTwo, pluginProjectTwoPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin2", + legacyPlugins = listOf("com.android.library", "kotlin-android") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1476,15 +1235,14 @@ class FlutterPluginUtilsTest { verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } projectsEvaluatedActionSlot.captured.execute(mockGradle) - verify { - mockLogger.error( - """ - WARNING: Your Android app project: app located at: ${appBuildGradleFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - """.trimIndent() - ) + """.trimIndent() + verify { + mockLogger.error(expectedAppWarning) } verify { @@ -1502,7 +1260,7 @@ class FlutterPluginUtilsTest { ) } - verify(exactly = 0) { appProjectPluginManager.apply("kotlin-android") } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } verify(exactly = 0) { pluginProjectOnePluginManager.apply("kotlin-android") } verify(exactly = 0) { pluginProjectTwoPluginManager.apply("kotlin-android") } } @@ -1511,56 +1269,16 @@ class FlutterPluginUtilsTest { fun `does not log when migrated to Built-in Kotlin`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - apply plugin: 'com.android.application' - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - apply plugin: 'com.android.library' - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + legacyPlugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + legacyPlugins = listOf("com.android.library") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1581,68 +1299,24 @@ class FlutterPluginUtilsTest { mockLogger.error(any()) } - verify(exactly = 1) { appProjectPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginProjectPluginManager.apply("kotlin-android") } + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } } @Test fun `logs when KGP is applied but fails to apply`( @TempDir tempDir: Path ) { - val appDir = tempDir.resolve("app").toFile().apply { mkdirs() } - val appBuildGradleFile = - File(appDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.application") - } - """.trimIndent() - ) - } - - val pluginDir = tempDir.resolve("plugin").toFile().apply { mkdirs() } - val pluginBuildGradleFile = - File(pluginDir, "build.gradle").apply { - createNewFile() - writeText( - """ - plugins { - id("com.android.library") - } - """.trimIndent() - ) - } - - val rootProject = mockk() - val mockGradle = mockk() - val mockLogger = mockk(relaxed = true) - - val appProjectPluginManager = mockk(relaxed = true) - val pluginProjectPluginManager = mockk(relaxed = true) - - val appProject = - createMockSubproject( - tempDir = tempDir, - buildFile = appBuildGradleFile, - projectName = "app", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = appProjectPluginManager - ) - - val pluginProject = - createMockSubproject( - tempDir = tempDir, - buildFile = pluginBuildGradleFile, - projectName = "plugin", - mockLogger = mockLogger, - rootProjectMock = rootProject, - gradleMock = mockGradle, - pluginManager = pluginProjectPluginManager - ) + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1650,8 +1324,8 @@ class FlutterPluginUtilsTest { every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - every { appProjectPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - every { pluginProjectPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") + every { appPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") + every { pluginPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") detectApplyingKotlinGradlePlugin(appProject) From c8ca919d531f81a4c6a87b2cb22c085266264940 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 4 Jun 2026 16:01:57 -0700 Subject: [PATCH 5/8] all necessary unit tests in --- .../src/main/kotlin/FlutterPluginUtils.kt | 4 +- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 218 +++++++++++++++++- 2 files changed, 219 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 4452479fb30e7..1eb9d043bf2bc 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -536,9 +536,9 @@ object FlutterPluginUtils { val gradlePropertiesFile = project.rootProject.file("gradle.properties") val properties = readPropertiesIfExist(gradlePropertiesFile) val isBuiltInKotlinEnabled = - properties.getProperty("android.builtInKotlin")?.toBoolean() ?: false + properties.getProperty("android.builtInKotlin")?.lowercase()?.toBooleanStrictOrNull() - if (isBuiltInKotlinEnabled) { + if (isBuiltInKotlinEnabled != false) { val allSubprojectsDoNotApplyKgp = project.rootProject.subprojects.all { subproject -> val pluginState = getSubprojectPluginState(subproject) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index 0af7edce74a68..bbf3866aa0b13 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -996,6 +996,87 @@ class FlutterPluginUtilsTest { verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } } + @Test + fun `exits early when built-in Kotlin is enabled with all caps TRUE and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=TRUE\n") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + detectApplyingKotlinGradlePlugin(appProject) + + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + } + + @Test + fun `exits early when built-in Kotlin is null and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + detectApplyingKotlinGradlePlugin(appProject) + + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + } + + @Test + fun `exits early when built-in Kotlin is an invalid value and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=5\n") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + detectApplyingKotlinGradlePlugin(appProject) + + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + } + @Test fun `does not exit early when built-in Kotlin is enabled but a subproject applies KGP`( @TempDir tempDir: Path @@ -1037,7 +1118,124 @@ class FlutterPluginUtilsTest { verify { mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + } + + @Test + fun `does not exit early when built-in Kotlin is null but a subproject applies KGP`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + val subprojectsActionSlot = slot>() + val projectsEvaluatedActionSlot = slot>() + + every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit + every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + + detectApplyingKotlinGradlePlugin(appProject) + + verify { rootProject.subprojects(capture(subprojectsActionSlot)) } + subprojectsActionSlot.captured.execute(appProject) + subprojectsActionSlot.captured.execute(pluginProject) + + verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } + projectsEvaluatedActionSlot.captured.execute(mockGradle) + + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } + verify { + mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + } + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + } + + @Test + fun `does not exit early when built-in Kotlin is false`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=false\n") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + val subprojectsActionSlot = slot>() + val projectsEvaluatedActionSlot = slot>() + + every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit + every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + + detectApplyingKotlinGradlePlugin(appProject) + + verify { rootProject.subprojects(capture(subprojectsActionSlot)) } + subprojectsActionSlot.captured.execute(appProject) + subprojectsActionSlot.captured.execute(pluginProject) + + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } + } + + @Test + fun `does not exit early when built-in Kotlin is disabled with all caps FALSE`( + @TempDir tempDir: Path + ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=FALSE\n") + + val (appProject, appPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "app", + plugins = listOf("com.android.application") + ) + val (pluginProject, pluginPluginManager) = createSubproject( + tempDir = tempDir, + projectName = "plugin", + plugins = listOf("com.android.library") + ) + + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + + val subprojectsActionSlot = slot>() + val projectsEvaluatedActionSlot = slot>() + + every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit + every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + + detectApplyingKotlinGradlePlugin(appProject) + + verify { rootProject.subprojects(capture(subprojectsActionSlot)) } + subprojectsActionSlot.captured.execute(appProject) + subprojectsActionSlot.captured.execute(pluginProject) + + verify(exactly = 1) { appPluginManager.apply("kotlin-android") } verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } } @@ -1056,6 +1254,8 @@ class FlutterPluginUtilsTest { plugins = listOf("com.android.library") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1103,6 +1303,8 @@ class FlutterPluginUtilsTest { plugins = listOf("com.android.library", "kotlin-android") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1155,6 +1357,8 @@ class FlutterPluginUtilsTest { plugins = listOf("com.android.library", "kotlin-android") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1219,6 +1423,8 @@ class FlutterPluginUtilsTest { legacyPlugins = listOf("com.android.library", "kotlin-android") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProjectOne, pluginProjectTwo) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1269,6 +1475,9 @@ class FlutterPluginUtilsTest { fun `does not log when migrated to Built-in Kotlin`( @TempDir tempDir: Path ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=false\n") + val (appProject, appPluginManager) = createSubproject( tempDir = tempDir, projectName = "app", @@ -1280,6 +1489,8 @@ class FlutterPluginUtilsTest { legacyPlugins = listOf("com.android.library") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() @@ -1307,6 +1518,9 @@ class FlutterPluginUtilsTest { fun `logs when KGP is applied but fails to apply`( @TempDir tempDir: Path ) { + val rootDir = tempDir.toFile() + writeGradleProperties(rootDir, "android.builtInKotlin=false\n") + val (appProject, appPluginManager) = createSubproject( tempDir = tempDir, projectName = "app", @@ -1318,6 +1532,8 @@ class FlutterPluginUtilsTest { plugins = listOf("com.android.library") ) + every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val subprojectsActionSlot = slot>() val projectsEvaluatedActionSlot = slot>() From 6d019a989d4c056a8a3a424bc2e5a6e190fd1e46 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 4 Jun 2026 16:12:56 -0700 Subject: [PATCH 6/8] refactor --- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 645 +++++++----------- 1 file changed, 238 insertions(+), 407 deletions(-) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index bbf3866aa0b13..c09a41bcbc2f2 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -48,6 +48,23 @@ import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertFalse +private data class SubprojectConfig( + val name: String, + val plugins: List = emptyList(), + val legacyPlugins: List = emptyList() +) + +private class TestEnvironment( + val appProject: Project, + val appPluginManager: PluginManager, + val plugins: List>, + val subprojectsActionSlot: io.mockk.CapturingSlot> = io.mockk.slot(), + val projectsEvaluatedActionSlot: io.mockk.CapturingSlot> = io.mockk.slot() +) { + val firstPluginProject: Project get() = plugins[0].first + val firstPluginManager: PluginManager get() = plugins[0].second +} + class FlutterPluginUtilsTest { companion object { const val EXAMPLE_ENGINE_VERSION = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23" @@ -969,310 +986,232 @@ class FlutterPluginUtilsTest { return Pair(project, pluginManager) } - @Test - fun `exits early when built-in Kotlin is enabled and no subproject applies KGP`( - @TempDir tempDir: Path - ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=true\n") - - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - detectApplyingKotlinGradlePlugin(appProject) - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } - } - - @Test - fun `exits early when built-in Kotlin is enabled with all caps TRUE and no subproject applies KGP`( - @TempDir tempDir: Path - ) { + private fun setupTest( + tempDir: Path, + builtInKotlin: String? = null, + appConfig: SubprojectConfig = SubprojectConfig("app", plugins = listOf("com.android.application")), + pluginConfigs: List = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library"))), + captureActions: Boolean = true + ): TestEnvironment { val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=TRUE\n") + if (builtInKotlin != null) { + writeGradleProperties(rootDir, "android.builtInKotlin=$builtInKotlin\n") + } else { + writeGradleProperties(rootDir, "") + } val (appProject, appPluginManager) = createSubproject( tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") + projectName = appConfig.name, + plugins = appConfig.plugins, + legacyPlugins = appConfig.legacyPlugins ) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - detectApplyingKotlinGradlePlugin(appProject) - - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } - } - - @Test - fun `exits early when built-in Kotlin is null and no subproject applies KGP`( - @TempDir tempDir: Path - ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "") + val pluginProjects = pluginConfigs.map { config -> + createSubproject( + tempDir = tempDir, + projectName = config.name, + plugins = config.plugins, + legacyPlugins = config.legacyPlugins + ) + } - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") - ) + val allProjects = setOf(appProject) + pluginProjects.map { it.first } + every { rootProject.subprojects } returns allProjects - every { rootProject.subprojects } returns setOf(appProject, pluginProject) + val env = TestEnvironment(appProject, appPluginManager, pluginProjects) - detectApplyingKotlinGradlePlugin(appProject) + if (captureActions) { + every { rootProject.subprojects(capture(env.subprojectsActionSlot)) } returns Unit + every { mockGradle.projectsEvaluated(capture(env.projectsEvaluatedActionSlot)) } returns Unit + } - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + return env } - @Test - fun `exits early when built-in Kotlin is an invalid value and no subproject applies KGP`( - @TempDir tempDir: Path - ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=5\n") + private fun executeDetectApplyingKotlinGradlePlugin(env: TestEnvironment) { + detectApplyingKotlinGradlePlugin(env.appProject) - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - detectApplyingKotlinGradlePlugin(appProject) + verify { rootProject.subprojects(capture(env.subprojectsActionSlot)) } + env.subprojectsActionSlot.captured.execute(env.appProject) + for (plugin in env.plugins) { + env.subprojectsActionSlot.captured.execute(plugin.first) + } - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + verify { mockGradle.projectsEvaluated(capture(env.projectsEvaluatedActionSlot)) } + env.projectsEvaluatedActionSlot.captured.execute(mockGradle) } + @Nested + inner class BuiltInKotlinIsEnabled { + @Test + fun `exits early when built-in Kotlin is enabled and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "true", + captureActions = false + ) - @Test - fun `does not exit early when built-in Kotlin is enabled but a subproject applies KGP`( - @TempDir tempDir: Path - ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=true\n") - - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) + detectApplyingKotlinGradlePlugin(env.appProject) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + } - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() + @Test + fun `exits early when built-in Kotlin is enabled with all caps TRUE and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "TRUE", + captureActions = false + ) - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + detectApplyingKotlinGradlePlugin(env.appProject) - detectApplyingKotlinGradlePlugin(appProject) + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + } - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) + @Test + fun `exits early when built-in Kotlin is null and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = null, + captureActions = false + ) - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + detectApplyingKotlinGradlePlugin(env.appProject) - verify(exactly = 0) { - mockLogger.error(match { it.contains("Your Android app project") }) - } - verify { - mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } - } - @Test - fun `does not exit early when built-in Kotlin is null but a subproject applies KGP`( - @TempDir tempDir: Path - ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "") + @Test + fun `exits early when built-in Kotlin is an invalid value and no subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "5", + captureActions = false + ) - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) + detectApplyingKotlinGradlePlugin(env.appProject) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) + verify(exactly = 0) { rootProject.subprojects(any>()) } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + } - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() + @Test + fun `does not exit early when built-in Kotlin is enabled but a subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "true", + pluginConfigs = listOf( + SubprojectConfig( + "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) + ) + ) - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + executeDetectApplyingKotlinGradlePlugin(env) - detectApplyingKotlinGradlePlugin(appProject) + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } + verify { + mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + } - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) + @Test + fun `does not exit early when built-in Kotlin is null but a subproject applies KGP`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = null, + pluginConfigs = listOf( + SubprojectConfig( + "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) + ) + ) - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 0) { - mockLogger.error(match { it.contains("Your Android app project") }) - } - verify { - mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } + verify { + mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } } + @Nested + inner class BuiltInKotlinIsDisabled { @Test fun `does not exit early when built-in Kotlin is false`( @TempDir tempDir: Path ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=false\n") - - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") + builtInKotlin = "false" ) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) + executeDetectApplyingKotlinGradlePlugin(env) - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `does not exit early when built-in Kotlin is disabled with all caps FALSE`( @TempDir tempDir: Path ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=FALSE\n") - - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") + builtInKotlin = "FALSE" ) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + executeDetectApplyingKotlinGradlePlugin(env) - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `logs app warning when KGP is only applied in app`( @TempDir tempDir: Path ) { - val (appProject, appPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application", "kotlin-android") + appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")) ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS @@ -1284,41 +1223,20 @@ class FlutterPluginUtilsTest { verify(exactly = 0) { mockLogger.error(match { it.contains("Your app uses the following plugins") }) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `logs plugin warning when KGP is only applied in one plugin`( @TempDir tempDir: Path ) { - val (appProject, appPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") + pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) verify { mockLogger.error( @@ -1338,44 +1256,24 @@ class FlutterPluginUtilsTest { verify(exactly = 0) { mockLogger.error(match { it.contains("Your Android app project") }) } - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `logs app and plugin warning when KGP is applied in both app and plugins`( @TempDir tempDir: Path ) { - val (appProject, appPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application", "kotlin-android") + appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")), + pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS @@ -1399,50 +1297,27 @@ class FlutterPluginUtilsTest { ) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `logs app and plugin warning when legacy KGP configuration is applied in both app and plugins`( @TempDir tempDir: Path ) { - val (appProject, appPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "app", - legacyPlugins = listOf("com.android.application", "kotlin-android") - ) - val (pluginProjectOne, pluginProjectOnePluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin1", - legacyPlugins = listOf("com.android.library", "kotlin-android") - ) - val (pluginProjectTwo, pluginProjectTwoPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin2", - legacyPlugins = listOf("com.android.library", "kotlin-android") + appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application", "kotlin-android")), + pluginConfigs = listOf( + SubprojectConfig("plugin1", legacyPlugins = listOf("com.android.library", "kotlin-android")), + SubprojectConfig("plugin2", legacyPlugins = listOf("com.android.library", "kotlin-android")) + ) ) - every { rootProject.subprojects } returns setOf(appProject, pluginProjectOne, pluginProjectTwo) - - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProjectOne) - subprojectsActionSlot.captured.execute(pluginProjectTwo) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${appProject.buildFile.absolutePath} + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS @@ -1466,91 +1341,46 @@ class FlutterPluginUtilsTest { ) } - verify(exactly = 0) { appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginProjectOnePluginManager.apply("kotlin-android") } - verify(exactly = 0) { pluginProjectTwoPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + for (plugin in env.plugins) { + verify(exactly = 0) { plugin.second.apply("kotlin-android") } + } } @Test fun `does not log when migrated to Built-in Kotlin`( @TempDir tempDir: Path ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=false\n") - - val (appProject, appPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "app", - legacyPlugins = listOf("com.android.application") + builtInKotlin = "false", + appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application")), + pluginConfigs = listOf(SubprojectConfig("plugin", legacyPlugins = listOf("com.android.library"))) ) - val (pluginProject, pluginPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "plugin", - legacyPlugins = listOf("com.android.library") - ) - - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) verify(exactly = 0) { mockLogger.error(any()) } - verify(exactly = 1) { appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { pluginPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test fun `logs when KGP is applied but fails to apply`( @TempDir tempDir: Path ) { - val rootDir = tempDir.toFile() - writeGradleProperties(rootDir, "android.builtInKotlin=false\n") - - val (appProject, appPluginManager) = createSubproject( - tempDir = tempDir, - projectName = "app", - plugins = listOf("com.android.application") - ) - val (pluginProject, pluginPluginManager) = createSubproject( + val env = setupTest( tempDir = tempDir, - projectName = "plugin", - plugins = listOf("com.android.library") + builtInKotlin = "false" ) - every { rootProject.subprojects } returns setOf(appProject, pluginProject) - - val subprojectsActionSlot = slot>() - val projectsEvaluatedActionSlot = slot>() - - every { rootProject.subprojects(capture(subprojectsActionSlot)) } returns Unit - every { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } returns Unit + every { env.appPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") + every { env.firstPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - every { appPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - every { pluginPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - - detectApplyingKotlinGradlePlugin(appProject) - - verify { rootProject.subprojects(capture(subprojectsActionSlot)) } - subprojectsActionSlot.captured.execute(appProject) - subprojectsActionSlot.captured.execute(pluginProject) - - verify { mockGradle.projectsEvaluated(capture(projectsEvaluatedActionSlot)) } - projectsEvaluatedActionSlot.captured.execute(mockGradle) + executeDetectApplyingKotlinGradlePlugin(env) verify(exactly = 0) { mockLogger.error(any()) @@ -1568,28 +1398,29 @@ class FlutterPluginUtilsTest { } } - private fun createMockSubproject( - tempDir: Path, - buildFile: File, - projectName: String, - mockLogger: Logger, - rootProjectMock: Project? = null, - gradleMock: Gradle? = null, - pluginManager: PluginManager - ): Project { - val projectDir = tempDir.resolve(projectName).toFile().apply { mkdirs() } - - val project = mockk() - every { project.name } returns projectName - every { project.projectDir } returns projectDir - every { project.buildFile } returns buildFile - every { project.logger } returns mockLogger - every { project.pluginManager } returns pluginManager - - if (rootProjectMock != null) every { project.rootProject } returns rootProjectMock - if (gradleMock != null) every { project.gradle } returns gradleMock - - return project + private fun createMockSubproject( + tempDir: Path, + buildFile: File, + projectName: String, + mockLogger: Logger, + rootProjectMock: Project? = null, + gradleMock: Gradle? = null, + pluginManager: PluginManager + ): Project { + val projectDir = tempDir.resolve(projectName).toFile().apply { mkdirs() } + + val project = mockk() + every { project.name } returns projectName + every { project.projectDir } returns projectDir + every { project.buildFile } returns buildFile + every { project.logger } returns mockLogger + every { project.pluginManager } returns pluginManager + + if (rootProjectMock != null) every { project.rootProject } returns rootProjectMock + if (gradleMock != null) every { project.gradle } returns gradleMock + + return project + } } } From bfc5bc215fa81bafcc5659b090fc8dc5bb132760 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 4 Jun 2026 16:31:55 -0700 Subject: [PATCH 7/8] some more --- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 378 +++++++++--------- 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index c09a41bcbc2f2..bb28c3eb5ab11 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -1045,7 +1045,7 @@ class FlutterPluginUtilsTest { @Nested inner class BuiltInKotlinIsEnabled { @Test - fun `exits early when built-in Kotlin is enabled and no subproject applies KGP`( + fun `exits early when no subproject applies KGP and property is true`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1062,7 +1062,7 @@ class FlutterPluginUtilsTest { } @Test - fun `exits early when built-in Kotlin is enabled with all caps TRUE and no subproject applies KGP`( + fun `exits early when no subproject applies KGP and property is TRUE`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1079,7 +1079,7 @@ class FlutterPluginUtilsTest { } @Test - fun `exits early when built-in Kotlin is null and no subproject applies KGP`( + fun `exits early when no subproject applies KGP and property is null`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1096,7 +1096,7 @@ class FlutterPluginUtilsTest { } @Test - fun `exits early when built-in Kotlin is an invalid value and no subproject applies KGP`( + fun `exits early when no subproject applies KGP and property is invalid`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1113,7 +1113,7 @@ class FlutterPluginUtilsTest { } @Test - fun `does not exit early when built-in Kotlin is enabled but a subproject applies KGP`( + fun `does not exit early when a subproject applies KGP and property is true`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1140,7 +1140,7 @@ class FlutterPluginUtilsTest { } @Test - fun `does not exit early when built-in Kotlin is null but a subproject applies KGP`( + fun `does not exit early when a subproject applies KGP and property is null`( @TempDir tempDir: Path ) { val env = setupTest( @@ -1165,238 +1165,238 @@ class FlutterPluginUtilsTest { verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } - } - @Nested - inner class BuiltInKotlinIsDisabled { - @Test - fun `does not exit early when built-in Kotlin is false`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "false" - ) + @Test + fun `logs app warning when KGP is only applied in app`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")) + ) - executeDetectApplyingKotlinGradlePlugin(env) + executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - @Test - fun `does not exit early when built-in Kotlin is disabled with all caps FALSE`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "FALSE" - ) + """.trimIndent() + verify { + mockLogger.error(expectedAppWarning) + } - executeDetectApplyingKotlinGradlePlugin(env) + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your app uses the following plugins") }) + } + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } + } - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } + @Test + fun `logs plugin warning when KGP is only applied in one plugin`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) + ) - @Test - fun `logs app warning when KGP is only applied in app`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")) - ) + executeDetectApplyingKotlinGradlePlugin(env) - executeDetectApplyingKotlinGradlePlugin(env) + verify { + mockLogger.error( + """ + WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin + Future versions of Flutter will fail to build if your app uses plugins that apply KGP. - val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. + If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing + an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS - """.trimIndent() - verify { - mockLogger.error(expectedAppWarning) - } + If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS + """.trimIndent() + ) + } - verify(exactly = 0) { - mockLogger.error(match { it.contains("Your app uses the following plugins") }) + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } - verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } - - @Test - fun `logs plugin warning when KGP is only applied in one plugin`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) - ) - executeDetectApplyingKotlinGradlePlugin(env) + @Test + fun `logs app and plugin warning when KGP is applied in both app and plugins`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")), + pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) + ) - verify { - mockLogger.error( - """ - WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin - Future versions of Flutter will fail to build if your app uses plugins that apply KGP. + executeDetectApplyingKotlinGradlePlugin(env) - Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. - If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing - an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS """.trimIndent() - ) - } - - verify(exactly = 0) { - mockLogger.error(match { it.contains("Your Android app project") }) - } - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } - } + verify { + mockLogger.error(expectedAppWarning) + } - @Test - fun `logs app and plugin warning when KGP is applied in both app and plugins`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")), - pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) - ) + verify { + mockLogger.error( + """ + WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin + Future versions of Flutter will fail to build if your app uses plugins that apply KGP. - executeDetectApplyingKotlinGradlePlugin(env) + Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. + If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing + an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS - val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS + """.trimIndent() + ) + } - """.trimIndent() - verify { - mockLogger.error(expectedAppWarning) + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } - verify { - mockLogger.error( - """ - WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin - Future versions of Flutter will fail to build if your app uses plugins that apply KGP. + @Test + fun `logs app and plugin warning when legacy KGP configuration is applied in both app and plugins`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application", "kotlin-android")), + pluginConfigs = listOf( + SubprojectConfig("plugin1", legacyPlugins = listOf("com.android.library", "kotlin-android")), + SubprojectConfig("plugin2", legacyPlugins = listOf("com.android.library", "kotlin-android")) + ) + ) - Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. - If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing - an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS + executeDetectApplyingKotlinGradlePlugin(env) + + val expectedAppWarning = """ + WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} + applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. + Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS - If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS """.trimIndent() - ) - } + verify { + mockLogger.error(expectedAppWarning) + } + + verify { + mockLogger.error( + """ + WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin1, plugin2 + Future versions of Flutter will fail to build if your app uses plugins that apply KGP. - verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. + If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing + an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS + + If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS + """.trimIndent() + ) + } + + verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + for (plugin in env.plugins) { + verify(exactly = 0) { plugin.second.apply("kotlin-android") } + } + } } - @Test - fun `logs app and plugin warning when legacy KGP configuration is applied in both app and plugins`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application", "kotlin-android")), - pluginConfigs = listOf( - SubprojectConfig("plugin1", legacyPlugins = listOf("com.android.library", "kotlin-android")), - SubprojectConfig("plugin2", legacyPlugins = listOf("com.android.library", "kotlin-android")) + @Nested + inner class BuiltInKotlinIsDisabled { + @Test + fun `does not exit early when property is false`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "false" ) - ) - - executeDetectApplyingKotlinGradlePlugin(env) - val expectedAppWarning = """ - WARNING: Your Android app project: app located at: ${env.appProject.buildFile.absolutePath} - applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. - Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS + executeDetectApplyingKotlinGradlePlugin(env) - """.trimIndent() - verify { - mockLogger.error(expectedAppWarning) + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } - verify { - mockLogger.error( - """ - WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin1, plugin2 - Future versions of Flutter will fail to build if your app uses plugins that apply KGP. + @Test + fun `does not exit early when property is FALSE`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "FALSE" + ) - Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. - If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing - an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS + executeDetectApplyingKotlinGradlePlugin(env) - If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS - """.trimIndent() - ) + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } - verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } - for (plugin in env.plugins) { - verify(exactly = 0) { plugin.second.apply("kotlin-android") } - } - } + @Test + fun `does not log when migrated to Built-in Kotlin`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "false", + appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application")), + pluginConfigs = listOf(SubprojectConfig("plugin", legacyPlugins = listOf("com.android.library"))) + ) - @Test - fun `does not log when migrated to Built-in Kotlin`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "false", - appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application")), - pluginConfigs = listOf(SubprojectConfig("plugin", legacyPlugins = listOf("com.android.library"))) - ) + executeDetectApplyingKotlinGradlePlugin(env) - executeDetectApplyingKotlinGradlePlugin(env) + verify(exactly = 0) { + mockLogger.error(any()) + } - verify(exactly = 0) { - mockLogger.error(any()) + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } - - @Test - fun `logs when KGP is applied but fails to apply`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "false" - ) + @Test + fun `logs quiet warning when KGP application fails`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "false" + ) - every { env.appPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - every { env.firstPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") + every { env.appPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") + every { env.firstPluginManager.apply("kotlin-android") } throws Exception("KGP not on classpath") - executeDetectApplyingKotlinGradlePlugin(env) + executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 0) { - mockLogger.error(any()) - } + verify(exactly = 0) { + mockLogger.error(any()) + } - verify { - mockLogger.quiet( - """ - Applying the Kotlin Android Plugin (KGP) was unsuccessful. KGP was not found on the classpath. - If your project uses Kotlin, ensure KGP is declared in the root plugins block. - For more details check: $BUILT_IN_KOTLIN_DOCS - """.trimIndent() - ) + verify { + mockLogger.quiet( + """ + Applying the Kotlin Android Plugin (KGP) was unsuccessful. KGP was not found on the classpath. + If your project uses Kotlin, ensure KGP is declared in the root plugins block. + For more details check: $BUILT_IN_KOTLIN_DOCS + """.trimIndent() + ) + } } } - } private fun createMockSubproject( tempDir: Path, From 2220ed80c12db3b2e6ac86a5e132fd3e50366d25 Mon Sep 17 00:00:00 2001 From: jesswrd Date: Fri, 5 Jun 2026 12:07:11 -0700 Subject: [PATCH 8/8] null is not possible due to migrator. --- .../src/main/kotlin/FlutterPluginUtils.kt | 4 +- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 147 +++++++----------- 2 files changed, 54 insertions(+), 97 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 1eb9d043bf2bc..62be799b3371e 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -536,9 +536,9 @@ object FlutterPluginUtils { val gradlePropertiesFile = project.rootProject.file("gradle.properties") val properties = readPropertiesIfExist(gradlePropertiesFile) val isBuiltInKotlinEnabled = - properties.getProperty("android.builtInKotlin")?.lowercase()?.toBooleanStrictOrNull() + properties.getProperty("android.builtInKotlin")?.lowercase()?.toBoolean() ?: false - if (isBuiltInKotlinEnabled != false) { + if (isBuiltInKotlinEnabled) { val allSubprojectsDoNotApplyKgp = project.rootProject.subprojects.all { subproject -> val pluginState = getSubprojectPluginState(subproject) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index bb28c3eb5ab11..8bc0d8cedf978 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -1079,91 +1079,98 @@ class FlutterPluginUtilsTest { } @Test - fun `exits early when no subproject applies KGP and property is null`( + fun `does not exit early when a subproject applies KGP and property is true`( @TempDir tempDir: Path ) { val env = setupTest( tempDir = tempDir, - builtInKotlin = null, - captureActions = false + builtInKotlin = "true", + pluginConfigs = listOf( + SubprojectConfig( + "plugin", + plugins = listOf("com.android.library", "kotlin-android") + ) + ) ) - detectApplyingKotlinGradlePlugin(env.appProject) + executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 0) { + mockLogger.error(match { it.contains("Your Android app project") }) + } + verify { + mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } + } + @Nested + inner class BuiltInKotlinIsDisabled { @Test - fun `exits early when no subproject applies KGP and property is invalid`( + fun `does not exit early when property is false`( @TempDir tempDir: Path ) { val env = setupTest( tempDir = tempDir, - builtInKotlin = "5", - captureActions = false + builtInKotlin = "false" ) - detectApplyingKotlinGradlePlugin(env.appProject) + executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 0) { rootProject.subprojects(any>()) } - verify(exactly = 0) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test - fun `does not exit early when a subproject applies KGP and property is true`( + fun `does not exit early when property is FALSE`( @TempDir tempDir: Path ) { val env = setupTest( tempDir = tempDir, - builtInKotlin = "true", - pluginConfigs = listOf( - SubprojectConfig( - "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) - ) + builtInKotlin = "FALSE" ) executeDetectApplyingKotlinGradlePlugin(env) - verify(exactly = 0) { - mockLogger.error(match { it.contains("Your Android app project") }) - } - verify { - mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) - } verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } } @Test - fun `does not exit early when a subproject applies KGP and property is null`( + fun `does not exit early when property is invalid`( @TempDir tempDir: Path ) { val env = setupTest( tempDir = tempDir, - builtInKotlin = null, - pluginConfigs = listOf( - SubprojectConfig( - "plugin", - plugins = listOf("com.android.library", "kotlin-android") - ) - ) + builtInKotlin = "5" + ) + + executeDetectApplyingKotlinGradlePlugin(env) + + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } + } + + @Test + fun `does not log when migrated to Built-in Kotlin`( + @TempDir tempDir: Path + ) { + val env = setupTest( + tempDir = tempDir, + builtInKotlin = "false", + appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application")), + pluginConfigs = listOf(SubprojectConfig("plugin", legacyPlugins = listOf("com.android.library"))) ) executeDetectApplyingKotlinGradlePlugin(env) verify(exactly = 0) { - mockLogger.error(match { it.contains("Your Android app project") }) - } - verify { - mockLogger.error(match { it.contains("Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): plugin") }) + mockLogger.error(any()) } + verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 0) { env.firstPluginManager.apply("kotlin-android") } + verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } } @Test @@ -1172,6 +1179,7 @@ class FlutterPluginUtilsTest { ) { val env = setupTest( tempDir = tempDir, + builtInKotlin = "false", appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")) ) @@ -1200,6 +1208,7 @@ class FlutterPluginUtilsTest { ) { val env = setupTest( tempDir = tempDir, + builtInKotlin = "false", pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) ) @@ -1233,6 +1242,7 @@ class FlutterPluginUtilsTest { ) { val env = setupTest( tempDir = tempDir, + builtInKotlin = "false", appConfig = SubprojectConfig("app", plugins = listOf("com.android.application", "kotlin-android")), pluginConfigs = listOf(SubprojectConfig("plugin", plugins = listOf("com.android.library", "kotlin-android"))) ) @@ -1274,6 +1284,7 @@ class FlutterPluginUtilsTest { ) { val env = setupTest( tempDir = tempDir, + builtInKotlin = "false", appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application", "kotlin-android")), pluginConfigs = listOf( SubprojectConfig("plugin1", legacyPlugins = listOf("com.android.library", "kotlin-android")), @@ -1313,60 +1324,6 @@ class FlutterPluginUtilsTest { verify(exactly = 0) { plugin.second.apply("kotlin-android") } } } - } - - @Nested - inner class BuiltInKotlinIsDisabled { - @Test - fun `does not exit early when property is false`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "false" - ) - - executeDetectApplyingKotlinGradlePlugin(env) - - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } - - @Test - fun `does not exit early when property is FALSE`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "FALSE" - ) - - executeDetectApplyingKotlinGradlePlugin(env) - - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } - - @Test - fun `does not log when migrated to Built-in Kotlin`( - @TempDir tempDir: Path - ) { - val env = setupTest( - tempDir = tempDir, - builtInKotlin = "false", - appConfig = SubprojectConfig("app", legacyPlugins = listOf("com.android.application")), - pluginConfigs = listOf(SubprojectConfig("plugin", legacyPlugins = listOf("com.android.library"))) - ) - - executeDetectApplyingKotlinGradlePlugin(env) - - verify(exactly = 0) { - mockLogger.error(any()) - } - - verify(exactly = 1) { env.appPluginManager.apply("kotlin-android") } - verify(exactly = 1) { env.firstPluginManager.apply("kotlin-android") } - } @Test fun `logs quiet warning when KGP application fails`(