Skip to content

Commit e422eca

Browse files
Run android emulator on ubuntu build agent. gradle-managed-devices is used instead of reactivecircus/android-emulator-runner GitHub action task
1 parent 096f468 commit e422eca

File tree

24 files changed

+203
-108
lines changed

24 files changed

+203
-108
lines changed

.github/workflows/pull_request.yml

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,37 @@ on:
88
branches: [ master ]
99

1010
jobs:
11+
jobEmulatorMatrixSetup:
12+
runs-on: ubuntu-latest
13+
outputs:
14+
emulator_jobs_matrix: ${{ steps.dataStep.outputs.emulator_jobs_matrix }}
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up JDK
18+
uses: actions/setup-java@v3
19+
with:
20+
distribution: 'zulu'
21+
java-version: '17'
22+
cache: gradle
23+
- name: Prepare the matrix JSON
24+
run: ./gradlew ciEmulatorJobsMatrixSetup
25+
- id: dataStep
26+
run: echo "emulator_jobs_matrix=$(jq -c . < ./build/emulator_jobs_matrix.json)" >> $GITHUB_OUTPUT
1127
build-android:
12-
runs-on: macos-13
28+
needs: jobEmulatorMatrixSetup
29+
runs-on: ubuntu-latest
30+
env:
31+
api-level: 34
1332
strategy:
14-
matrix:
15-
api-level: [ 34 ]
33+
fail-fast: false
34+
matrix: ${{ fromJson(needs.jobEmulatorMatrixSetup.outputs.emulator_jobs_matrix) }}
1635
steps:
1736
- uses: actions/checkout@v3
37+
- name: Enable KVM group perms
38+
run: |
39+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
40+
sudo udevadm control --reload-rules
41+
sudo udevadm trigger --name-match=kvm
1842
- name: Setup test environment
1943
uses: ./.github/actions/setup_test_action
2044
- name: AVD cache
@@ -24,30 +48,30 @@ jobs:
2448
path: |
2549
~/.android/avd/*
2650
~/.android/adb*
27-
key: avd-${{ matrix.api-level }}-${{ runner.os }}-${{ runner.arch }}
51+
key: avd-${{ env.api-level }}-${{ runner.os }}-${{ runner.arch }}
2852
- name: create AVD and generate snapshot for caching
2953
if: steps.avd-cache.outputs.cache-hit != 'true'
3054
uses: reactivecircus/android-emulator-runner@v2
3155
with:
32-
api-level: ${{ matrix.api-level }}
56+
api-level: ${{ env.api-level }}
3357
arch: x86_64
3458
target: google_apis
35-
avd-name: pixel6_API${{ matrix.api-level }}
59+
avd-name: pixel6_API${{ env.api-level }}
3660
force-avd-creation: false
3761
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
3862
disable-animations: false
3963
script: echo "Generated AVD snapshot for caching."
4064
- name: Run Android Instrumented Tests
4165
uses: reactivecircus/android-emulator-runner@v2
4266
with:
43-
api-level: ${{ matrix.api-level }}
67+
api-level: ${{ env.api-level }}
4468
arch: x86_64
4569
target: google_apis
46-
avd-name: pixel6_API${{ matrix.api-level }}
70+
avd-name: pixel6_API${{ env.api-level }}
4771
force-avd-creation: false
4872
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
4973
disable-animations: true
50-
script: ./gradlew connectedAndroidTest
74+
script: ./gradlew ${{ matrix.gradle_tasks }}:connectedAndroidTest
5175
- name: Upload Android test artifact
5276
uses: actions/upload-artifact@v3
5377
if: failure()

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Project exclude paths
2-
/.gradle/
2+
/**/.gradle/
33
/**/build/
44
/.idea/
55
local.properties

build.gradle.kts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
22
import org.gradle.api.tasks.testing.logging.TestLogEvent
3+
import java.io.InputStream
34

