Skip to content

Commit

Permalink
Add Jetpack Compose compilation unit to the project (#257)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mannodermaus authored Dec 19, 2021
1 parent 080159b commit 3305fc3
Show file tree
Hide file tree
Showing 20 changed files with 602 additions and 46 deletions.
40 changes: 39 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
name: (Plugin) Test
command: cd plugin && ./gradlew :android-junit5:check --stacktrace --no-daemon

- run:
name: (Instrumentation) Prepare local.properties
command: |
touch instrumentation/local.properties
echo "junit5.includeCompose = false" > instrumentation/local.properties
- run:
name: (Instrumentation) Download Dependencies
command: cd instrumentation && ./gradlew androidDependencies --no-daemon
Expand All @@ -45,6 +50,24 @@ jobs:
name: (Instrumentation) Test
command: cd instrumentation && ./gradlew :core:check :runner:check --stacktrace --no-daemon

- run:
name: (Compose) Prepare local.properties
command: |
touch instrumentation/local.properties
echo "junit5.includeCompose = true" > instrumentation/local.properties
- run:
name: (Compose) Download Dependencies
command: cd instrumentation && ./gradlew androidDependencies --no-daemon
- run:
name: (Compose) Build
command: |
cd instrumentation
./gradlew :compose:assemble :compose:assembleDebugAndroidTest --stacktrace --no-daemon
- run:
name: (Compose) Test
command: cd instrumentation && ./gradlew :compose:check --stacktrace --no-daemon

- save_cache:
<<: *cache_key
paths:
Expand Down Expand Up @@ -95,6 +118,9 @@ jobs:
- store_artifacts:
path: instrumentation/runner/build/reports
destination: instrumentation-runner
- store_artifacts:
path: instrumentation/compose/build/reports
destination: instrumentation-compose

deploy:
<<: *defaults
Expand All @@ -109,7 +135,16 @@ jobs:
command: cd plugin && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
- run:
name: (Instrumentation) Build & Deploy
command: cd instrumentation && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
command: |
cd instrumentation
echo "junit5.includeCompose = false" > local.properties
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
- run:
name: (Compose) Build & Deploy
command: |
cd instrumentation
echo "junit5.includeCompose = true" > local.properties
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --stacktrace --no-daemon
- store_artifacts:
path: plugin/android-junit5/build/publications
destination: plugin/publications/snapshots
Expand All @@ -119,6 +154,9 @@ jobs:
- store_artifacts:
path: instrumentation/runner/build/publications
destination: instrumentation-runner/publications/snapshots
- store_artifacts:
path: instrumentation/compose/build/publications
destination: instrumentation-compose/publications/snapshots

workflows:
version: 2
Expand Down
15 changes: 13 additions & 2 deletions build-logic/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@

object libs {
object versions {
const val kotlin = "1.5.21"
const val kotlin = "1.5.31"
const val junitJupiter = "5.8.0"
const val junitVintage = "5.8.0"
const val junitPlatform = "1.8.0"
const val truth = "1.1.3"
const val androidXTest = "1.4.0"
const val compose = "1.0.5"
}

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

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

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

const val composeUi = "androidx.compose.ui:ui:${versions.compose}"
const val composeUiTooling = "androidx.compose.ui:ui-tooling:${versions.compose}"
const val composeFoundation = "androidx.compose.foundation:foundation:${versions.compose}"
const val composeMaterial = "androidx.compose.material:material:${versions.compose}"

// Testing
const val junit4 = "junit:junit:4.13.2"
const val korte = "com.soywiz.korlibs.korte:korte:2.2.0"
Expand All @@ -41,4 +48,8 @@ object libs {
const val androidXTestRunner = "androidx.test:runner:${versions.androidXTest}"
const val androidXTestMonitor = "androidx.test:monitor:${versions.androidXTest}"
const val espressoCore = "androidx.test.espresso:espresso-core:3.4.0"

const val composeUiTest = "androidx.compose.ui:ui-test:${versions.compose}"
const val composeUiTestJUnit4 = "androidx.compose.ui:ui-test-junit4:${versions.compose}"
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${versions.compose}"
}
72 changes: 43 additions & 29 deletions build-logic/src/main/kotlin/Deployment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ fun Project.configureDeployment(deployConfig: Deployed) {
throw IllegalStateException("This method can not be called on the root project")
}

// Deployment of modules needs to be conditionally locked.
// If the project is set to Compose mode, only the Compose modules may be deployed.
// On the other hand, if the project is set to Default mode, only the ordinary
// instrumentation modules are deployed. This has to do with the restrictions
// of the Nexus Publishing plugin, which must use the same group and version declaration
// for all modules. It's impossible to use Version A for instrumentation modules and Version B
// for Compose modules at the same time, hence this conditional.
if (shouldSkipDeployment(deployConfig)) {
return
}

val credentials = DeployedCredentials(this)

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

/* Private */

private fun Project.shouldSkipDeployment(deployConfig: Deployed): Boolean {
return if (this.isComposeIncluded) {
// If Compose is included, any non-compose module should be skipped
deployConfig != Artifacts.Instrumentation.Compose
} else {
// If Compose is disabled, any compose module should be skipped
deployConfig == Artifacts.Instrumentation.Compose
}
}

private fun Project.configureRootDeployment(deployConfig: Deployed, credentials: DeployedCredentials) {
if (this != rootProject) {
throw IllegalStateException("This method can only be called on the root project")
Expand Down Expand Up @@ -173,18 +194,27 @@ private fun MavenPublication.applyPublicationDetails(
.none { it.name().toString().endsWith("dependencies") }
) {
val dependenciesNode = appendNode("dependencies")
val dependencies =
project.configurations.getByName("implementation").allDependencies +
project.configurations.getByName("runtimeOnly").allDependencies

val compileDeps = project.configurations.getByName("api").allDependencies
val runtimeDeps = project.configurations.getByName("implementation").allDependencies +
project.configurations.getByName("runtimeOnly").allDependencies -
compileDeps

val dependencies = mapOf(
"runtime" to runtimeDeps,
"compile" to compileDeps
)

dependencies
.filter { it.name != "unspecified" }
.forEach {
with(dependenciesNode.appendNode("dependency")) {
appendNode("groupId", it.group)
appendNode("artifactId", it.name)
appendNode("version", it.version)
appendNode("scope", "runtime")
.mapValues { entry -> entry.value.filter { it.name != "unspecified" } }
.forEach { (scope, dependencies) ->
dependencies.forEach {
with(dependenciesNode.appendNode("dependency")) {
appendNode("groupId", it.group)
appendNode("artifactId", it.name)
appendNode("version", it.version)
appendNode("scope", scope)
}
}
}
}
Expand Down Expand Up @@ -261,11 +291,9 @@ private class AndroidDsl(project: Project) {
val java = JavaDsl(delegate)

class JavaDsl(main: Any) {
private val delegate = main.javaClass.getDeclaredMethod("getJava").invoke(main)

val srcDirs: Set<File> = delegate.javaClass
.getDeclaredMethod("getSrcDirs")
.invoke(delegate) as Set<File>
val srcDirs = main.javaClass
.getDeclaredMethod("getJavaDirectories")
.invoke(main) as Set<File>
}
}
}
Expand Down Expand Up @@ -300,20 +328,6 @@ private fun Project.signing(action: SigningExtension.() -> Unit) {

// Nexus Staging & Publishing Plugins facade

//private fun Project.nexusStaging(
// packageGroup: String,
// stagingProfileId: String?,
// username: String?,
// password: String?
//) {
// rootProject.extensions.getByName("nexusStaging").withGroovyBuilder {
// setProperty("packageGroup", packageGroup)
// setProperty("stagingProfileId", stagingProfileId)
// setProperty("username", username)
// setProperty("password", password)
// }
//}

private fun Project.nexusPublishing(
packageGroup: String,
stagingProfileId: String?,
Expand Down
29 changes: 22 additions & 7 deletions build-logic/src/main/kotlin/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ enum class SupportedAgp(
AGP_7_1("7.1.0-beta05", gradle = "7.2"),
AGP_7_2("7.2.0-alpha06", gradle = "7.3");

companion object {
val oldest = values().first()
}

val shortVersion: String = run {
// Extract first two components of the Maven dependency's version string.
val components = version.split('.')
Expand All @@ -29,13 +33,14 @@ enum class SupportedAgp(
}

object Android {
const val compileSdkVersion = "android-28"
const val compileSdkVersion = 30
const val javaMaxHeapSize = "3g"

const val targetSdkVersion = 28
const val targetSdkVersion = 30
const val sampleMinSdkVersion = 14
val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Platform.Android).minSdk
val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Platform.Android).minSdk
val testRunnerMinSdkVersion = (Artifacts.Instrumentation.Runner.platform as Android).minSdk
val testCoreMinSdkVersion = (Artifacts.Instrumentation.Core.platform as Android).minSdk
val testComposeMinSdkVersion = (Artifacts.Instrumentation.Compose.platform as Android).minSdk
}


Expand Down Expand Up @@ -92,9 +97,9 @@ object Artifacts {
* Instrumentation Test artifacts
*/
object Instrumentation {
private val groupId = "de.mannodermaus.junit5"
private val currentVersion = "1.3.1-SNAPSHOT"
val latestStableVersion = "1.3.0"
private const val groupId = "de.mannodermaus.junit5"
private const val currentVersion = "1.3.1-SNAPSHOT"
const val latestStableVersion = "1.3.0"

val Core = Deployed(
platform = Android(minSdk = 14),
Expand All @@ -115,6 +120,16 @@ object Artifacts {
license = license,
description = "Runner for integration of instrumented Android tests with JUnit 5."
)

val Compose = Deployed(
platform = Android(minSdk = 21),
groupId = groupId,
artifactId = "android-test-compose",
currentVersion = "1.0.0-SNAPSHOT",
latestStableVersion = "0.1.0-SNAPSHOT",
license = license,
description = "Extensions for Jetpack Compose tests with JUnit 5."
)
}
}

Expand Down
4 changes: 2 additions & 2 deletions build-logic/src/main/kotlin/Tasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fun Project.configureTestResources() {
// for different versions of the Android Gradle Plugin
tasks.named("processTestResources", Copy::class.java).configure {
val tokens = mapOf(
"COMPILE_SDK_VERSION" to Android.compileSdkVersion,
"COMPILE_SDK_VERSION" to Android.compileSdkVersion.toString(),
"MIN_SDK_VERSION" to Android.sampleMinSdkVersion.toString(),
"TARGET_SDK_VERSION" to Android.targetSdkVersion.toString(),

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

val agpDependency = libs.plugins.android.substringBeforeLast(":")
val agpDependency = libs.plugins.android(plugin).substringBeforeLast(":")
project.dependencies.add(this.name, "${agpDependency}:${plugin.version}")
}
}
Expand Down
9 changes: 9 additions & 0 deletions build-logic/src/main/kotlin/Utilities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ fun Project.findLocalPluginJar(): File? {
return localPluginJar
}

/**
* Returns whether or not the Compose library module is included in the project.
* This depends on the presence of the :compose module, which is configured
* in settings.gradle.
*/
val Project.isComposeIncluded: Boolean get() {
return findProject(":compose") != null
}

/* File */

/**
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3305fc3

Please sign in to comment.