From e735c4f7e76e80598570ff01bf8cd9ae39fcc270 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 02:42:28 +0000 Subject: [PATCH 01/35] Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.5.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce51ccea4f..937abc42c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,7 +52,7 @@ kotlin = "1.8.10" kotlinx-binary-compatibility = "0.13.2" kotlinx-coroutines = "1.7.1" -kotlinx-serialization-json = "1.3.2" +kotlinx-serialization-json = "1.5.1" kotlinx-atomicfu = "0.17.2" ktlint = "0.49.1" From 9e9c4919aae5978b4ab9e739fd8dcd5083f6b80a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:04:33 +0000 Subject: [PATCH 02/35] Update dependency io.mockk:mockk to v1.13.5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 937abc42c5..426c315b5a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ mavenPublish = "0.13.0" mockito-core = "3.3.3" mockito-kotlin = "3.2.0" -mockk = "1.11.0" +mockk = "1.13.5" robolectric = "4.9.2" rxjava2-android = "2.1.1" From 7e266527683088ee69a423b30ce620076c30ff37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 16:38:30 +0000 Subject: [PATCH 03/35] Update dependency org.jetbrains.kotlinx:atomicfu to v0.21.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 426c315b5a..d972ffd55e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ kotlin = "1.8.10" kotlinx-binary-compatibility = "0.13.2" kotlinx-coroutines = "1.7.1" kotlinx-serialization-json = "1.5.1" -kotlinx-atomicfu = "0.17.2" +kotlinx-atomicfu = "0.21.0" ktlint = "0.49.1" ktlint-gradle = "0.1.7" From 8e992459acac7c786a61d8a2493bab41315ba516 Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 14 Jun 2023 13:08:41 -0500 Subject: [PATCH 04/35] use delegating shard tasks for `connectedCheck` These shard tasks collectively depend upon all Android `connectedCheck` tasks in the entire project. Each shard depends upon the `connectedCheck` tasks of some subset of Android projects. Projects are assigned to a shard by counting the number of `@Test` annotations within their `androidTest` directory, then associating those projects to a shard in a round-robin fashion. These shards are invoked in CI using a GitHub Actions matrix. If the number of shards changes, the `connectedCheckShardMatrixYamlUpdate` task can automatically update the workflow file so that they're all invoked. The shard tasks are invoked as: ```shell # roughly 1/3 of the tests ./gradlew connectedCheckShard1 # the second third ./gradlew connectedCheckShard2 # the last third ./gradlew connectedCheckShard3 ``` The task filtering we currently use in CI (`./gradlew fooShard1 -x :my-project:foo`) will still work here, however the "cost" of the excluded task's tests is still accounted for when the sharding is performed. --- .github/workflows/kotlin.yml | 193 ++++----------- build-logic/build.gradle.kts | 1 + .../com/squareup/workflow1/buildsrc/diff.kt | 56 +++++ .../buildsrc/shardConnectedChecks.kt | 213 ++++++++++++++++ .../buildsrc/sharding/ShardMatrixYamlTask.kt | 227 ++++++++++++++++++ build.gradle.kts | 3 + dependencies/classpath.txt | 1 + gradle/libs.versions.toml | 3 + 8 files changed, 555 insertions(+), 142 deletions(-) create mode 100644 build-logic/src/main/java/com/squareup/workflow1/buildsrc/diff.kt create mode 100644 build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt create mode 100644 build-logic/src/main/java/com/squareup/workflow1/buildsrc/sharding/ShardMatrixYamlTask.kt diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 72a305a4ec..8e7b6c54c1 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -14,22 +14,22 @@ concurrency : jobs : - build-all: - name: Build all - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - - name: main build - uses: ./.github/actions/gradle-task - with: - task: compileKotlin compileDebugKotlin - write-cache-key: main-build-artifacts + build-all : + name : Build all + runs-on : macos-latest + steps : + - uses : actions/checkout@v3 + + - name : main build + uses : ./.github/actions/gradle-task + with : + task : compileKotlin assembleDebug + write-cache-key : main-build-artifacts dokka : name : Assemble & Dokka runs-on : ubuntu-latest - needs: build-all + needs : build-all steps : - uses : actions/checkout@v3 @@ -39,6 +39,19 @@ jobs : task : siteDokka write-cache-key : main-build-artifacts + shards-and-version : + name : Shard Matrix Yaml + runs-on : ubuntu-latest + steps : + - uses : actions/checkout@v3 + + - name : check published artifacts + uses : ./.github/actions/gradle-task-with-commit + with : + check-task : connectedCheckShardMatrixYamlCheck checkVersionIsSnapshot + fix-task : connectedCheckShardMatrixYamlUpdate checkVersionIsSnapshot + write-cache-key : build-logic + artifacts-check : name : ArtifactsCheck # the `artifactsCheck` task has to run on macOS in order to see the iOS KMP artifacts @@ -101,7 +114,7 @@ jobs : android-lint : name : Android Lint runs-on : ubuntu-latest - needs: build-all + needs : build-all timeout-minutes : 20 steps : - uses : actions/checkout@v3 @@ -114,7 +127,7 @@ jobs : check : name : Check runs-on : ubuntu-latest - needs: build-all + needs : build-all timeout-minutes : 20 steps : - uses : actions/checkout@v3 @@ -122,9 +135,8 @@ jobs : uses : ./.github/actions/gradle-task with : task : | - checkVersionIsSnapshot allTests - test + test --continue restore-cache-key : build-logic write-cache-key : main-build-artifacts @@ -286,43 +298,31 @@ jobs : name : renderpass-counting-results-${{ matrix.api-level }} path : ./**/build/reports/androidTests/connected/** - build-instrumentation-tests : - name : Build Instrumentation tests - runs-on : macos-latest - needs: build-all - timeout-minutes : 45 - steps : - - uses : actions/checkout@v3 - - - name : Build instrumented tests - uses : ./.github/actions/gradle-task - with : - task : assembleDebugAndroidTest - restore-cache-key : main-build-artifacts - write-cache-key : androidTest-build-artifacts - instrumentation-tests : name : Instrumentation tests - needs: build-instrumentation-tests runs-on : macos-latest timeout-minutes : 45 strategy : # Allow tests to continue on other devices if they fail on one device. fail-fast : false matrix : + # Unclear that older versions actually honor command to disable animation. + # Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222 api-level : - 29 - # Unclear that older versions actually honor command to disable animation. - # Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222 + ### + shardNum : [ 1, 2, 3 ] + ### steps : - uses : actions/checkout@v3 - # This really just pulls the cache from the dependency job + ## Build before running tests, using cache. - name : Build instrumented tests uses : ./.github/actions/gradle-task with : task : assembleDebugAndroidTest - restore-cache-key : androidTest-build-artifacts + write-cache-key : androidTest-build-artifacts + restore-cache-key : main-build-artifacts ## Actual task - name : Instrumentation Tests @@ -333,16 +333,16 @@ jobs : api-level : ${{ matrix.api-level }} arch : x86_64 # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck + script : ./gradlew connectedCheckShard${{ matrix.shardNum }} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck - name : Upload results if : ${{ always() }} uses : actions/upload-artifact@v3 with : - name : instrumentation-test-results-${{ matrix.api-level }} + name : instrumentation-test-results-${{ matrix.api-level }}-shard_${{ matrix.shardNum }} path : ./**/build/reports/androidTests/connected/** - conflate-renderings-instrumentation-tests : + runtime-instrumentation-tests : name : Conflate Stale Renderings Instrumentation tests runs-on : macos-latest timeout-minutes : 45 @@ -352,110 +352,20 @@ jobs : matrix : api-level : - 29 - # Unclear that older versions actually honor command to disable animation. - # Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222 + ### + shardNum : [ 1, 2, 3 ] + ### + runtime : [ conflate, baseline-stateChange, conflate-stateChange ] steps : - uses : actions/checkout@v3 - ## Build before running tests, using cache. - - name : Build instrumented tests - uses : ./.github/actions/gradle-task - with : - # Unfortunately I don't think we can key this cache based on our project property so - # we clean and rebuild. - task : clean assembleDebugAndroidTest -Pworkflow.runtime=conflate - - ## Actual task - - name : Instrumentation Tests - uses : reactivecircus/android-emulator-runner@v2 - with : - # @ychescale9 suspects Galaxy Nexus is the fastest one - profile : Galaxy Nexus - api-level : ${{ matrix.api-level }} - arch : x86_64 - # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck -Pworkflow.runtime=conflate - - - name : Upload results - if : ${{ always() }} - uses : actions/upload-artifact@v3 - with : - name : instrumentation-test-results-${{ matrix.api-level }} - path : ./**/build/reports/androidTests/connected/** - - stateChange-runtime-instrumentation-tests : - name : Render on State Change Only Instrumentation tests - runs-on : macos-latest - timeout-minutes : 45 - strategy : - # Allow tests to continue on other devices if they fail on one device. - fail-fast : false - matrix : - api-level : - - 29 - # Unclear that older versions actually honor command to disable animation. - # Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222 - steps : - - uses : actions/checkout@v3 - - name : set up JDK 11 - uses : actions/setup-java@v3 - with : - distribution : 'zulu' - java-version : 11 - - ## Build before running tests, using cache. - - name : Build instrumented tests - uses : ./.github/actions/gradle-task - with : - # Unfortunately I don't think we can key this cache based on our project property so - # we clean and rebuild. - task : clean assembleDebugAndroidTest -Pworkflow.runtime=baseline-stateChange - - ## Actual task - - name : Instrumentation Tests - uses : reactivecircus/android-emulator-runner@v2 - with : - # @ychescale9 suspects Galaxy Nexus is the fastest one - profile : Galaxy Nexus - api-level : ${{ matrix.api-level }} - arch : x86_64 - # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck -Pworkflow.runtime=baseline-stateChange - - - name : Upload results - if : ${{ always() }} - uses : actions/upload-artifact@v3 - with : - name : stateChange-instrumentation-test-results-${{ matrix.api-level }} - path : ./**/build/reports/androidTests/connected/** - - conflate-stateChange-runtime-instrumentation-tests : - name : Render on State Change Only and Conflate Stale Renderings Instrumentation tests - runs-on : macos-latest - timeout-minutes : 45 - strategy : - # Allow tests to continue on other devices if they fail on one device. - fail-fast : false - matrix : - api-level : - - 29 - # Unclear that older versions actually honor command to disable animation. - # Newer versions are reputed to be too slow: https://github.com/ReactiveCircus/android-emulator-runner/issues/222 - steps : - - uses : actions/checkout@v3 - - name : set up JDK 11 - uses : actions/setup-java@v3 - with : - distribution : 'zulu' - java-version : 11 - - ## Build before running tests, using cache. + # This really just pulls the cache from the dependency job - name : Build instrumented tests uses : ./.github/actions/gradle-task with : - # Unfortunately I don't think we can key this cache based on our project property so - # we clean and rebuild. - task : clean assembleDebugAndroidTest -Pworkflow.runtime=conflate-stateChange + task : assembleDebugAndroidTest -Pworkflow.runtime=${{matrix.runtime}} + write-cache-key : androidTest-build-artifacts-${{matrix.runtime}} + restore-cache-key : main-build-artifacts ## Actual task - name : Instrumentation Tests @@ -466,13 +376,13 @@ jobs : api-level : ${{ matrix.api-level }} arch : x86_64 # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheck -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck -Pworkflow.runtime=conflate-stateChange + script : ./gradlew connectedCheckShard${{ matrix.shardNum }} -Pworkflow.runtime=${{matrix.runtime}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck - name : Upload results if : ${{ always() }} uses : actions/upload-artifact@v3 with : - name : conflate-stateChange-instrumentation-test-results-${{ matrix.api-level }} + name : conflate-instrumentation-test-results-${{ matrix.api-level }}-shard_${{ matrix.shardNum }} path : ./**/build/reports/androidTests/connected/** all-green : @@ -483,8 +393,6 @@ jobs : - api-check - artifacts-check - check - - conflate-renderings-instrumentation-tests - - conflate-stateChange-runtime-instrumentation-tests - dependency-guard - dokka - instrumentation-tests @@ -495,7 +403,8 @@ jobs : - jvm-stateChange-runtime-test - ktlint - performance-tests - - stateChange-runtime-instrumentation-tests + - runtime-instrumentation-tests + - shards-and-version - tutorials steps : diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 21caf00735..b17cf94ea6 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(libs.squareup.moshi) implementation(libs.squareup.moshi.adapters) implementation(libs.vanniktech.publish) + implementation(libs.java.diff.utils) ksp(libs.squareup.moshi.codegen) } diff --git a/build-logic/src/main/java/com/squareup/workflow1/buildsrc/diff.kt b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/diff.kt new file mode 100644 index 0000000000..4be2b35912 --- /dev/null +++ b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/diff.kt @@ -0,0 +1,56 @@ +package com.squareup.workflow1.buildsrc + +import com.github.difflib.text.DiffRow.Tag +import com.github.difflib.text.DiffRowGenerator +import com.squareup.workflow1.buildsrc.Color.Companion.colorized +import com.squareup.workflow1.buildsrc.Color.LIGHT_GREEN +import com.squareup.workflow1.buildsrc.Color.LIGHT_YELLOW + +fun diffString(oldStr: String, newStr: String): String { + + return buildString { + + val rows = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag { _: Boolean? -> "" } + .newTag { _: Boolean? -> "" } + .build() + .generateDiffRows(oldStr.lines(), newStr.lines()) + + val linePadding = rows.size.toString().length + 1 + + rows.forEachIndexed { line, diffRow -> + if (diffRow.tag != Tag.EQUAL) { + append("line ${line.inc().toString().padEnd(linePadding)} ") + } + + if (diffRow.tag == Tag.CHANGE || diffRow.tag == Tag.DELETE) { + appendLine("-- ${diffRow.oldLine}".colorized(LIGHT_YELLOW)) + } + if (diffRow.tag == Tag.CHANGE) { + append(" " + " ".repeat(linePadding)) + } + if (diffRow.tag == Tag.CHANGE || diffRow.tag == Tag.INSERT) { + appendLine("++ ${diffRow.newLine}".colorized(LIGHT_GREEN)) + } + } + } +} + +@Suppress("MagicNumber") +internal enum class Color(val code: Int) { + LIGHT_GREEN(92), + LIGHT_YELLOW(93); + + companion object { + + private val supported = "win" !in System.getProperty("os.name").lowercase() + + fun String.colorized(color: Color) = if (supported) { + "\u001B[${color.code}m$this\u001B[0m" + } else { + this + } + } +} diff --git a/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt new file mode 100644 index 0000000000..f64057d7bd --- /dev/null +++ b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt @@ -0,0 +1,213 @@ +package com.squareup.workflow1.buildsrc + +import com.squareup.workflow1.buildsrc.sharding.ShardMatrixYamlTask.Companion.registerYamlShardsTasks +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.kotlin.dsl.provideDelegate +import kotlin.LazyThreadSafetyMode.NONE + +private const val SHARD_COUNT = 3 + +/** + * Create "shard" tasks which collectively depend upon all Android `connectedCheck` tasks in the + * entire project. + * + * Each shard depends upon the `connectedCheck` tasks of some subset of Android projects. + * Projects are assigned to a shard by counting the number of `@Test` annotations within their + * `androidTest` directory, then associating those projects to a shard in a round-robin fashion. + * + * These shards are invoked in CI using a GitHub Actions matrix. If the number of shards changes, + * the `connectedCheckShardMatrixYamlUpdate` task can automatically update the workflow file so + * that they're all invoked. + * + * The shard tasks are invoked as: + * ```shell + * # roughly 1/3 of the tests + * ./gradlew connectedCheckShard1 + * # the second third + * ./gradlew connectedCheckShard2 + * # the last third + * ./gradlew connectedCheckShard3 + * ``` + * + * @param target the root project which gets the shard tasks + */ +fun shardConnectedCheckTasks(target: Project) { + if (target != target.rootProject) { + throw GradleException("Only add connectedCheck shard tasks from the root project.") + } + + target.registerYamlShardsTasks( + shardCount = SHARD_COUNT, + startTagName = "### ", + endTagName = "### ", + taskNamePart = "connectedCheck", + yamlFile = target.rootProject.file(".github/workflows/kotlin.yml") + ) + + // Calculate the cost of each project's tests + val projectsWithTestCount = lazy(NONE) { + target.subprojects + // Only Android projects can have these tasks. + // Use the KGP Android plugin instead of AGP since KGP has only one ID to look for. + .filter { it.plugins.hasPlugin("org.jetbrains.kotlin.android") } + .map { it to it.androidTestCost() } + } + + // Assign each project to a shard. + // The values are lazy so that the work only happens at task configuration time, but they're + // outside the task configuration block so that it only happens once. + val shardAssignments = projectsWithTestCount.shards() + + val connectedTestName = "connectedCheck" + + shardAssignments.forEach { shard -> + + target.tasks.register("connectedCheckShard${shard.number}") { + + group = "Verification" + + val projects = shard.projects + + validateSharding( + projectsWithTestCount = projectsWithTestCount.value, + shardAssignments = shardAssignments + ) + + val paths = projects.joinToString(prefix = "[ ", postfix = " ]") { it.path } + + description = "Runs $connectedTestName in projects: $paths" + + val assignedTests = projects.map { project -> + project.tasks.matching { it.name == connectedTestName } + } + + dependsOn(assignedTests) + } + } +} + +/** + * Assigns each project to a shard, distributing them by the number of tests they have. + * The combined test costs of all shards should be approximately equal. + * + * There's a lot of `Lazy` here so that defer parsing all the tests until task configuration. + * If the tasks aren't actually being invoked, no parsing happens. + * + * @receiver Every project with its associated test cost. + * @return A list of shards, where each shard encapsulates a subset of projects. + */ +private fun Lazy>>.shards(): List { + + val shards by lazy { + List>>(SHARD_COUNT) { mutableListOf() } + .also { shards -> + + fun next(): MutableList> { + return shards.minBy { it.sumOf { (_, count) -> count } } + } + + // Sort the projects by descending test cost, then fall back to the project paths + // The path sort is just so that the shard composition is stable. If the shard composition + // isn't stable, the shard tasks may not be up-to-date and build caching in CI is broken. + val sorted = value.sortedWith(compareBy({ it.second }, { it.first })) + .reversed() + + for (pair in sorted) { + next().add(pair) + } + } + } + + return List(SHARD_COUNT) { index -> + Shard( + number = index + 1, + testCountLazy = lazy { shards[index].sumOf { (_, count) -> count } }, + projectsLazy = lazy { shards[index].map { (project, _) -> project } } + ) + } +} + +private data class Shard( + val number: Int, + val testCountLazy: Lazy, + val projectsLazy: Lazy> +) { + val testCount by testCountLazy + val projects by projectsLazy + override fun toString(): String { + return "Shard(number=$number, testCount=$testCount, projects=${projects.joinToString("\n") { it.path }})" + } +} + +private fun validateSharding( + projectsWithTestCount: List>, + shardAssignments: List, +) { + + val allShardsText by lazy(NONE) { shardAssignments.joinToString("\n") } + + if (shardAssignments.size != SHARD_COUNT) { + throw GradleException( + "Unexpected shard configuration. There should be $SHARD_COUNT shards, " + + "but `shardAssignments` is:\n$allShardsText" + ) + } + + val allShardedProjects = shardAssignments.flatMap { it.projects } + + val duplicates = allShardedProjects.groupingBy { it } + .eachCount() + .filter { it.value > 1 } + .keys + + if (duplicates.isNotEmpty()) { + throw GradleException( + "There are duplicated projects in shards.\n" + + "Duplicated projects: ${duplicates.map { it.path }}\n" + + "All shards:\n$allShardsText" + ) + } + + val missingInShards = projectsWithTestCount + .map { it.first } + .minus(allShardedProjects.toSet()) + + if (missingInShards.isNotEmpty()) { + throw GradleException( + "There are projects missing from all shards.\n" + + "Missing projects: $missingInShards\n" + + "All shards:\n$allShardsText" + ) + } +} + +/** + * matches: + * ``` + * @org.junit.Test + * @Test + * ``` + */ +private val testAnnotationRegex = """@(?:org\.junit\.)?Test\s+""".toRegex() + +/** + * Counts all the `androidTest` functions annotated with `@Test` within this project. + * + * Each test function has a cost of 1. A project with 20 tests has a cost of 20. + */ +private fun Project.androidTestCost(): Int { + + val androidTestSrc = file("src/androidTest/java") + + if (!androidTestSrc.exists()) return 0 + + return androidTestSrc + .walkTopDown() + .filter { it.isFile && it.extension == "kt" } + .sumOf { file -> + val fileText = file.readText() + + testAnnotationRegex.findAll(fileText).count() + } +} diff --git a/build-logic/src/main/java/com/squareup/workflow1/buildsrc/sharding/ShardMatrixYamlTask.kt b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/sharding/ShardMatrixYamlTask.kt new file mode 100644 index 0000000000..0437afbc4a --- /dev/null +++ b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/sharding/ShardMatrixYamlTask.kt @@ -0,0 +1,227 @@ +package com.squareup.workflow1.buildsrc.sharding + +import com.android.build.gradle.internal.tasks.factory.dependsOn +import com.squareup.workflow1.buildsrc.diffString +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.language.base.plugins.LifecycleBasePlugin +import java.io.File +import javax.inject.Inject +import kotlin.LazyThreadSafetyMode.NONE + +/** + * This task manages test shard matrix configuration in a GitHub Actions workflow file, + * ensuring that it matches the value of [numShards]. + * + * @property yamlFile The CI configuration file this task works on. + * @property startTagProperty The start tag to identify the matrix section in the CI configuration file. + * @property endTagProperty The end tag to identify the matrix section in the CI configuration file. + * @property numShards The number of shards to use for tests. + * @property autoCorrect If `true`, the task will automatically correct any incorrect test shard + * matrix configurations. + * @property updateTaskName The name of the task that updates the test shard matrix. + */ +abstract class ShardMatrixYamlTask @Inject constructor( + objectFactory: ObjectFactory +) : DefaultTask() { + + /** kotlin.yml */ + @get:InputFile + @get:PathSensitive(RELATIVE) + val yamlFile = objectFactory.fileProperty() + + /** + * Used to identify the start of the matrix. + * Everything after this tag and before the end tag will be overwritten. + * + * ex: `### ` + */ + @get:Input abstract val startTagProperty: Property + private val startTag: String + get() = startTagProperty.get() + + /** + * Used to identify the end of the matrix. + * ex: `### ` + * */ + @get:Input abstract val endTagProperty: Property + private val endTag: String + get() = endTagProperty.get() + + /** for `3`, the matrix value would be `[ 1, 2, 3]` */ + @get:Input + abstract val numShards: Property + + /** + * If true the file will be updated. If false, the task will fail if the matrix is out of date. + */ + @get:Input + abstract val autoCorrect: Property + + @get:Input + abstract val updateTaskName: Property + + private val matrixSectionRegex by lazy(NONE) { + + val startTagEscaped = Regex.escape(startTag) + val endTagEscaped = Regex.escape(endTag) + + Regex("""( *)(.*$startTagEscaped.*\n)[\s\S]+?(.*$endTagEscaped)""") + } + + @TaskAction + fun execute() { + val ciFile = requireCiFile() + + val ciText = ciFile.readText() + + val newText = replaceYamlSections(ciText) + + if (ciText != newText) { + + if (autoCorrect.get()) { + + ciFile.writeText(newText) + + val message = "Updated the test shard matrix in the CI file.\n" + + "\tfile://${yamlFile.get()}" + + services + .get(StyledTextOutputFactory::class.java) + .create("workflow-yaml-matrix") + .withStyle(StyledTextOutput.Style.Description) + .println(message) + + println() + println(diffString(ciText, newText)) + println() + } else { + val message = "The test shard matrix in the CI file is out of date.\n" + + "\tfile://${yamlFile.get()}\n\n" + + "Run ./gradlew ${updateTaskName.get()} to automatically update." + + throw GradleException(message) + } + } + } + + private fun replaceYamlSections(ciText: String): String { + + if (!ciText.contains(matrixSectionRegex)) { + val message = + "Couldn't find any `$startTag`/`$endTag` sections in the CI file:" + + "\tfile://${yamlFile.get()}\n\n" + + "\tSurround the matrix section with the comments '$startTag' and `$endTag':\n\n" + + "\t strategy:\n" + + "\t ### $startTag\n" + + "\t matrix:\n" + + "\t [ ... ]\n" + + "\t ### $endTag\n" + + throw GradleException(message) + } + + return ciText.replace(matrixSectionRegex) { match -> + + val (indent, startTag, closingLine) = match.destructured + + val newContent = createYaml(indent, numShards.get()) + + "$indent$startTag$newContent$closingLine" + } + } + + private fun requireCiFile(): File { + val ciFile = yamlFile.get().asFile + + require(ciFile.exists()) { + "Could not resolve file: file://$ciFile" + } + + return ciFile + } + + private fun createYaml( + indent: String, + numShards: Int + ): String { + + val shardList = buildString { + append("[ ") + repeat(numShards) { + val i = it + 1 + append("$i") + if (i < numShards) append(", ") + } + append(" ]") + } + + return "${indent}shardNum : $shardList\n" + } + + companion object { + /** + * Registers tasks to check and update the test shard matrix configuration in `kotlin.yml`. + * + * @param shardCount The number of test shards. + * @param startTagName The start tag to identify the matrix section in `kotlin.yml`. + * @param endTagName The end tag to identify the matrix section in `kotlin.yml`. + * @param taskNamePart The part of the sharded task name which will be prepended to + * the matrix update task names. + * @param yamlFile presumably `kotlin.yml`. + */ + fun Project.registerYamlShardsTasks( + shardCount: Int, + startTagName: String, + endTagName: String, + taskNamePart: String, + yamlFile: File + ) { + + require(yamlFile.exists()) { + "Could not resolve '$yamlFile'." + } + + val updateName = "${taskNamePart}ShardMatrixYamlUpdate" + val updateTask = tasks.register( + updateName, + ShardMatrixYamlTask::class.java + ) { + val task = this + task.yamlFile.set(yamlFile) + numShards.set(shardCount) + startTagProperty.set(startTagName) + endTagProperty.set(endTagName) + autoCorrect.set(true) + updateTaskName.set(updateName) + } + + val checkTask = tasks.register( + "${taskNamePart}ShardMatrixYamlCheck", + ShardMatrixYamlTask::class.java + ) { + val task = this + task.yamlFile.set(yamlFile) + numShards.set(shardCount) + startTagProperty.set(startTagName) + endTagProperty.set(endTagName) + autoCorrect.set(false) + updateTaskName.set(updateName) + mustRunAfter(updateTask) + } + + // Automatically run this check task when running the `check` lifecycle task + tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(checkTask) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index b2b5487f40..dbca9edf6d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import com.squareup.workflow1.buildsrc.shardConnectedCheckTasks import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask import java.net.URL @@ -28,6 +29,8 @@ plugins { alias(libs.plugins.ktlint) } +shardConnectedCheckTasks(project) + subprojects { afterEvaluate { diff --git a/dependencies/classpath.txt b/dependencies/classpath.txt index 5db68a654c..5c1c3de28c 100644 --- a/dependencies/classpath.txt +++ b/dependencies/classpath.txt @@ -86,6 +86,7 @@ com.vanniktech:nexus:0.22.0 commons-codec:commons-codec:1.11 commons-io:commons-io:2.4 commons-logging:commons-logging:1.2 +io.github.java-diff-utils:java-diff-utils:4.12 io.grpc:grpc-api:1.39.0 io.grpc:grpc-context:1.39.0 io.grpc:grpc-core:1.39.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d972ffd55e..143f4e97f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,6 +46,7 @@ google-material = "1.4.0" groovy = "3.0.9" jUnit = "4.13.2" +java-diff-utils = "4.12" javaParser = "3.24.0" kotest = "5.1.0" kotlin = "1.8.10" @@ -182,6 +183,8 @@ google-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin hamcrest = "org.hamcrest:hamcrest-core:2.2" +java-diff-utils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "java-diff-utils" } + jetbrains-annotations = "org.jetbrains:annotations:19.0.0" junit = { module = "junit:junit", version.ref = "jUnit" } From b44638faa6cd8122fc7eb6ee20047cb6ea3f2ad7 Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Tue, 27 Jun 2023 13:23:21 -0500 Subject: [PATCH 05/35] Hash all source files for cache keys, and retry progressively smaller keys when there's a miss --- .github/actions/gradle-task/action.yml | 33 +++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/actions/gradle-task/action.yml b/.github/actions/gradle-task/action.yml index 50b9874beb..ff91471749 100644 --- a/.github/actions/gradle-task/action.yml +++ b/.github/actions/gradle-task/action.yml @@ -41,6 +41,18 @@ runs : cache-read-only : false gradle-home-cache-cleanup : true + # Calculate all the hashes for keys just one time. + # These should only be referenced before the actual task action, since that action + # may generate changes and we want the final cache key to reflect its current state. + - name : Calculate hashes + id : hashes + shell: bash + run : | + echo "lib_versions=${{ hashFiles('**/libs.versions.toml') }}" >> $GITHUB_OUTPUT + echo "gradle_props=${{ hashFiles('**/gradle.properties') }}" >> $GITHUB_OUTPUT + echo "gradle_kts=${{ hashFiles('**/*.gradle.kts') }}" >> $GITHUB_OUTPUT + echo "src_kt=${{ hashFiles('**/src/**/*.kt') }}" >> $GITHUB_OUTPUT + # Attempt to restore from the write-cache-key, or fall back to a partial match for the write key. # Skipped if the write-cache-key wasn't set. # This step's "cache_hit" output will only be true if an exact match was found. @@ -51,9 +63,14 @@ runs : with : path : | ~/.gradle/caches/build-cache-1 - ./**/build/** - key : ${{runner.os}}-${{inputs.write-cache-key}}-${{hashFiles('**/*.gradle.kt*')}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}} - restore-keys : ${{runner.os}}-${{inputs.write-cache-key}} + ./**/build + ./**/.gradle + key : ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} + restore-keys : | + ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}} + ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}} + ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}} + ${{runner.os}}-${{inputs.write-cache-key}} # Attempt to restore from the restore-cache-key, or fall back to a partial match for the restore key. # Skipped if the restore-cache-key wasn't set, or if the write-cache-key restore had an exact match. @@ -64,8 +81,12 @@ runs : path : | ~/.gradle/caches/build-cache-1 ./**/build/** - key : ${{runner.os}}-${{inputs.restore-cache-key}}-${{hashFiles('**/*.gradle.kt*')}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}} - restore-keys : ${{runner.os}}-${{inputs.restore-cache-key}} + key : ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} + restore-keys : | + ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}} + ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}} + ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}} + ${{runner.os}}-${{inputs.restore-cache-key}} - uses : gradle/wrapper-validation-action@v1 @@ -93,7 +114,7 @@ runs : path : | ~/.gradle/caches/build-cache-1 ./**/build/** - key : ${{runner.os}}-${{inputs.write-cache-key}}-${{hashFiles('**/*.gradle.kt*')}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}} + key : ${{runner.os}}-${{inputs.write-cache-key}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}}-${{hashFiles('**/*.gradle.kts')}}-${{hashFiles('**/src/**/*.kt')}} - name : Upload heap dump if : failure() From 0c0cbb380250e3df89b1ab24c8a2e3d04dda36d9 Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Tue, 27 Jun 2023 16:10:38 -0500 Subject: [PATCH 06/35] add caching support for AVDs --- .github/actions/gradle-task/action.yml | 9 +- .../gradle-tasks-with-emulator/action.yml | 88 +++++++++++++++++++ .github/workflows/kotlin.yml | 84 ++++-------------- .../buildsrc/shardConnectedChecks.kt | 28 +++++- .../src/main/java/kotlin-android.gradle.kts | 27 ++++++ 5 files changed, 163 insertions(+), 73 deletions(-) create mode 100644 .github/actions/gradle-tasks-with-emulator/action.yml diff --git a/.github/actions/gradle-task/action.yml b/.github/actions/gradle-task/action.yml index ff91471749..855cac7473 100644 --- a/.github/actions/gradle-task/action.yml +++ b/.github/actions/gradle-task/action.yml @@ -63,6 +63,7 @@ runs : with : path : | ~/.gradle/caches/build-cache-1 + ~/.konan ./**/build ./**/.gradle key : ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} @@ -80,7 +81,9 @@ runs : with : path : | ~/.gradle/caches/build-cache-1 - ./**/build/** + ~/.konan + ./**/build + ./**/.gradle key : ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} restore-keys : | ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}} @@ -113,7 +116,9 @@ runs : with : path : | ~/.gradle/caches/build-cache-1 - ./**/build/** + ~/.konan + ./**/build + ./**/.gradle key : ${{runner.os}}-${{inputs.write-cache-key}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}}-${{hashFiles('**/*.gradle.kts')}}-${{hashFiles('**/src/**/*.kt')}} - name : Upload heap dump diff --git a/.github/actions/gradle-tasks-with-emulator/action.yml b/.github/actions/gradle-tasks-with-emulator/action.yml new file mode 100644 index 0000000000..ef628fc4ef --- /dev/null +++ b/.github/actions/gradle-tasks-with-emulator/action.yml @@ -0,0 +1,88 @@ +name : Run Android Instrumentation Tests with Artifact and AVD Caching +description: This action sets up Gradle, runs a preparatory task, runs Android tests on an emulator, and uploads test results. + +inputs: + prepare-task: + description: 'Gradle task for preparing necessary artifacts. Supports multi-line input.' + required: true + test-task: + description: 'Gradle task for running instrumentation tests. Supports multi-line input.' + required: true + api-level : + description : 'The Android SDK api level, like `29`' + required : true + build-root-directory : + description : 'Path to the root directory of the build' + required : false + java-version : + description : 'The Java version to set up.' + default : '11' + distribution : + description : 'The JDK distribution to use.' + default : 'zulu' + restore-cache-key : + description : 'The unique identifier for the associated cache. Any other consumers or producers for this cache must use the same name.' + default : 'null' + write-cache-key : + description : 'The unique identifier for the associated cache. Any other consumers or producers for this cache must use the same name.' + default : 'null' + +runs : + using : 'composite' + steps : + + # Create or fetch the artifacts used for these tests. + - name : Run ${{ inputs.prepare-task }} + uses : ./.github/actions/gradle-task + with : + build-root-directory : ${{ inputs.build-root-directory }} + distribution : ${{ inputs.distribution }} + java-version : ${{ inputs.java-version }} + restore-cache-key : ${{ inputs.restore-cache-key }} + task : ${{ inputs.prepare-task }} + write-cache-key : ${{ inputs.write-cache-key }} + + # Get the AVD if it's already cached. + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + # If the AVD cache didn't exist, create an AVD and cache it. + - 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: ${{ inputs.api-level }} + arch : x86_64 + disable-animations: false + emulator-boot-timeout: 12000 + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + force-avd-creation: false + profile : Galaxy Nexus + ram-size: 4096M + script: echo "Generated AVD snapshot." + + # Run the actual emulator tests. + # At this point every task should be up-to-date and the AVD should be ready to go. + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ inputs.api-level }} + arch : x86_64 + disable-animations: true + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + force-avd-creation: false + profile : Galaxy Nexus + script : ./gradlew ${{ inputs.test-task }} + + - name : Upload results + if : ${{ always() }} + uses : actions/upload-artifact@v3 + with : + name : instrumentation-test-results + path : ./**/build/reports/androidTests/connected/** diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 8e7b6c54c1..36915096f3 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -274,29 +274,13 @@ jobs : steps : - uses : actions/checkout@v3 - ## Build before running tests, using cache. - - name : Build instrumented tests - uses : ./.github/actions/gradle-task + - name : Instrumented tests + uses : ./.github/actions/gradle-tasks-with-emulator with : - task : :benchmarks:performance-poetry:complex-poetry:assembleDebugAndroidTest - restore-cache-key : main-build-artifacts - - ## Actual task - - name : Render Pass Counting Test - uses : reactivecircus/android-emulator-runner@v2 - with : - # @ychescale9 suspects Galaxy Nexus is the fastest one - profile : Galaxy Nexus api-level : ${{ matrix.api-level }} - arch : x86_64 - script : ./gradlew :benchmarks:performance-poetry:complex-poetry:connectedCheck --continue - - - name : Upload results - if : ${{ always() }} - uses : actions/upload-artifact@v3 - with : - name : renderpass-counting-results-${{ matrix.api-level }} - path : ./**/build/reports/androidTests/connected/** + prepare-task : :benchmarks:performance-poetry:complex-poetry:prepareDebugAndroidTestArtifacts + test-task : :benchmarks:performance-poetry:complex-poetry:connectedCheck --continue + restore-cache-key : androidTest-build-artifacts instrumentation-tests : name : Instrumentation tests @@ -316,31 +300,14 @@ jobs : steps : - uses : actions/checkout@v3 - ## Build before running tests, using cache. - - name : Build instrumented tests - uses : ./.github/actions/gradle-task - with : - task : assembleDebugAndroidTest - write-cache-key : androidTest-build-artifacts - restore-cache-key : main-build-artifacts - - ## Actual task - - name : Instrumentation Tests - uses : reactivecircus/android-emulator-runner@v2 + - name : Instrumented tests + uses : ./.github/actions/gradle-tasks-with-emulator with : - # @ychescale9 suspects Galaxy Nexus is the fastest one - profile : Galaxy Nexus api-level : ${{ matrix.api-level }} - arch : x86_64 - # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheckShard${{ matrix.shardNum }} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck - - - name : Upload results - if : ${{ always() }} - uses : actions/upload-artifact@v3 - with : - name : instrumentation-test-results-${{ matrix.api-level }}-shard_${{ matrix.shardNum }} - path : ./**/build/reports/androidTests/connected/** + prepare-task : prepareConnectedCheckShard${{matrix.shardNum}} + test-task : connectedCheckShard${{matrix.shardNum}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck + write-cache-key : androidTest-build-artifacts-${{matrix.shardNum}} + restore-cache-key : main-build-artifacts runtime-instrumentation-tests : name : Conflate Stale Renderings Instrumentation tests @@ -359,31 +326,14 @@ jobs : steps : - uses : actions/checkout@v3 - # This really just pulls the cache from the dependency job - - name : Build instrumented tests - uses : ./.github/actions/gradle-task - with : - task : assembleDebugAndroidTest -Pworkflow.runtime=${{matrix.runtime}} - write-cache-key : androidTest-build-artifacts-${{matrix.runtime}} - restore-cache-key : main-build-artifacts - - ## Actual task - - name : Instrumentation Tests - uses : reactivecircus/android-emulator-runner@v2 + - name : Instrumented tests + uses : ./.github/actions/gradle-tasks-with-emulator with : - # @ychescale9 suspects Galaxy Nexus is the fastest one - profile : Galaxy Nexus api-level : ${{ matrix.api-level }} - arch : x86_64 - # Skip the benchmarks as this is running on emulators - script : ./gradlew connectedCheckShard${{ matrix.shardNum }} -Pworkflow.runtime=${{matrix.runtime}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck - - - name : Upload results - if : ${{ always() }} - uses : actions/upload-artifact@v3 - with : - name : conflate-instrumentation-test-results-${{ matrix.api-level }}-shard_${{ matrix.shardNum }} - path : ./**/build/reports/androidTests/connected/** + prepare-task : prepareConnectedCheckShard${{matrix.shardNum}} -Pworkflow.runtime=${{matrix.runtime}} + test-task : connectedCheckShard${{matrix.shardNum}} -Pworkflow.runtime=${{matrix.runtime}} -x :benchmarks:dungeon-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-benchmark:connectedCheck -x :benchmarks:performance-poetry:complex-poetry:connectedCheck + write-cache-key : androidTest-build-artifacts-${{matrix.shardNum}}-${{matrix.runtime}} + restore-cache-key : main-build-artifacts all-green : if : always() diff --git a/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt index f64057d7bd..cdf1e59762 100644 --- a/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt +++ b/build-logic/src/main/java/com/squareup/workflow1/buildsrc/shardConnectedChecks.kt @@ -63,19 +63,21 @@ fun shardConnectedCheckTasks(target: Project) { shardAssignments.forEach { shard -> + val projects by shard.projectsLazy + + val paths by lazy { + projects.joinToString(prefix = "[ ", postfix = " ]") { it.path } + } + target.tasks.register("connectedCheckShard${shard.number}") { group = "Verification" - val projects = shard.projects - validateSharding( projectsWithTestCount = projectsWithTestCount.value, shardAssignments = shardAssignments ) - val paths = projects.joinToString(prefix = "[ ", postfix = " ]") { it.path } - description = "Runs $connectedTestName in projects: $paths" val assignedTests = projects.map { project -> @@ -84,6 +86,24 @@ fun shardConnectedCheckTasks(target: Project) { dependsOn(assignedTests) } + + target.tasks.register("prepareConnectedCheckShard${shard.number}") { + + validateSharding( + projectsWithTestCount = projectsWithTestCount.value, + shardAssignments = shardAssignments + ) + + description = "Builds all artifacts for running connected tests in projects: $paths" + + val regex = Regex("""prepare[A-Z]\w*AndroidTestArtifacts""") + + val prepareTasks = projects.map { project -> + project.tasks.matching { it.name.matches(regex) } + } + + dependsOn(prepareTasks) + } } } diff --git a/build-logic/src/main/java/kotlin-android.gradle.kts b/build-logic/src/main/java/kotlin-android.gradle.kts index a4f228adde..d2afd125a3 100644 --- a/build-logic/src/main/java/kotlin-android.gradle.kts +++ b/build-logic/src/main/java/kotlin-android.gradle.kts @@ -1,4 +1,6 @@ +import com.android.build.api.variant.AndroidComponentsExtension import com.squareup.workflow1.buildsrc.kotlinCommonSettings +import org.gradle.configurationcache.extensions.capitalized plugins { kotlin("android") @@ -10,3 +12,28 @@ extensions.getByType(JavaPluginExtension::class).apply { } project.kotlinCommonSettings(bomConfigurationName = "implementation") + +// For every variant which extends the `debug` type, create a new task which generates all the +// artifacts used in the associated `connected____AndroidTest` task. +extensions.configure>("androidComponents") { + onVariants(selector().withBuildType("debug")) { variant -> + + val nameCaps = variant.name.capitalized() + val testTask = "connected${nameCaps}AndroidTest" + + tasks.register("prepare${nameCaps}AndroidTestArtifacts") { + description = "Creates all artifacts used in `$testTask` without trying to execute tests." + + dependsOn(tasks.getByName(testTask).taskDependencies) + } + } +} + + +/* + +macOS-main-build-artifacts-09555650eb7d6a7cc5d80ddfef5d6ea7bcf31c6cacdf093c62360bd678cb852f-3bd2c177794706d31840536092bfe0e2022bde51249e328646cb585e83e3c119-11a8ec8535b59590a882b41cbf4c2a235a2995758775ba8f9d82847ed5f3f87c-470e8ec00b1701c50f78eb15d625d72753e92d468b096562a31ffab8e1d3f1dd +macOS-main-build-artifacts-09555650eb7d6a7cc5d80ddfef5d6ea7bcf31c6cacdf093c62360bd678cb852f-3bd2c177794706d31840536092bfe0e2022bde51249e328646cb585e83e3c119-ea20820e0c811509dddd86ac8a9f53b6d1bd0785caed7898f1ef2d3390cfae19-470e8ec00b1701c50f78eb15d625d72753e92d468b096562a31ffab8e1d3f1dd + + + */ From 1065080ee8c69dd0f3523dd47b32935227a1c8e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 03:53:29 +0000 Subject: [PATCH 07/35] Update robolectric to v4.10.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 143f4e97f1..70f2c8b820 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,7 +65,7 @@ mockito-core = "3.3.3" mockito-kotlin = "3.2.0" mockk = "1.13.5" -robolectric = "4.9.2" +robolectric = "4.10.3" rxjava2-android = "2.1.1" rxjava2-core = "2.2.21" From 2c515f9c45087626c281a4f06487b1716aebadbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 05:14:54 +0000 Subject: [PATCH 08/35] Update dependency org.mockito:mockito-core to v3.12.4 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70f2c8b820..0e0f2a6e6e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ ktlint-gradle = "0.1.7" material = "1.3.0" mavenPublish = "0.13.0" -mockito-core = "3.3.3" +mockito-core = "3.12.4" mockito-kotlin = "3.2.0" mockk = "1.13.5" From 9e38ca2d3526fbd214a52ae968671a16c9e614cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 06:17:13 +0000 Subject: [PATCH 09/35] Update dependency com.google.truth:truth to v1.1.5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e0f2a6e6e..da68a9980a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,7 +82,7 @@ squareup-seismic = "1.0.3" squareup-workflow = "1.0.0" timber = "4.7.1" -truth = "1.1.3" +truth = "1.1.5" turbine = "0.13.0" vanniktech-publish = "0.22.0" From ca9e81cb8d0cdcf9e25c995afae69e580b75042b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 07:01:16 +0000 Subject: [PATCH 10/35] Update squareup.leakcanary to v2.11 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da68a9980a..5188ff0ff0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ rxjava2-core = "2.2.21" squareup-curtains = "1.2.4" squareup-cycler = "0.1.9" -squareup-leakcanary = "2.10" +squareup-leakcanary = "2.11" squareup-moshi = "1.13.0" squareup-okhttp = "4.9.1" squareup-okio = "3.0.0" From 240620c534fa4fe3923c449157cf09c50b5f92ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 08:06:41 +0000 Subject: [PATCH 11/35] Update dependency androidx.test.ext:truth to v1.5.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5188ff0ff0..4c0ded440f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ androidx-test = "1.5.0" androidx-test-espresso = "3.5.1" androidx-test-junit-ext = "1.1.5" androidx-test-runner = "1.5.2" -androidx-test-truth-ext = "1.4.0" +androidx-test-truth-ext = "1.5.0" androidx-tracing = "1.1.0" androidx-transition = "1.4.1" androidx-viewbinding = "4.2.1" From d7b38c9b2b8c7be0345a5a69453f2f8d4ed0c5d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 10:37:57 +0000 Subject: [PATCH 12/35] Update dependency androidx.savedstate:savedstate to v1.2.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c0ded440f..b757a4c6a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ androidx-paging = "3.0.1" androidx-profileinstaller = "1.2.0-alpha02" androidx-recyclerview = "1.2.1" androidx-room = "2.4.0-alpha04" -androidx-savedstate = "1.1.0" +androidx-savedstate = "1.2.1" androidx-startup = "1.1.0" androidx-test = "1.5.0" androidx-test-espresso = "3.5.1" From ff5649b6854aee776919838598bb4f08777a0565 Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 28 Jun 2023 06:20:07 -0500 Subject: [PATCH 13/35] update dependencyGuard baseline --- .../compose-tooling/dependencies/releaseRuntimeClasspath.txt | 4 ++-- workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt | 4 ++-- .../dependencies/releaseRuntimeClasspath.txt | 2 +- .../core-android/dependencies/releaseRuntimeClasspath.txt | 2 +- .../radiography/dependencies/releaseRuntimeClasspath.txt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt index b3fbbd67d9..8beb2049cc 100644 --- a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt @@ -34,8 +34,8 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 androidx.lifecycle:lifecycle-viewmodel:2.5.1 androidx.profileinstaller:profileinstaller:1.2.0 -androidx.savedstate:savedstate-ktx:1.2.0 -androidx.savedstate:savedstate:1.2.0 +androidx.savedstate:savedstate-ktx:1.2.1 +androidx.savedstate:savedstate:1.2.1 androidx.startup:startup-runtime:1.1.1 androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 diff --git a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt index af7f4e383d..a4ccab55d2 100644 --- a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt @@ -31,8 +31,8 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 androidx.lifecycle:lifecycle-viewmodel:2.5.1 androidx.profileinstaller:profileinstaller:1.2.0 -androidx.savedstate:savedstate-ktx:1.2.0 -androidx.savedstate:savedstate:1.2.0 +androidx.savedstate:savedstate-ktx:1.2.1 +androidx.savedstate:savedstate:1.2.1 androidx.startup:startup-runtime:1.1.1 androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 diff --git a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt index 7fece34677..73fcee77dc 100644 --- a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt @@ -26,7 +26,7 @@ androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 androidx.lifecycle:lifecycle-viewmodel:2.5.1 androidx.loader:loader:1.0.0 androidx.resourceinspection:resourceinspection-annotation:1.0.1 -androidx.savedstate:savedstate:1.2.0 +androidx.savedstate:savedstate:1.2.1 androidx.startup:startup-runtime:1.1.1 androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 diff --git a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt index 7d7d9bc105..05d187039e 100644 --- a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt @@ -13,7 +13,7 @@ androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 androidx.lifecycle:lifecycle-runtime:2.5.1 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 androidx.lifecycle:lifecycle-viewmodel:2.5.1 -androidx.savedstate:savedstate:1.2.0 +androidx.savedstate:savedstate:1.2.1 androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 diff --git a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt index 1db03758e3..2b2a6f75e2 100644 --- a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt @@ -13,7 +13,7 @@ androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 androidx.lifecycle:lifecycle-runtime:2.5.1 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 androidx.lifecycle:lifecycle-viewmodel:2.5.1 -androidx.savedstate:savedstate:1.2.0 +androidx.savedstate:savedstate:1.2.1 androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 From f6786991ce464fccc7a1180838dc7fcd5e2a2b24 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 12:14:58 +0000 Subject: [PATCH 14/35] Update dependency app.cash.turbine:turbine to v1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b757a4c6a5..67c63c190a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,7 +83,7 @@ squareup-workflow = "1.0.0" timber = "4.7.1" truth = "1.1.5" -turbine = "0.13.0" +turbine = "1.0.0" vanniktech-publish = "0.22.0" [plugins] From c1bab40f3913c87d12c668df29ace81eb4020fef Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 28 Jun 2023 07:55:44 -0500 Subject: [PATCH 15/35] add `PR_UPDATE_TOKEN` parameter to auto-fix CI jobs --- .github/actions/gradle-task-with-commit/action.yml | 4 +++- .github/workflows/kotlin.yml | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/gradle-task-with-commit/action.yml b/.github/actions/gradle-task-with-commit/action.yml index 1e01d520f2..bf41a202d3 100644 --- a/.github/actions/gradle-task-with-commit/action.yml +++ b/.github/actions/gradle-task-with-commit/action.yml @@ -29,12 +29,14 @@ inputs : runs: using: 'composite' steps: - - name: Check if PERSONAL_ACCESS_TOKEN is set + - name: Check if access token is set id: can-push shell: bash run: | if [[ "${{ inputs.personal-access-token }}" == '' ]]; then echo "can_push=false" >> $GITHUB_OUTPUT + elif [[ "${{ env.GITHUB_REF_PROTECTED }}" == 'true' ]]; then + echo "can_push=false" >> $GITHUB_OUTPUT elif [[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then echo "can_push=false" >> $GITHUB_OUTPUT else diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 36915096f3..9c6c19df3d 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -50,6 +50,7 @@ jobs : with : check-task : connectedCheckShardMatrixYamlCheck checkVersionIsSnapshot fix-task : connectedCheckShardMatrixYamlUpdate checkVersionIsSnapshot + personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }} write-cache-key : build-logic artifacts-check : @@ -64,6 +65,7 @@ jobs : with : check-task : artifactsCheck fix-task : artifactsDump + personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }} write-cache-key : build-logic dependency-guard : @@ -79,6 +81,7 @@ jobs : with : check-task : dependencyGuard --refresh-dependencies fix-task : dependencyGuardBaseline --refresh-dependencies + personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }} write-cache-key : build-logic ktlint : @@ -94,6 +97,7 @@ jobs : with : check-task : ktLintCheck fix-task : ktLintFormat + personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }} write-cache-key : build-logic api-check : @@ -109,6 +113,7 @@ jobs : with : check-task : apiCheck fix-task : apiDump + personal-access-token: ${{ secrets.PR_UPDATE_TOKEN }} write-cache-key : build-logic android-lint : From 7975c05c24ff9c661e3f54f0dc4391ce422bb57e Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 28 Jun 2023 08:25:36 -0500 Subject: [PATCH 16/35] exclude .dex files from caching --- .github/actions/gradle-task/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/gradle-task/action.yml b/.github/actions/gradle-task/action.yml index 855cac7473..9171e02394 100644 --- a/.github/actions/gradle-task/action.yml +++ b/.github/actions/gradle-task/action.yml @@ -64,7 +64,7 @@ runs : path : | ~/.gradle/caches/build-cache-1 ~/.konan - ./**/build + ./**/build/**/!(*.dex) ./**/.gradle key : ${{runner.os}}-${{inputs.write-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} restore-keys : | @@ -82,7 +82,7 @@ runs : path : | ~/.gradle/caches/build-cache-1 ~/.konan - ./**/build + ./**/build/**/!(*.dex) ./**/.gradle key : ${{runner.os}}-${{inputs.restore-cache-key}}-${{steps.hashes.outputs.lib_versions}}-${{steps.hashes.outputs.gradle_props}}-${{steps.hashes.outputs.gradle_kts}}-${{steps.hashes.outputs.src_kt}} restore-keys : | @@ -117,7 +117,7 @@ runs : path : | ~/.gradle/caches/build-cache-1 ~/.konan - ./**/build + ./**/build/**/!(*.dex) ./**/.gradle key : ${{runner.os}}-${{inputs.write-cache-key}}-${{hashFiles('**/libs.versions.toml')}}-${{hashFiles('**/gradle.properties')}}-${{hashFiles('**/*.gradle.kts')}}-${{hashFiles('**/src/**/*.kt')}} From 65d5d74328dac5977d6db60f70ee6c274b4ad0f0 Mon Sep 17 00:00:00 2001 From: RBusarow Date: Wed, 28 Jun 2023 13:33:35 +0000 Subject: [PATCH 17/35] Apply changes from dependencyGuardBaseline --refresh-dependencies Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- workflow-testing/dependencies/runtimeClasspath.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workflow-testing/dependencies/runtimeClasspath.txt b/workflow-testing/dependencies/runtimeClasspath.txt index e28f00ccab..c0c152c04f 100644 --- a/workflow-testing/dependencies/runtimeClasspath.txt +++ b/workflow-testing/dependencies/runtimeClasspath.txt @@ -1,10 +1,10 @@ -app.cash.turbine:turbine-jvm:0.13.0 -app.cash.turbine:turbine:0.13.0 +app.cash.turbine:turbine-jvm:1.0.0 +app.cash.turbine:turbine:1.0.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-reflect:1.8.10 -org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 From 802da924b047128171e42f608151de3d9558d085 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 08:06:51 +0000 Subject: [PATCH 18/35] Update squareup.moshi to v1.15.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67c63c190a..4ef36f9e2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -73,7 +73,7 @@ rxjava2-core = "2.2.21" squareup-curtains = "1.2.4" squareup-cycler = "0.1.9" squareup-leakcanary = "2.11" -squareup-moshi = "1.13.0" +squareup-moshi = "1.15.0" squareup-okhttp = "4.9.1" squareup-okio = "3.0.0" squareup-radiography = "2.4.1" From 0bbb78268985c5ca0bdca52cbb04d1f895417acf Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 28 Jun 2023 05:23:29 -0500 Subject: [PATCH 19/35] update dependencyGuard baseline for Moshi --- dependencies/classpath.txt | 4 ++-- trace-encoder/dependencies/runtimeClasspath.txt | 12 ++++++------ workflow-tracing/dependencies/runtimeClasspath.txt | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dependencies/classpath.txt b/dependencies/classpath.txt index 5c1c3de28c..f932da5741 100644 --- a/dependencies/classpath.txt +++ b/dependencies/classpath.txt @@ -69,8 +69,8 @@ com.googlecode.java-diff-utils:diffutils:1.3.0 com.googlecode.juniversalchardet:juniversalchardet:1.0.3 com.rickbusarow.ktlint:com.rickbusarow.ktlint.gradle.plugin:0.1.7 com.rickbusarow.ktlint:ktlint-gradle-plugin:0.1.7 -com.squareup.moshi:moshi-adapters:1.13.0 -com.squareup.moshi:moshi:1.13.0 +com.squareup.moshi:moshi-adapters:1.15.0 +com.squareup.moshi:moshi:1.15.0 com.squareup.okhttp3:okhttp:4.10.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 diff --git a/trace-encoder/dependencies/runtimeClasspath.txt b/trace-encoder/dependencies/runtimeClasspath.txt index 8e1e76cbc2..286434ad94 100644 --- a/trace-encoder/dependencies/runtimeClasspath.txt +++ b/trace-encoder/dependencies/runtimeClasspath.txt @@ -1,12 +1,12 @@ -com.squareup.moshi:moshi-adapters:1.13.0 -com.squareup.moshi:moshi:1.13.0 +com.squareup.moshi:moshi-adapters:1.15.0 +com.squareup.moshi:moshi:1.15.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 -org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib:1.8.20 +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib:1.8.21 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1 org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 diff --git a/workflow-tracing/dependencies/runtimeClasspath.txt b/workflow-tracing/dependencies/runtimeClasspath.txt index 8e1e76cbc2..286434ad94 100644 --- a/workflow-tracing/dependencies/runtimeClasspath.txt +++ b/workflow-tracing/dependencies/runtimeClasspath.txt @@ -1,12 +1,12 @@ -com.squareup.moshi:moshi-adapters:1.13.0 -com.squareup.moshi:moshi:1.13.0 +com.squareup.moshi:moshi-adapters:1.15.0 +com.squareup.moshi:moshi:1.15.0 com.squareup.okio:okio-jvm:3.0.0 com.squareup.okio:okio:3.0.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 -org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -org.jetbrains.kotlin:kotlin-stdlib:1.8.20 +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 +org.jetbrains.kotlin:kotlin-stdlib:1.8.21 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1 org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 From a136289ec9bd6e6083d46e1b9a554374f960d108 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:40:08 +0000 Subject: [PATCH 20/35] Update dependency org.jetbrains:annotations to v24 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ef36f9e2d..b812fc664e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -185,7 +185,7 @@ hamcrest = "org.hamcrest:hamcrest-core:2.2" java-diff-utils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "java-diff-utils" } -jetbrains-annotations = "org.jetbrains:annotations:19.0.0" +jetbrains-annotations = "org.jetbrains:annotations:24.0.1" junit = { module = "junit:junit", version.ref = "jUnit" } From ec5b535138596cb7b9e4321a18c6800eb37fcb60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:26:45 +0000 Subject: [PATCH 21/35] Update vanniktech.publish to v0.25.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b812fc664e..c9a6206c03 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,7 +84,7 @@ squareup-workflow = "1.0.0" timber = "4.7.1" truth = "1.1.5" turbine = "1.0.0" -vanniktech-publish = "0.22.0" +vanniktech-publish = "0.25.2" [plugins] From 32b34f7e9d25b9b6d00d6d2824804ef5fbdade8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 28 Jun 2023 18:29:31 +0000 Subject: [PATCH 22/35] Apply changes from dependencyGuardBaseline --refresh-dependencies Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- dependencies/classpath.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/classpath.txt b/dependencies/classpath.txt index f932da5741..70061480b4 100644 --- a/dependencies/classpath.txt +++ b/dependencies/classpath.txt @@ -81,8 +81,8 @@ com.squareup:javawriter:2.5.0 com.sun.activation:javax.activation:1.2.0 com.sun.istack:istack-commons-runtime:3.0.8 com.sun.xml.fastinfoset:FastInfoset:1.2.16 -com.vanniktech:gradle-maven-publish-plugin:0.22.0 -com.vanniktech:nexus:0.22.0 +com.vanniktech:gradle-maven-publish-plugin:0.25.2 +com.vanniktech:nexus:0.25.2 commons-codec:commons-codec:1.11 commons-io:commons-io:2.4 commons-logging:commons-logging:1.2 From aeda0ca1863a7345c8cd7d331c52d1c0bfe43344 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:11:04 +0000 Subject: [PATCH 23/35] Update dependency com.jakewharton.timber:timber to v5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9a6206c03..eac090483c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,7 +81,7 @@ squareup-retrofit = "2.9.0" squareup-seismic = "1.0.3" squareup-workflow = "1.0.0" -timber = "4.7.1" +timber = "5.0.1" truth = "1.1.5" turbine = "1.0.0" vanniktech-publish = "0.25.2" From 3a7232be58006a66dda6f9d58aaf5e6b15b5ec6c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:06:27 +0000 Subject: [PATCH 24/35] Update squareup.leakcanary to v2.12 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eac090483c..e4c34b1115 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ rxjava2-core = "2.2.21" squareup-curtains = "1.2.4" squareup-cycler = "0.1.9" -squareup-leakcanary = "2.11" +squareup-leakcanary = "2.12" squareup-moshi = "1.15.0" squareup-okhttp = "4.9.1" squareup-okio = "3.0.0" From 424c2866d0a46e1dc044f563bd29a2bf95d0c146 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:28:33 +0000 Subject: [PATCH 25/35] Update dependency com.android.tools.build:gradle to v7.4.2 --- samples/tutorial/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/tutorial/build.gradle b/samples/tutorial/build.gradle index d0b671dd6c..763cd2306e 100644 --- a/samples/tutorial/build.gradle +++ b/samples/tutorial/build.gradle @@ -8,7 +8,7 @@ buildscript { deps = [ activityktx: 'androidx.activity:activity-ktx:1.3.0', - agp: "com.android.tools.build:gradle:7.4.1", + agp: "com.android.tools.build:gradle:7.4.2", appcompat: 'androidx.appcompat:appcompat:1.3.1', constraintlayout: 'androidx.constraintlayout:constraintlayout:2.0.1', kotlin: [ From f55b9e6560d87bb73c6b9abdd8ca91bac9aafb86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:08:00 +0000 Subject: [PATCH 26/35] Update androidTools to v7.4.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4c34b1115..a8ec5f500b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -androidTools = "7.4.1" +androidTools = "7.4.2" compileSdk = "33" minSdk = "21" From 28b504cbeb8008338530d479708a027840f1ed90 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 29 Jun 2023 23:12:33 +0000 Subject: [PATCH 27/35] Apply changes from dependencyGuardBaseline --refresh-dependencies Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- dependencies/classpath.txt | 72 +++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/dependencies/classpath.txt b/dependencies/classpath.txt index 70061480b4..44ff8b93ef 100644 --- a/dependencies/classpath.txt +++ b/dependencies/classpath.txt @@ -1,43 +1,43 @@ -androidx.databinding:databinding-common:7.4.1 -androidx.databinding:databinding-compiler-common:7.4.1 -com.android.databinding:baseLibrary:7.4.1 -com.android.tools.analytics-library:crash:30.4.1 -com.android.tools.analytics-library:protos:30.4.1 -com.android.tools.analytics-library:shared:30.4.1 -com.android.tools.analytics-library:tracker:30.4.1 +androidx.databinding:databinding-common:7.4.2 +androidx.databinding:databinding-compiler-common:7.4.2 +com.android.databinding:baseLibrary:7.4.2 +com.android.tools.analytics-library:crash:30.4.2 +com.android.tools.analytics-library:protos:30.4.2 +com.android.tools.analytics-library:shared:30.4.2 +com.android.tools.analytics-library:tracker:30.4.2 com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10 com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10 -com.android.tools.build:aapt2-proto:7.4.1-8841542 -com.android.tools.build:aaptcompiler:7.4.1 -com.android.tools.build:apksig:7.4.1 -com.android.tools.build:apkzlib:7.4.1 -com.android.tools.build:builder-model:7.4.1 -com.android.tools.build:builder-test-api:7.4.1 -com.android.tools.build:builder:7.4.1 +com.android.tools.build:aapt2-proto:7.4.2-8841542 +com.android.tools.build:aaptcompiler:7.4.2 +com.android.tools.build:apksig:7.4.2 +com.android.tools.build:apkzlib:7.4.2 +com.android.tools.build:builder-model:7.4.2 +com.android.tools.build:builder-test-api:7.4.2 +com.android.tools.build:builder:7.4.2 com.android.tools.build:bundletool:1.11.4 -com.android.tools.build:gradle-api:7.4.1 -com.android.tools.build:gradle-settings-api:7.4.1 -com.android.tools.build:gradle:7.4.1 -com.android.tools.build:manifest-merger:30.4.1 +com.android.tools.build:gradle-api:7.4.2 +com.android.tools.build:gradle-settings-api:7.4.2 +com.android.tools.build:gradle:7.4.2 +com.android.tools.build:manifest-merger:30.4.2 com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api -com.android.tools.ddms:ddmlib:30.4.1 -com.android.tools.layoutlib:layoutlib-api:30.4.1 -com.android.tools.lint:lint-model:30.4.1 -com.android.tools.lint:lint-typedef-remover:30.4.1 -com.android.tools.utp:android-device-provider-ddmlib-proto:30.4.1 -com.android.tools.utp:android-device-provider-gradle-proto:30.4.1 -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.4.1 -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.4.1 -com.android.tools.utp:android-test-plugin-host-retention-proto:30.4.1 -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.4.1 -com.android.tools:annotations:30.4.1 -com.android.tools:common:30.4.1 -com.android.tools:dvlib:30.4.1 -com.android.tools:repository:30.4.1 -com.android.tools:sdk-common:30.4.1 -com.android.tools:sdklib:30.4.1 -com.android:signflinger:7.4.1 -com.android:zipflinger:7.4.1 +com.android.tools.ddms:ddmlib:30.4.2 +com.android.tools.layoutlib:layoutlib-api:30.4.2 +com.android.tools.lint:lint-model:30.4.2 +com.android.tools.lint:lint-typedef-remover:30.4.2 +com.android.tools.utp:android-device-provider-ddmlib-proto:30.4.2 +com.android.tools.utp:android-device-provider-gradle-proto:30.4.2 +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.4.2 +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.4.2 +com.android.tools.utp:android-test-plugin-host-retention-proto:30.4.2 +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.4.2 +com.android.tools:annotations:30.4.2 +com.android.tools:common:30.4.2 +com.android.tools:dvlib:30.4.2 +com.android.tools:repository:30.4.2 +com.android.tools:sdk-common:30.4.2 +com.android.tools:sdklib:30.4.2 +com.android:signflinger:7.4.2 +com.android:zipflinger:7.4.2 com.dropbox.dependency-guard:dependency-guard:0.4.3 com.fasterxml.jackson.core:jackson-annotations:2.12.7 com.fasterxml.jackson.core:jackson-core:2.12.7 From f74df73967bd416b683fa8434d7c9a50405404b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:08:15 +0000 Subject: [PATCH 28/35] Update dependency com.squareup.okio:okio to v3.3.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a8ec5f500b..97d58a2511 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,7 +75,7 @@ squareup-cycler = "0.1.9" squareup-leakcanary = "2.12" squareup-moshi = "1.15.0" squareup-okhttp = "4.9.1" -squareup-okio = "3.0.0" +squareup-okio = "3.3.0" squareup-radiography = "2.4.1" squareup-retrofit = "2.9.0" squareup-seismic = "1.0.3" From 3edbb9280694be51960cdd3d18900646f8aeecb3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 29 Jun 2023 23:12:05 +0000 Subject: [PATCH 29/35] Apply changes from dependencyGuardBaseline --refresh-dependencies Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- trace-encoder/dependencies/runtimeClasspath.txt | 4 ++-- .../config-android/dependencies/releaseRuntimeClasspath.txt | 4 ++-- workflow-config/config-jvm/dependencies/runtimeClasspath.txt | 4 ++-- workflow-core/dependencies/jsRuntimeClasspath.txt | 4 ++-- workflow-core/dependencies/jvmRuntimeClasspath.txt | 4 ++-- workflow-core/dependencies/runtimeClasspath.txt | 4 ++-- workflow-runtime/dependencies/jsRuntimeClasspath.txt | 4 ++-- workflow-runtime/dependencies/jvmRuntimeClasspath.txt | 4 ++-- workflow-rx2/dependencies/runtimeClasspath.txt | 4 ++-- workflow-testing/dependencies/runtimeClasspath.txt | 4 ++-- workflow-tracing/dependencies/runtimeClasspath.txt | 4 ++-- .../compose-tooling/dependencies/releaseRuntimeClasspath.txt | 4 ++-- workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt | 4 ++-- .../dependencies/releaseRuntimeClasspath.txt | 4 ++-- .../container-common/dependencies/runtimeClasspath.txt | 4 ++-- .../core-android/dependencies/releaseRuntimeClasspath.txt | 4 ++-- workflow-ui/core-common/dependencies/runtimeClasspath.txt | 4 ++-- .../radiography/dependencies/releaseRuntimeClasspath.txt | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) diff --git a/trace-encoder/dependencies/runtimeClasspath.txt b/trace-encoder/dependencies/runtimeClasspath.txt index 286434ad94..aaeae6d225 100644 --- a/trace-encoder/dependencies/runtimeClasspath.txt +++ b/trace-encoder/dependencies/runtimeClasspath.txt @@ -1,7 +1,7 @@ com.squareup.moshi:moshi-adapters:1.15.0 com.squareup.moshi:moshi:1.15.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 diff --git a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt index f244b70938..b1b31b28eb 100644 --- a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 diff --git a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt index f244b70938..b1b31b28eb 100644 --- a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt +++ b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 diff --git a/workflow-core/dependencies/jsRuntimeClasspath.txt b/workflow-core/dependencies/jsRuntimeClasspath.txt index 28f247bb61..f94d8469cf 100644 --- a/workflow-core/dependencies/jsRuntimeClasspath.txt +++ b/workflow-core/dependencies/jsRuntimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-js:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-js:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-js:1.8.20 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 diff --git a/workflow-core/dependencies/jvmRuntimeClasspath.txt b/workflow-core/dependencies/jvmRuntimeClasspath.txt index ae34ead52c..66d9bc7ac4 100644 --- a/workflow-core/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-core/dependencies/jvmRuntimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10 diff --git a/workflow-core/dependencies/runtimeClasspath.txt b/workflow-core/dependencies/runtimeClasspath.txt index 3d4e96caf5..c0a590ef48 100644 --- a/workflow-core/dependencies/runtimeClasspath.txt +++ b/workflow-core/dependencies/runtimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 diff --git a/workflow-runtime/dependencies/jsRuntimeClasspath.txt b/workflow-runtime/dependencies/jsRuntimeClasspath.txt index 28f247bb61..f94d8469cf 100644 --- a/workflow-runtime/dependencies/jsRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jsRuntimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-js:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-js:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-js:1.8.20 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 diff --git a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt index 353cfc3b58..42f31697d0 100644 --- a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 diff --git a/workflow-rx2/dependencies/runtimeClasspath.txt b/workflow-rx2/dependencies/runtimeClasspath.txt index f2b5510c57..c84b70adf5 100644 --- a/workflow-rx2/dependencies/runtimeClasspath.txt +++ b/workflow-rx2/dependencies/runtimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 io.reactivex.rxjava2:rxjava:2.2.21 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 diff --git a/workflow-testing/dependencies/runtimeClasspath.txt b/workflow-testing/dependencies/runtimeClasspath.txt index c0c152c04f..1598c01128 100644 --- a/workflow-testing/dependencies/runtimeClasspath.txt +++ b/workflow-testing/dependencies/runtimeClasspath.txt @@ -1,7 +1,7 @@ app.cash.turbine:turbine-jvm:1.0.0 app.cash.turbine:turbine:1.0.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-reflect:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 diff --git a/workflow-tracing/dependencies/runtimeClasspath.txt b/workflow-tracing/dependencies/runtimeClasspath.txt index 286434ad94..aaeae6d225 100644 --- a/workflow-tracing/dependencies/runtimeClasspath.txt +++ b/workflow-tracing/dependencies/runtimeClasspath.txt @@ -1,7 +1,7 @@ com.squareup.moshi:moshi-adapters:1.15.0 com.squareup.moshi:moshi:1.15.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 diff --git a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt index 8beb2049cc..628e5d9746 100644 --- a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt @@ -41,8 +41,8 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 diff --git a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt index a4ccab55d2..0cc2b7342d 100644 --- a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt @@ -38,8 +38,8 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20 diff --git a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt index 73fcee77dc..53130e672f 100644 --- a/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/container-android/dependencies/releaseRuntimeClasspath.txt @@ -35,8 +35,8 @@ androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 com.google.guava:listenablefuture:1.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 diff --git a/workflow-ui/container-common/dependencies/runtimeClasspath.txt b/workflow-ui/container-common/dependencies/runtimeClasspath.txt index 3d4e96caf5..c0a590ef48 100644 --- a/workflow-ui/container-common/dependencies/runtimeClasspath.txt +++ b/workflow-ui/container-common/dependencies/runtimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 diff --git a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt index 05d187039e..4612d1ccac 100644 --- a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt @@ -18,8 +18,8 @@ androidx.tracing:tracing:1.0.0 androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 diff --git a/workflow-ui/core-common/dependencies/runtimeClasspath.txt b/workflow-ui/core-common/dependencies/runtimeClasspath.txt index 3d4e96caf5..c0a590ef48 100644 --- a/workflow-ui/core-common/dependencies/runtimeClasspath.txt +++ b/workflow-ui/core-common/dependencies/runtimeClasspath.txt @@ -1,5 +1,5 @@ -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10 diff --git a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt index 2b2a6f75e2..b3bd6821b9 100644 --- a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt @@ -19,8 +19,8 @@ androidx.transition:transition:1.4.1 androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 com.squareup.curtains:curtains:1.2.2 -com.squareup.okio:okio-jvm:3.0.0 -com.squareup.okio:okio:3.0.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 com.squareup.radiography:radiography:2.4.1 org.jetbrains.kotlin:kotlin-bom:1.8.10 org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 From 618befe808d209e24c9ce4caecfbb55c299ffd72 Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Thu, 22 Jun 2023 19:20:07 +0530 Subject: [PATCH 30/35] Add complete tutorial 1 --- samples/tutorial/tutorial-base/build.gradle | 4 ++ .../workflow/tutorial/TutorialActivity.kt | 38 +++++++++++++++++++ .../java/workflow/tutorial/WelcomeScreen.kt | 11 ++++++ .../tutorial/WelcomeScreenViewRunner.kt | 31 +++++++++++++++ .../java/workflow/tutorial/WelcomeWorkflow.kt | 35 +++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreen.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreenViewRunner.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt diff --git a/samples/tutorial/tutorial-base/build.gradle b/samples/tutorial/tutorial-base/build.gradle index a58e90c29c..5cfe30bd22 100644 --- a/samples/tutorial/tutorial-base/build.gradle +++ b/samples/tutorial/tutorial-base/build.gradle @@ -32,4 +32,8 @@ dependencies { implementation deps.material implementation deps.workflow.core_android implementation project(':tutorial-views') + + implementation deps.activityktx + implementation deps.viewmodelktx + implementation deps.viewmodelsavedstate } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt index 1a5ea328cf..adf3da63a7 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -1,12 +1,50 @@ +@file:OptIn(WorkflowUiExperimentalApi::class) package workflow.tutorial import android.os.Bundle +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope +import com.squareup.workflow1.ui.ViewRegistry +import com.squareup.workflow1.ui.WorkflowLayout +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.asScreen +import com.squareup.workflow1.ui.container.withRegistry +import com.squareup.workflow1.ui.renderWorkflowIn +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map class TutorialActivity : AppCompatActivity() { + + private val viewRegistry = ViewRegistry(WelcomeScreenViewRunner) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.welcome_view) + + val viewModel: TutorialViewModel by viewModels() + + setContentView( + WorkflowLayout(this).apply { + take( + this@TutorialActivity.lifecycle, + viewModel.renderings.map { asScreen(it).withRegistry(viewRegistry) } + ) + } + ) + } +} + +class TutorialViewModel(savedState: SavedStateHandle): ViewModel() { + val renderings : StateFlow by lazy { + renderWorkflowIn( + workflow = WelcomeWorkflow, + scope = viewModelScope, + savedStateHandle = savedState + ) } } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreen.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreen.kt new file mode 100644 index 0000000000..7aa22bb645 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreen.kt @@ -0,0 +1,11 @@ +package workflow.tutorial + +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +@OptIn(WorkflowUiExperimentalApi::class) +data class WelcomeScreen( + val username: String, + val onUsernameChanged: (String) -> Unit, + val onLoginTapped: () -> Unit +): Screen diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreenViewRunner.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreenViewRunner.kt new file mode 100644 index 0000000000..10d8c55c93 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeScreenViewRunner.kt @@ -0,0 +1,31 @@ +package workflow.tutorial + +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ViewEnvironment +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.setTextChangedListener +import com.squareup.workflow1.ui.updateText +import workflow.tutorial.views.databinding.WelcomeViewBinding + +@OptIn(WorkflowUiExperimentalApi::class) +class WelcomeScreenViewRunner( + private val binding: WelcomeViewBinding +) : ScreenViewRunner { + + override fun showRendering( + rendering: WelcomeScreen, + viewEnvironment: ViewEnvironment + ) { + binding.username.updateText(rendering.username) + binding.username.setTextChangedListener { + rendering.onUsernameChanged(it.toString()) + } + binding.login.setOnClickListener { rendering.onLoginTapped() } + } + + companion object : ScreenViewFactory by ScreenViewFactory.Companion.fromViewBinding( + bindingInflater = WelcomeViewBinding::inflate, + constructor = { welcomeViewBinding -> WelcomeScreenViewRunner(welcomeViewBinding) } + ) +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt new file mode 100644 index 0000000000..ce481c27dd --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt @@ -0,0 +1,35 @@ +package workflow.tutorial + +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.action +import workflow.tutorial.WelcomeWorkflow.Output +import workflow.tutorial.WelcomeWorkflow.State + +object WelcomeWorkflow : StatefulWorkflow() { + + data class State( + val username: String + ) + + object Output + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = State(username = "") + + override fun render( + renderProps: Unit, + renderState: State, + context: RenderContext + ): WelcomeScreen = WelcomeScreen( + username = renderState.username, + onUsernameChanged = { context.actionSink.send(onUsernameChanged(it)) }, + onLoginTapped = {} + ) + override fun snapshotState(state: State): Snapshot? = null + + private fun onUsernameChanged(username: String) = action { + state = state.copy(username = username + "a") + } +} From b42502c65a0fd9d0fc8b1959fc60bcc4b88a1178 Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Thu, 22 Jun 2023 20:25:15 +0530 Subject: [PATCH 31/35] Add Tutorial 2 --- samples/tutorial/tutorial-base/build.gradle | 4 ++ .../java/workflow/tutorial/RootWorkflow.kt | 65 +++++++++++++++++++ .../java/workflow/tutorial/TodoListScreen.kt | 12 ++++ .../tutorial/TodoListScreenViewRunner.kt | 43 ++++++++++++ .../workflow/tutorial/TodoListWorkflow.kt | 54 +++++++++++++++ .../workflow/tutorial/TutorialActivity.kt | 12 +++- .../java/workflow/tutorial/WelcomeWorkflow.kt | 12 ++-- 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreen.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt diff --git a/samples/tutorial/tutorial-base/build.gradle b/samples/tutorial/tutorial-base/build.gradle index 5cfe30bd22..8e6f8a4f66 100644 --- a/samples/tutorial/tutorial-base/build.gradle +++ b/samples/tutorial/tutorial-base/build.gradle @@ -36,4 +36,8 @@ dependencies { implementation deps.activityktx implementation deps.viewmodelktx implementation deps.viewmodelsavedstate + + + implementation deps.workflow.container_android + implementation deps.workflow.core_android } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt new file mode 100644 index 0000000000..a50590cbb7 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -0,0 +1,65 @@ +package workflow.tutorial + +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.action +import com.squareup.workflow1.renderChild +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.container.BackStackScreen +import com.squareup.workflow1.ui.container.toBackStackScreen +import workflow.tutorial.RootWorkflow.State +import workflow.tutorial.RootWorkflow.State.Todo +import workflow.tutorial.RootWorkflow.State.Welcome +import workflow.tutorial.TodoListWorkflow.ListProps + +@OptIn(WorkflowUiExperimentalApi::class) +object RootWorkflow : StatefulWorkflow>() { + + sealed class State { + object Welcome : State() + data class Todo(val username: String) : State() + } + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = Welcome + + override fun render( + renderProps: Unit, + renderState: State, + context: RenderContext + ): BackStackScreen { + + val backStackScreens = mutableListOf() + + val welcomeScreen = context.renderChild(child = WelcomeWorkflow, key = "") { output -> + login(output.username) + } + + backStackScreens += welcomeScreen + + + when(renderState) { + is Welcome -> {} + is Todo -> { + val todoScreen = context.renderChild(child = TodoListWorkflow, ListProps(username = renderState.username)) { + logout() + } + backStackScreens.add(todoScreen) + } + } + + return backStackScreens.toBackStackScreen() + } + + override fun snapshotState(state: State): Snapshot? = null + + private fun login(username: String) = action { + state = Todo(username) + } + + private fun logout() = action { + state = Welcome + } +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreen.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreen.kt new file mode 100644 index 0000000000..99b422ecee --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreen.kt @@ -0,0 +1,12 @@ +package workflow.tutorial + +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +@OptIn(WorkflowUiExperimentalApi::class) +data class TodoListScreen( + val username: String, + val todoTitles: List, + val onTodoSelected: (Int) -> Unit, + val onBack: () -> Unit +): Screen diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt new file mode 100644 index 0000000000..0ae74817ea --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt @@ -0,0 +1,43 @@ +package workflow.tutorial + +import androidx.recyclerview.widget.LinearLayoutManager +import com.squareup.workflow1.ui.LayoutRunner +import com.squareup.workflow1.ui.LayoutRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewFactory.Companion +import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ViewEnvironment +import com.squareup.workflow1.ui.ViewFactory +import workflow.tutorial.views.databinding.TodoListViewBinding +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.backPressedHandler +import workflow.tutorial.views.TodoListAdapter + +@OptIn(WorkflowUiExperimentalApi::class) +class TodoListScreenViewRunner( + private val binding: TodoListViewBinding +) : ScreenViewRunner { + + val adapter = TodoListAdapter() + init { + binding.todoList.layoutManager = LinearLayoutManager(binding.root.context) + binding.todoList.adapter = adapter + } + override fun showRendering( + rendering: TodoListScreen, + viewEnvironment: ViewEnvironment + ) { + binding.root.backPressedHandler = rendering.onBack + + with(binding.todoListWelcome) { + text = resources.getString(R.string.todo_list_welcome, rendering.username) + } + + adapter.todoList = rendering.todoTitles + adapter.notifyDataSetChanged() + } + + companion object : ScreenViewFactory by ScreenViewFactory.fromViewBinding( + TodoListViewBinding::inflate, ::TodoListScreenViewRunner + ) +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt new file mode 100644 index 0000000000..6f79967b7c --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt @@ -0,0 +1,54 @@ +package workflow.tutorial + +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.action +import workflow.tutorial.TodoListWorkflow.Back +import workflow.tutorial.TodoListWorkflow.ListProps +import workflow.tutorial.TodoListWorkflow.State + +object TodoListWorkflow : StatefulWorkflow() { + + object Back + data class ListProps(val username: String) + data class TodoModel( + val title: String, + val note: String + ) + data class State( + val todos: List + ) + + override fun initialState( + props: ListProps, + snapshot: Snapshot? + ): State = State( + listOf( + TodoModel( + title = "Take the cat for a walk", + note = "Cats really need their outside sunshine time. Don't forget to walk " + + "Charlie. Hamilton is less excited about the prospect." + ) + ) + ) + + override fun render( + renderProps: ListProps, + renderState: State, + context: RenderContext + ): TodoListScreen { + val titles = renderState.todos.map { it.title } + return TodoListScreen( + username = renderProps.username, + todoTitles = titles, + onTodoSelected = {}, + onBack = { context.actionSink.send(onBack()) } + ) + } + + private fun onBack() = action { + setOutput(Back) + } + + override fun snapshotState(state: State): Snapshot? = null +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt index adf3da63a7..15b2a27486 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -12,6 +12,7 @@ import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowLayout import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.asScreen +import com.squareup.workflow1.ui.container.BackStackContainer import com.squareup.workflow1.ui.container.withRegistry import com.squareup.workflow1.ui.renderWorkflowIn import kotlinx.coroutines.flow.StateFlow @@ -20,7 +21,12 @@ import kotlinx.coroutines.flow.map class TutorialActivity : AppCompatActivity() { - private val viewRegistry = ViewRegistry(WelcomeScreenViewRunner) + private val viewRegistry = ViewRegistry( + // No need to add BackStackContainer. Its now by default built in ViewRegistry + // com.squareup.workflow1.ui.backstack.BackStackContainer, + WelcomeScreenViewRunner, + TodoListScreenViewRunner + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -40,9 +46,9 @@ class TutorialActivity : AppCompatActivity() { } class TutorialViewModel(savedState: SavedStateHandle): ViewModel() { - val renderings : StateFlow by lazy { + val renderings : StateFlow by lazy { renderWorkflowIn( - workflow = WelcomeWorkflow, + workflow = RootWorkflow, scope = viewModelScope, savedStateHandle = savedState ) diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt index ce481c27dd..1917d7f7e2 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/WelcomeWorkflow.kt @@ -3,16 +3,16 @@ package workflow.tutorial import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action -import workflow.tutorial.WelcomeWorkflow.Output +import workflow.tutorial.WelcomeWorkflow.LoggedIn import workflow.tutorial.WelcomeWorkflow.State -object WelcomeWorkflow : StatefulWorkflow() { +object WelcomeWorkflow : StatefulWorkflow() { + data class LoggedIn(val username: String) data class State( val username: String ) - object Output override fun initialState( props: Unit, snapshot: Snapshot? @@ -25,11 +25,15 @@ object WelcomeWorkflow : StatefulWorkflow() ): WelcomeScreen = WelcomeScreen( username = renderState.username, onUsernameChanged = { context.actionSink.send(onUsernameChanged(it)) }, - onLoginTapped = {} + onLoginTapped = { context.actionSink.send(onLogin()) } ) override fun snapshotState(state: State): Snapshot? = null private fun onUsernameChanged(username: String) = action { state = state.copy(username = username + "a") } + + private fun onLogin() =action { + setOutput(LoggedIn(username = state.username)) + } } From 7ac813b00141d906e72aea2ac0d4bdf1a2c64431 Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Mon, 26 Jun 2023 13:19:22 +0530 Subject: [PATCH 32/35] Add Tutorial 3 --- .../java/workflow/tutorial/RootWorkflow.kt | 4 +- .../java/workflow/tutorial/TodoEditScreen.kt | 15 ++++ .../tutorial/TodoEditScreenViewRunner.kt | 37 ++++++++++ .../workflow/tutorial/TodoEditWorkflow.kt | 71 +++++++++++++++++++ .../tutorial/TodoListScreenViewRunner.kt | 7 +- .../workflow/tutorial/TodoListWorkflow.kt | 70 +++++++++++++++--- .../workflow/tutorial/TutorialActivity.kt | 3 +- 7 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreen.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreenViewRunner.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt index a50590cbb7..48ee0413e1 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -43,10 +43,10 @@ object RootWorkflow : StatefulWorkflow {} is Todo -> { - val todoScreen = context.renderChild(child = TodoListWorkflow, ListProps(username = renderState.username)) { + val todoScreens = context.renderChild(child = TodoListWorkflow, ListProps(username = renderState.username)) { logout() } - backStackScreens.add(todoScreen) + backStackScreens.addAll(todoScreens) } } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreen.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreen.kt new file mode 100644 index 0000000000..e221ee1071 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreen.kt @@ -0,0 +1,15 @@ +package workflow.tutorial + +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +@OptIn(WorkflowUiExperimentalApi::class) +data class TodoEditScreen( + val title: String, + val note: String, + val onTitleChanged: (String) -> Unit, + val onNoteChanged: (String) -> Unit, + + val discardChanges: () -> Unit, + val saveChanges: () -> Unit +): Screen diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreenViewRunner.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreenViewRunner.kt new file mode 100644 index 0000000000..c024bae759 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditScreenViewRunner.kt @@ -0,0 +1,37 @@ +package workflow.tutorial + +import com.squareup.workflow1.ui.LayoutRunner +import com.squareup.workflow1.ui.LayoutRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewFactory.Companion +import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ViewEnvironment +import com.squareup.workflow1.ui.ViewFactory +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.backPressedHandler +import com.squareup.workflow1.ui.setTextChangedListener +import com.squareup.workflow1.ui.updateText +import workflow.tutorial.views.databinding.TodoEditViewBinding + +@OptIn(WorkflowUiExperimentalApi::class) +class TodoEditScreenViewRunner( + private val binding: TodoEditViewBinding +) : ScreenViewRunner { + + override fun showRendering( + rendering: TodoEditScreen, + viewEnvironment: ViewEnvironment + ) { + binding.root.backPressedHandler = rendering.discardChanges + binding.save.setOnClickListener { rendering.saveChanges() } + binding.todoTitle.updateText(rendering.title) + binding.todoTitle.setTextChangedListener { rendering.onTitleChanged(it.toString()) } + binding.todoNote.updateText(rendering.note) + binding.todoNote.setTextChangedListener { rendering.onNoteChanged(it.toString()) } + } + + companion object : ScreenViewFactory by ScreenViewFactory.Companion.fromViewBinding( + bindingInflater = TodoEditViewBinding::inflate, + constructor = ::TodoEditScreenViewRunner + ) +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt new file mode 100644 index 0000000000..07286f0d37 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt @@ -0,0 +1,71 @@ +package workflow.tutorial + +import android.icu.text.CaseMap.Title +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.action +import workflow.tutorial.TodoEditWorkflow.EditProps +import workflow.tutorial.TodoEditWorkflow.Output +import workflow.tutorial.TodoEditWorkflow.Output.Discard +import workflow.tutorial.TodoEditWorkflow.Output.Save +import workflow.tutorial.TodoEditWorkflow.State +import workflow.tutorial.TodoListWorkflow.TodoModel + +object TodoEditWorkflow : StatefulWorkflow() { + + data class EditProps( + val initialTodo: TodoModel + ) + + data class State( + val todo:TodoModel + ) + + sealed class Output { + object Discard: Output() + data class Save(val todo: TodoModel): Output() + } + override fun initialState( + props: EditProps, + snapshot: Snapshot? + ): State = State(props.initialTodo) + + override fun onPropsChanged( + old: EditProps, + new: EditProps, + state: State + ): State { + if (old.initialTodo != new.initialTodo) { + return state.copy(todo = new.initialTodo) + } + return state + } + + override fun render( + renderProps: EditProps, + renderState: State, + context: RenderContext + ): TodoEditScreen { + return TodoEditScreen( + title = renderState.todo.title, + note = renderState.todo.note, + onTitleChanged = { context.actionSink.send(onTitleChanged(it)) }, + onNoteChanged = { context.actionSink.send(onNoteChanged(it)) }, + saveChanges = { context.actionSink.send(onSave()) }, + discardChanges = { context.actionSink.send(onDiscard()) } + ) + } + + override fun snapshotState(state: State): Snapshot? = null + + private fun onTitleChanged(title: String) = action { state = state.withTitle(title) } + + private fun onNoteChanged(note: String) = action { state = state.withNote(note) } + + private fun onDiscard() = action { setOutput(Discard) } + + private fun onSave() = action { setOutput(Save(state.todo)) } + + private fun State.withTitle(title: String) = copy(todo = todo.copy(title = title)) + private fun State.withNote(note: String) = copy(todo = todo.copy(note = note)) +} diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt index 0ae74817ea..2b532a74cb 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListScreenViewRunner.kt @@ -1,17 +1,13 @@ package workflow.tutorial import androidx.recyclerview.widget.LinearLayoutManager -import com.squareup.workflow1.ui.LayoutRunner -import com.squareup.workflow1.ui.LayoutRunner.Companion.bind import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewFactory.Companion import com.squareup.workflow1.ui.ScreenViewRunner import com.squareup.workflow1.ui.ViewEnvironment -import com.squareup.workflow1.ui.ViewFactory -import workflow.tutorial.views.databinding.TodoListViewBinding import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler import workflow.tutorial.views.TodoListAdapter +import workflow.tutorial.views.databinding.TodoListViewBinding @OptIn(WorkflowUiExperimentalApi::class) class TodoListScreenViewRunner( @@ -34,6 +30,7 @@ class TodoListScreenViewRunner( } adapter.todoList = rendering.todoTitles + adapter.onTodoSelected = rendering.onTodoSelected adapter.notifyDataSetChanged() } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt index 6f79967b7c..60ab270df8 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt @@ -3,11 +3,18 @@ package workflow.tutorial import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import workflow.tutorial.TodoEditWorkflow.EditProps +import workflow.tutorial.TodoEditWorkflow.Output.Discard +import workflow.tutorial.TodoEditWorkflow.Output.Save import workflow.tutorial.TodoListWorkflow.Back import workflow.tutorial.TodoListWorkflow.ListProps import workflow.tutorial.TodoListWorkflow.State +import workflow.tutorial.TodoListWorkflow.State.Step -object TodoListWorkflow : StatefulWorkflow() { +@OptIn(WorkflowUiExperimentalApi::class) +object TodoListWorkflow : StatefulWorkflow>() { object Back data class ListProps(val username: String) @@ -16,39 +23,84 @@ object TodoListWorkflow : StatefulWorkflow - ) + val todos: List, + val step: Step + ) { + sealed class Step { + object List: Step() + + data class Edit(val index: Int) : Step() + } + } override fun initialState( props: ListProps, snapshot: Snapshot? ): State = State( - listOf( + todos = listOf( TodoModel( title = "Take the cat for a walk", note = "Cats really need their outside sunshine time. Don't forget to walk " + - "Charlie. Hamilton is less excited about the prospect." + "Charlie. Hamilton is less excited about the prospect.", ) - ) + ), + step = Step.List ) override fun render( renderProps: ListProps, renderState: State, context: RenderContext - ): TodoListScreen { + ): List { val titles = renderState.todos.map { it.title } - return TodoListScreen( + val todoListScreen = TodoListScreen( username = renderProps.username, todoTitles = titles, - onTodoSelected = {}, + onTodoSelected = {context.actionSink.send(selectTodo(it))}, onBack = { context.actionSink.send(onBack()) } ) + + return when(val step = renderState.step) { + Step.List -> listOf(todoListScreen) + is Step.Edit -> { + val todoEditScreen = context.renderChild( + TodoEditWorkflow, + props = EditProps(renderState.todos[step.index]) + ) { output -> + when(output) { + Discard -> discardChanges() + is Save -> saveChanges(output.todo, step.index) + } + } + return listOf(todoListScreen, todoEditScreen) + } + } + } private fun onBack() = action { setOutput(Back) } + private fun selectTodo(index: Int) = action { + state = state.copy(step = Step.Edit(index)) + } + + private fun discardChanges() = action { + // When a discard action is received, return to the list. + state = state.copy(step = Step.List) + } + + private fun saveChanges( + todo: TodoModel, + index: Int + ) = action { + // When changes are saved, update the state of that todo item and return to the list. + state = state.copy( + todos = state.todos.toMutableList().also { it[index] = todo }, + step = Step.List + ) + } + override fun snapshotState(state: State): Snapshot? = null } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt index 15b2a27486..d926c65633 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TutorialActivity.kt @@ -25,7 +25,8 @@ class TutorialActivity : AppCompatActivity() { // No need to add BackStackContainer. Its now by default built in ViewRegistry // com.squareup.workflow1.ui.backstack.BackStackContainer, WelcomeScreenViewRunner, - TodoListScreenViewRunner + TodoListScreenViewRunner, + TodoEditScreenViewRunner ) override fun onCreate(savedInstanceState: Bundle?) { From 04372af2f4214b5203b1ba76f2633a8cfda7b766 Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Tue, 27 Jun 2023 09:59:47 +0530 Subject: [PATCH 33/35] Add Tutorial 4 --- .../java/workflow/tutorial/RootWorkflow.kt | 3 +- .../workflow/tutorial/TodoEditWorkflow.kt | 1 - .../workflow/tutorial/TodoListWorkflow.kt | 103 ++++------------ .../main/java/workflow/tutorial/TodoModel.kt | 6 + .../java/workflow/tutorial/TodoWorkflow.kt | 114 ++++++++++++++++++ 5 files changed, 143 insertions(+), 84 deletions(-) create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoModel.kt create mode 100644 samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoWorkflow.kt diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt index 48ee0413e1..c9b7feb01f 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/RootWorkflow.kt @@ -12,6 +12,7 @@ import workflow.tutorial.RootWorkflow.State import workflow.tutorial.RootWorkflow.State.Todo import workflow.tutorial.RootWorkflow.State.Welcome import workflow.tutorial.TodoListWorkflow.ListProps +import workflow.tutorial.TodoWorkflow.TodoProps @OptIn(WorkflowUiExperimentalApi::class) object RootWorkflow : StatefulWorkflow>() { @@ -43,7 +44,7 @@ object RootWorkflow : StatefulWorkflow {} is Todo -> { - val todoScreens = context.renderChild(child = TodoListWorkflow, ListProps(username = renderState.username)) { + val todoScreens = context.renderChild(child = TodoWorkflow, TodoProps(username = renderState.username)) { logout() } backStackScreens.addAll(todoScreens) diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt index 07286f0d37..e6d07ec21b 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt @@ -9,7 +9,6 @@ import workflow.tutorial.TodoEditWorkflow.Output import workflow.tutorial.TodoEditWorkflow.Output.Discard import workflow.tutorial.TodoEditWorkflow.Output.Save import workflow.tutorial.TodoEditWorkflow.State -import workflow.tutorial.TodoListWorkflow.TodoModel object TodoEditWorkflow : StatefulWorkflow() { diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt index 60ab270df8..a5fbd3fa83 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoListWorkflow.kt @@ -2,105 +2,44 @@ package workflow.tutorial import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.StatelessWorkflow import com.squareup.workflow1.action -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import workflow.tutorial.TodoEditWorkflow.EditProps -import workflow.tutorial.TodoEditWorkflow.Output.Discard -import workflow.tutorial.TodoEditWorkflow.Output.Save -import workflow.tutorial.TodoListWorkflow.Back import workflow.tutorial.TodoListWorkflow.ListProps -import workflow.tutorial.TodoListWorkflow.State -import workflow.tutorial.TodoListWorkflow.State.Step +import workflow.tutorial.TodoListWorkflow.Output @OptIn(WorkflowUiExperimentalApi::class) -object TodoListWorkflow : StatefulWorkflow>() { +object TodoListWorkflow : StatelessWorkflow() { - object Back - data class ListProps(val username: String) - data class TodoModel( - val title: String, - val note: String + data class ListProps( + val username: String, + val todos: List ) - data class State( - val todos: List, - val step: Step - ) { - sealed class Step { - object List: Step() - data class Edit(val index: Int) : Step() - } + sealed class Output { + object Back : Output() + data class SelectTodo(val index: Int) : Output() } - override fun initialState( - props: ListProps, - snapshot: Snapshot? - ): State = State( - todos = listOf( - TodoModel( - title = "Take the cat for a walk", - note = "Cats really need their outside sunshine time. Don't forget to walk " + - "Charlie. Hamilton is less excited about the prospect.", - ) - ), - step = Step.List - ) - - override fun render( - renderProps: ListProps, - renderState: State, - context: RenderContext - ): List { - val titles = renderState.todos.map { it.title } - val todoListScreen = TodoListScreen( - username = renderProps.username, - todoTitles = titles, - onTodoSelected = {context.actionSink.send(selectTodo(it))}, - onBack = { context.actionSink.send(onBack()) } - ) - - return when(val step = renderState.step) { - Step.List -> listOf(todoListScreen) - is Step.Edit -> { - val todoEditScreen = context.renderChild( - TodoEditWorkflow, - props = EditProps(renderState.todos[step.index]) - ) { output -> - when(output) { - Discard -> discardChanges() - is Save -> saveChanges(output.todo, step.index) - } - } - return listOf(todoListScreen, todoEditScreen) - } - } - - } private fun onBack() = action { - setOutput(Back) + setOutput(Output.Back) } private fun selectTodo(index: Int) = action { - state = state.copy(step = Step.Edit(index)) - } - - private fun discardChanges() = action { - // When a discard action is received, return to the list. - state = state.copy(step = Step.List) + setOutput(Output.SelectTodo(index)) } - private fun saveChanges( - todo: TodoModel, - index: Int - ) = action { - // When changes are saved, update the state of that todo item and return to the list. - state = state.copy( - todos = state.todos.toMutableList().also { it[index] = todo }, - step = Step.List + override fun render( + renderProps: ListProps, + context: RenderContext + ): TodoListScreen { + val titles = renderProps.todos.map { it.title } + return TodoListScreen( + username = renderProps.username, + todoTitles = titles, + onTodoSelected = { context.actionSink.send(selectTodo(it)) }, + onBack = { context.actionSink.send(onBack()) } ) } - - override fun snapshotState(state: State): Snapshot? = null } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoModel.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoModel.kt new file mode 100644 index 0000000000..d9eb22fef2 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoModel.kt @@ -0,0 +1,6 @@ +package workflow.tutorial + +data class TodoModel( + val title: String, + val note: String +) diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoWorkflow.kt new file mode 100644 index 0000000000..85a03167c4 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoWorkflow.kt @@ -0,0 +1,114 @@ +package workflow.tutorial + +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.StatefulWorkflow +import com.squareup.workflow1.action +import com.squareup.workflow1.ui.Screen +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import workflow.tutorial.TodoEditWorkflow.EditProps +import workflow.tutorial.TodoEditWorkflow.Output.Discard +import workflow.tutorial.TodoEditWorkflow.Output.Save +import workflow.tutorial.TodoListWorkflow.ListProps +import workflow.tutorial.TodoListWorkflow.Output +import workflow.tutorial.TodoListWorkflow.Output.SelectTodo +import workflow.tutorial.TodoWorkflow.Back +import workflow.tutorial.TodoWorkflow.State +import workflow.tutorial.TodoWorkflow.State.Step +import workflow.tutorial.TodoWorkflow.TodoProps + +@OptIn(WorkflowUiExperimentalApi::class) +object TodoWorkflow : StatefulWorkflow>() { + + data class TodoProps(val username: String) + object Back + data class State( + val todos: List, + val step: Step + ) { + sealed class Step { + object List: Step() + + data class Edit(val index: Int) : Step() + } + } + + override fun initialState( + props: TodoProps, + snapshot: Snapshot? + ): State = State( + todos = listOf( + TodoModel( + title = "Take the cat for a walk", + note = "Cats really need their outside sunshine time. Don't forget to walk " + + "Charlie. Hamilton is less excited about the prospect.", + ) + ), + step = Step.List + ) + + override fun render( + renderProps: TodoProps, + renderState: State, + context: RenderContext + ): List { + val todoListScreen = context.renderChild( + TodoListWorkflow, + props = ListProps( + username = renderProps.username, + todos = renderState.todos + ) + ) { output -> + when (output) { + Output.Back -> onBack() + is SelectTodo -> editTodo(output.index) + } + } + + return when (val step = renderState.step) { + // On the "list" step, return just the list screen. + Step.List -> listOf(todoListScreen) + is Step.Edit -> { + // On the "edit" step, return both the list and edit screens. + val todoEditScreen = context.renderChild( + TodoEditWorkflow, + EditProps(renderState.todos[step.index]) + ) { output -> + when (output) { + // Send the discardChanges action when the discard output is received. + Discard -> discardChanges() + // Send the saveChanges action when the save output is received. + is Save -> saveChanges(output.todo, step.index) + } + } + return listOf(todoListScreen, todoEditScreen) + } + } + } + + private fun discardChanges() = action { + // When a discard action is received, return to the list. + state = state.copy(step = Step.List) + } + + private fun saveChanges( + todo: TodoModel, + index: Int + ) = action { + // When changes are saved, update the state of that todo item and return to the list. + state = state.copy( + todos = state.todos.toMutableList().also { it[index] = todo }, + step = Step.List + ) + } + + private fun onBack() = action { + // When an onBack action is received, emit a Back output. + setOutput(Back) + } + + private fun editTodo(index: Int) = action { + // When a todo item is selected, edit it. + state = state.copy(step = Step.Edit(index)) + } + override fun snapshotState(state: State): Snapshot? = null +} From 5024550b67ca1f62e2dfea83c007c068160b23b5 Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Tue, 4 Jul 2023 13:41:25 +0530 Subject: [PATCH 34/35] Add Tutorial 5: Unit tests: actions and rendering --- samples/tutorial/tutorial-base/build.gradle | 3 + .../workflow/tutorial/TodoEditWorkflow.kt | 8 +- .../java/workflow/tutorial/WelcomeWorkflow.kt | 8 +- .../workflow/tutorial/TodoEditWorkflowTest.kt | 66 ++++++++++++++++ .../workflow/tutorial/WelcomeWorkflowTest.kt | 78 +++++++++++++++++++ 5 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt create mode 100644 samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt diff --git a/samples/tutorial/tutorial-base/build.gradle b/samples/tutorial/tutorial-base/build.gradle index 8e6f8a4f66..aef3354b83 100644 --- a/samples/tutorial/tutorial-base/build.gradle +++ b/samples/tutorial/tutorial-base/build.gradle @@ -40,4 +40,7 @@ dependencies { implementation deps.workflow.container_android implementation deps.workflow.core_android + + testImplementation deps.kotlin.test + testImplementation deps.workflow.testing } diff --git a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt index e6d07ec21b..1aee563e38 100644 --- a/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt +++ b/samples/tutorial/tutorial-base/src/main/java/workflow/tutorial/TodoEditWorkflow.kt @@ -57,13 +57,13 @@ object TodoEditWorkflow : StatefulWorkflow( ) override fun snapshotState(state: State): Snapshot? = null - private fun onUsernameChanged(username: String) = action { + internal fun onUsernameChanged(username: String) = action { state = state.copy(username = username + "a") } - private fun onLogin() =action { - setOutput(LoggedIn(username = state.username)) + internal fun onLogin() =action { + if (state.username.isNotEmpty()) { + setOutput(LoggedIn(username = state.username)) + } } } diff --git a/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt new file mode 100644 index 0000000000..21ac6301a3 --- /dev/null +++ b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoEditWorkflowTest.kt @@ -0,0 +1,66 @@ +package workflow.tutorial + +import com.squareup.workflow1.applyTo +import org.junit.Test +import workflow.tutorial.TodoEditWorkflow.EditProps +import workflow.tutorial.TodoEditWorkflow.Output.Save +import workflow.tutorial.TodoEditWorkflow.State +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class TodoEditWorkflowTest { + + private val startState = State(todo = TodoModel(title = "Title", note = "Note")) + + @Test + fun `title is updated`() { + val props = EditProps(initialTodo = TodoModel(title = "", note = "")) + + val (newState, actionApplied) = TodoEditWorkflow.onTitleChanged("Updated Title") + .applyTo(props, startState) + + assertNull(actionApplied.output) + assertEquals(TodoModel(title = "Updated Title", note = "Note"), newState.todo) + } + + @Test + fun `note is updated`() { + val props = EditProps(initialTodo = TodoModel(title = "", note = "")) + + val (newState, actionApplied) = TodoEditWorkflow.onNoteChanged("Updated Note") + .applyTo(props, startState) + + assertNull(actionApplied.output) + assertEquals(TodoModel(title = "Title", note = "Updated Note"), newState.todo) + } + + @Test + fun `save emits model`() { + val props = EditProps(initialTodo = TodoModel(title = "", note = "")) + + val (_, actionApplied) = TodoEditWorkflow.onSave().applyTo(props, startState) + + assertEquals(Save(TodoModel(title = "Title", note = "Note")), actionApplied.output?.value) + } + + @Test + fun `changed props updated local state`() { + val initialProps = EditProps(TodoModel(title = "Title", note = "Note")) + var state = TodoEditWorkflow.initialState(initialProps, null) + + assertEquals("Title", state.todo.title) + assertEquals("Note", state.todo.note) + + state = State(TodoModel(title = "Updated Title", note = "Note")) + + state = TodoEditWorkflow.onPropsChanged(initialProps, initialProps, state) + assertEquals("Updated Title", state.todo.title) + assertEquals("Note", state.todo.note) + + val updatedProps = EditProps(initialTodo = TodoModel(title = "New Title", note = "New Note")) + state = TodoEditWorkflow.onPropsChanged(initialProps, updatedProps, state) + assertEquals("New Title", state.todo.title) + assertEquals("New Note", state.todo.note) + + } +} diff --git a/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt new file mode 100644 index 0000000000..2fe309768a --- /dev/null +++ b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/WelcomeWorkflowTest.kt @@ -0,0 +1,78 @@ +package workflow.tutorial + +import com.squareup.workflow1.applyTo +import com.squareup.workflow1.testing.testRender +import org.junit.Test +import workflow.tutorial.WelcomeWorkflow.LoggedIn +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class WelcomeWorkflowTest { + + @Test + fun `username updates`() { + val startState = WelcomeWorkflow.State("") + val action = WelcomeWorkflow.onUsernameChanged("myName") + + val (state, output) = action.applyTo(state = startState, props = Unit) + assertNull(output.output) + + assertEquals("myNamea", state.username) + } + + @Test + fun `login works`() { + val startState = WelcomeWorkflow.State("myName") + val action = WelcomeWorkflow.onLogin() + + val (_, actionApplied) = action.applyTo(state = startState, props = Unit) + + assertEquals(LoggedIn("myName"), actionApplied.output?.value) + } + + @Test + fun `login does nothing when name is empty`() { + val startState = WelcomeWorkflow.State("") + val action = WelcomeWorkflow.onLogin() + val (state, actionApplied) = action.applyTo(state = startState, props = Unit) + + assertNull(actionApplied.output) + + assertEquals("", state.username) + } + + @Test + fun `rendering initial`() { + WelcomeWorkflow.testRender(props = Unit) + .render { screen -> + assertEquals("", screen.username) + + screen.onLoginTapped() + } + .verifyActionResult { _, output -> + assertNull(output) + } + } + + @Test + fun `rendering name change`() { + WelcomeWorkflow.testRender(props = Unit) + .render { screen -> + screen.onUsernameChanged("myName") + } + .verifyActionResult { state, _ -> + assertEquals("myNamea", (state as WelcomeWorkflow.State).username) + } + } + + @Test + fun `rendering login`() { + WelcomeWorkflow.testRender( initialState = WelcomeWorkflow.State("myName"),props = Unit) + .render { screen -> + screen.onLoginTapped() + } + .verifyActionResult { _, output -> + assertEquals(LoggedIn("myName"), output?.value) + } + } +} From 59d9354c472872ac05eb278c8d0cb92f12d5df6d Mon Sep 17 00:00:00 2001 From: Rohan Mighty Date: Tue, 4 Jul 2023 16:01:38 +0530 Subject: [PATCH 35/35] Add Tutorial 5: Unit test: composition test + integration test --- .../workflow/tutorial/RootWorkflowTest.kt | 63 +++++++ .../workflow/tutorial/TodoWorkflowTest.kt | 155 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/RootWorkflowTest.kt create mode 100644 samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoWorkflowTest.kt diff --git a/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/RootWorkflowTest.kt b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/RootWorkflowTest.kt new file mode 100644 index 0000000000..ae32e95b4f --- /dev/null +++ b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/RootWorkflowTest.kt @@ -0,0 +1,63 @@ +package workflow.tutorial + +import com.squareup.workflow1.WorkflowOutput +import com.squareup.workflow1.testing.expectWorkflow +import com.squareup.workflow1.testing.testRender +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import org.junit.Test +import workflow.tutorial.RootWorkflow.State.Todo +import workflow.tutorial.RootWorkflow.State.Welcome +import workflow.tutorial.WelcomeWorkflow.LoggedIn +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@OptIn(WorkflowUiExperimentalApi::class) +class RootWorkflowTest { + @Test + fun `welcome rendering`() { + RootWorkflow + .testRender(initialState = Welcome, props = Unit) + .expectWorkflow( + workflowType = WelcomeWorkflow::class, + rendering = WelcomeScreen( + username = "Ada", + onUsernameChanged = {}, + onLoginTapped = {} + ) + ) + .render {rendering -> + val backstack = rendering.frames + assertEquals(1, backstack.size) + + val welcomeScreen = backstack[0] as WelcomeScreen + assertEquals("Ada", welcomeScreen.username) + } + .verifyActionResult { _, output -> + assertNull(output) + } + } + + @Test + fun `login event`() { + RootWorkflow + .testRender(initialState = Welcome, props = Unit) + .expectWorkflow( + workflowType = WelcomeWorkflow::class, + rendering = WelcomeScreen( + username = "Ada", + onUsernameChanged = {}, + onLoginTapped = {} + ), + output = WorkflowOutput(LoggedIn("Ada")) + ) + .render { rendering -> + val backStack = rendering.frames + val welcomeScreen = backStack[0] as WelcomeScreen + assertEquals(1, backStack.size) + assertEquals("Ada", welcomeScreen.username) + } + .verifyActionResult { newState, _ -> + assertEquals(Todo(username = "Ada"), newState) + } + } +} diff --git a/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoWorkflowTest.kt b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoWorkflowTest.kt new file mode 100644 index 0000000000..7021356b3c --- /dev/null +++ b/samples/tutorial/tutorial-base/src/test/java/workflow/tutorial/TodoWorkflowTest.kt @@ -0,0 +1,155 @@ +package workflow.tutorial + +import com.squareup.workflow1.WorkflowOutput +import com.squareup.workflow1.testing.expectWorkflow +import com.squareup.workflow1.testing.launchForTestingFromStartWith +import com.squareup.workflow1.testing.testRender +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import org.junit.Test +import workflow.tutorial.RootWorkflow.State.Todo +import workflow.tutorial.TodoEditWorkflow.Output.Save +import workflow.tutorial.TodoListWorkflow.Output.SelectTodo +import workflow.tutorial.TodoWorkflow.State +import workflow.tutorial.TodoWorkflow.State.Step +import workflow.tutorial.TodoWorkflow.TodoProps +import workflow.tutorial.TodoWorkflow.initialState +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +@OptIn(WorkflowUiExperimentalApi::class) +class TodoWorkflowTest { + + @Test + fun `selecting todo`() { + val todos = listOf(TodoModel("Title", note = "Note")) + + TodoWorkflow + .testRender( + props = TodoProps(username = "Ada"), + initialState = State(todos = todos, step = Step.List) + ) + .expectWorkflow( + workflowType = TodoListWorkflow::class, + rendering = TodoListScreen( + username = "", + todoTitles = listOf("Title"), + onTodoSelected = {}, + onBack = {} + ), + output = WorkflowOutput(SelectTodo(index = 0)) + ) + .render { rendering -> + assertEquals(1, rendering.size) + } + .verifyActionResult { newState, _ -> + assertEquals(State(todos = listOf(TodoModel(title = "Title", note = "Note")), step = Step.Edit(0)), newState) + } + } + + @Test + fun `saving todo`() { + val todos = listOf(TodoModel("Title", note = "Note")) + + TodoWorkflow + .testRender( + props = TodoProps(username = "Ada"), + initialState = State(todos = todos, step = Step.Edit(0)) + ) + .expectWorkflow( + workflowType = TodoListWorkflow::class, + rendering = TodoListScreen( + username = "", + todoTitles = listOf("Title"), + onTodoSelected = {}, + onBack = {} + ), + ) + .expectWorkflow( + workflowType = TodoEditWorkflow::class, + rendering = TodoEditScreen( + title = "Title", + note = "Note", + onTitleChanged = {}, + onNoteChanged = {}, + discardChanges = {}, + saveChanges = {} + ), + output = WorkflowOutput( + Save(TodoModel(title = "Updated Title", note = "Updated Note")) + ) + ) + .render { rendering -> + assertEquals(2, rendering.size) + } + .verifyActionResult { newState, _ -> + assertEquals( + State( + todos = listOf(TodoModel(title = "Updated Title", note = "Updated Note")), + step = Step.List + ), + newState + ) + } + } + + + /////// Integration testing /////// + + @Test + fun `app flow`() { + RootWorkflow.launchForTestingFromStartWith { + awaitNextRendering().let { rendering -> + assertEquals(1, rendering.frames.size) + val welcomeScreen = rendering.frames[0] as WelcomeScreen + + welcomeScreen.onUsernameChanged("Ada") + } + + awaitNextRendering().let { rendering -> + assertEquals(1, rendering.frames.size) + + val welcomeScreen = rendering.frames[0] as WelcomeScreen + + welcomeScreen.onLoginTapped() + } + + awaitNextRendering().let {rendering -> + assertEquals(2, rendering.frames.size) + assertTrue(rendering.frames[0] is WelcomeScreen) + val todoScreen = rendering.frames[1] as TodoListScreen + + assertEquals(1, todoScreen.todoTitles.size) + + todoScreen.onTodoSelected(0) + } + + awaitNextRendering().let { rendering -> + assertEquals(3, rendering.frames.size) + assertTrue(rendering.frames[0] is WelcomeScreen) + assertTrue(rendering.frames[1] is TodoListScreen) + + val editScreen = rendering.frames[2] as TodoEditScreen + editScreen.onTitleChanged("New title") + } + + awaitNextRendering().let { rendering -> + assertEquals(3, rendering.frames.size) + assertTrue(rendering.frames[0] is WelcomeScreen) + assertTrue(rendering.frames[1] is TodoListScreen) + + val editScreen = rendering.frames[2] as TodoEditScreen + editScreen.saveChanges() + } + + awaitNextRendering().let { rendering -> + assertEquals(2, rendering.frames.size) + assertTrue(rendering.frames[0] is WelcomeScreen) + + val todoScreen = rendering.frames[1] as TodoListScreen + assertEquals(1, todoScreen.todoTitles.size) + assertEquals("New title", todoScreen.todoTitles[0]) + } + } + } + +}