Skip to content

Commit 8b6bf9e

Browse files
romtsnclaude
andauthored
fix(matrix): Fetch AGP<->Gradle compat from Android Studio source (#1165)
* fix(matrix): Fetch AGP<->Gradle compat from Android Studio source The developer.android.com compatibility page Google publishes is not always kept up-to-date, and relying on it meant our CI matrix fell back to release-notes scraping for newly released AGP versions. Pull the mapping directly from Android Studio's CompatibleGradleVersion.kt (the source of truth) and resolve VERSION_FOR_DEV via SdkConstants.GRADLE_LATEST_VERSION so bleeding-edge AGP pre-releases pick up the right Gradle version. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style(matrix): Apply spotless formatting Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9c1b367 commit 8b6bf9e

1 file changed

Lines changed: 70 additions & 41 deletions

File tree

scripts/generate-compat-matrix.main.kts

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,11 @@ class GenerateMatrix : CliktCommand() {
5959

6060
val agpToGradle =
6161
try {
62-
val (legacyVersions, _) = fetchAgpCompatibilityTable(agpVersions, legacy = true)
63-
val (currentVersions, latestVersion) = fetchAgpCompatibilityTable(agpVersions)
62+
val (compatVersions, latestVersion) = fetchAgpCompatibilityTable(agpVersions)
6463
buildMap<Version, Version> {
6564
for (agpVersion in agpVersions) {
6665
val gradleVersion =
67-
legacyVersions[agpVersion]
68-
?: currentVersions[agpVersion]
66+
compatVersions[agpVersion]
6967
?: fetchGradleVersionFromReleaseNotes(agpVersion)
7068
?: latestVersion
7169
put(agpVersion, gradleVersion)
@@ -289,49 +287,63 @@ class GenerateMatrix : CliktCommand() {
289287
return listOf(latestPreRelease, secondToLatestPreRelease, latest, previousMajorLatest)
290288
}
291289

290+
/**
291+
* Fetches the AGP -> Gradle compatibility table from Android Studio's CompatibleGradleVersion.kt,
292+
* which is kept more up-to-date than the public developer.android.com page. The file is served
293+
* base64-encoded by gitiles when using `?format=TEXT`.
294+
*/
292295
private fun fetchAgpCompatibilityTable(
293-
agpVersions: List<Version>,
294-
legacy: Boolean = false,
296+
agpVersions: List<Version>
295297
): Pair<Map<Version, Version>, Version> {
296-
val gradleVersions = mutableMapOf<Version, Version>()
297-
val html = URL("https://developer.android.com/build/releases/about-agp").readText()
298-
val doc = Jsoup.parse(html)
299-
val tables = doc.select("table") ?: error("No table found")
300-
val table = if (legacy) tables[1] else tables[0]
301-
val rows = table.select("tr")
302-
val headers = rows.first()?.select("th")?.map { it.text() } ?: listOf()
303-
304-
if (headers.none { it.contains("plugin", ignoreCase = true) }) {
305-
error("Wrong table selected")
298+
val source =
299+
URL(
300+
"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"
301+
)
302+
.readText()
303+
.let { String(java.util.Base64.getDecoder().decode(it)) }
304+
305+
// Enum entries: VERSION_X_Y_Z(GradleVersion.version("X.Y.Z")).
306+
// VERSION_FOR_DEV references a constant instead of a literal and is handled separately below.
307+
val enumRegex = Regex("""(VERSION_[A-Z0-9_]+)\s*\(\s*GradleVersion\.version\("([^"]+)"\)""")
308+
val enumToGradle = mutableMapOf<String, Version>()
309+
enumRegex.findAll(source).forEach { match ->
310+
val (enumName, versionStr) = match.destructured
311+
try {
312+
enumToGradle[enumName] = Version.parse(versionStr, strict = false)
313+
} catch (e: Throwable) {
314+
echo("Warning: could not parse Gradle version '$versionStr' for $enumName")
315+
}
306316
}
307-
308-
// the table is in format
309-
// AGP version (without .patch) - Gradle version
317+
// VERSION_FOR_DEV points at SdkConstants.GRADLE_LATEST_VERSION, which is declared in a
318+
// different file — fetch it so AGP pre-releases get the correct bleeding-edge Gradle version.
319+
enumToGradle["VERSION_FOR_DEV"] = fetchGradleLatestVersion()
320+
val latestGradle =
321+
enumToGradle.values.maxOrNull()
322+
?: error("No parseable Gradle versions found in CompatibleGradleVersion.kt")
323+
324+
// Map entries: AgpVersion.parse("X.Y.Z") to VERSION_A_B_C
325+
val mapRegex = Regex("""AgpVersion\.parse\("([^"]+)"\)\s*to\s*(VERSION_[A-Z0-9_]+)""")
310326
val agpToGradle = LinkedHashMap<Version, Version>()
311-
for (row in rows) {
312-
val cells = row.select("td").map { it.text() }
313-
if (cells.size > 0) {
314-
val agp =
315-
try {
316-
Version.parse(cells[0], strict = false)
317-
} catch (e: Throwable) {
318-
// if version cant be parsed, we're probably past the versions we're interested in
319-
break
320-
}
321-
val gradle =
322-
try {
323-
Version.parse(cells[1], strict = false)
324-
} catch (e: Throwable) {
325-
// if version cant be parsed, we're probably past the versions we're interested in
326-
break
327-
}
328-
agpToGradle[agp] = gradle
329-
}
327+
mapRegex.findAll(source).forEach { match ->
328+
val (agpStr, enumName) = match.destructured
329+
val agp =
330+
try {
331+
Version.parse(agpStr, strict = false)
332+
} catch (e: Throwable) {
333+
echo("Warning: could not parse AGP version '$agpStr'")
334+
return@forEach
335+
}
336+
val gradle = enumToGradle[enumName] ?: return@forEach
337+
agpToGradle[agp] = gradle
338+
}
339+
340+
if (agpToGradle.isEmpty()) {
341+
error("No AGP->Gradle entries parsed from CompatibleGradleVersion.kt")
330342
}
331343

332-
val latest = agpToGradle.entries.first()
344+
val gradleVersions = mutableMapOf<Version, Version>()
333345
for (agpVersion in agpVersions) {
334-
// the compat table does not contain the .patch part, so we compare major and minor
346+
// the table keys use representative .0 patches, so match by major/minor
335347
val entry =
336348
agpToGradle.entries.find {
337349
it.key.major == agpVersion.major && it.key.minor == agpVersion.minor
@@ -341,7 +353,24 @@ class GenerateMatrix : CliktCommand() {
341353
}
342354
}
343355

344-
return gradleVersions to latest.value
356+
return gradleVersions to latestGradle
357+
}
358+
359+
/**
360+
* Fetches the value of `SdkConstants.GRADLE_LATEST_VERSION` from Android Studio's source, used to
361+
* resolve `CompatibleGradleVersion.VERSION_FOR_DEV` for bleeding-edge AGP versions.
362+
*/
363+
private fun fetchGradleLatestVersion(): Version {
364+
val source =
365+
URL(
366+
"https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/common/src/main/java/com/android/SdkConstants.java?format=TEXT"
367+
)
368+
.readText()
369+
.let { String(java.util.Base64.getDecoder().decode(it)) }
370+
val match =
371+
Regex("""GRADLE_LATEST_VERSION\s*=\s*"([^"]+)"""").find(source)
372+
?: error("GRADLE_LATEST_VERSION not found in SdkConstants.java")
373+
return Version.parse(match.groupValues[1], strict = false)
345374
}
346375

347376
/**

0 commit comments

Comments
 (0)