[Gradle][MPP] KMP <-> AGP compatibility check: Restrict AGP minor version

^KT-53138 Verification Pending
This commit is contained in:
sebastian.sellmair
2022-07-21 09:19:34 +02:00
committed by Space
parent 1b0b09dd83
commit 2143180b40
2 changed files with 207 additions and 45 deletions
@@ -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<AndroidGradlePluginVersion> {
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<String>() }.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")
}
}
@@ -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()