Skip to content

Commit 4954305

Browse files
committed
Update to Android Gradle Plugin 9.0.1
- Migrate from legacy variant API (AppExtension, TestedExtension, BaseVariant) to new variant API (ApplicationExtension, ApplicationAndroidComponentsExtension, Variant) - Replace testVariants.configureEach with onVariants callbacks for APK path detection - Add VariantApkInfo data class to capture variant info during configuration - Update Gradle wrapper from 8.14.3 to 9.1.0 (minimum required by AGP 9.0) - Update Kotlin language/API version from 1.7 to 2.0 - Remove kotlin-android plugin from sample projects (built-in in AGP 9.0) - Add <T : Any> type bounds for Kotlin 2.0 compatibility - Change task classes from open to abstract (AGP 9 requirement) - Update minimum Gradle version from 7.3 to 9.1 Fixes #478
1 parent 8bf89f3 commit 4954305

28 files changed

Lines changed: 403 additions & 248 deletions

android-library-no-tests/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
plugins {
22
id("com.android.library")
3-
kotlin("android")
43
}
54

65
android {

build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ buildscript {
77

88
plugins {
99
alias(libs.plugins.agp) apply false
10-
alias(libs.plugins.kgp) apply false
1110
alias(libs.plugins.ben.manes.versions)
1211
id "com.osacky.fulladle"
1312
alias(libs.plugins.kotlinter)
@@ -22,7 +21,7 @@ fladle {
2221
}
2322

2423
tasks.wrapper.configure {
25-
gradleVersion = '8.14.3'
24+
gradleVersion = '9.1.0'
2625
}
2726

2827
def isNonStable = { String version ->

fladle-plugin/build.gradle.kts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ plugins {
1818
alias(libs.plugins.vanniktech.publish)
1919
}
2020

21-
// See https://github.com/slackhq/keeper/pull/11#issuecomment-579544375 for context
22-
val isReleaseMode : Boolean = hasProperty("fladle.releaseMode")
23-
2421
dependencies {
2522
compileOnly(gradleApi())
26-
if (isReleaseMode) {
27-
compileOnly(libs.agp)
28-
} else {
29-
implementation(libs.agp)
23+
compileOnly(libs.agp) {
24+
exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-embeddable")
25+
exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-runner")
3026
}
3127
compileOnly(libs.gradle.enterprise)
3228

29+
// AGP must be on the runtime classpath so GradleTestKit's withPluginClasspath()
30+
// can resolve the com.android.application and com.android.library plugins.
31+
runtimeOnly(libs.agp)
32+
3333
testImplementation(gradleTestKit())
3434
testImplementation(libs.junit)
3535
testImplementation(libs.truth)
@@ -106,8 +106,8 @@ tasks.withType(ValidatePlugins::class.java).configureEach {
106106
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).configureEach {
107107
compilerOptions {
108108
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
109-
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_7)
110-
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_7)
109+
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
110+
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
111111
}
112112
}
113113

fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt

Lines changed: 85 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.osacky.flank.gradle
22

3-
import com.android.build.gradle.AppExtension
4-
import com.android.build.gradle.TestedExtension
5-
import com.android.build.gradle.internal.tasks.factory.dependsOn
6-
import com.android.builder.model.TestOptions
3+
import com.android.build.api.dsl.ApplicationExtension
4+
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
5+
import com.android.build.api.variant.FilterConfiguration
6+
import com.android.build.api.variant.HasAndroidTest
77
import com.osacky.flank.gradle.validation.checkForExclusionUsage
88
import com.osacky.flank.gradle.validation.validateOptionsUsed
99
import org.gradle.api.GradleException
1010
import org.gradle.api.Project
1111
import org.gradle.api.artifacts.Configuration
12+
import org.gradle.api.plugins.BasePluginExtension
1213
import org.gradle.api.tasks.TaskContainer
1314
import org.gradle.kotlin.dsl.create
1415
import org.gradle.util.GradleVersion
@@ -34,7 +35,6 @@ class FladlePluginDelegate {
3435
}
3536

3637
private fun checkMinimumGradleVersion() {
37-
// Gradle 4.9 is required because we use the lazy task configuration API.
3838
if (GRADLE_MIN_VERSION > GradleVersion.current()) {
3939
throw GradleException("Fladle requires at minimum version $GRADLE_MIN_VERSION. Detected version ${GradleVersion.current()}.")
4040
}
@@ -44,24 +44,22 @@ class FladlePluginDelegate {
4444
project: Project,
4545
base: FlankGradleExtension,
4646
) {
47-
if (GradleVersion.current() > GradleVersion.version("6.1")) {
48-
base.flankVersion.finalizeValueOnRead()
49-
base.flankCoordinates.finalizeValueOnRead()
50-
base.serviceAccountCredentials.finalizeValueOnRead()
47+
base.flankVersion.finalizeValueOnRead()
48+
base.flankCoordinates.finalizeValueOnRead()
49+
base.serviceAccountCredentials.finalizeValueOnRead()
50+
51+
// Register onVariants callbacks before afterEvaluate for APK path detection
52+
project.pluginManager.withPlugin("com.android.application") {
53+
if (!base.debugApk.isPresent || !base.instrumentationApk.isPresent) {
54+
findDebugAndInstrumentationApk(project, base)
55+
}
5156
}
57+
5258
project.afterEvaluate {
5359
// Add Flank dependency to Fladle Configuration
5460
// Must be done afterEvaluate otherwise extension values will not be set.
5561
project.dependencies.add(FLADLE_CONFIG, "${base.flankCoordinates.get()}:${base.flankVersion.get()}")
5662

57-
// Only use automatic apk path detection for 'com.android.application' projects.
58-
project.pluginManager.withPlugin("com.android.application") {
59-
// This doesn't work properly for multiple configs since they likely are inheriting the config from root already. See #60 https://github.com/runningcode/fladle/issues/60
60-
if (!base.debugApk.isPresent || !base.instrumentationApk.isPresent) {
61-
findDebugAndInstrumentationApk(project, base)
62-
}
63-
}
64-
6563
tasks.apply {
6664
createTasksForConfig(base, base, project, "")
6765

@@ -95,7 +93,7 @@ class FladlePluginDelegate {
9593

9694
val writeConfigProps = register("writeConfigProps$name", YamlConfigWriterTask::class.java, base, config, name)
9795

98-
writeConfigProps.dependsOn(validateFladle)
96+
writeConfigProps.configure { dependsOn(validateFladle) }
9997

10098
register("printYml$name") {
10199
description = "Print the flank.yml file to the console."
@@ -136,17 +134,15 @@ class FladlePluginDelegate {
136134
}
137135
dependsOn(writeConfigProps)
138136
if (config.dependOnAssemble.isPresent && config.dependOnAssemble.get()) {
139-
val testedExtension =
140-
requireNotNull(project.extensions.findByType(TestedExtension::class.java)) { "Could not find TestedExtension in ${project.name}" }
141-
testedExtension.testVariants.configureEach {
142-
if (testedVariant.isExpectedVariant(config)) {
143-
if (testedVariant.assembleProvider.isPresent) {
144-
dependsOn(testedVariant.assembleProvider)
145-
}
146-
if (assembleProvider.isPresent) {
147-
dependsOn(assembleProvider)
148-
}
149-
}
137+
// Find assemble tasks by convention name pattern
138+
val variantName = config.variant.orNull
139+
if (variantName != null) {
140+
val capitalizedVariant = variantName.capitalize()
141+
dependsOn("assemble$capitalizedVariant")
142+
dependsOn("assemble${capitalizedVariant}AndroidTest")
143+
} else {
144+
dependsOn("assembleDebug")
145+
dependsOn("assembleDebugAndroidTest")
150146
}
151147
}
152148
if (config.localResultsDir.hasValue) {
@@ -172,45 +168,79 @@ class FladlePluginDelegate {
172168
private fun automaticallyConfigureTestOrchestrator(
173169
project: Project,
174170
config: FladleConfig,
175-
androidExtension: AppExtension,
171+
androidExtension: ApplicationExtension,
176172
) {
177173
project.afterEvaluate {
174+
val execution = androidExtension.testOptions.execution.uppercase()
178175
val useOrchestrator =
179-
androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROIDX_TEST_ORCHESTRATOR ||
180-
androidExtension.testOptions.getExecutionEnum() == TestOptions.Execution.ANDROID_TEST_ORCHESTRATOR
176+
execution == "ANDROIDX_TEST_ORCHESTRATOR" ||
177+
execution == "ANDROID_TEST_ORCHESTRATOR"
181178
if (useOrchestrator) {
182179
log("Automatically detected the use of Android Test Orchestrator")
180+
config.useOrchestrator.set(true)
183181
}
184-
config.useOrchestrator.set(useOrchestrator)
185182
}
186183
}
187184

188185
private fun findDebugAndInstrumentationApk(
189186
project: Project,
190187
config: FladleConfig,
191188
) {
192-
val baseExtension =
193-
requireNotNull(project.extensions.findByType(AppExtension::class.java)) { "Could not find AppExtension in ${project.name}" }
194-
automaticallyConfigureTestOrchestrator(project, config, baseExtension)
195-
baseExtension.testVariants.configureEach {
196-
val appVariant = testedVariant
197-
outputs.configureEach test@{
198-
appVariant.outputs
199-
.matching { it.isExpectedAbiOutput(config) }
200-
.configureEach app@{
201-
if (appVariant.isExpectedVariant(config)) {
202-
if (!config.debugApk.isPresent) {
203-
// Don't set debug apk if not already set. #172
204-
project.log("Configuring fladle.debugApk from variant ${this@app.name}")
205-
config.debugApk.set(this@app.outputFile.absolutePath)
206-
}
207-
if (!config.roboScript.isPresent && !config.instrumentationApk.isPresent && !config.sanityRobo.get()) {
208-
// Don't set instrumentation apk if not already set. #172
209-
project.log("Configuring fladle.instrumentationApk from variant ${this@test.name}")
210-
config.instrumentationApk.set(this@test.outputFile.absolutePath)
211-
}
212-
}
189+
val androidExtension =
190+
requireNotNull(project.extensions.findByType(ApplicationExtension::class.java)) { "Could not find ApplicationExtension in ${project.name}" }
191+
automaticallyConfigureTestOrchestrator(project, config, androidExtension)
192+
193+
val androidComponents =
194+
requireNotNull(project.extensions.findByType(ApplicationAndroidComponentsExtension::class.java)) {
195+
"Could not find ApplicationAndroidComponentsExtension in ${project.name}"
196+
}
197+
198+
androidComponents.onVariants { variant ->
199+
if (!variant.isExpectedVariant(config)) return@onVariants
200+
val androidTest = (variant as? HasAndroidTest)?.androidTest ?: return@onVariants
201+
202+
val buildType = variant.buildType ?: return@onVariants
203+
val flavorName = variant.productFlavors.joinToString("") { it.second }
204+
val flavorPath = variant.productFlavors.joinToString("/") { it.second }
205+
val archivesName = project.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
206+
val buildDir = project.layout.buildDirectory
207+
208+
// Test APK path
209+
val testApkDirPath = if (flavorPath.isNotEmpty()) "androidTest/$flavorPath/$buildType" else "androidTest/$buildType"
210+
val testApkFileName =
211+
if (flavorName.isNotEmpty()) {
212+
"$archivesName-$flavorName-$buildType-androidTest.apk"
213+
} else {
214+
"$archivesName-$buildType-androidTest.apk"
215+
}
216+
val testApkPath = buildDir.file("outputs/apk/$testApkDirPath/$testApkFileName").get().asFile.absolutePath
217+
218+
variant.outputs.forEach { output ->
219+
if (!output.isExpectedAbiOutput(config)) return@forEach
220+
221+
val abiFilter = output.filters.firstOrNull { it.filterType == FilterConfiguration.FilterType.ABI }
222+
val abiName = abiFilter?.identifier
223+
224+
val appApkDirPath = if (flavorPath.isNotEmpty()) "$flavorPath/$buildType" else buildType
225+
val appApkFileName =
226+
buildString {
227+
append(archivesName)
228+
if (flavorName.isNotEmpty()) append("-$flavorName")
229+
if (abiName != null) append("-$abiName")
230+
append("-$buildType.apk")
213231
}
232+
val appApkPath = buildDir.file("outputs/apk/$appApkDirPath/$appApkFileName").get().asFile.absolutePath
233+
234+
if (!config.debugApk.isPresent) {
235+
// Don't set debug apk if not already set. #172
236+
project.log("Configuring fladle.debugApk from variant ${variant.name}")
237+
config.debugApk.set(appApkPath)
238+
}
239+
if (!config.roboScript.isPresent && !config.instrumentationApk.isPresent && !config.sanityRobo.get()) {
240+
// Don't set instrumentation apk if not already set. #172
241+
project.log("Configuring fladle.instrumentationApk from variant ${variant.name}")
242+
config.instrumentationApk.set(testApkPath)
243+
}
214244
}
215245
}
216246
}
@@ -219,7 +249,7 @@ class FladlePluginDelegate {
219249
get() = configurations.getByName(FLADLE_CONFIG)
220250

221251
companion object {
222-
val GRADLE_MIN_VERSION: GradleVersion = GradleVersion.version("7.3")
252+
val GRADLE_MIN_VERSION: GradleVersion = GradleVersion.version("9.1")
223253
const val TASK_GROUP = "fladle"
224254
const val FLADLE_CONFIG = "fladle"
225255

fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import javax.inject.Inject
88
@DisableCachingByDefault(
99
because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.",
1010
)
11-
open class FlankExecutionTask
11+
abstract class FlankExecutionTask
1212
@Inject
1313
constructor(
1414
projectLayout: ProjectLayout,

fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import javax.inject.Inject
88
@DisableCachingByDefault(
99
because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.",
1010
)
11-
open class FlankJavaExec
11+
abstract class FlankJavaExec
1212
@Inject
1313
constructor(projectLayout: ProjectLayout) : JavaExec() {
1414
init {

fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,10 @@ open class FulladleModuleExtension
4646
* can be a match.
4747
*/
4848
val variant: Property<String> = objects.property<String>().convention(null as String?)
49+
50+
/**
51+
* Variant APK info collected during configuration via onVariants callbacks.
52+
* Used by FulladlePlugin at execution time to build YAML entries.
53+
*/
54+
internal val variantApks: MutableList<VariantApkInfo> = mutableListOf()
4955
}

0 commit comments

Comments
 (0)