From 2143180b40de7b8e6891e619e19eb341a38fff30 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Thu, 21 Jul 2022 09:19:34 +0200 Subject: [PATCH] [Gradle][MPP] KMP <-> AGP compatibility check: Restrict AGP minor version ^KT-53138 Verification Pending --- ...oidGradlePluginCompatibilityHealthCheck.kt | 75 +++++++- ...radlePluginCompatibilityHealthCheckTest.kt | 177 ++++++++++++++---- 2 files changed, 207 insertions(+), 45 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.kt index aa3d271b6d4..fed0a870878 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.kt @@ -17,20 +17,67 @@ import java.util.concurrent.atomic.AtomicBoolean internal object KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck { - const val MIN_SUPPORTED_AGP_MAJOR_VERSION = 4 - const val MAX_SUPPORTED_AGP_MAJOR_VERSION = 7 + val compatibleAndroidGradlePluginVersionRange = AndroidGradlePluginVersionRange( + minSupportedVersion = AndroidGradlePluginVersion(4, 0), + maxSupportedVersion = AndroidGradlePluginVersion(7, 2) + ) /** * Used to store previously emitted messages in the build */ - const val PROPERTY_KEY_EMITTED_MESSAGES = "KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.emittedMessages" + private const val PROPERTY_KEY_EMITTED_MESSAGES = "KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.emittedMessages" /** * Used to store project paths that executed this health check */ const val PROPERTY_KEY_EXECUTED_PROJECT_PATHS = "KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.executedProjects" - data class AndroidGradlePluginVersion(val major: Int, val minor: Int) + data class AndroidGradlePluginVersion(val major: Int, val minor: Int) : Comparable { + override fun compareTo(other: AndroidGradlePluginVersion): Int { + if (this.major != other.major) return this.major.compareTo(other.major) + return this.minor.compareTo(other.minor) + } + + override fun toString(): String { + return "$major.$minor" + } + } + + data class AndroidGradlePluginVersionRange( + val minSupportedVersion: AndroidGradlePluginVersion, + val maxSupportedVersionMajor: Int, + val maxSupportedVersionMinor: Int? + ) { + constructor( + minSupportedVersionMajor: Int, + minSupportedVersionMinor: Int, + maxSupportedVersionMajor: Int, + maxSupportedVersionMinor: Int? = null + ) : this( + minSupportedVersion = AndroidGradlePluginVersion(minSupportedVersionMajor, minSupportedVersionMinor), + maxSupportedVersionMajor = maxSupportedVersionMajor, + maxSupportedVersionMinor = maxSupportedVersionMinor + ) + + constructor( + minSupportedVersion: AndroidGradlePluginVersion, + maxSupportedVersion: AndroidGradlePluginVersion + ) : this( + minSupportedVersion = minSupportedVersion, + maxSupportedVersionMajor = maxSupportedVersion.major, + maxSupportedVersionMinor = maxSupportedVersion.minor + ) + + fun isTooHigh(version: AndroidGradlePluginVersion): Boolean { + return if (maxSupportedVersionMinor != null) + version > AndroidGradlePluginVersion(maxSupportedVersionMajor, maxSupportedVersionMinor) + else version.major > maxSupportedVersionMajor + } + + fun isTooLow(version: AndroidGradlePluginVersion): Boolean { + return version < minSupportedVersion + } + } interface AndroidGradlePluginStringProvider { fun getAndroidGradlePluginString(): String? @@ -84,7 +131,9 @@ internal object KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck { fun Project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( warningLogger: (warningMessage: String) -> Unit = project.logger::warn, androidGradlePluginStringProvider: AndroidGradlePluginStringProvider = AndroidGradlePluginStringProvider.Default, - androidGradlePluginVersionParser: AndroidGradlePluginVersionParser = AndroidGradlePluginVersionParser.Default + androidGradlePluginVersionParser: AndroidGradlePluginVersionParser = AndroidGradlePluginVersionParser.Default, + compatibleAndroidGradlePluginVersionRange: AndroidGradlePluginVersionRange = + KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.compatibleAndroidGradlePluginVersionRange ) = runProjectConfigurationHealthCheck check@{ if (project.kotlinPropertiesProvider.ignoreAndroidGradlePluginCompatibilityIssues) return@check getOrPutRootProjectProperty(PROPERTY_KEY_EXECUTED_PROJECT_PATHS) { mutableSetOf() }.add(path) @@ -99,11 +148,11 @@ internal object KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck { ?: return warningLogger(Messages.failedParsingAndroidGradlePluginVersion(androidGradlePluginVersionString)) - if (androidGradlePluginVersion.major < MIN_SUPPORTED_AGP_MAJOR_VERSION) { + if (compatibleAndroidGradlePluginVersionRange.isTooLow(androidGradlePluginVersion)) { warningLogger(Messages.androidGradlePluginVersionTooLow(androidGradlePluginVersionString)) } - if (androidGradlePluginVersion.major > MAX_SUPPORTED_AGP_MAJOR_VERSION) { + if (compatibleAndroidGradlePluginVersionRange.isTooHigh(androidGradlePluginVersion)) { warningLogger(Messages.androidGradlePluginVersionTooHigh(androidGradlePluginVersionString)) } } @@ -124,8 +173,16 @@ internal object KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck { private fun createCompatibilityWarningMessage(warning: String) = buildString { appendLine("w: Kotlin Multiplatform <-> Android Gradle Plugin compatibility issue: $warning") - appendLine("Minimum supported Android Gradle Plugin version: $MIN_SUPPORTED_AGP_MAJOR_VERSION.0") - appendLine("Maximum supported Android Gradle Plugin version: $MAX_SUPPORTED_AGP_MAJOR_VERSION.+") + appendLine( + "Minimum supported Android Gradle Plugin version: " + + "${compatibleAndroidGradlePluginVersionRange.minSupportedVersion.major}." + + "${compatibleAndroidGradlePluginVersionRange.minSupportedVersion.minor}" + ) + appendLine( + "Maximum supported Android Gradle Plugin version: " + + "${compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMajor}." + + "${compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMinor ?: "*"}" + ) appendLine("To suppress this message add $KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN=true to your gradle.properties") } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest.kt index c58e7344f81..d4dcc2fd061 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("FunctionName") +@file:Suppress("FunctionName", "DuplicatedCode") package org.jetbrains.kotlin.gradle @@ -15,16 +15,18 @@ import org.jetbrains.kotlin.gradle.mpp.kotlin import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.AndroidGradlePluginStringProvider import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.AndroidGradlePluginVersion import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.AndroidGradlePluginVersionParser -import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.MAX_SUPPORTED_AGP_MAJOR_VERSION -import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.MIN_SUPPORTED_AGP_MAJOR_VERSION +import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.AndroidGradlePluginVersionRange import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.Messages import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.PROPERTY_KEY_EXECUTED_PROJECT_PATHS +import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.compatibleAndroidGradlePluginVersionRange import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheck.runMultiplatformAndroidGradlePluginCompatibilityHealthCheckWhenAndroidIsApplied import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.utils.getOrPutRootProjectProperty import org.junit.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue import kotlin.test.fail class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { @@ -48,29 +50,72 @@ class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { } @Test - fun `test - version too low`() { - val androidGradlePluginVersion = "${MIN_SUPPORTED_AGP_MAJOR_VERSION - 1}.0" - - project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( - testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion) + fun `test - version too low - major`() { + val androidGradlePluginVersion = AndroidGradlePluginVersion( + major = compatibleAndroidGradlePluginVersionRange.minSupportedVersion.major - 1, + minor = compatibleAndroidGradlePluginVersionRange.minSupportedVersion.minor ) - assertEquals(Messages.androidGradlePluginVersionTooLow(androidGradlePluginVersion), assertSingleWarningMessage()) + project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( + testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion.toString()) + ) + + assertEquals(Messages.androidGradlePluginVersionTooLow(androidGradlePluginVersion.toString()), assertSingleWarningMessage()) } @Test - fun `test - version too high`() { - val androidGradlePluginVersion = "${MAX_SUPPORTED_AGP_MAJOR_VERSION + 1}.0" + fun `test - version too low - minor`() { + val androidGradlePluginVersion = AndroidGradlePluginVersion(1, 1) + + val compatibleAndroidGradlePluginVersionRange = AndroidGradlePluginVersionRange( + minSupportedVersionMajor = 1, minSupportedVersionMinor = 2, + maxSupportedVersionMajor = 2 + ) project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( - testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion) + testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion.toString()), + compatibleAndroidGradlePluginVersionRange = compatibleAndroidGradlePluginVersionRange + ) + + assertEquals(Messages.androidGradlePluginVersionTooLow(androidGradlePluginVersion.toString()), assertSingleWarningMessage()) + } + + @Test + fun `test - version too high - major`() { + val androidGradlePluginVersion = AndroidGradlePluginVersion( + major = compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMajor + 1, + minor = compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMinor ?: 0 + ) + + project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( + testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion.toString()) ) assertEquals( - Messages.androidGradlePluginVersionTooHigh(androidGradlePluginVersion), assertSingleWarningMessage() + Messages.androidGradlePluginVersionTooHigh(androidGradlePluginVersion.toString()), assertSingleWarningMessage() ) } + @Test + fun `test - version too high - minor`() { + val androidGradlePluginVersion = AndroidGradlePluginVersion(2, 1) + + val compatibleAndroidGradlePluginVersionRange = AndroidGradlePluginVersionRange( + minSupportedVersion = AndroidGradlePluginVersion(1, 0), + maxSupportedVersion = AndroidGradlePluginVersion(2, 0) + ) + + project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( + testLogger, FixedAndroidGradlePluginVersionProvider(androidGradlePluginVersion.toString()), + compatibleAndroidGradlePluginVersionRange = compatibleAndroidGradlePluginVersionRange + ) + + assertEquals( + Messages.androidGradlePluginVersionTooHigh(androidGradlePluginVersion.toString()), assertSingleWarningMessage() + ) + } + + @Test fun `test - missing Android Gradle Plugin version string`() { project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( @@ -84,7 +129,7 @@ class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { @Test fun `test - failed parsing Android Gradle Plugin version`() { - val androidGradlePluginVersion = "${MIN_SUPPORTED_AGP_MAJOR_VERSION}.0" + val androidGradlePluginVersion = compatibleAndroidGradlePluginVersionRange.minSupportedVersion.toString() val failingParser = object : AndroidGradlePluginVersionParser { override fun parseVersionString(version: String): AndroidGradlePluginVersion? { @@ -109,13 +154,14 @@ class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { /* Test with too low AGP version */ project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( - testLogger, FixedAndroidGradlePluginVersionProvider("${MIN_SUPPORTED_AGP_MAJOR_VERSION - 1}.0") + testLogger, FixedAndroidGradlePluginVersionProvider("${compatibleAndroidGradlePluginVersionRange.minSupportedVersion}") ) assertNoWarningMessage() /* Test with too high AGP version */ project.runMultiplatformAndroidGradlePluginCompatibilityHealthCheck( - testLogger, FixedAndroidGradlePluginVersionProvider("${MAX_SUPPORTED_AGP_MAJOR_VERSION + 1}.0") + testLogger, + FixedAndroidGradlePluginVersionProvider("${compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMajor + 1}.0") ) assertNoWarningMessage() @@ -134,31 +180,35 @@ class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { assertNoWarningMessage() } - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0.0") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0.0-alpha01") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0.0-m-281") + val minSupportedVersion = compatibleAndroidGradlePluginVersionRange.minSupportedVersion - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.1") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.1.0") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.1.0-alpha01") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.1.0-m-281") + val midSupportedVersion = minSupportedVersion.copy(minor = minSupportedVersion.minor + 1) - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0.1") - assertNoWarnings("$MIN_SUPPORTED_AGP_MAJOR_VERSION.0.1-alpha01") + val maxSupportedVersion = AndroidGradlePluginVersion( + compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMajor, + compatibleAndroidGradlePluginVersionRange.maxSupportedVersionMinor ?: 0 + ) - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0.0") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0.0-alpha01") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0.0-m-281") + assertNoWarnings("$minSupportedVersion") + assertNoWarnings("$minSupportedVersion.0") + assertNoWarnings("$minSupportedVersion.0-alpha01") + assertNoWarnings("$minSupportedVersion.0-m-281") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.1") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.1.0") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.1.0-alpha01") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.1.0-m-281") + assertNoWarnings("$midSupportedVersion") + assertNoWarnings("$midSupportedVersion.0") + assertNoWarnings("$midSupportedVersion.0-alpha01") + assertNoWarnings("$midSupportedVersion.0-m-281") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0.1") - assertNoWarnings("$MAX_SUPPORTED_AGP_MAJOR_VERSION.0.1-alpha01") + assertNoWarnings("$minSupportedVersion.1") + assertNoWarnings("$minSupportedVersion.1-alpha01") + + assertNoWarnings("$maxSupportedVersion") + assertNoWarnings("$maxSupportedVersion.0") + assertNoWarnings("$maxSupportedVersion.0-alpha01") + assertNoWarnings("$maxSupportedVersion.0-m-281") + + assertNoWarnings("$maxSupportedVersion.1") + assertNoWarnings("$maxSupportedVersion.1-alpha01") } @Test @@ -234,6 +284,61 @@ class KotlinMultiplatformAndroidGradlePluginCompatibilityHealthCheckTest { if (executedProjectPaths.isNotEmpty()) fail("Expected 'executed' project paths to be empty") } + @Test + fun `test - AndroidGradlePluginVersion - compareTo`() { + assertEquals(0, AndroidGradlePluginVersion(1, 0).compareTo(AndroidGradlePluginVersion(1, 0))) + assertTrue(AndroidGradlePluginVersion(1, 0) < AndroidGradlePluginVersion(2, 0)) + assertTrue(AndroidGradlePluginVersion(2, 0) > AndroidGradlePluginVersion(1, 0)) + assertTrue(AndroidGradlePluginVersion(2, 0) > AndroidGradlePluginVersion(1, 3)) + + assertTrue(AndroidGradlePluginVersion(1, 0) < AndroidGradlePluginVersion(1, 1)) + assertTrue(AndroidGradlePluginVersion(1, 1) > AndroidGradlePluginVersion(1, 0)) + } + + @Test + fun `test - AndroidGradlePluginRange`() { + /* Run test with specified maxSupportedVersionMinor */ + AndroidGradlePluginVersionRange( + minSupportedVersionMajor = 2, minSupportedVersionMinor = 1, + maxSupportedVersionMajor = 4, maxSupportedVersionMinor = 2 + ).apply { + assertFalse(isTooHigh(AndroidGradlePluginVersion(1, 0))) + assertTrue(isTooLow(AndroidGradlePluginVersion(1, 0))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(2, 1))) + assertFalse(isTooHigh(AndroidGradlePluginVersion(2, 1))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(3, 0))) + assertFalse(isTooHigh(AndroidGradlePluginVersion(3, 0))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(4, 3))) + assertTrue(isTooHigh(AndroidGradlePluginVersion(4, 3))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(5, 0))) + assertTrue(isTooHigh(AndroidGradlePluginVersion(5, 0))) + } + + /* Run test with *un*-specified maxSupportedVersionMinor */ + AndroidGradlePluginVersionRange( + minSupportedVersionMajor = 2, minSupportedVersionMinor = 1, + maxSupportedVersionMajor = 4 + ).apply { + assertFalse(isTooHigh(AndroidGradlePluginVersion(1, 0))) + assertTrue(isTooLow(AndroidGradlePluginVersion(1, 0))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(2, 1))) + assertFalse(isTooHigh(AndroidGradlePluginVersion(2, 1))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(3, 0))) + assertFalse(isTooHigh(AndroidGradlePluginVersion(3, 0))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(4, 3))) + assertFalse(isTooHigh(AndroidGradlePluginVersion(4, 3))) + + assertFalse(isTooLow(AndroidGradlePluginVersion(5, 0))) + } + } + private fun assertSingleWarningMessage(): String { if (testLogger.messages.size != 1) fail("Expected exactly one warning message logged. Found: ${testLogger.messages}") return testLogger.messages.single()