Skip to content

Commit 6902e10

Browse files
authored
AGP 8.12.2, 8.13.0, 9.0.0 Alpha 4 & gracefully handle AGP 9's new DSL (#384)
* AGP 8.12.2, 8.13.0, 9.0.0 Alpha 4 * Remove usages of old AGP DSL * Gracefully disable Jacoco on AGP 9.0.0-alpha04 with new DSL
1 parent 56045d7 commit 6902e10

File tree

19 files changed

+232
-172
lines changed

19 files changed

+232
-172
lines changed

build-logic/src/main/kotlin/Environment.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ enum class SupportedAgp(
1919
AGP_8_9("8.9.3", gradle = "8.11.1"),
2020
AGP_8_10("8.10.1", gradle = "8.11.1"),
2121
AGP_8_11("8.11.1", gradle = "8.13"),
22-
AGP_8_12("8.12.1", gradle = "8.13"),
23-
AGP_8_13("8.13.0-rc01", gradle = "8.13"),
24-
AGP_9_0("9.0.0-alpha02", gradle = "9.0.0"),
22+
AGP_8_12("8.12.2", gradle = "8.13"),
23+
AGP_8_13("8.13.0", gradle = "8.13"),
24+
AGP_9_0("9.0.0-alpha04", gradle = "9.0.0"),
2525
;
2626

2727
companion object {

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/config/PluginConfig.kt

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import com.android.build.api.variant.AndroidComponentsExtension
77
import com.android.build.api.variant.Variant
88
import com.android.build.gradle.AppExtension
99
import com.android.build.gradle.AppPlugin
10-
import com.android.build.gradle.BaseExtension
1110
import com.android.build.gradle.BasePlugin
1211
import com.android.build.gradle.DynamicFeaturePlugin
1312
import com.android.build.gradle.LibraryExtension
@@ -16,13 +15,12 @@ import com.android.build.gradle.api.BaseVariant
1615
import de.mannodermaus.gradle.plugins.junit5.internal.providers.DirectoryProvider
1716
import de.mannodermaus.gradle.plugins.junit5.internal.providers.JavaDirectoryProvider
1817
import de.mannodermaus.gradle.plugins.junit5.internal.providers.KotlinDirectoryProvider
19-
import org.gradle.api.DomainObjectSet
2018
import org.gradle.api.Project
2119

2220
internal class PluginConfig
2321
private constructor(
2422
private val project: Project,
25-
private val legacyVariants: DomainObjectSet<out BaseVariant>,
23+
private val legacyPlugin: BasePlugin,
2624
private val componentsExtension: AndroidComponentsExtension<*, *, *>
2725
) {
2826

@@ -32,18 +30,7 @@ private constructor(
3230
.findByName("androidComponents") as? AndroidComponentsExtension<*, *, *>
3331
?: return null
3432

35-
val legacyExtension = project.extensions
36-
.findByName("android") as? BaseExtension
37-
?: return null
38-
39-
val legacyVariants = when (plugin) {
40-
is AppPlugin -> (legacyExtension as AppExtension).applicationVariants
41-
is LibraryPlugin -> (legacyExtension as LibraryExtension).libraryVariants
42-
is DynamicFeaturePlugin -> (legacyExtension as AppExtension).applicationVariants
43-
else -> return null
44-
}
45-
46-
return PluginConfig(project, legacyVariants, componentsExtension)
33+
return PluginConfig(project, plugin, componentsExtension)
4734
}
4835
}
4936

@@ -63,9 +50,26 @@ private constructor(
6350
fun directoryProvidersOf(variant: Variant): Set<DirectoryProvider> {
6451
// Locate the legacy variant for the given one, since the new API
6552
// does not give access to variant-specific source sets and class outputs
66-
return legacyVariants.firstOrNull { it.name == variant.name }
67-
?.run { directoryProvidersOf(this) }
68-
?: emptySet()
53+
val legacyExtension = project.extensions.findByName("android")
54+
55+
val legacyVariants = try {
56+
when (legacyPlugin) {
57+
is AppPlugin -> (legacyExtension as AppExtension).applicationVariants
58+
is LibraryPlugin -> (legacyExtension as LibraryExtension).libraryVariants
59+
is DynamicFeaturePlugin -> (legacyExtension as AppExtension).applicationVariants
60+
else -> null
61+
}
62+
} catch (_: ClassCastException) {
63+
// AGP 9 removes access to the legacy API and thus, Jacoco integration
64+
// is deprecated henceforth. When the above block yields a ClassCastException,
65+
// we know that we're using exclusively against the new DSL and return an empty set to the caller
66+
null
67+
}
68+
69+
return legacyVariants
70+
?.firstOrNull { it.name == variant.name }
71+
?.let(::directoryProvidersOf)
72+
.orEmpty()
6973
}
7074

7175
/* Private */

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import de.mannodermaus.gradle.plugins.junit5.dsl.AndroidJUnitPlatformExtension
1111
import de.mannodermaus.gradle.plugins.junit5.internal.config.ANDROID_JUNIT5_RUNNER_BUILDER_CLASS
1212
import de.mannodermaus.gradle.plugins.junit5.internal.config.JUnit5TaskConfig
1313
import de.mannodermaus.gradle.plugins.junit5.internal.config.PluginConfig
14-
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
1514
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getAsList
1615
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.getTaskName
1716
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.instrumentationTestVariant
@@ -97,16 +96,7 @@ private fun AndroidJUnitPlatformExtension.prepareUnitTests(project: Project, and
9796
// so that consumers don't need to do this explicitly
9897
val options = excludedPackagingOptions()
9998

100-
try {
101-
android.packaging.resources.excludes.addAll(options)
102-
} catch (e: NoSuchMethodError) {
103-
// TODO Because of https://issuetracker.google.com/issues/263387063,
104-
// there is a breaking API change in AGP 8.x that causes a NoSuchMethodError
105-
// (renaming PackagingOptions to Packaging without any fallback).
106-
// Fall back to the old DSL when this happens
107-
options.forEach(project.android.packagingOptions::exclude)
108-
}
109-
99+
android.packaging.resources.excludes.addAll(options)
110100
attachDependencies(project, "testImplementation", includeRunner = false)
111101
}
112102

@@ -206,10 +196,48 @@ private fun AndroidJUnitPlatformExtension.configureJacoco(
206196
// Create a Jacoco friend task
207197
val enabledVariants = jacocoOptions.onlyGenerateTasksForVariants.get()
208198
if (enabledVariants.isEmpty() || variant.name in enabledVariants) {
199+
// Capture an empty return value here and highlight
200+
// the unavailability of Jacoco integration on certain AGP versions
201+
// (namely, AGP 9.0.0+ with the new DSL). This feature is effectively deprecated
209202
val directoryProviders = config.directoryProvidersOf(variant)
210-
val registered = AndroidJUnit5JacocoReport.register(project, variant, testTask, directoryProviders)
211-
if (!registered) {
212-
project.logger.junit5Warn("Jacoco task for variant '${variant.name}' already exists. Disabling customization for JUnit 5...")
203+
val registeredTask = AndroidJUnit5JacocoReport.register(
204+
project = project,
205+
variant = variant,
206+
testTask = testTask,
207+
directoryProviders = directoryProviders
208+
)
209+
210+
if (directoryProviders.isNotEmpty()) {
211+
// Log a warning if Jacoco tasks already existed
212+
if (registeredTask == null) {
213+
project.logger.junit5Warn(
214+
"Jacoco task for variant '${variant.name}' already exists." +
215+
"Disabling customization for JUnit 5..."
216+
)
217+
}
218+
} else {
219+
// Disable any task that may have been registered above
220+
registeredTask?.configure { it.enabled = false }
221+
222+
project.logger.junit5Warn(
223+
buildString {
224+
append(
225+
"Cannot configure Jacoco for this project because directory providers cannot be found."
226+
)
227+
228+
if (config.currentAgpVersion.major >= 9) {
229+
append(
230+
" This integration is deprecated from AGP 9.0.0 onwards because of the new DSL."
231+
)
232+
append(
233+
" Please consult the link below for more information: "
234+
)
235+
append(
236+
"https://developer.android.com/build/releases/agp-preview"
237+
)
238+
}
239+
}
240+
)
213241
}
214242
}
215243
}

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/extensions/ProjectExt.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
package de.mannodermaus.gradle.plugins.junit5.internal.extensions
22

3-
import com.android.build.gradle.BaseExtension
43
import com.android.build.gradle.BasePlugin
54
import de.mannodermaus.gradle.plugins.junit5.dsl.AndroidJUnitPlatformExtension
65
import de.mannodermaus.gradle.plugins.junit5.internal.config.EXTENSION_NAME
76
import org.gradle.api.Project
87
import org.gradle.api.artifacts.Dependency
98
import java.util.concurrent.atomic.AtomicBoolean
109
import kotlin.contracts.ExperimentalContracts
11-
import kotlin.contracts.InvocationKind
12-
import kotlin.contracts.contract
1310

1411
internal val Project.junitPlatform
1512
get() = extensionByName<AndroidJUnitPlatformExtension>(EXTENSION_NAME)
1613

17-
internal val Project.android
18-
get() = extensionByName<BaseExtension>("android")
19-
2014
@OptIn(ExperimentalContracts::class)
2115
internal fun Project.whenAndroidPluginAdded(block: (BasePlugin) -> Unit) {
2216
val configured = AtomicBoolean(false)

plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/tasks/AndroidJUnit5JacocoReport.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ public abstract class AndroidJUnit5JacocoReport : JacocoReport() {
4040
variant: Variant,
4141
testTask: Test,
4242
directoryProviders: Collection<DirectoryProvider>
43-
): Boolean {
43+
): TaskProvider<AndroidJUnit5JacocoReport>? {
4444
val configAction = ConfigAction(project, variant, testTask, directoryProviders)
45-
if (project.tasks.namedOrNull<Task>(configAction.name) != null) {
45+
project.tasks.namedOrNull<Task>(configAction.name)?.let {
4646
// Already exists; abort
47-
return false
47+
return null
4848
}
4949

5050
val provider = project.tasks.register(
@@ -57,7 +57,7 @@ public abstract class AndroidJUnit5JacocoReport : JacocoReport() {
5757
provider.dependsOn(testTask.name)
5858
findOrRegisterDefaultJacocoTask(project).dependsOn(provider)
5959

60-
return true
60+
return provider
6161
}
6262

6363
private fun findOrRegisterDefaultJacocoTask(project: Project): TaskProvider<Task> =

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/FunctionalTests.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,18 @@ class FunctionalTests {
9090
.withProjectDir(project)
9191
.build()
9292

93+
// Print Gradle logs from the embedded invocation
94+
result.prettyPrint()
95+
9396
// Check that the task execution was successful in general
94-
when (val outcome = result.task(":$taskName")?.outcome) {
95-
TaskOutcome.UP_TO_DATE -> {
97+
val outcome = result.task(":$taskName")?.outcome
98+
when {
99+
outcome == TaskOutcome.UP_TO_DATE -> {
96100
// Nothing to do, a previous build already checked this
97-
println("Test task up-to-date; skipping assertions.")
101+
println("Task '$taskName' up-to-date; skipping assertions.")
98102
}
99103

100-
TaskOutcome.SUCCESS -> {
104+
outcome == TaskOutcome.SUCCESS -> {
101105
// Based on the spec's configuration in the test project,
102106
// assert that all test classes have been executed as expected
103107
for (expectation in spec.expectedTests) {
@@ -109,11 +113,14 @@ class FunctionalTests {
109113
}
110114
}
111115

116+
outcome == TaskOutcome.SKIPPED && spec.allowSkipped -> {
117+
// It might be acceptable to allow "skipped" as the result depending on the test spec
118+
println("Task '$taskName' was skipped.")
119+
}
120+
112121
else -> {
113122
// Unexpected result; fail
114-
fail {
115-
"Unexpected task outcome: $outcome\n\nRaw output:\n\n${result.output}"
116-
}
123+
fail { "Unexpected task outcome: $outcome\n\nRaw output:\n\n${result.output}" }
117124
}
118125
}
119126
}
@@ -164,8 +171,6 @@ class FunctionalTests {
164171
productFlavor: String? = null,
165172
tests: List<String>
166173
) {
167-
this.prettyPrint()
168-
169174
// Construct task name from given build type and/or product flavor
170175
// Examples:
171176
// - buildType="debug", productFlavor=null --> ":testDebugUnitTest"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package de.mannodermaus.gradle.plugins.junit5.extensions
2+
3+
import com.android.build.api.dsl.ApplicationExtension
4+
import com.android.build.api.dsl.CommonExtension
5+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.extensionByName
6+
import org.gradle.api.Project
7+
8+
internal val Project.android
9+
get() = extensionByName<CommonExtension<*, *, *, *, *>>("android")
10+
11+
internal val Project.androidApp
12+
get() = extensionByName<ApplicationExtension>("android")

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/plugin/AgpFilterTests.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package de.mannodermaus.gradle.plugins.junit5.plugin
22

33
import com.google.common.truth.Truth.assertThat
4-
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
4+
import de.mannodermaus.gradle.plugins.junit5.extensions.android
5+
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.capitalized
56
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junitPlatform
67
import de.mannodermaus.gradle.plugins.junit5.util.evaluate
78
import de.mannodermaus.gradle.plugins.junit5.util.get
@@ -35,7 +36,7 @@ interface AgpFilterTests : AgpVariantAwareTests {
3536
}
3637
}
3738
) { project, buildType ->
38-
val task = project.tasks.get<Test>("test${buildType.capitalize()}UnitTest")
39+
val task = project.tasks.get<Test>("test${buildType.capitalized()}UnitTest")
3940
assertThat(task.junitPlatformOptions.includeTags).contains("global-include-tag")
4041
assertThat(task.junitPlatformOptions.excludeTags).contains("global-exclude-tag")
4142
assertThat(task.junitPlatformOptions.includeEngines).contains("global-include-engine")
@@ -48,8 +49,8 @@ interface AgpFilterTests : AgpVariantAwareTests {
4849
fun `using custom build types & multiple flavor dimensions`(): List<DynamicTest> {
4950
val project = createProject().build()
5051
project.registerProductFlavors(advancedFlavorList)
51-
project.android.buildTypes { container ->
52-
container.create("ci").initWith(container.getByName("debug"))
52+
with(project.android.buildTypes) {
53+
create("ci").initWith(getByName("debug"))
5354
}
5455
project.evaluate()
5556

plugin/android-junit5/src/test/kotlin/de/mannodermaus/gradle/plugins/junit5/plugin/AgpInstrumentationSupportTests.kt

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package de.mannodermaus.gradle.plugins.junit5.plugin
22

33
import com.android.build.gradle.TestedExtension
44
import com.google.common.truth.Truth.assertThat
5-
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.android
5+
import de.mannodermaus.gradle.plugins.junit5.extensions.android
66
import de.mannodermaus.gradle.plugins.junit5.internal.extensions.junitPlatform
77
import de.mannodermaus.gradle.plugins.junit5.tasks.AndroidJUnit5WriteFilters
88
import de.mannodermaus.gradle.plugins.junit5.util.assertAll
@@ -41,19 +41,19 @@ interface AgpInstrumentationSupportTests : AgpVariantAwareTests {
4141
project.evaluate()
4242

4343
return listOf(
44-
dynamicTest("has a task for writing the debug filters DSL to a resource file") {
45-
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersDebugAndroidTest")
46-
assertAll(
47-
{ assertThat(task).isNotNull() },
48-
{ assertThat(task.includeTags.get()).containsExactly("global-include-tag") },
49-
{ assertThat(task.excludeTags.get()).containsExactly("debug-exclude-tag") }
50-
)
51-
},
44+
dynamicTest("has a task for writing the debug filters DSL to a resource file") {
45+
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersDebugAndroidTest")
46+
assertAll(
47+
{ assertThat(task).isNotNull() },
48+
{ assertThat(task.includeTags.get()).containsExactly("global-include-tag") },
49+
{ assertThat(task.excludeTags.get()).containsExactly("debug-exclude-tag") }
50+
)
51+
},
5252

53-
dynamicTest("has no task for writing the release DSL to a resource file") {
54-
val task = project.tasks.findByName("writeFiltersReleaseAndroidTest")
55-
assertThat(task).isNull()
56-
}
53+
dynamicTest("has no task for writing the release DSL to a resource file") {
54+
val task = project.tasks.findByName("writeFiltersReleaseAndroidTest")
55+
assertThat(task).isNull()
56+
}
5757
)
5858
}
5959

@@ -96,31 +96,31 @@ interface AgpInstrumentationSupportTests : AgpVariantAwareTests {
9696
project.evaluate()
9797

9898
return listOf(
99-
dynamicTest("has a task for writing the freeDebug filters DSL to a resource file") {
100-
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersFreeDebugAndroidTest")
101-
assertThat(task).isNotNull()
102-
assertThat(task.includeTags.get()).containsExactly("global-include-tag", "freeDebug-include-tag")
103-
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
104-
},
99+
dynamicTest("has a task for writing the freeDebug filters DSL to a resource file") {
100+
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersFreeDebugAndroidTest")
101+
assertThat(task).isNotNull()
102+
assertThat(task.includeTags.get()).containsExactly("global-include-tag", "freeDebug-include-tag")
103+
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
104+
},
105105

106-
dynamicTest("has a task for writing the paidDebug filters DSL to a resource file") {
107-
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersPaidDebugAndroidTest")
108-
assertThat(task).isNotNull()
109-
assertThat(task.includeTags.get()).containsExactly("global-include-tag")
110-
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
111-
},
106+
dynamicTest("has a task for writing the paidDebug filters DSL to a resource file") {
107+
val task = project.tasks.get<AndroidJUnit5WriteFilters>("writeFiltersPaidDebugAndroidTest")
108+
assertThat(task).isNotNull()
109+
assertThat(task.includeTags.get()).containsExactly("global-include-tag")
110+
assertThat(task.excludeTags.get()).containsExactly("global-exclude-tag")
111+
},
112112

113-
dynamicTest("doesn't have tasks for writing the release filters DSL to a resource file") {
114-
assertThat(project.tasks.findByName("writeFiltersFreeReleaseAndroidTest")).isNull()
115-
assertThat(project.tasks.findByName("writeFiltersPaidReleaseAndroidTest")).isNull()
116-
}
113+
dynamicTest("doesn't have tasks for writing the release filters DSL to a resource file") {
114+
assertThat(project.tasks.findByName("writeFiltersFreeReleaseAndroidTest")).isNull()
115+
assertThat(project.tasks.findByName("writeFiltersPaidReleaseAndroidTest")).isNull()
116+
}
117117
)
118118
}
119119
}
120120

121121
private fun Project.setupInstrumentationTests() {
122122
android.defaultConfig {
123-
it.testInstrumentationRunnerArgument("runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder")
123+
testInstrumentationRunnerArguments["runnerBuilder"] = "de.mannodermaus.junit5.AndroidJUnit5Builder"
124124
}
125125
dependencies.add("androidTestRuntimeOnly", "de.mannodermaus.junit5:android-test-runner:+")
126126
}

0 commit comments

Comments
 (0)