Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/app-distribution-gradle-compatibility-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: App Distribution Gradle Compatibility Tests

on:
schedule:
- cron: '0 6 * * *' # Run daily at 6 AM
workflow_dispatch: # Allow manual triggering

permissions:
contents: read

jobs:
app-distribution-plugin:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
with:
java-version: 17
distribution: temurin
cache: gradle
- name: Create credentials file
run: |
echo "${{ secrets.INTEG_TESTS_FAD_SERVICE_CREDENTIALS }}" > firebase-appdistribution-gradle/service-credentials.json
- name: Run tests
id: tests
run: |
./gradlew \
:firebase-appdistribution-gradle:integrationTest
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ task integrationTest(type: Test) {
classpath = sourceSets.integrationTest.runtimeClasspath

shouldRunAfter(test)

testLogging {
showStandardStreams = true
}
}

check.dependsOn(integrationTest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,9 @@ class UploadDistributionTaskTest {
// out.
// Also remember to update the latest AGP/gradle versions in BeePlusGradleProject.java.
// firebase-appdistribution-gradle/src/prodTest/java/com/google/firebase/appdistribution/gradle/BeePlusGradleProject.java#L59-L60
private const val LATEST_GRADLE_VERSION = "9.4.1"
private const val LATEST_AGP_VERSION = "9.2.0-alpha02"
private const val LATEST_GOOGLE_SERVICES_VERSION = "4.4.4"
private val LATEST_GRADLE_VERSION = VersionUtils.fetchLatestGradleVersion()
private val LATEST_AGP_VERSION = VersionUtils.fetchLatestAgpVersion()
private val LATEST_GOOGLE_SERVICES_VERSION = VersionUtils.fetchLatestGoogleServicesVersion()
// For tests against Gradle 9, we get the error:
// "In order to compile Java 9+ source, please set compileSdkVersion to 30 or above"
// when we don't set this to at least 30.
Expand All @@ -739,5 +739,16 @@ class UploadDistributionTaskTest {

// google-services Gradle plugin 4.3.2 was released in September 2019.
private const val OLDER_GOOGLE_SERVICES_VERSION = "4.3.2"

private val LOGGER =
java.util.logging.Logger.getLogger(UploadDistributionTaskTest::class.java.name)

@org.junit.BeforeClass
@JvmStatic
fun logVersions() {
LOGGER.info("Latest Gradle Version: $LATEST_GRADLE_VERSION")
LOGGER.info("Latest AGP Version: $LATEST_AGP_VERSION")
LOGGER.info("Latest Google Services Version: $LATEST_GOOGLE_SERVICES_VERSION")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.appdistribution.gradle

import java.net.HttpURLConnection
import java.net.URL
import javax.xml.parsers.DocumentBuilderFactory

object VersionUtils {
private const val AGP_METADATA_URL =
"https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/maven-metadata.xml"
private const val GRADLE_RELEASES_URL = "https://github.com/gradle/gradle/releases"
private const val GOOGLE_SERVICES_METADATA_URL =
"https://dl.google.com/dl/android/maven2/com/google/gms/google-services/maven-metadata.xml"

fun fetchLatestAgpVersion(): String {
return fetchLatestDependencyVersion(AGP_METADATA_URL, "AGP")
}

fun fetchLatestGoogleServicesVersion(): String {
return fetchLatestDependencyVersion(GOOGLE_SERVICES_METADATA_URL, "Google Services")
}

private fun fetchLatestDependencyVersion(url: String, name: String): String {
val doc =
try {
fetchUrl(url) { connection ->
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
dBuilder.parse(connection.inputStream)
}
} catch (e: Exception) {
throw RuntimeException("Failed to fetch latest $name version", e)
}

doc.documentElement.normalize()
val versionsNodeList = doc.getElementsByTagName("version")
// Iterate backwards through all versions to find the first stable one
for (i in versionsNodeList.length - 1 downTo 0) {
val version = versionsNodeList.item(i).textContent
if (!isUnstable(version)) {
return version
}
}
throw RuntimeException("Failed to find any stable version in $name metadata")
}

fun fetchLatestGradleVersion(): String {
val content =
try {
fetchUrl(GRADLE_RELEASES_URL) { connection ->
connection.inputStream.bufferedReader().use { it.readText() }
}
} catch (e: Exception) {
throw RuntimeException("Failed to fetch latest Gradle version", e)
}

val matchResults = Regex("/gradle/gradle/releases/tag/v?([^\"/\\s>]+)").findAll(content)

// Find the first stable version in the releases list
for (match in matchResults) {
val version = match.groupValues[1]
// Convert RC versions like 9.5.0-RC1 to 9.5.0-rc-1 for the isUnstable check
val normalizedVersion =
version.replace(Regex("-RC(\\d+)", RegexOption.IGNORE_CASE)) { "-rc-${it.groupValues[1]}" }

if (!isUnstable(normalizedVersion)) {
// Return the version in the rc-1 format if it was an RC
return normalizedVersion
}
}
throw RuntimeException("Failed to find any stable Gradle version in HTML")
}

private fun <T> fetchUrl(url: String, parser: (HttpURLConnection) -> T): T {
val connection = URL(url).openConnection() as HttpURLConnection
try {
connection.requestMethod = "GET"
connection.connect()
if (connection.responseCode == 200) {
return parser(connection)
}
throw RuntimeException("Received response code ${connection.responseCode} from $url")
} finally {
connection.disconnect()
}
}

private fun isUnstable(version: String): Boolean {
val unstableKeywords = listOf("alpha", "beta", "milestone", "canary", "m")
return unstableKeywords.any { version.contains(it, ignoreCase = true) }
}
}
Loading