Skip to content

Commit 3b42df2

Browse files
authored
chore: update kotlin [WPB-20393] (#3846)
* chore: update Gradle and Kotlin configurations * use kotlin 2.3.0 * update shadow to 9.3.1 * chore: update package names and rename destination files * set the correct gradle distributionSha256Sum * chore: configure shadowJar task for testservice * chore: update commonAndroidLibConfig to conditionally include consumer proguard files * chore: update compile SDK version to 36 and refactor configurations * detekt * update mokkery * chore: implement DagCommandTask for affected module detection and update testing configurations * chore: optimize task retrieval logic in OnlyAffectedTestTask * chore: update test compilation commands for Android device and host tests * chore: rename test files and update detekt configuration exclusions * chore: update test logging configuration and add silent logger for database * chore: update test logging configuration and add silent logger for database
1 parent 952cadd commit 3b42df2

File tree

77 files changed

+647
-310
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+647
-310
lines changed

.github/workflows/gradle-android-instrumented-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272

7373
- name: Build the samples
7474
run: |
75-
./gradlew :sample:samples:compileDebugSources
75+
./gradlew :sample:samples:compileAndroidDeviceTestSources
7676
7777
# API 30+ emulators only have x86_64 system images.
7878
- name: Get AVD info

.github/workflows/gradle-android-unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
GITHUB_USER: ${{ github.actor }}
3232
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3333
run: |
34-
./gradlew :sample:samples:compileDebugSources
34+
./gradlew :sample:samples:compileAndroidHostTestSources
3535
3636
- name: Android Unit Tests
3737
run: ./gradlew androidUnitOnlyAffectedTest

build.gradle.kts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ plugins {
5151
id("scripts.testing")
5252
id("scripts.detekt")
5353
alias(libs.plugins.moduleGraph)
54-
alias(libs.plugins.dagCommand)
5554
alias(libs.plugins.compose.compiler) apply false
5655
alias(libs.plugins.compose.jetbrains) apply false
5756
}
@@ -70,14 +69,25 @@ tasks.withType<Test> {
7069
// For some reason xml and html generation is failing, looks like tests running in parallel
7170
subprojects {
7271
tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest>().configureEach {
72+
val fullSqliterTraces = providers.gradleProperty("kalium.sqliter.fullTraces")
73+
.orNull
74+
?.lowercase()
75+
?.let { it == "true" || it == "1" || it == "yes" }
76+
?: false
77+
environment("KALIUM_SQLITER_FULL_TRACES", fullSqliterTraces.toString())
78+
79+
providers.gradleProperty("kalium.sqliter.traceFile").orNull?.takeIf { it.isNotBlank() }?.let {
80+
environment("KALIUM_SQLITER_TRACE_FILE", it)
81+
}
82+
7383
reports.junitXml.required.set(false)
7484
reports.html.required.set(false)
7585

76-
// workaround for knowing which tests passed failed since HTML reporting is disabled for native tests
77-
// can be removed once HTML reporting is working
86+
// Keep failed test visibility without forwarding all native stdout/stderr to Gradle's
87+
// test output store, which can fail after Gradle/Kotlin upgrades.
7888
testLogging {
7989
events("failed")
80-
showStandardStreams = true
90+
showStandardStreams = false
8191
}
8292
}
8393

@@ -104,12 +114,6 @@ allprojects {
104114
}
105115
}
106116

107-
dagCommand {
108-
defaultBranch = "origin/develop"
109-
outputType = "json"
110-
printModulesInfo = true
111-
}
112-
113117
kover {
114118
useJacoco()
115119
}

buildSrc/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ dependencies {
3232
implementation("org.jetbrains.dokka:dokka-gradle-plugin:${libs.versions.dokka.get()}")
3333
implementation("com.android.tools.build:gradle:${libs.versions.agp.get()}")
3434
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${libs.versions.detekt.get()}")
35+
testImplementation(gradleTestKit())
36+
testImplementation(kotlin("test-junit5"))
37+
}
38+
39+
tasks.test {
40+
useJUnitPlatform()
3541
}
3642

3743
gradlePlugin {

buildSrc/src/main/kotlin/Android.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object Android {
2121
const val testRunner = "androidx.test.runner.AndroidJUnitRunner"
2222
object Sdk {
2323
const val min = 26
24-
const val compile = 35
24+
const val compile = 36
2525
const val target = compile
2626
}
2727
object Ndk {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
19+
import org.gradle.api.DefaultTask
20+
import org.gradle.api.artifacts.ProjectDependency
21+
import org.gradle.api.tasks.Input
22+
import org.gradle.api.tasks.TaskAction
23+
24+
/**
25+
* Gradle 9 compatible replacement for the external dag-command plugin.
26+
* It writes the same affected-modules output used by [OnlyAffectedTestTask].
27+
*/
28+
open class DagCommandTask : DefaultTask() {
29+
30+
@Input
31+
var defaultBranch: String = "origin/develop"
32+
33+
init {
34+
group = "verification"
35+
description = "Computes affected modules and writes build/dag-command/affected-modules.json"
36+
}
37+
38+
@TaskAction
39+
fun computeAffectedModules() {
40+
val outputFile = project.layout.buildDirectory.file(OUTPUT_FILE).get().asFile
41+
val changedModules = changedModules() ?: run {
42+
outputFile.delete()
43+
return
44+
}
45+
val affectedModules = expandToDependents(changedModules)
46+
47+
outputFile.parentFile.mkdirs()
48+
outputFile.writeText(
49+
affectedModules
50+
.sorted()
51+
.joinToString(prefix = "[", postfix = "]") { "\"$it\"" }
52+
)
53+
54+
println("Default branch: $defaultBranch")
55+
println("Output type: json")
56+
println("Output path: ${project.layout.buildDirectory.get().asFile.absolutePath}")
57+
println("Changed modules: ${changedModules.size}, affected modules: ${affectedModules.size}")
58+
}
59+
60+
private fun changedModules(): Set<String>? {
61+
val process = ProcessBuilder(
62+
"git",
63+
"-C",
64+
project.rootDir.absolutePath,
65+
"diff",
66+
defaultBranch,
67+
"--dirstat=files,0"
68+
)
69+
.redirectErrorStream(true)
70+
.start()
71+
72+
val output = process.inputStream.bufferedReader().readText()
73+
if (process.waitFor() != 0) {
74+
println("Unable to resolve changed files from git. Falling back to running all tests.")
75+
return null
76+
}
77+
78+
val allModules = project.subprojects.map { it.path }.toSet()
79+
val parsedModules = output
80+
.lineSequence()
81+
.map { it.trim() }
82+
.filter { it.isNotEmpty() }
83+
.flatMap { parseModuleCandidates(it).asSequence() }
84+
.toSet()
85+
86+
return if (parsedModules.contains(BUILD_SRC) || parsedModules.contains(GRADLE)) {
87+
allModules
88+
} else {
89+
parsedModules.filter(allModules::contains).toSet()
90+
}
91+
}
92+
93+
private fun parseModuleCandidates(dirstatLine: String): Set<String> {
94+
val fullPath = dirstatLine
95+
.trimStart()
96+
.split(" ", limit = 2)
97+
.getOrNull(1)
98+
?.trim()
99+
.orEmpty()
100+
101+
val words = fullPath.split("/")
102+
.takeWhile { it != "src" }
103+
.filter { it.isNotEmpty() }
104+
105+
return words.fold(emptySet()) { acc, word ->
106+
if (acc.isEmpty()) {
107+
setOf(":$word")
108+
} else {
109+
val lastWord = acc.last()
110+
acc + "$lastWord:$word"
111+
}
112+
}
113+
}
114+
115+
private fun expandToDependents(changedModules: Set<String>): Set<String> {
116+
if (changedModules.isEmpty()) return emptySet()
117+
118+
val reverseGraph = mutableMapOf<String, MutableSet<String>>()
119+
project.subprojects.forEach { module ->
120+
val dependencies = module.configurations.flatMap { configuration ->
121+
configuration.dependencies
122+
.withType(ProjectDependency::class.java)
123+
.map { dependency -> dependency.path }
124+
}.toSet()
125+
126+
dependencies.forEach { dependencyPath ->
127+
reverseGraph.getOrPut(dependencyPath) { mutableSetOf() }.add(module.path)
128+
}
129+
}
130+
131+
val visited = changedModules.toMutableSet()
132+
val queue = ArrayDeque(changedModules)
133+
while (queue.isNotEmpty()) {
134+
val current = queue.removeFirst()
135+
reverseGraph[current].orEmpty().forEach { dependent ->
136+
if (visited.add(dependent)) {
137+
queue.add(dependent)
138+
}
139+
}
140+
}
141+
return visited
142+
}
143+
144+
private companion object {
145+
const val OUTPUT_FILE = "dag-command/affected-modules.json"
146+
const val BUILD_SRC = ":buildSrc"
147+
const val GRADLE = ":gradle"
148+
}
149+
}

buildSrc/src/main/kotlin/OnlyAffectedTestTask.kt

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ import org.gradle.kotlin.dsl.support.get
2323
import org.gradle.process.ExecOperations
2424

2525
/**
26-
* This task will run only the tests for affected modules and dependants.
27-
*
28-
* This task dependsOn: [https://github.com/leandroBorgesFerreira/dag-command]
29-
* That will generate for us a list of affected modules
26+
* This task will run only the tests for affected modules and dependants when affected-module
27+
* information is available in [AFFECTED_MODULES_FILE].
3028
*
3129
* You can define your own task by manually.
3230
* Or you can add it to the [TestTaskConfiguration] enum, and it will be added automatically
@@ -42,51 +40,62 @@ open class OnlyAffectedTestTask : DefaultTask() {
4240

4341
init {
4442
group = "verification"
45-
description = "Installs and runs the tests for debug on connected devices (Only for affected modules)."
46-
// TODO(refactor): The task should not exec other gradlew tasks,
47-
// but rather be configured based on dagCommand, and add other tasks as dependency,
48-
// simplifying the logic and making it cacheable
43+
description = "Runs tests for affected modules when available, otherwise runs all tests."
4944
setDependsOn(mutableListOf("dag-command"))
5045
}
5146

5247
@TaskAction
5348
fun runOnlyAffectedConnectedTest() {
54-
var affectedModules: Set<String> = setOf()
55-
project.layout.buildDirectory.file("dag-command/affected-modules.json").get().asFile.useLines {
56-
affectedModules = it.joinToString()
57-
.removeSurrounding("[", "]")
58-
.replace("\"", "")
59-
.split(",")
60-
.toSet()
61-
}
49+
val affectedModules = readAffectedModules()
50+
val missingAffectedModulesData = affectedModules == null
51+
val runAllTests = hasToRunAllTests() || missingAffectedModulesData
6252

63-
if (!hasToRunAllTests() && (affectedModules.isEmpty() || affectedModules.first().isEmpty())) {
53+
if (!runAllTests && affectedModules.orEmpty().isEmpty()) {
6454
println("\uD83E\uDD8B It is not necessary to run any test, ending here to free up some resources.")
6555
return
6656
}
6757

68-
executeTask(affectedModules)
58+
executeTask(
59+
affectedModules = affectedModules.orEmpty(),
60+
runAllTests = runAllTests,
61+
missingAffectedModulesData = missingAffectedModulesData
62+
)
6963
}
7064

71-
private fun executeTask(affectedModules: Set<String>) {
65+
private fun executeTask(affectedModules: Set<String>, runAllTests: Boolean, missingAffectedModulesData: Boolean) {
7266
val tasksName = mutableListOf<String>()
73-
val hasToRunAllTests = hasToRunAllTests()
7467
project.subprojects
75-
.filter { (hasToRunAllTests || affectedModules.contains(it.path)) && !ignoredModules.contains(it.name) }
68+
.filter { (runAllTests || affectedModules.contains(it.path)) && !ignoredModules.contains(it.path) }
7669
.forEach { childProject ->
77-
tasksName.addAll(
78-
childProject.tasks
79-
.filter { it.name.equals(configuration.testTarget, true) }
80-
.map { task ->
81-
println("Adding task: ${childProject.path}:${task.name}")
82-
"${childProject.path}:${task.name}"
83-
}.toList()
84-
)
70+
val targetTaskName = childProject.tasks.names.firstOrNull { it.equals(configuration.testTarget, true) }
71+
targetTaskName?.let { taskName ->
72+
println("Adding task: ${childProject.path}:$taskName")
73+
tasksName.add("${childProject.path}:$taskName")
74+
}
8575
}
8676

77+
if (missingAffectedModulesData) {
78+
println("\uD83D\uDD27 Running all tests because affected-modules data is unavailable.")
79+
}
8780
tasksName.forEach(::runTargetTask)
8881
}
8982

83+
private fun readAffectedModules(): Set<String>? {
84+
val affectedModulesFile = project.layout.buildDirectory.file(AFFECTED_MODULES_FILE).get().asFile
85+
if (!affectedModulesFile.exists()) {
86+
println("\uD83D\uDD27 Missing '$AFFECTED_MODULES_FILE', falling back to all modules.")
87+
return null
88+
}
89+
90+
return affectedModulesFile.readText()
91+
.trim()
92+
.removeSurrounding("[", "]")
93+
.split(",")
94+
.map { it.trim().removeSurrounding("\"") }
95+
.filter { it.isNotBlank() }
96+
.toSet()
97+
}
98+
9099
private fun runTargetTask(targetTask: String) {
91100
println("\uD83D\uDD27 Running tests on '$targetTask'.")
92101
val execOperations = services.get<ExecOperations>()
@@ -97,7 +106,7 @@ open class OnlyAffectedTestTask : DefaultTask() {
97106
}
98107

99108
/**
100-
* Check if we have to run all tests, by looking at untracked by dag-command files [globalBuildSettingsFiles].
109+
* Check if we have to run all tests by looking at root-level build files [globalBuildSettingsFiles].
101110
*/
102111
private fun hasToRunAllTests(): Boolean {
103112
val globalBuildSettingsFiles = listOf(
@@ -131,5 +140,6 @@ open class OnlyAffectedTestTask : DefaultTask() {
131140

132141
private companion object {
133142
val IGNORED_MODULES = listOf(":data:protobuf", ":tools:protobuf-codegen")
143+
const val AFFECTED_MODULES_FILE = "dag-command/affected-modules.json"
134144
}
135145
}

0 commit comments

Comments
 (0)