Improving android-all Downloading on CI
In recent years, the team has received multiple issues regarding Robolectric's inability to download/resolve the necessary android-all jars when running Robolectric tests in a CI environment. Some examples include:
-
android-all not downloaded as part of robolectric, or is it a separate dependency?
-
Robolectric failing because not downloading dependencies in Jenkins when using Artifactory
-
Flaky SHA mismatch on CI builds when retrieving Maven artifacts since upgrading to 4.10.x
Robolectric downloads the necessary android-all jars using its MavenArtifactFetcher when running Robolectric tests. It does not use any proxies defined by the Gradle build system. In a CI environment, especially in environments used by large companies internally, there are often network restrictions that can cause the aforementioned issues. This article provides some solutions to mitigate these issues as much as possible, including setting a custom proxy for MavenArtifactFetcher, leveraging Robolectric's offline mode, and manually fetching the necessary android-all jars before running Robolectric tests.
Setting custom proxy for MavenArtifactFetcher
The first solution is setting a custom proxy for MavenArtifactFetcher
like the following snippet:
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
isIncludeUnitTestDependencies = true
all {
it.systemProperty("robolectric.dependency.repo.url", "https://local-mirror/repo")
it.systemProperty("robolectric.dependency.repo.id", "local")
// Username and password only needed when local repository
// needs account information.
it.systemProperty("robolectric.dependency.repo.username", "username")
it.systemProperty("robolectric.dependency.repo.password", "password")
// Since Robolectric 4.9.1, these are available
it.systemProperty("robolectric.dependency.proxy.host", System.getenv("ROBOLECTRIC_PROXY_HOST"))
it.systemProperty("robolectric.dependency.proxy.port", System.getenv("ROBOLECTRIC_PROXY_PORT"))
}
}
}
The MavenArtifactFetcher
supports the above system properties to leverage a custom Maven
repository link, although it needs a username and password. It also supports a custom proxy
host and port for internally allowed proxy servers.
robolectric.dependency.repo.url
and robolectric.dependency.repo.id
are enough for
most scenarios. For example, I often set the repository to a custom Chinese popular Maven mirror
for my custom projects:
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
isIncludeUnitTestDependencies = true
all {
it.systemProperty("robolectric.dependency.repo.url", "https://maven.aliyun.com/repository")
it.systemProperty("robolectric.dependency.repo.id", "public")
}
}
}
Robolectric's configuration documentation contains a detailed description of these special Robolectric properties, and you can read it for more details.
Leveraging Robolectric's offline mode
Robolectric supports using android-all jars in a local directory with its offline mode without downloading any android-all jars from the network when running Robolectric tests. We can follow the following snippet to enable Robolectric's offline mode for the project:
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
isIncludeUnitTestDependencies = true
all {
it.systemProperty("robolectric.offline", "true")
it.systemProperty(
"robolectric.dependency.dir",
"${rootDir}/robolectric-jars/preinstrumented"
)
}
}
}
To make it work, we need to download android-all jars into the
${rootDir}/robolectric-jars/preinstrumented
directory before running any Robolectric tests.
I created a sample project to provide build scripts to download these
android-all jars into this preinstrumented directory:
robolectric-offline-sample.
plugins {
`java-library`
}
val versions = listOf(
"14-robolectric-10818077-i4",
"13-robolectric-9030017-i4",
"12.1-robolectric-8229987-i4",
"12-robolectric-7732740-i4",
"11-robolectric-6757853-i4",
"10-robolectric-5803371-i4",
"9-robolectric-4913185-2-i4",
"8.1.0-robolectric-4611349-i4",
"8.0.0_r4-robolectric-r1-i4",
"7.1.0_r7-robolectric-r1-i4",
"7.0.0_r1-robolectric-r1-i4",
"6.0.1_r3-robolectric-r1-i4",
"5.1.1_r9-robolectric-r2-i4",
"5.0.2_r3-robolectric-r0-i4",
"4.4_r1-robolectric-r2-i4"
)
val downloadTasks = versions.map { version ->
val configurationName = "robolectric$version".replace(".", "_").replace("-", "_")
val customConfiguration = configurations.create(configurationName) {
extendsFrom(configurations.implementation.get())
isCanBeResolved = true
isCanBeConsumed = false
}
dependencies {
add(configurationName, "org.robolectric:android-all-instrumented:$version")
}
val jarFileDirectory = customConfiguration.resolve().map { it.parentFile.absolutePath }
val allFilesInDirectory = jarFileDirectory.flatMap { fileTree(it).files }
val downloadTask = tasks.register<Copy>("downloadRobolectricJars$version") {
from(allFilesInDirectory)
into("preinstrumented")
}
downloadTask
}
val deleteTask = tasks.register<Delete>("deleteRobolectricJars") { delete("preinstrumented") }
tasks.register("downloadAllRobolectricJars") {
dependsOn(deleteTask)
dependsOn(downloadTasks)
}
The above build.gradle.kts
is just a sample to download necessary android-all jars
manually before running Robolectric tests. It's easy to maintain. Because Robolectric
might add a new android-all jar for a new Android version or modify internal logic
to update an existing android-all jar's version, these android-all jars might change
across different Robolectric versions. If you store them in a git repository,
your git repository might become bigger and bigger. If you like this approach,
you can store android-all jars in an external repository like
AndroidX.
Robolectric's configuring documentation contains a detailed description of these special Robolectric properties, and you can read it for details.
Fetching android-all jars manually before running Robolectric tests
If you don't store android-all jars in your git repository to leverage offline mode,
and you don't want to modify your system properties for Robolectric in your
build.gradle.kts
, you can try to download android-all jars in a script and
download them manually before running any Gradle tasks.
For example, I created a project to do it for myself: robolectric-android-all-fetcher. You can change the Maven mirror to any one you like and run the script to download all android-all jars for a specific Robolectric version.
Conclusion
Most of these issues are caused by a network issue when downloading necessary android-all jars,
and we can fix them or ease them by making android-all jars accessible before running Robolectric tests
and letting MavenArtifactFetcher
use them directly. The above potential solutions are some stable
and recommended solutions for developers to try. Hope it can help you.