Skip to content

Commit d3f7cb4

Browse files
romtsnclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 8b6bf9e commit d3f7cb4

1 file changed

Lines changed: 65 additions & 19 deletions

File tree

scripts/generate-compat-matrix.main.kts

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,35 @@ class GenerateMatrix : CliktCommand() {
8585
throw ProgramResult(1)
8686
}
8787

88-
// TODO: for now this is manual, but we could try get it from Gradle's github in the future
89-
val gradleToKotlin =
90-
mapOf(
91-
"7.5".toVersion(strict = false) to "1.8.20",
92-
"9.0.0".toVersion(strict = false) to "2.1.0",
93-
"9.5.0-0".toVersion(strict = false) to "2.3.0",
94-
)
95-
// TODO: make it dynamic too
96-
val kotlinVersion = "2.1.0".toVersion()
88+
val agpToKotlin =
89+
try {
90+
fetchAgpKotlinCompatibility()
91+
} catch (e: Exception) {
92+
print(e.printStackTrace())
93+
echo("Error parsing AGP Kotlin compatibility")
94+
throw ProgramResult(1)
95+
}
96+
9797
val baseIncludes = buildList {
9898
for (entry in agpToGradle.entries) {
9999
add(
100100
buildMap {
101-
put("agp", entry.key.toString())
101+
val agpVersion = entry.key
102+
put("agp", agpVersion.toString())
102103
val gradle = entry.value
103104

104-
// Check if the Gradle version meets Kotlin's minimum requirement
105-
// Use the current Kotlin version's minimum requirement
105+
// Pick the latest Kotlin whose required AGP <= this AGP
106+
val kotlinVersion =
107+
agpToKotlin
108+
.filter { (minAgp, _) -> agpVersion >= minAgp }
109+
.maxByOrNull { it.first }
110+
?.second
111+
112+
// Floor: if the chosen Kotlin requires a newer Gradle than AGP does, bump Gradle up
106113
val kotlinMinGradle =
107-
kotlinToGradleMap.entries
108-
.find { (kotlin, _) -> kotlin.inRange(kotlinVersion) }
109-
?.value
110-
?.min
114+
kotlinVersion?.let { kv ->
115+
kotlinToGradleMap.entries.find { (kotlin, _) -> kotlin.inRange(kv) }?.value?.min
116+
}
111117
val finalGradle =
112118
if (kotlinMinGradle != null && gradle < kotlinMinGradle) {
113119
echo(
@@ -127,9 +133,8 @@ class GenerateMatrix : CliktCommand() {
127133
)
128134
// TODO: if needed we can test against different Java versions
129135
put("java", "17")
130-
val kotlin = gradleToKotlin.entries.findLast { finalGradle >= it.key }?.value
131-
if (kotlin != null) {
132-
put("kotlin", kotlin)
136+
if (kotlinVersion != null) {
137+
put("kotlin", kotlinVersion.toString())
133138
}
134139
}
135140
)
@@ -356,6 +361,47 @@ class GenerateMatrix : CliktCommand() {
356361
return gradleVersions to latestGradle
357362
}
358363

364+
/**
365+
* Fetches the AGP -> Kotlin compatibility table from developer.android.com/build/kotlin-support.
366+
* Each row lists a Kotlin minor and the minimum AGP version required for it; returned as pairs
367+
* (minAGP, Kotlin) so callers can pick the highest Kotlin whose minAGP is <= a given AGP.
368+
*/
369+
private fun fetchAgpKotlinCompatibility(): List<Pair<Version, Version>> {
370+
val html = URL("https://developer.android.com/build/kotlin-support").readText()
371+
val doc = Jsoup.parse(html)
372+
val table =
373+
doc.select("table").find { t ->
374+
val headers = t.select("th").map { it.text() }
375+
headers.any { it.contains("Kotlin version", ignoreCase = true) } &&
376+
headers.any { it.contains("Required AGP", ignoreCase = true) }
377+
} ?: error("Could not find AGP/Kotlin compatibility table")
378+
379+
// Cells may carry footnote markers like "8.13.19[1]"; extract the leading x.y[.z] token.
380+
val versionRegex = Regex("""\d+\.\d+(\.\d+)?""")
381+
val result = mutableListOf<Pair<Version, Version>>()
382+
for (row in table.select("tr").drop(1)) {
383+
val cells = row.select("td").map { it.text() }
384+
if (cells.size < 2) continue
385+
val kotlinStr = versionRegex.find(cells[0])?.value ?: continue
386+
val agpStr = versionRegex.find(cells[1])?.value ?: continue
387+
val kotlin =
388+
try {
389+
Version.parse(kotlinStr, strict = false)
390+
} catch (e: Throwable) {
391+
continue
392+
}
393+
val agp =
394+
try {
395+
Version.parse(agpStr, strict = false)
396+
} catch (e: Throwable) {
397+
continue
398+
}
399+
result += agp to kotlin
400+
}
401+
if (result.isEmpty()) error("No rows parsed from AGP/Kotlin compatibility table")
402+
return result
403+
}
404+
359405
/**
360406
* Fetches the value of `SdkConstants.GRADLE_LATEST_VERSION` from Android Studio's source, used to
361407
* resolve `CompatibleGradleVersion.VERSION_FOR_DEV` for bleeding-edge AGP versions.

0 commit comments

Comments
 (0)