From d3f7cb40fb9e757a5e368df8a96c999365575d97 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 13:31:21 +0200 Subject: [PATCH 1/8] feat(matrix): Fetch AGP<->Kotlin compat dynamically Replace the static gradleToKotlin dict with a fetch of the AGP/Kotlin compatibility table from developer.android.com/build/kotlin-support. For each AGP version we now pick the highest Kotlin whose required AGP is <= ours, which is a more direct semantic fit and also catches new Kotlin minors automatically. The existing Kotlin->min-Gradle floor check (sourced from kotlinlang.org) stays in place. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/generate-compat-matrix.main.kts | 84 +++++++++++++++++++------ 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/scripts/generate-compat-matrix.main.kts b/scripts/generate-compat-matrix.main.kts index 03f888f3..08a2006c 100755 --- a/scripts/generate-compat-matrix.main.kts +++ b/scripts/generate-compat-matrix.main.kts @@ -85,29 +85,35 @@ class GenerateMatrix : CliktCommand() { throw ProgramResult(1) } - // TODO: for now this is manual, but we could try get it from Gradle's github in the future - val gradleToKotlin = - mapOf( - "7.5".toVersion(strict = false) to "1.8.20", - "9.0.0".toVersion(strict = false) to "2.1.0", - "9.5.0-0".toVersion(strict = false) to "2.3.0", - ) - // TODO: make it dynamic too - val kotlinVersion = "2.1.0".toVersion() + val agpToKotlin = + try { + fetchAgpKotlinCompatibility() + } catch (e: Exception) { + print(e.printStackTrace()) + echo("Error parsing AGP Kotlin compatibility") + throw ProgramResult(1) + } + val baseIncludes = buildList { for (entry in agpToGradle.entries) { add( buildMap { - put("agp", entry.key.toString()) + val agpVersion = entry.key + put("agp", agpVersion.toString()) val gradle = entry.value - // Check if the Gradle version meets Kotlin's minimum requirement - // Use the current Kotlin version's minimum requirement + // Pick the latest Kotlin whose required AGP <= this AGP + val kotlinVersion = + agpToKotlin + .filter { (minAgp, _) -> agpVersion >= minAgp } + .maxByOrNull { it.first } + ?.second + + // Floor: if the chosen Kotlin requires a newer Gradle than AGP does, bump Gradle up val kotlinMinGradle = - kotlinToGradleMap.entries - .find { (kotlin, _) -> kotlin.inRange(kotlinVersion) } - ?.value - ?.min + kotlinVersion?.let { kv -> + kotlinToGradleMap.entries.find { (kotlin, _) -> kotlin.inRange(kv) }?.value?.min + } val finalGradle = if (kotlinMinGradle != null && gradle < kotlinMinGradle) { echo( @@ -127,9 +133,8 @@ class GenerateMatrix : CliktCommand() { ) // TODO: if needed we can test against different Java versions put("java", "17") - val kotlin = gradleToKotlin.entries.findLast { finalGradle >= it.key }?.value - if (kotlin != null) { - put("kotlin", kotlin) + if (kotlinVersion != null) { + put("kotlin", kotlinVersion.toString()) } } ) @@ -356,6 +361,47 @@ class GenerateMatrix : CliktCommand() { return gradleVersions to latestGradle } + /** + * Fetches the AGP -> Kotlin compatibility table from developer.android.com/build/kotlin-support. + * Each row lists a Kotlin minor and the minimum AGP version required for it; returned as pairs + * (minAGP, Kotlin) so callers can pick the highest Kotlin whose minAGP is <= a given AGP. + */ + private fun fetchAgpKotlinCompatibility(): List> { + val html = URL("https://developer.android.com/build/kotlin-support").readText() + val doc = Jsoup.parse(html) + val table = + doc.select("table").find { t -> + val headers = t.select("th").map { it.text() } + headers.any { it.contains("Kotlin version", ignoreCase = true) } && + headers.any { it.contains("Required AGP", ignoreCase = true) } + } ?: error("Could not find AGP/Kotlin compatibility table") + + // Cells may carry footnote markers like "8.13.19[1]"; extract the leading x.y[.z] token. + val versionRegex = Regex("""\d+\.\d+(\.\d+)?""") + val result = mutableListOf>() + for (row in table.select("tr").drop(1)) { + val cells = row.select("td").map { it.text() } + if (cells.size < 2) continue + val kotlinStr = versionRegex.find(cells[0])?.value ?: continue + val agpStr = versionRegex.find(cells[1])?.value ?: continue + val kotlin = + try { + Version.parse(kotlinStr, strict = false) + } catch (e: Throwable) { + continue + } + val agp = + try { + Version.parse(agpStr, strict = false) + } catch (e: Throwable) { + continue + } + result += agp to kotlin + } + if (result.isEmpty()) error("No rows parsed from AGP/Kotlin compatibility table") + return result + } + /** * Fetches the value of `SdkConstants.GRADLE_LATEST_VERSION` from Android Studio's source, used to * resolve `CompatibleGradleVersion.VERSION_FOR_DEV` for bleeding-edge AGP versions. From abd5c094dfbe3885079e4fbf322e5d12809e9755 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 13:41:22 +0200 Subject: [PATCH 2/8] fix(matrix): Skip Kotlin minors with no stable release The developer.android.com/build/kotlin-support table lists upcoming Kotlin minors before they ship (e.g. Kotlin 2.4 with AGP 9.1.0 while only 2.4.0-Beta* is on Maven Central). Resolve each Kotlin minor to its latest stable patch by reading kotlin-stdlib's maven-metadata.xml and drop rows whose minor has no stable release yet, so the generated matrix never references an unpublished version. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/generate-compat-matrix.main.kts | 42 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/scripts/generate-compat-matrix.main.kts b/scripts/generate-compat-matrix.main.kts index 08a2006c..5f845fe3 100755 --- a/scripts/generate-compat-matrix.main.kts +++ b/scripts/generate-compat-matrix.main.kts @@ -362,9 +362,10 @@ class GenerateMatrix : CliktCommand() { } /** - * Fetches the AGP -> Kotlin compatibility table from developer.android.com/build/kotlin-support. - * Each row lists a Kotlin minor and the minimum AGP version required for it; returned as pairs - * (minAGP, Kotlin) so callers can pick the highest Kotlin whose minAGP is <= a given AGP. + * Fetches the AGP -> Kotlin compatibility table from developer.android.com/build/kotlin-support, + * and resolves each Kotlin minor to its latest stable patch on Maven Central. Rows whose Kotlin + * minor has no stable release yet (e.g. Kotlin 2.4 while only 2.4.0-Beta* is published) are + * dropped so the matrix never references an unreleased version. */ private fun fetchAgpKotlinCompatibility(): List> { val html = URL("https://developer.android.com/build/kotlin-support").readText() @@ -376,6 +377,8 @@ class GenerateMatrix : CliktCommand() { headers.any { it.contains("Required AGP", ignoreCase = true) } } ?: error("Could not find AGP/Kotlin compatibility table") + val latestStablePatches = fetchLatestStableKotlinPatches() + // Cells may carry footnote markers like "8.13.19[1]"; extract the leading x.y[.z] token. val versionRegex = Regex("""\d+\.\d+(\.\d+)?""") val result = mutableListOf>() @@ -396,12 +399,43 @@ class GenerateMatrix : CliktCommand() { } catch (e: Throwable) { continue } - result += agp to kotlin + val stableKotlin = latestStablePatches[kotlin.major to kotlin.minor] + if (stableKotlin == null) { + echo("Warning: Kotlin ${kotlin.major}.${kotlin.minor} has no stable release yet, skipping") + continue + } + result += agp to stableKotlin } if (result.isEmpty()) error("No rows parsed from AGP/Kotlin compatibility table") return result } + /** + * Reads kotlin-stdlib's maven-metadata.xml and returns, per Kotlin major.minor, the highest + * published stable patch version. + */ + private fun fetchLatestStableKotlinPatches(): Map, Version> { + val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val document = + documentBuilder.parse( + "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/maven-metadata.xml" + ) + document.documentElement.normalize() + val versionNodes = document.documentElement.getElementsByTagName("version") + val stable = mutableListOf() + for (i in 0 until versionNodes.length) { + val text = versionNodes.item(i).textContent + val v = + try { + Version.parse(text, strict = false) + } catch (e: Throwable) { + continue + } + if (v.isStable) stable += v + } + return stable.groupBy { it.major to it.minor }.mapValues { (_, vs) -> vs.max() } + } + /** * Fetches the value of `SdkConstants.GRADLE_LATEST_VERSION` from Android Studio's source, used to * resolve `CompatibleGradleVersion.VERSION_FOR_DEV` for bleeding-edge AGP versions. From 7ab8c9b5328ea7e33255b39715f50ab4b2a665ea Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 13:58:20 +0200 Subject: [PATCH 3/8] build(deps): Bump KSP to 2.3.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KSP 2.1.0-1.0.29 is pinned to Kotlin compiler 2.1.0 and fails with "ksp-2.1.0-1.0.29 is too old for kotlin-2.3.21" when the test matrix runs the android-instrumentation-sample against newer Kotlin versions. Bump to 2.3.7, which is the latest KSP2 release — KSP2 is decoupled from the Kotlin compiler version and works across the Kotlin versions the matrix now picks from developer.android.com/build/kotlin-support. Co-Authored-By: Claude Opus 4.7 (1M context) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5cde3d1f..b2ff237c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinSpring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } -ksp = { id = "com.google.devtools.ksp", version = "2.1.0-1.0.29" } +ksp = { id = "com.google.devtools.ksp", version = "2.3.7" } dokka = { id = "org.jetbrains.dokka", version = "1.9.20" } spotless = { id = "com.diffplug.spotless", version = "7.0.4" } groovyGradlePlugin = { id = "dev.gradleplugins.groovy-gradle-plugin", version = "1.7.1" } From 1b4be0df4b523abd04e2e3141eec6c3bd29ec978 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 14:05:41 +0200 Subject: [PATCH 4/8] build(deps): Swap in KSP2 only when matrix overrides Kotlin The pinned KSP (2.1.0-1.0.29, KSP1) is bound to the Kotlin 2.1 compiler and fails with "ksp is too old for kotlin-X" when the test matrix picks newer Kotlin versions from developer.android.com/build/kotlin-support. Bumping KSP directly breaks the default build because KSP2 requires Kotlin language version 2.0+, but the default Kotlin in Dependencies.kt is 1.8.20. Intercept plugin resolution in settings.gradle.kts: when VERSION_KOTLIN is set (i.e. the matrix is driving the build), use KSP2 2.3.7, which is decoupled from the Kotlin compiler. Otherwise leave the pinned KSP1 in place so default / pre-merge builds keep working. Co-Authored-By: Claude Opus 4.7 (1M context) --- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b2ff237c..5cde3d1f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinSpring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } -ksp = { id = "com.google.devtools.ksp", version = "2.3.7" } +ksp = { id = "com.google.devtools.ksp", version = "2.1.0-1.0.29" } dokka = { id = "org.jetbrains.dokka", version = "1.9.20" } spotless = { id = "com.diffplug.spotless", version = "7.0.4" } groovyGradlePlugin = { id = "dev.gradleplugins.groovy-gradle-plugin", version = "1.7.1" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0e8912da..23b5200e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,17 @@ pluginManagement { content { includeGroup("com.android.tools") } } } + // The pinned KSP (KSP1, 2.1.0-1.0.29) is bound to the Kotlin 2.1 compiler and rejects the + // newer Kotlin versions the test matrix picks (e.g. 2.3.21). When the matrix overrides the + // Kotlin version via VERSION_KOTLIN, swap in the latest KSP2 release, which is decoupled + // from the Kotlin compiler and supports language version 2.0+. + resolutionStrategy { + eachPlugin { + if (requested.id.id == "com.google.devtools.ksp" && System.getenv("VERSION_KOTLIN") != null) { + useVersion("2.3.7") + } + } + } } dependencyResolutionManagement { From 059375ba55f4ae1133b680712abc5a093669d6bb Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 14:20:28 +0200 Subject: [PATCH 5/8] build(deps): Pick KSP version via BuildPluginsVersion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings.gradle.kts resolutionStrategy override didn't work because plugin-alias requests through the version catalog bypass it — Gradle rejected the rewrite with "the plugin is already on the classpath with a different version". Switch to the same pattern the other Kotlin plugins use: a `BuildPluginsVersion.KSP` field sourced from VERSION_KOTLIN (KSP2 2.3.7 when Kotlin 2.x, KSP1 2.1.0-1.0.29 otherwise), applied via `version BuildPluginsVersion.KSP` on both the root `apply false` and the sample's plugins block. Verified locally that both default builds (Kotlin 1.8.20, KSP1) and the matrix path (VERSION_KOTLIN=2.3.21, KSP2) resolve and run `kspStagingReleaseKotlin` successfully. Co-Authored-By: Claude Opus 4.7 (1M context) --- build.gradle.kts | 2 +- buildSrc/src/main/java/Dependencies.kt | 5 +++++ .../android-instrumentation-sample/build.gradle.kts | 2 +- settings.gradle.kts | 11 ----------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 56813d6b..4a885230 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ plugins { alias(libs.plugins.kotlin) version BuildPluginsVersion.KOTLIN apply false alias(libs.plugins.kotlinAndroid) version BuildPluginsVersion.KOTLIN apply false alias(libs.plugins.kapt) version BuildPluginsVersion.KOTLIN apply false - alias(libs.plugins.ksp) apply false + alias(libs.plugins.ksp) version BuildPluginsVersion.KSP apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.androidApplication) version BuildPluginsVersion.AGP apply false alias(libs.plugins.androidLibrary) version BuildPluginsVersion.AGP apply false diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 7564e87c..df13c139 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -1,6 +1,11 @@ object BuildPluginsVersion { val AGP = System.getenv("VERSION_AGP") ?: "8.10.1" val KOTLIN = System.getenv("VERSION_KOTLIN") ?: "1.8.20" + // KSP1 (X.Y.Z-A.B.C) is bound to a specific Kotlin compiler version; KSP2 (e.g. 2.3.7) is + // decoupled and supports Kotlin language version 2.0+. Default to KSP1 for the default + // Kotlin 1.8.20, and switch to KSP2 when the matrix sets a Kotlin 2.x version. + val KSP = System.getenv("VERSION_KOTLIN")?.let { if (it.startsWith("2.")) "2.3.7" else null } + ?: "2.1.0-1.0.29" } object LibsVersion { diff --git a/examples/android-instrumentation-sample/build.gradle.kts b/examples/android-instrumentation-sample/build.gradle.kts index cd7c3253..343e2890 100644 --- a/examples/android-instrumentation-sample/build.gradle.kts +++ b/examples/android-instrumentation-sample/build.gradle.kts @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion plugins { alias(libs.plugins.androidApplication) version BuildPluginsVersion.AGP alias(libs.plugins.kotlinAndroid) version BuildPluginsVersion.KOTLIN - alias(libs.plugins.ksp) + alias(libs.plugins.ksp) version BuildPluginsVersion.KSP id("io.sentry.android.gradle") id("io.sentry.kotlin.compiler.gradle") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 23b5200e..0e8912da 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,17 +8,6 @@ pluginManagement { content { includeGroup("com.android.tools") } } } - // The pinned KSP (KSP1, 2.1.0-1.0.29) is bound to the Kotlin 2.1 compiler and rejects the - // newer Kotlin versions the test matrix picks (e.g. 2.3.21). When the matrix overrides the - // Kotlin version via VERSION_KOTLIN, swap in the latest KSP2 release, which is decoupled - // from the Kotlin compiler and supports language version 2.0+. - resolutionStrategy { - eachPlugin { - if (requested.id.id == "com.google.devtools.ksp" && System.getenv("VERSION_KOTLIN") != null) { - useVersion("2.3.7") - } - } - } } dependencyResolutionManagement { From 4c4ab7fc2aed875d2a89fce66d1df4ab5845f720 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 22:08:25 +0200 Subject: [PATCH 6/8] fix(matrix): Strip AGP pre-release and select Kotlin by value Two related bugs in the AGP -> Kotlin lookup: 1. `agpVersion >= minAgp` evaluates to false for pre-release AGP with the same major.minor.patch as a stable minAgp (semver rule: 9.3.0-alpha01 < 9.3.0). Strip the pre-release identifier before the comparison so a row that should match isn't skipped. 2. `maxByOrNull { it.first }` picks the highest minAgp, not the highest Kotlin. Works by coincidence today under monotonic data but breaks on ties (e.g. Kotlin 1.4 and 1.5 both require AGP 7.0). Switch to `it.second` to select by Kotlin version directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/generate-compat-matrix.main.kts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/generate-compat-matrix.main.kts b/scripts/generate-compat-matrix.main.kts index 5f845fe3..4afbb84a 100755 --- a/scripts/generate-compat-matrix.main.kts +++ b/scripts/generate-compat-matrix.main.kts @@ -102,11 +102,14 @@ class GenerateMatrix : CliktCommand() { put("agp", agpVersion.toString()) val gradle = entry.value - // Pick the latest Kotlin whose required AGP <= this AGP + // Pick the latest Kotlin whose required AGP <= this AGP. Strip the pre-release + // identifier so 9.3.0-alpha01 isn't treated as < 9.3.0 by semver rules, which would + // skip a row that should match. + val agpStable = Version(agpVersion.major, agpVersion.minor, agpVersion.patch) val kotlinVersion = agpToKotlin - .filter { (minAgp, _) -> agpVersion >= minAgp } - .maxByOrNull { it.first } + .filter { (minAgp, _) -> agpStable >= minAgp } + .maxByOrNull { it.second } ?.second // Floor: if the chosen Kotlin requires a newer Gradle than AGP does, bump Gradle up From 8bf553515a906fd813262d82d10283cdd2653a95 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 23 Apr 2026 22:49:18 +0200 Subject: [PATCH 7/8] ref(matrix): Extract helpers and harden Kotlin major parsing Code cleanup from review: - Add fetchGooglesourceText() and parseVersionOrNull() helpers to collapse the repeated URL-base64-decode and try/catch-parse patterns across six call sites in the compat fetchers. - Parse the Kotlin major as an Int in BuildPluginsVersion.KSP instead of matching `startsWith("2.")`, so a future Kotlin "20.x" won't be misclassified as Kotlin 2.x. No behavior change; matrix output verified identical. Co-Authored-By: Claude Opus 4.7 (1M context) --- buildSrc/src/main/java/Dependencies.kt | 5 +- scripts/generate-compat-matrix.main.kts | 85 +++++++++++-------------- 2 files changed, 41 insertions(+), 49 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index df13c139..3fbf6171 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -4,7 +4,10 @@ object BuildPluginsVersion { // KSP1 (X.Y.Z-A.B.C) is bound to a specific Kotlin compiler version; KSP2 (e.g. 2.3.7) is // decoupled and supports Kotlin language version 2.0+. Default to KSP1 for the default // Kotlin 1.8.20, and switch to KSP2 when the matrix sets a Kotlin 2.x version. - val KSP = System.getenv("VERSION_KOTLIN")?.let { if (it.startsWith("2.")) "2.3.7" else null } + val KSP = System.getenv("VERSION_KOTLIN") + ?.substringBefore('.') + ?.toIntOrNull() + ?.let { if (it >= 2) "2.3.7" else null } ?: "2.1.0-1.0.29" } diff --git a/scripts/generate-compat-matrix.main.kts b/scripts/generate-compat-matrix.main.kts index 4afbb84a..43e2a183 100755 --- a/scripts/generate-compat-matrix.main.kts +++ b/scripts/generate-compat-matrix.main.kts @@ -304,11 +304,9 @@ class GenerateMatrix : CliktCommand() { agpVersions: List ): Pair, Version> { val source = - URL( - "https://android.googlesource.com/platform/tools/adt/idea/+/refs/heads/mirror-goog-studio-main/build-common/src/com/android/tools/idea/gradle/util/CompatibleGradleVersion.kt?format=TEXT" - ) - .readText() - .let { String(java.util.Base64.getDecoder().decode(it)) } + fetchGooglesourceText( + "https://android.googlesource.com/platform/tools/adt/idea/+/refs/heads/mirror-goog-studio-main/build-common/src/com/android/tools/idea/gradle/util/CompatibleGradleVersion.kt?format=TEXT" + ) // Enum entries: VERSION_X_Y_Z(GradleVersion.version("X.Y.Z")). // VERSION_FOR_DEV references a constant instead of a literal and is handled separately below. @@ -316,11 +314,13 @@ class GenerateMatrix : CliktCommand() { val enumToGradle = mutableMapOf() enumRegex.findAll(source).forEach { match -> val (enumName, versionStr) = match.destructured - try { - enumToGradle[enumName] = Version.parse(versionStr, strict = false) - } catch (e: Throwable) { - echo("Warning: could not parse Gradle version '$versionStr' for $enumName") - } + val v = + parseVersionOrNull(versionStr) + ?: run { + echo("Warning: could not parse Gradle version '$versionStr' for $enumName") + return@forEach + } + enumToGradle[enumName] = v } // VERSION_FOR_DEV points at SdkConstants.GRADLE_LATEST_VERSION, which is declared in a // different file — fetch it so AGP pre-releases get the correct bleeding-edge Gradle version. @@ -335,12 +335,11 @@ class GenerateMatrix : CliktCommand() { mapRegex.findAll(source).forEach { match -> val (agpStr, enumName) = match.destructured val agp = - try { - Version.parse(agpStr, strict = false) - } catch (e: Throwable) { - echo("Warning: could not parse AGP version '$agpStr'") - return@forEach - } + parseVersionOrNull(agpStr) + ?: run { + echo("Warning: could not parse AGP version '$agpStr'") + return@forEach + } val gradle = enumToGradle[enumName] ?: return@forEach agpToGradle[agp] = gradle } @@ -388,20 +387,8 @@ class GenerateMatrix : CliktCommand() { for (row in table.select("tr").drop(1)) { val cells = row.select("td").map { it.text() } if (cells.size < 2) continue - val kotlinStr = versionRegex.find(cells[0])?.value ?: continue - val agpStr = versionRegex.find(cells[1])?.value ?: continue - val kotlin = - try { - Version.parse(kotlinStr, strict = false) - } catch (e: Throwable) { - continue - } - val agp = - try { - Version.parse(agpStr, strict = false) - } catch (e: Throwable) { - continue - } + val kotlin = versionRegex.find(cells[0])?.value?.let(::parseVersionOrNull) ?: continue + val agp = versionRegex.find(cells[1])?.value?.let(::parseVersionOrNull) ?: continue val stableKotlin = latestStablePatches[kotlin.major to kotlin.minor] if (stableKotlin == null) { echo("Warning: Kotlin ${kotlin.major}.${kotlin.minor} has no stable release yet, skipping") @@ -427,13 +414,7 @@ class GenerateMatrix : CliktCommand() { val versionNodes = document.documentElement.getElementsByTagName("version") val stable = mutableListOf() for (i in 0 until versionNodes.length) { - val text = versionNodes.item(i).textContent - val v = - try { - Version.parse(text, strict = false) - } catch (e: Throwable) { - continue - } + val v = parseVersionOrNull(versionNodes.item(i).textContent) ?: continue if (v.isStable) stable += v } return stable.groupBy { it.major to it.minor }.mapValues { (_, vs) -> vs.max() } @@ -445,17 +426,26 @@ class GenerateMatrix : CliktCommand() { */ private fun fetchGradleLatestVersion(): Version { val source = - URL( - "https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/common/src/main/java/com/android/SdkConstants.java?format=TEXT" - ) - .readText() - .let { String(java.util.Base64.getDecoder().decode(it)) } + fetchGooglesourceText( + "https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/common/src/main/java/com/android/SdkConstants.java?format=TEXT" + ) val match = Regex("""GRADLE_LATEST_VERSION\s*=\s*"([^"]+)"""").find(source) ?: error("GRADLE_LATEST_VERSION not found in SdkConstants.java") return Version.parse(match.groupValues[1], strict = false) } + /** Decodes gitiles' `?format=TEXT` response (base64-encoded) to raw source text. */ + private fun fetchGooglesourceText(url: String): String = + URL(url).readText().let { String(java.util.Base64.getDecoder().decode(it)) } + + private fun parseVersionOrNull(s: String): Version? = + try { + Version.parse(s, strict = false) + } catch (_: Throwable) { + null + } + /** * Fetches the minimum required Gradle version from the AGP release notes page. This is used as a * fallback when the AGP version is not yet listed in the compatibility table. @@ -484,12 +474,11 @@ class GenerateMatrix : CliktCommand() { for (row in table.select("tr")) { val cells = row.select("td").map { it.text() } if (cells.size >= 2 && cells[0].trim().equals("Gradle", ignoreCase = true)) { - return try { - Version.parse(cells[1], strict = false) - } catch (e: Throwable) { - echo("Warning: Could not parse Gradle version '${cells[1]}' from release notes") - null - } + return parseVersionOrNull(cells[1]) + ?: run { + echo("Warning: Could not parse Gradle version '${cells[1]}' from release notes") + null + } } } } From 961ec25fa29ab36b597b8b78dc8c45f23842a836 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Apr 2026 12:56:50 +0200 Subject: [PATCH 8/8] drop ksp version from toml --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5cde3d1f..21b38176 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinSpring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } -ksp = { id = "com.google.devtools.ksp", version = "2.1.0-1.0.29" } +ksp = { id = "com.google.devtools.ksp" } dokka = { id = "org.jetbrains.dokka", version = "1.9.20" } spotless = { id = "com.diffplug.spotless", version = "7.0.4" } groovyGradlePlugin = { id = "dev.gradleplugins.groovy-gradle-plugin", version = "1.7.1" }