45
plugins {
56
alias(libs.plugins.android.application) apply false
@@ -9,6 +10,8 @@ plugins {
910
alias(libs.plugins.test.logger.plugin) apply false
1011
alias(libs.plugins.ben.manes.versions) apply false
1112
id("base")
13+
id("testOptionsConvention")
14+
1215
}
1316

1417
val compileSdkVersion by extra(34)
@@ -201,3 +204,32 @@ tasks.withType<com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
201204
reportfileName = "dependency-updates"
202205
}
203206
// check for latest dependencies - ./gradlew dependencyUpdates -Drevision=release
207+
208+
tasks.register("ciEmulatorJobsMatrixSetup") {
209+
doLast {
210+
EmulatorJobsMatrix().createMatrixJsonFile(rootProject = rootProject)
211+
}
212+
}
213+
tasks.register("ciSdkManagerLicenses") {
214+
doLast {
215+
val sdkDirPath = getAndroidSdkPath(rootDir = rootDir)
216+
getSdkmanagerFile(rootDir = rootDir)?.let { sdkmanagerFile ->
217+
val yesInputStream = object : InputStream() {
218+
private val yesString = "y\n"
219+
private var counter = 0
220+
override fun read(): Int = yesString[counter % 2].also { counter++ }.code
221+
}
222+
exec {
223+
executable = sdkmanagerFile.absolutePath
224+
args = listOf("--list", "--sdk_root=$sdkDirPath")
225+
println("exec: ${this.commandLine.joinToString(separator = " ")}")
226+
}.apply { println("ExecResult: $this") }
227+
exec {
228+
executable = sdkmanagerFile.absolutePath
229+
args = listOf("--licenses", "--sdk_root=$sdkDirPath")
230+
standardInput = yesInputStream
231+
println("exec: ${this.commandLine.joinToString(separator = " ")}")
232+
}.apply { println("ExecResult: $this") }
233+
}
234+
}
235+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
`kotlin-dsl`
3+
}
4+
5+
dependencies {
6+
compileOnly(libs.android.gradle.plugin)
7+
compileOnly(libs.gson)
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
dependencyResolutionManagement {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
}
6+
versionCatalogs {
7+
create("libs") {
8+
from(files("../gradle/libs.versions.toml"))
9+
}
10+
}
11+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import com.google.gson.GsonBuilder
2+
import org.gradle.api.Project
3+
import java.io.File
4+
import java.util.Properties
5+
6+
class EmulatorJobsMatrix {
7+
8+
private val gson by lazy {
9+
GsonBuilder()
10+
.disableHtmlEscaping()
11+
.setPrettyPrinting()
12+
.create()
13+
}
14+
15+
fun createMatrixJsonFile(rootProject: Project) {
16+
val matrix = mapOf("gradle_tasks" to getTaskList(rootProject = rootProject))
17+
val jsonText = gson.toJson(matrix)
18+
rootProject.layout.buildDirectory.asFile.get().also { buildDir ->
19+
buildDir.mkdirs()
20+
File(buildDir, "emulator_jobs_matrix.json").writeText(jsonText)
21+
}
22+
}
23+
24+
fun getTaskList(rootProject: Project): List<String> = rootProject.subprojects
25+
.filter { subProject ->
26+
File(subProject.projectDir, "src${File.separator}androidInstrumentedTest").exists()
27+
}
28+
.map { subProject -> subProject.path }
29+
.sorted()
30+
}
31+
32+
fun getAndroidSdkPath(rootDir: File): String? =
33+
Properties().apply {
34+
val propertiesFile = File(rootDir, "local.properties")
35+
if (propertiesFile.exists()) {
36+
load(propertiesFile.inputStream())
37+
}
38+
}.getProperty("sdk.dir").let { propertiesSdkDirPath ->
39+
(propertiesSdkDirPath ?: System.getenv("ANDROID_HOME"))
40+
}
41+
42+
fun getSdkmanagerFile(rootDir: File): File? =
43+
getAndroidSdkPath(rootDir = rootDir)?.let { sdkDirPath ->
44+
println("sdkDirPath: $sdkDirPath")
45+
val files = File(sdkDirPath).walk().filter { file ->
46+
file.path.contains("cmdline-tools") && file.path.endsWith("sdkmanager")
47+
}
48+
files.forEach { println("walk: ${it.absolutePath}") }
49+
val sdkmanagerFile = files.firstOrNull()
50+
println("sdkmanagerFile: $sdkmanagerFile")
51+
sdkmanagerFile
52+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import com.android.build.api.dsl.ManagedVirtualDevice
2+
import com.android.build.api.dsl.TestOptions
3+
import org.gradle.kotlin.dsl.create
4+
5+
fun TestOptions.configureTestOptions() {
6+
unitTests {
7+
isIncludeAndroidResources = true
8+
all { test: org.gradle.api.tasks.testing.Test ->
9+
test.testLogging {
10+
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
11+
events = setOf(
12+
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
13+
org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED,
14+
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
15+
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT,
16+
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR,
17+
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
18+
)
19+
}
20+
}
21+
}
22+
animationsDisabled = true
23+
emulatorSnapshots {
24+
enableForTestFailures = false
25+
}
26+
managedDevices.devices.create<ManagedVirtualDevice>("gradleManagedDevice") {
27+
device = "Pixel 2"
28+
apiLevel = 33
29+
systemImageSource = "google-atd"
30+
require64Bit = true
31+
}
32+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
plugins {
2+
}

firebase-analytics/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ plugins {
1010
id("com.android.library")
1111
kotlin("native.cocoapods")
1212
kotlin("multiplatform")
13+
id("testOptionsConvention")
1314
}
1415

1516
android {
@@ -29,11 +30,7 @@ android {
2930
targetCompatibility = JavaVersion.VERSION_11
3031
}
3132

32-
testOptions {
33-
unitTests.apply {
34-
isIncludeAndroidResources = true
35-
}
36-
}
33+
testOptions.configureTestOptions()
3734
packaging {
3835
resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module")
3936
resources.pickFirsts.add("META-INF/AL2.0")

firebase-app/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ plugins {
1111
id("com.android.library")
1212
kotlin("native.cocoapods")
1313
kotlin("multiplatform")
14+
id("testOptionsConvention")
1415
}
1516

1617
android {
@@ -30,11 +31,7 @@ android {
3031
targetCompatibility = JavaVersion.VERSION_11
3132
}
3233

33-
testOptions {
34-
unitTests.apply {
35-
isIncludeAndroidResources = true
36-
}
37-
}
34+
testOptions.configureTestOptions()
3835
packaging {
3936
resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module")
4037
resources.pickFirsts.add("META-INF/AL2.0")

firebase-auth/build.gradle.kts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010
id("com.android.library")
1111
kotlin("multiplatform")
1212
kotlin("native.cocoapods")
13-
//id("com.quittle.android-emulator") version "0.2.0"
13+
id("testOptionsConvention")
1414
}
1515

1616
android {
@@ -30,11 +30,7 @@ android {
3030
targetCompatibility = JavaVersion.VERSION_11
3131
}
3232

33-
testOptions {
34-
unitTests.apply {
35-
isIncludeAndroidResources = true
36-
}
37-
}
33+
testOptions.configureTestOptions()
3834
packaging {
3935
resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module")
4036
resources.pickFirsts.add("META-INF/AL2.0")
@@ -45,19 +41,6 @@ android {
4541
}
4642
}
4743

48-
// Optional configuration
49-
//androidEmulator {
50-
// emulator {
51-
// name("givlive_emulator")
52-
// sdkVersion(28)
53-
// abi("x86_64")
54-
// includeGoogleApis(true) // Defaults to false
55-
//
56-
// }
57-
// headless(false)
58-
// logEmulatorOutput(false)
59-
//}
60-
6144
val supportIosTarget = project.property("skipIosTarget") != "true"
6245

6346
kotlin {

firebase-common-internal/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ plugins {
1010
id("com.android.library")
1111
kotlin("multiplatform")
1212
kotlin("plugin.serialization")
13+
id("testOptionsConvention")
1314
}
1415

1516
android {
@@ -28,11 +29,7 @@ android {
2829
targetCompatibility = JavaVersion.VERSION_11
2930
}
3031

31-
testOptions {
32-
unitTests.apply {
33-
isIncludeAndroidResources = true
34-
}
35-
}
32+
testOptions.configureTestOptions()
3633

3734
packaging {
3835
resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module")

firebase-common/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ plugins {
1010
id("com.android.library")
1111
kotlin("multiplatform")
1212
kotlin("plugin.serialization")
13+
id("testOptionsConvention")
1314
}
1415

1516
android {
@@ -28,11 +29,7 @@ android {
2829
targetCompatibility = JavaVersion.VERSION_11
2930
}
3031

31-
testOptions {
32-
unitTests.apply {
33-
isIncludeAndroidResources = true
34-
}
35-
}
32+
testOptions.configureTestOptions()
3633

3734
packaging {
3835
resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module")

0 commit comments

Comments
 (0)