Skip to content

Commit 3305fc3

Browse files
authored
Add Jetpack Compose compilation unit to the project (#257)
* Use Gradle 7.1 for instrumentation libraries * Add empty Compose module & conditional selection of AGP version If 'junit5.includeCompose' property is present in either the Gradle command line or local.properties file, enable the compose project and use AGP 7.0. Otherwise, use the oldest supported plugin instead * Configure conditional deployment of modules * Add first rudimentary bridge between Jetpack Compose and JUnit 5 Update compileSdk to 30 along the way, too * Update plugin build scripts - Update compileSdkVersion to String parameter in test projects - Update Android Gradle Plugin declaration to function * Combined instrumentation/compose config for CI, Attempt #1 * Use AGP 4.2 as minimum plugin version for instrumentation libs No need to go as low as 4.0 for those, and also it doesn't work with Gradle 7, which is required for working with the Compose stuff * Simplify invocation of ComposeTestRule statement without multithreading On the same token, allow resolving ComposeExtension via parameters * Acknowledge api dependencies in POM distribution * Compose 1.0.5 * Kotlin 1.5.31 * Prepare 1.0.0 SNAPSHOT of Compose artifact * Add KDoc to public Compose methods/classes
1 parent 080159b commit 3305fc3

File tree

20 files changed

+602
-46
lines changed

20 files changed

+602
-46
lines changed

.circleci/config.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ jobs:
3131
name: (Plugin) Test
3232
command: cd plugin && ./gradlew :android-junit5:check --stacktrace --no-daemon
3333

34+
- run:
35+
name: (Instrumentation) Prepare local.properties
36+
command: |
37+
touch instrumentation/local.properties
38+
echo "junit5.includeCompose = false" > instrumentation/local.properties
3439
- run:
3540
name: (Instrumentation) Download Dependencies
3641
command: cd instrumentation && ./gradlew androidDependencies --no-daemon
@@ -45,6 +50,24 @@ jobs:
4550
name: (Instrumentation) Test
4651
command: cd instrumentation && ./gradlew :core:check :runner:check --stacktrace --no-daemon
4752

53+
- run:
54+
name: (Compose) Prepare local.properties
55+
command: |
56+
touch instrumentation/local.properties
57+
echo "junit5.includeCompose = true" > instrumentation/local.properties
58+
59+
- run:
60+
name: (Compose) Download Dependencies
61+
command: cd instrumentation && ./gradlew androidDependencies --no-daemon
62+
- run:
63+
name: (Compose) Build
64+
command: |
65+
cd instrumentation
66+
./gradlew :compose:assemble :compose:assembleDebugAndroidTest --stacktrace --no-daemon
67+
- run:
68+
name: (Compose) Test
69+
command: cd instrumentation && ./gradlew :compose:check --stacktrace --no-daemon
70+
4871
- save_cache:
4972
<<: *cache_key
5073
paths:
@@ -95,6 +118,9 @@ jobs:
95118
- store_artifacts:
96119
path: instrumentation/runner/build/reports
97120
destination: instrumentation-runner
121+
- store_artifacts:
122+
path: instrumentation/compose/build/reports
123+
destination: instrumentation-compose
98124

99125
deploy:
100126
<<: *defaults
@@ -109,7 +135,16 @@ jobs:
109135
command: cd plugin && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
110136
- run:
111137
name: (Instrumentation) Build & Deploy
112-
command: cd instrumentation && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
138+
command: |
139+
cd instrumentation
140+
echo "junit5.includeCompose = false" > local.properties
141+
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
142+
- run:
143+
name: (Compose) Build & Deploy
144+
command: |
145+
cd instrumentation
146+
echo "junit5.includeCompose = true" > local.properties
147+
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
113148
- store_artifacts:
114149
path: plugin/android-junit5/build/publications
115150
destination: plugin/publications/snapshots
@@ -119,6 +154,9 @@ jobs:
119154
- store_artifacts:
120155
path: instrumentation/runner/build/publications
121156
destination: instrumentation-runner/publications/snapshots
157+
- store_artifacts:
158+
path: instrumentation/compose/build/publications
159+
destination: instrumentation-compose/publications/snapshots
122160

123161
workflows:
124162
version: 2

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22

33
object libs {
44
object versions {
5-
const val kotlin = "1.5.21"
5+
const val kotlin = "1.5.31"
66
const val junitJupiter = "5.8.0"
77
const val junitVintage = "5.8.0"
88
const val junitPlatform = "1.8.0"
99
const val truth = "1.1.3"
1010
const val androidXTest = "1.4.0"
11+
const val compose = "1.0.5"
1112
}
1213

1314
object plugins {
14-
val android = "com.android.tools.build:gradle:${SupportedAgp.values().first().version}"
15+
fun android(version: SupportedAgp) = "com.android.tools.build:gradle:${version.version}"
1516
const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin}"
1617
const val shadow = "com.github.jengelman.gradle.plugins:shadow:6.1.0"
1718
const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.5.0"
1819
}
1920

2021
// Libraries
2122
const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}"
23+
const val kotlinCoroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
2224
const val javaSemver = "com.github.zafarkhaja:java-semver:0.9.0"
2325

