From 9a9babfd62e0bc8b48b0b502f5e660322760de78 Mon Sep 17 00:00:00 2001 From: Siarhei Luskanau Date: Thu, 6 Jun 2024 15:25:40 +0200 Subject: [PATCH] Run android emulator on ubuntu build agent. `gradle-managed-devices` is used instead of reactivecircus/android-emulator-runner GitHub action task --- .github/workflows/pull_request.yml | 61 +++++++++--------- .gitignore | 2 +- build.gradle.kts | 32 ++++++++++ .../build.gradle.kts | 8 +++ .../settings.gradle.kts | 11 ++++ .../src/main/kotlin/EmulatorJobsMatrix.kt | 63 +++++++++++++++++++ .../src/main/kotlin/TestOptionsConfig.kt | 31 +++++++++ .../kotlin/testOptionsConvention.gradle.kts | 2 + firebase-app/build.gradle.kts | 7 +-- firebase-auth/build.gradle.kts | 21 +------ firebase-common-internal/build.gradle.kts | 7 +-- firebase-common/build.gradle.kts | 7 +-- firebase-config/build.gradle.kts | 21 +------ firebase-crashlytics/build.gradle.kts | 7 +-- firebase-database/build.gradle.kts | 7 +-- firebase-firestore/build.gradle.kts | 7 +-- firebase-functions/build.gradle.kts | 7 +-- firebase-installations/build.gradle.kts | 7 +-- firebase-messaging/build.gradle.kts | 7 +-- firebase-perf/build.gradle.kts | 7 +-- firebase-storage/build.gradle.kts | 7 +-- gradle/libs.versions.toml | 3 + settings.gradle.kts | 1 + 23 files changed, 206 insertions(+), 127 deletions(-) create mode 100644 convention-plugin-test-option/build.gradle.kts create mode 100644 convention-plugin-test-option/settings.gradle.kts create mode 100644 convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt create mode 100644 convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt create mode 100644 convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 11004c8f3..c03ccfe5a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,46 +8,41 @@ on: branches: [ master ] jobs: + jobEmulatorMatrixSetup: + runs-on: ubuntu-latest + outputs: + emulator_jobs_matrix: ${{ steps.dataStep.outputs.emulator_jobs_matrix }} + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + cache: gradle + - name: Prepare the matrix JSON + run: ./gradlew ciEmulatorJobsMatrixSetup + - id: dataStep + run: echo "emulator_jobs_matrix=$(jq -c . < ./build/emulator_jobs_matrix.json)" >> $GITHUB_OUTPUT build-android: - runs-on: macos-13 + needs: jobEmulatorMatrixSetup + runs-on: ubuntu-latest strategy: - matrix: - api-level: [ 34 ] + fail-fast: false + matrix: ${{ fromJson(needs.jobEmulatorMatrixSetup.outputs.emulator_jobs_matrix) }} steps: - uses: actions/checkout@v3 + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Setup test environment uses: ./.github/actions/setup_test_action - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{ runner.os }}-${{ runner.arch }} - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86_64 - target: google_apis - avd-name: pixel6_API${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." + - name: Apply Android licenses + run: ./gradlew ciSdkManagerLicenses - name: Run Android Instrumented Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86_64 - target: google_apis - avd-name: pixel6_API${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: ./gradlew connectedAndroidTest + run: ./gradlew gradleManagedDeviceDebugAndroidTest - name: Upload Android test artifact uses: actions/upload-artifact@v3 if: failure() diff --git a/.gitignore b/.gitignore index adbab484d..4feaf670b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Project exclude paths -/.gradle/ +/**/.gradle/ /**/build/ /.idea/ local.properties diff --git a/build.gradle.kts b/build.gradle.kts index 1e2ac451b..579f83db1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent +import java.io.InputStream plugins { alias(libs.plugins.android.application) apply false @@ -9,6 +10,8 @@ plugins { alias(libs.plugins.test.logger.plugin) apply false alias(libs.plugins.ben.manes.versions) apply false id("base") + id("testOptionsConvention") + } val compileSdkVersion by extra(34) @@ -201,3 +204,32 @@ tasks.withType + val yesInputStream = object : InputStream() { + private val yesString = "y\n" + private var counter = 0 + override fun read(): Int = yesString[counter % 2].also { counter++ }.code + } + exec { + executable = sdkmanagerFile.absolutePath + args = listOf("--list", "--sdk_root=$sdkDirPath") + println("exec: ${this.commandLine.joinToString(separator = " ")}") + }.apply { println("ExecResult: $this") } + exec { + executable = sdkmanagerFile.absolutePath + args = listOf("--licenses", "--sdk_root=$sdkDirPath") + standardInput = yesInputStream + println("exec: ${this.commandLine.joinToString(separator = " ")}") + }.apply { println("ExecResult: $this") } + } + } +} diff --git a/convention-plugin-test-option/build.gradle.kts b/convention-plugin-test-option/build.gradle.kts new file mode 100644 index 000000000..67975cc59 --- /dev/null +++ b/convention-plugin-test-option/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + compileOnly(libs.android.gradle.plugin) + compileOnly(libs.gson) +} diff --git a/convention-plugin-test-option/settings.gradle.kts b/convention-plugin-test-option/settings.gradle.kts new file mode 100644 index 000000000..5f0643dfc --- /dev/null +++ b/convention-plugin-test-option/settings.gradle.kts @@ -0,0 +1,11 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt b/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt new file mode 100644 index 000000000..6244d0dc5 --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/EmulatorJobsMatrix.kt @@ -0,0 +1,63 @@ +import com.google.gson.GsonBuilder +import org.gradle.api.Project +import java.io.File +import java.util.Properties + +class EmulatorJobsMatrix { + + private val gson by lazy { + GsonBuilder() + .disableHtmlEscaping() + .setPrettyPrinting() + .create() + } + + fun createMatrixJsonFile(rootProject: Project) { + val matrix = mapOf("gradle_tasks" to getTaskList(rootProject = rootProject)) + val jsonText = gson.toJson(matrix) + rootProject.layout.buildDirectory.asFile.get().also { buildDir -> + buildDir.mkdirs() + File(buildDir, "emulator_jobs_matrix.json").writeText(jsonText) + } + } + + fun getTaskList(rootProject: Project): List = + rootProject.subprojects.filter { subProject -> + File(subProject.projectDir, "src${File.separator}androidInstrumentedTest").exists() + }.map { subProject -> + "${subProject.path}:gradleManagedDeviceDebugAndroidTest" + }.map { taskName -> + mutableListOf(taskName).also { + it.add("--no-parallel") + it.add("--max-workers=1") + it.add("-Pandroid.testoptions.manageddevices.emulator.gpu=swiftshader_indirect") + it.add("-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true") + }.also { + if (!true.toString().equals(other = System.getenv("CI"), ignoreCase = true)) { + it.add("--enable-display") + } + }.joinToString(separator = " ") + }.sorted() +} + +fun getAndroidSdkPath(rootDir: File): String? = + Properties().apply { + val propertiesFile = File(rootDir, "local.properties") + if (propertiesFile.exists()) { + load(propertiesFile.inputStream()) + } + }.getProperty("sdk.dir").let { propertiesSdkDirPath -> + (propertiesSdkDirPath ?: System.getenv("ANDROID_HOME")) + } + +fun getSdkmanagerFile(rootDir: File): File? = + getAndroidSdkPath(rootDir = rootDir)?.let { sdkDirPath -> + println("sdkDirPath: $sdkDirPath") + val files = File(sdkDirPath).walk().filter { file -> + file.path.contains("cmdline-tools") && file.path.endsWith("sdkmanager") + } + files.forEach { println("walk: ${it.absolutePath}") } + val sdkmanagerFile = files.firstOrNull() + println("sdkmanagerFile: $sdkmanagerFile") + sdkmanagerFile + } diff --git a/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt b/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt new file mode 100644 index 000000000..725516729 --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/TestOptionsConfig.kt @@ -0,0 +1,31 @@ +import com.android.build.api.dsl.ManagedVirtualDevice +import com.android.build.api.dsl.TestOptions +import org.gradle.kotlin.dsl.create + +fun TestOptions.configureTestOptions() { + unitTests { + isIncludeAndroidResources = true + all { test: org.gradle.api.tasks.testing.Test -> + test.testLogging { + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + events = setOf( + org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED, + org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED, + org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED, + org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_OUT, + org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR, + org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED + ) + } + } + } + animationsDisabled = true + emulatorSnapshots { + enableForTestFailures = false + } + managedDevices.devices.create("gradleManagedDevice") { + device = "Pixel 2" + apiLevel = 34 + systemImageSource = "google-atd" + } +} diff --git a/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts b/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts new file mode 100644 index 000000000..4f298e665 --- /dev/null +++ b/convention-plugin-test-option/src/main/kotlin/testOptionsConvention.gradle.kts @@ -0,0 +1,2 @@ +plugins { +} diff --git a/firebase-app/build.gradle.kts b/firebase-app/build.gradle.kts index 0a4a68f8a..8300d70b0 100644 --- a/firebase-app/build.gradle.kts +++ b/firebase-app/build.gradle.kts @@ -11,6 +11,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -30,11 +31,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index 83dd8d287..4e897b973 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -10,7 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") - //id("com.quittle.android-emulator") version "0.2.0" + id("testOptionsConvention") } android { @@ -30,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -45,19 +41,6 @@ android { } } -// Optional configuration -//androidEmulator { -// emulator { -// name("givlive_emulator") -// sdkVersion(28) -// abi("x86_64") -// includeGoogleApis(true) // Defaults to false -// -// } -// headless(false) -// logEmulatorOutput(false) -//} - val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { diff --git a/firebase-common-internal/build.gradle.kts b/firebase-common-internal/build.gradle.kts index d92abf29a..1f2f0d714 100644 --- a/firebase-common-internal/build.gradle.kts +++ b/firebase-common-internal/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } android { @@ -28,11 +29,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") diff --git a/firebase-common/build.gradle.kts b/firebase-common/build.gradle.kts index 329b42905..5531ada19 100644 --- a/firebase-common/build.gradle.kts +++ b/firebase-common/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } android { @@ -28,11 +29,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") diff --git a/firebase-config/build.gradle.kts b/firebase-config/build.gradle.kts index 5d60db729..e9e896237 100644 --- a/firebase-config/build.gradle.kts +++ b/firebase-config/build.gradle.kts @@ -10,7 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") - //id("com.quittle.android-emulator") version "0.2.0" + id("testOptionsConvention") } android { @@ -30,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") @@ -45,19 +41,6 @@ android { } } -// Optional configuration -//androidEmulator { -// emulator { -// name("givlive_emulator") -// sdkVersion(28) -// abi("x86_64") -// includeGoogleApis(true) // Defaults to false -// -// } -// headless(false) -// logEmulatorOutput(false) -//} - val supportIosTarget = project.property("skipIosTarget") != "true" kotlin { diff --git a/firebase-crashlytics/build.gradle.kts b/firebase-crashlytics/build.gradle.kts index b218d27f2..6a3e503a6 100644 --- a/firebase-crashlytics/build.gradle.kts +++ b/firebase-crashlytics/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") + id("testOptionsConvention") } android { @@ -30,11 +31,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-database/build.gradle.kts b/firebase-database/build.gradle.kts index 26e7f4dd1..2c67ef315 100644 --- a/firebase-database/build.gradle.kts +++ b/firebase-database/build.gradle.kts @@ -11,6 +11,7 @@ plugins { kotlin("native.cocoapods") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } repositories { @@ -35,11 +36,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-firestore/build.gradle.kts b/firebase-firestore/build.gradle.kts index c5f769786..1f01fb95d 100644 --- a/firebase-firestore/build.gradle.kts +++ b/firebase-firestore/build.gradle.kts @@ -11,6 +11,7 @@ plugins { kotlin("native.cocoapods") kotlin("multiplatform") kotlin("plugin.serialization") + id("testOptionsConvention") } android { @@ -31,11 +32,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-functions/build.gradle.kts b/firebase-functions/build.gradle.kts index a3c8309a9..b148115c2 100644 --- a/firebase-functions/build.gradle.kts +++ b/firebase-functions/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -29,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-installations/build.gradle.kts b/firebase-installations/build.gradle.kts index 66ed3e5e6..c731db721 100644 --- a/firebase-installations/build.gradle.kts +++ b/firebase-installations/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -29,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-messaging/build.gradle.kts b/firebase-messaging/build.gradle.kts index ce13c9fc0..2257ba918 100644 --- a/firebase-messaging/build.gradle.kts +++ b/firebase-messaging/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") + id("testOptionsConvention") } android { @@ -29,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-perf/build.gradle.kts b/firebase-perf/build.gradle.kts index 357208b00..874041c85 100644 --- a/firebase-perf/build.gradle.kts +++ b/firebase-perf/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("multiplatform") kotlin("native.cocoapods") + id("testOptionsConvention") } android { @@ -30,11 +31,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/firebase-storage/build.gradle.kts b/firebase-storage/build.gradle.kts index 450e15bd3..a8048aa14 100644 --- a/firebase-storage/build.gradle.kts +++ b/firebase-storage/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("com.android.library") kotlin("native.cocoapods") kotlin("multiplatform") + id("testOptionsConvention") } android { @@ -29,11 +30,7 @@ android { targetCompatibility = JavaVersion.VERSION_11 } - testOptions { - unitTests.apply { - isIncludeAndroidResources = true - } - } + testOptions.configureTestOptions() packaging { resources.pickFirsts.add("META-INF/kotlinx-serialization-core.kotlin_module") resources.pickFirsts.add("META-INF/AL2.0") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56ea7d370..f88eacc8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ androidx-test-runner = "1.5.2" ben-manes-versions = "0.42.0" firebase-bom = "33.0.0" gitlive-firebase-java-sdk = "0.4.3" +gson = "2.11.0" junit = "4.13.2" kotlin = "1.9.23" kotlinx-coroutines = "1.7.3" @@ -15,6 +16,7 @@ settings-language = "1.9" test-logger-plugin = "3.2.0" [libraries] +android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" } androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } @@ -32,6 +34,7 @@ google-firebase-installations-ktx = { module = "com.google.firebase:firebase-ins google-firebase-messaging = { module = "com.google.firebase:firebase-messaging"} google-firebase-perf-ktx = { module = "com.google.firebase:firebase-perf-ktx"} google-firebase-storage = { module = "com.google.firebase:firebase-storage"} +gson = { module = "com.google.code.gson:gson", version.ref = "gson" } junit = { module = "junit:junit", version.ref = "junit" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinx-coroutines" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 129bba09e..1c2600981 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,6 +17,7 @@ include( ) pluginManagement { + includeBuild("convention-plugin-test-option") repositories { google() mavenCentral()