diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c8e797e83b..42c8280bc2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,7 +73,7 @@ jobs: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: ⚙️ Check coverage for debug variant (includes unit & screenshot tests) - run: ./gradlew testDebugUnitTest :tests:uitests:verifyPaparazziDebug :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES + run: ./gradlew testDebugUnitTest :tests:uitests:verifyPaparazziDebug :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll $CI_GRADLE_ARG_PROPERTIES - name: 🚫 Upload kover failed coverage reports if: failure() @@ -112,5 +112,5 @@ jobs: with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} - files: app/build/reports/kover/reportGplayDebug.xml + files: build/reports/kover/reportMerged.xml verbose: true diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts index 33e3cbeff2..42512d5248 100644 --- a/annotations/build.gradle.kts +++ b/annotations/build.gradle.kts @@ -6,6 +6,5 @@ * Please see LICENSE files in the repository root for full details. */ plugins { - alias(libs.plugins.kotlin.jvm) - id("com.android.lint") + id("io.element.jvm-library") } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5a3900d296..5461766425 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,10 +21,8 @@ import extension.allFeaturesImpl import extension.allLibrariesImpl import extension.allServicesImpl import extension.buildConfigFieldStr -import extension.koverDependencies import extension.locales import extension.setupDependencyInjection -import extension.setupKover import extension.testCommonDependencies import java.util.Locale @@ -40,8 +38,6 @@ plugins { // alias(libs.plugins.gms.google.services) } -setupKover() - android { namespace = "io.element.android.x" @@ -295,8 +291,6 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.toolbox.test) - - koverDependencies() } tasks.withType().configureEach { diff --git a/libraries/core/build.gradle.kts b/libraries/core/build.gradle.kts index d04efaa40e..7642543c55 100644 --- a/libraries/core/build.gradle.kts +++ b/libraries/core/build.gradle.kts @@ -6,20 +6,7 @@ * Please see LICENSE files in the repository root for full details. */ plugins { - id("java-library") - id("com.android.lint") - alias(libs.plugins.kotlin.jvm) -} - -java { - sourceCompatibility = Versions.javaVersion - targetCompatibility = Versions.javaVersion -} - -kotlin { - jvmToolchain { - languageVersion = Versions.javaLanguageVersion - } + id("io.element.jvm-library") } dependencies { diff --git a/libraries/di/build.gradle.kts b/libraries/di/build.gradle.kts index 2444392064..520c52edec 100644 --- a/libraries/di/build.gradle.kts +++ b/libraries/di/build.gradle.kts @@ -7,8 +7,7 @@ */ plugins { - alias(libs.plugins.kotlin.jvm) - id("com.android.lint") + id("io.element.jvm-library") } dependencies { diff --git a/plugins/src/main/kotlin/extension/KoverExtension.kt b/plugins/src/main/kotlin/extension/KoverExtension.kt index 4cb8398a39..fe73157450 100644 --- a/plugins/src/main/kotlin/extension/KoverExtension.kt +++ b/plugins/src/main/kotlin/extension/KoverExtension.kt @@ -13,10 +13,11 @@ import kotlinx.kover.gradle.plugin.dsl.CoverageUnit import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension import kotlinx.kover.gradle.plugin.dsl.KoverVariantCreateConfig -import org.gradle.api.Action import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.assign +import org.gradle.kotlin.dsl.configure +import java.io.File enum class KoverVariant(val variantName: String) { Presenters("presenters"), @@ -24,7 +25,7 @@ enum class KoverVariant(val variantName: String) { Views("views"), } -val koverVariants = KoverVariant.values().map { it.variantName } +val koverVariants = KoverVariant.entries.map { it.variantName } val localAarProjects = listOf( ":libraries:rustsdk", @@ -32,7 +33,7 @@ val localAarProjects = listOf( ) val excludedKoverSubProjects = listOf( - ":app", + ":appconfig", ":annotations", ":codegen", ":tests:testutils", @@ -42,24 +43,63 @@ val excludedKoverSubProjects = listOf( ":libraries:core", ":libraries:coroutines", ":libraries:di", + ":tests:detekt-rules", + ":tests:konsist", + ":tests:testutils", ) + localAarProjects -private fun Project.kover(action: Action) { - (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kover", action) +private fun Project.kover(any: Any) { + this.dependencies.add("kover", any) } fun Project.setupKover() { + // If the project is excluded from Kover, don't apply anything + if (path in excludedKoverSubProjects) return + + // Apply the plugin + apply(plugin = "org.jetbrains.kotlinx.kover") + // Create verify all task joining all existing verification tasks tasks.register("koverVerifyAll") { group = "verification" description = "Verifies the code coverage of all subprojects." - val dependencies = listOf(":app:koverVerifyGplayDebug") + koverVariants.map { ":app:koverVerify${it.replaceFirstChar(Char::titlecase)}" } + val dependencies = listOf(":koverVerifyMerged") + koverVariants.map { ":app:koverVerify${it.replaceFirstChar(Char::titlecase)}" } dependsOn(dependencies) } // https://kotlin.github.io/kotlinx-kover/ // Run `./gradlew :app:koverHtmlReport` to get report at ./app/build/reports/kover // Run `./gradlew :app:koverXmlReport` to get XML report - kover { + extensions.configure { + currentProject { + // Create custom variants for verification + for (variant in koverVariants) { + createVariant(variant) { + defaultVariants(project) + + // Using the cache for coverage verification seems to be flaky, so we disable it for now. + val taskName = "koverCachedVerify${variant.replaceFirstChar(Char::titlecase)}" + val cachedTask = project.tasks.findByName(taskName) + cachedTask?.let { + it.outputs.upToDateWhen { false } + } + } + } + + // Create merged variant + createVariant("merged") { + defaultVariants(project) + } + } + + // If it's the root project, set up kover for subprojects + if (project.path == ":") { + for (project in project.subprojects) { + if (project.path !in excludedKoverSubProjects && File(project.projectDir, "build.gradle.kts").exists()) { + kover(project) + } + } + } + reports { filters { excludes { @@ -194,54 +234,10 @@ fun Project.setupKover() { } } -fun Project.applyKoverPluginToAllSubProjects() = rootProject.subprojects { - if (project.path !in localAarProjects) { - apply(plugin = "org.jetbrains.kotlinx.kover") - kover { - currentProject { - for (variant in koverVariants) { - createVariant(variant) { - defaultVariants(project) - } - } - } - } - - project.afterEvaluate { - for (variant in koverVariants) { - // Using the cache for coverage verification seems to be flaky, so we disable it for now. - val taskName = "koverCachedVerify${variant.replaceFirstChar(Char::titlecase)}" - val cachedTask = project.tasks.findByName(taskName) - cachedTask?.let { - it.outputs.upToDateWhen { false } - } - } - } - } -} - fun KoverVariantCreateConfig.defaultVariants(project: Project) { - if (project.name == "app") { + if (project.path == ":app") { addWithDependencies("gplayDebug") } else { addWithDependencies("debug", "jvm", optional = true) } } - -fun Project.koverSubprojects() = project.rootProject.subprojects - .filter { - it.project.projectDir.resolve("build.gradle.kts").exists() - } - .map { it.path } - .sorted() - .filter { - it !in excludedKoverSubProjects - } - -fun Project.koverDependencies() { - project.koverSubprojects() - .forEach { - // println("Add $it to kover") - dependencies.add("kover", project(it)) - } -} diff --git a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts index d8f754e8e6..a489884df5 100644 --- a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts @@ -13,6 +13,7 @@ import extension.androidConfig import extension.commonDependencies import extension.composeConfig import extension.composeDependencies +import extension.setupKover import org.gradle.accessors.dm.LibrariesForLibs val libs = the() @@ -37,6 +38,8 @@ kotlin { } } +setupKover() + dependencies { commonDependencies(libs) composeDependencies(libs) diff --git a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts index 8a1020a5c3..26b1a1f122 100644 --- a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts @@ -13,6 +13,7 @@ import extension.androidConfig import extension.commonDependencies import extension.composeConfig import extension.composeDependencies +import extension.setupKover import org.gradle.accessors.dm.LibrariesForLibs val libs = the() @@ -37,6 +38,8 @@ kotlin { } } +setupKover() + dependencies { commonDependencies(libs) composeDependencies(libs) diff --git a/plugins/src/main/kotlin/io.element.android-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-library.gradle.kts index 3ee5e2b603..c10c1bb3d7 100644 --- a/plugins/src/main/kotlin/io.element.android-library.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-library.gradle.kts @@ -11,6 +11,7 @@ */ import extension.androidConfig import extension.commonDependencies +import extension.setupKover import org.gradle.accessors.dm.LibrariesForLibs val libs = the() @@ -33,6 +34,8 @@ kotlin { } } +setupKover() + dependencies { commonDependencies(libs) coreLibraryDesugaring(libs.android.desugar) diff --git a/plugins/src/main/kotlin/io.element.android-root.gradle.kts b/plugins/src/main/kotlin/io.element.android-root.gradle.kts index 44f8fb070e..702eab19b6 100644 --- a/plugins/src/main/kotlin/io.element.android-root.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-root.gradle.kts @@ -1,7 +1,7 @@ -import extension.applyKoverPluginToAllSubProjects +import extension.setupKover plugins { id("org.jetbrains.kotlinx.kover") apply false } -applyKoverPluginToAllSubProjects() +setupKover() diff --git a/plugins/src/main/kotlin/io.element.jvm-library.gradle.kts b/plugins/src/main/kotlin/io.element.jvm-library.gradle.kts new file mode 100644 index 0000000000..9488504d7d --- /dev/null +++ b/plugins/src/main/kotlin/io.element.jvm-library.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +/** + * This will generate the plugin "io.element.jvm-library", used in pure JVM libraries. + */ +import extension.setupKover +import org.gradle.accessors.dm.LibrariesForLibs + +val libs = the() +plugins { + id("org.jetbrains.kotlin.jvm") + id("com.autonomousapps.dependency-analysis") + id("com.android.lint") +} + +kotlin { + jvmToolchain { + languageVersion = Versions.javaLanguageVersion + } +} + +setupKover()