2426
const val junitJupiterApi = "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}"
@@ -28,6 +30,11 @@ object libs {
2830
const val junitPlatformCommons = "org.junit.platform:junit-platform-commons:${versions.junitPlatform}"
2931
const val junitPlatformRunner = "org.junit.platform:junit-platform-runner:${versions.junitPlatform}"
3032

33+
const val composeUi = "androidx.compose.ui:ui:${versions.compose}"
34+
const val composeUiTooling = "androidx.compose.ui:ui-tooling:${versions.compose}"
35+
const val composeFoundation = "androidx.compose.foundation:foundation:${versions.compose}"
36+
const val composeMaterial = "androidx.compose.material:material:${versions.compose}"
37+
3138
// Testing
3239
const val junit4 = "junit:junit:4.13.2"
3340
const val korte = "com.soywiz.korlibs.korte:korte:2.2.0"
@@ -41,4 +48,8 @@ object libs {
4148
const val androidXTestRunner = "androidx.test:runner:${versions.androidXTest}"
4249
const val androidXTestMonitor = "androidx.test:monitor:${versions.androidXTest}"
4350
const val espressoCore = "androidx.test.espresso:espresso-core:3.4.0"
51+
52+
const val composeUiTest = "androidx.compose.ui:ui-test:${versions.compose}"
53+
const val composeUiTestJUnit4 = "androidx.compose.ui:ui-test-junit4:${versions.compose}"
54+
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${versions.compose}"
4455
}

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

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ fun Project.configureDeployment(deployConfig: Deployed) {
2222
throw IllegalStateException("This method can not be called on the root project")
2323
}
2424

25+
// Deployment of modules needs to be conditionally locked.
26+
// If the project is set to Compose mode, only the Compose modules may be deployed.
27+
// On the other hand, if the project is set to Default mode, only the ordinary
28+
// instrumentation modules are deployed. This has to do with the restrictions
29+
// of the Nexus Publishing plugin, which must use the same group and version declaration
30+
// for all modules. It's impossible to use Version A for instrumentation modules and Version B
31+
// for Compose modules at the same time, hence this conditional.
32+
if (shouldSkipDeployment(deployConfig)) {
33+
return
34+
}
35+
2536
val credentials = DeployedCredentials(this)
2637

2738
// Configure root project (this only happens once
@@ -113,6 +124,16 @@ fun Project.configureDeployment(deployConfig: Deployed) {
113124

114125
/* Private */
115126

127+
private fun Project.shouldSkipDeployment(deployConfig: Deployed): Boolean {
128+
return if (this.isComposeIncluded) {
129+
// If Compose is included, any non-compose module should be skipped
130+
deployConfig != Artifacts.Instrumentation.Compose
131+
} else {
132+
// If Compose is disabled, any compose module should be skipped
133+
deployConfig == Artifacts.Instrumentation.Compose
134+
}
135+
}
136+
116137
private fun Project.configureRootDeployment(deployConfig: Deployed, credentials: DeployedCredentials) {
117138
if (this != rootProject) {
118139
throw IllegalStateException("This method can only be called on the root project")
@@ -173,18 +194,27 @@ private fun MavenPublication.applyPublicationDetails(
173194
.none { it.name().toString().endsWith("dependencies") }
174195
) {
175196
val dependenciesNode = appendNode("dependencies")
176-
val dependencies =
177-
project.configurations.getByName("implementation").allDependencies +
178-
project.configurations.getByName("runtimeOnly").allDependencies
197+
198+
val compileDeps = project.configurations.getByName("api").allDependencies
199+
val runtimeDeps = project.configurations.getByName("implementation").allDependencies +
200+
project.configurations.getByName("runtimeOnly").allDependencies -
201+
compileDeps
202+
203+
val dependencies = mapOf(
204+
"runtime" to runtimeDeps,
205+
"compile" to compileDeps
206+
)
179207

180208
dependencies
181-
.filter { it.name != "unspecified" }
182-
.forEach {
183-
with(dependenciesNode.appendNode("dependency")) {
184-
appendNode("groupId", it.group)
185-
appendNode("artifactId", it.name)
186-
appendNode("version", it.version)
187-
appendNode("scope", "runtime")
209+
.mapValues { entry -> entry.value.filter { it.name != "unspecified" } }
210+
.forEach { (scope, dependencies) ->
211+
dependencies.forEach {
212+
with(dependenciesNode.appendNode("dependency")) {
213+
appendNode("groupId", it.group)
214+
appendNode("artifactId", it.name)
215+
appendNode("version", it.version)
216+
appendNode("scope", scope)
217+
}
188218
}
189219
}
190220
}
@@ -261,11 +291,9 @@ private class AndroidDsl(project: Project) {
261291
val java = JavaDsl(delegate)
262292

263293
class JavaDsl(main: Any) {
264-
private val delegate = main.javaClass.getDeclaredMethod("getJava").invoke(main)
265-
266-
val srcDirs: Set<File> = delegate.javaClass
267-
.getDeclaredMethod("getSrcDirs")
268-
.invoke(delegate) as Set<File>
294+
val srcDirs = main.javaClass
295+
.getDeclaredMethod("getJavaDirectories")
296+
.invoke(main) as Set<File>
269297
}
270298
}
271299
}
@@ -300,20 +328,6 @@ private fun Project.signing(action: SigningExtension.() -> Unit) {
300328

301329
// Nexus Staging & Publishing Plugins facade
302330

303-
//private fun Project.nexusStaging(
304-
// packageGroup: String,
305-
// stagingProfileId: String?,
306-
// username: String?,
307-
// password: String?
308-
//) {
309-
// rootProject.extensions.getByName("nexusStaging").withGroovyBuilder {
310-
// setProperty("packageGroup", packageGroup)
311-
// setProperty("stagingProfileId", stagingProfileId)
312-
// setProperty("username", username)
313-
// setProperty("password", password)
314-
// }
315-
//}
316-
317331
private fun Project.nexusPublishing(
318332
packageGroup: String,
319333
stagingProfileId: String?,

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ enum class SupportedAgp(
1515
AGP_7_1("7.1.0-beta05", gradle = "7.2"),
1616
AGP_7_2("7.2.0-alpha06", gradle = "7.3");
1717

18+
companion object {
19+
val oldest = values().first()
20+
}
21+
1822
val shortVersion: String = run {
1923
// Extract first two components of the Maven dependency's version string.
2024
val components = version.split('.')
@@ -29,13 +33,14 @@ enum class SupportedAgp(
2933
}
3034

3135
object Android {
32-
const val compileSdkVersion = "android-28"
36+
const val compileSdkVersion = 30
3337
const val javaMaxHeapSize = "3g"
3438

35-
const val targetSdkVersion = 28
39+
const val targetSdkVersion = 30
3640
const val sampleMinSdkVersion = 14
37-
val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk
38-
val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Platform.Android).minSdk
41+
val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Android).minSdk
42+
val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Android).minSdk
43+
val testComposeMinSdkVersion = (Artifacts.Instrumentation.Compose.platform as Android).minSdk
3944
}
4045

4146

@@ -92,9 +97,9 @@ object Artifacts {
9297
* Instrumentation Test artifacts
9398
*/
9499
object Instrumentation {
95-
private val groupId = "de.mannodermaus.junit5"
96-
private val currentVersion = "1.3.1-SNAPSHOT"
97-
val latestStableVersion = "1.3.0"
100+
private const val groupId = "de.mannodermaus.junit5"
101+
private const val currentVersion = "1.3.1-SNAPSHOT"
102+
const val latestStableVersion = "1.3.0"
98103

99104
val Core = Deployed(
100105
platform = Android(minSdk = 14),
@@ -115,6 +120,16 @@ object Artifacts {
115120
license = license,
116121
description = "Runner for integration of instrumented Android tests with JUnit 5."
117122
)
123+
124+
val Compose = Deployed(
125+
platform = Android(minSdk = 21),
126+
groupId = groupId,
127+
artifactId = "android-test-compose",
128+
currentVersion = "1.0.0-SNAPSHOT",
129+
latestStableVersion = "0.1.0-SNAPSHOT",
130+
license = license,
131+
description = "Extensions for Jetpack Compose tests with JUnit 5."
132+
)
118133
}
119134
}
120135

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fun Project.configureTestResources() {
1010
// for different versions of the Android Gradle Plugin
1111
tasks.named("processTestResources", Copy::class.java).configure {
1212
val tokens = mapOf(
13-
"COMPILE_SDK_VERSION" to Android.compileSdkVersion,
13+
"COMPILE_SDK_VERSION" to Android.compileSdkVersion.toString(),
1414
"MIN_SDK_VERSION" to Android.sampleMinSdkVersion.toString(),
1515
"TARGET_SDK_VERSION" to Android.targetSdkVersion.toString(),
1616

@@ -56,7 +56,7 @@ fun Project.configureTestResources() {
5656
"tests source code in Gradle functional tests against AGP ${plugin.version}"
5757
extendsFrom(configurations.getByName("implementation"))
5858

59-
val agpDependency = libs.plugins.android.substringBeforeLast(":")
59+
val agpDependency = libs.plugins.android(plugin).substringBeforeLast(":")
6060
project.dependencies.add(this.name, "${agpDependency}:${plugin.version}")
6161
}
6262
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ fun Project.findLocalPluginJar(): File? {
8181
return localPluginJar
8282
}
8383

84+
/**
85+
* Returns whether or not the Compose library module is included in the project.
86+
* This depends on the presence of the :compose module, which is configured
87+
* in settings.gradle.
88+
*/
89+
val Project.isComposeIncluded: Boolean get() {
90+
return findProject(":compose") != null
91+
}
92+
8493
/* File */
8594

8695
/**

instrumentation/.idea/runConfigurations/Compose__Run_Instrumentation_Tests.xml

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)