@@ -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