Browse Source

Don't apply Kover to projects with AAR libraries (#2211)

* Don't apply Kover to projects with AAR libraries

* Move the Kover configuration to the `plugins` project
pull/2220/head
Jorge Martin Espinosa 8 months ago committed by GitHub
parent
commit
f281c6c365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 199
      app/build.gradle.kts
  2. 6
      build.gradle.kts
  3. 4
      gradle/libs.versions.toml
  4. 1
      plugins/build.gradle.kts
  5. 229
      plugins/src/main/kotlin/extension/KoverExtension.kt
  6. 5
      plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts
  7. 7
      plugins/src/main/kotlin/io.element.android-root.gradle.kts

199
app/build.gradle.kts

@ -28,7 +28,8 @@ plugins { @@ -28,7 +28,8 @@ plugins {
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kapt)
alias(libs.plugins.firebaseAppDistribution)
// When using precompiled plugins, we need to apply the firebase plugin like this
id(libs.plugins.firebaseAppDistribution.get().pluginId)
alias(libs.plugins.knit)
id("kotlin-parcelize")
// To be able to update the firebase.xml files, uncomment and build the project
@ -191,45 +192,6 @@ knit { @@ -191,45 +192,6 @@ knit {
}
}
/**
* Kover configuration
*/
dependencies {
// Add all sub projects to kover except some of them
project.rootProject.subprojects
.filter {
it.project.projectDir.resolve("build.gradle.kts").exists()
}
.map { it.path }
.sorted()
.filter {
it !in listOf(
":app",
":samples",
":anvilannotations",
":anvilcodegen",
":samples:minimal",
":tests:testutils",
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix
// SDK api, so it is not really relevant to unit test it: there is no logic to test.
":libraries:matrix:impl",
// Exclude modules which are not Android libraries
// See https://github.com/Kotlin/kotlinx-kover/issues/312
":appconfig",
":libraries:core",
":libraries:coroutines",
":libraries:di",
":libraries:rustsdk",
":libraries:textcomposer:lib",
)
}
.forEach {
// println("Add $it to kover")
kover(project(it))
}
}
val ciBuildProperty = "ci-build"
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
val raw = project.property(ciBuildProperty) as? String
@ -250,163 +212,6 @@ kover { @@ -250,163 +212,6 @@ kover {
}
}
// 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
koverReport {
filters {
excludes {
classes(
// Exclude generated classes.
"*_ModuleKt",
"anvil.hint.binding.io.element.*",
"anvil.hint.merge.*",
"anvil.hint.multibinding.io.element.*",
"anvil.module.*",
"com.airbnb.android.showkase*",
"io.element.android.libraries.designsystem.showkase.*",
"io.element.android.x.di.DaggerAppComponent*",
"*_Factory",
"*_Factory_Impl",
"*_Factory$*",
"*_Module",
"*_Module$*",
"*Module_Provides*",
"Dagger*Component*",
"*ComposableSingletons$*",
"*_AssistedFactory_Impl*",
"*BuildConfig",
// Generated by Showkase
"*Ioelementandroid*PreviewKt$*",
"*Ioelementandroid*PreviewKt",
// Other
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
"*Node",
"*Node$*",
"*Presenter\$present\$*",
// Forked from compose
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
)
annotatedBy(
"androidx.compose.ui.tooling.preview.Preview",
"io.element.android.libraries.architecture.coverage.ExcludeFromCoverage",
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
)
}
}
defaults {
// add reports of both 'debug' and 'release' Android build variants to default reports
mergeWith("debug")
mergeWith("release")
verify {
onCheck = true
// General rule: minimum code coverage.
rule("Global minimum code coverage.") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
bound {
minValue = 65
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 25 and maxValue to 35.
maxValue = 75
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of Presenters is sufficient.
rule("Check code coverage of presenters") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*Presenter",
)
}
excludes {
classes(
"*Fake*Presenter",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
"*Presenter\$present\$*",
)
}
}
bound {
minValue = 85
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of States is sufficient.
rule("Check code coverage of states") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"^*State$",
)
}
excludes {
classes(
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
)
}
}
bound {
minValue = 90
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
rule("Check code coverage of views") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*ViewKt",
)
}
}
bound {
// TODO Update this value, for now there are too many missing tests.
minValue = 0
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
}
}
androidReports("release") {
}
}
dependencies {
allLibrariesImpl()
allServicesImpl()

6
build.gradle.kts

@ -26,6 +26,7 @@ buildscript { @@ -26,6 +26,7 @@ buildscript {
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("io.element.android-root")
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
@ -39,7 +40,6 @@ plugins { @@ -39,7 +40,6 @@ plugins {
alias(libs.plugins.ktlint)
alias(libs.plugins.dependencygraph)
alias(libs.plugins.sonarqube)
alias(libs.plugins.kover) apply false
}
tasks.register<Delete>("clean").configure {
@ -161,10 +161,6 @@ allprojects { @@ -161,10 +161,6 @@ allprojects {
}
}
allprojects {
apply(plugin = "org.jetbrains.kotlinx.kover")
}
// Register quality check tasks.
tasks.register("runQualityChecks") {
project.subprojects {

4
gradle/libs.versions.toml

@ -51,6 +51,7 @@ autoservice = "1.1.1" @@ -51,6 +51,7 @@ autoservice = "1.1.1"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
kover = "0.7.5"
[libraries]
# Project
@ -58,6 +59,7 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref @@ -58,6 +59,7 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
android_desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
gms_google_services = "com.google.gms:google-services:4.4.0"
# https://firebase.google.com/docs/android/setup#available-libraries
google_firebase_bom = "com.google.firebase:firebase-bom:32.7.0"
@ -215,7 +217,7 @@ dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" @@ -215,7 +217,7 @@ dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
dependencycheck = "org.owasp.dependencycheck:9.0.8"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
paparazzi = "app.cash.paparazzi:1.3.1"
kover = "org.jetbrains.kotlinx.kover:0.7.5"
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" }
knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" }

1
plugins/build.gradle.kts

@ -26,6 +26,7 @@ repositories { @@ -26,6 +26,7 @@ repositories {
dependencies {
implementation(libs.android.gradle.plugin)
implementation(libs.kotlin.gradle.plugin)
implementation(libs.kover.gradle.plugin)
implementation(platform(libs.google.firebase.bom))
implementation(libs.firebase.appdistribution.gradle)
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))

229
plugins/src/main/kotlin/extension/KoverExtension.kt

@ -0,0 +1,229 @@ @@ -0,0 +1,229 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package extension
import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
val localAarProjects = listOf(
":libraries:rustsdk",
":libraries:textcomposer:lib"
)
val excludedKoverSubProjects = listOf(
":app",
":samples",
":anvilannotations",
":anvilcodegen",
":samples:minimal",
":tests:testutils",
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix
// SDK api, so it is not really relevant to unit test it: there is no logic to test.
":libraries:matrix:impl",
// Exclude modules which are not Android libraries
// See https://github.com/Kotlin/kotlinx-kover/issues/312
":appconfig",
":libraries:core",
":libraries:coroutines",
":libraries:di",
) + localAarProjects
private fun Project.koverReport(action: Action<KoverReportExtension>) {
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("koverReport", action)
}
fun Project.setupKover() {
// 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
koverReport {
filters {
excludes {
classes(
// Exclude generated classes.
"*_ModuleKt",
"anvil.hint.binding.io.element.*",
"anvil.hint.merge.*",
"anvil.hint.multibinding.io.element.*",
"anvil.module.*",
"com.airbnb.android.showkase*",
"io.element.android.libraries.designsystem.showkase.*",
"io.element.android.x.di.DaggerAppComponent*",
"*_Factory",
"*_Factory_Impl",
"*_Factory$*",
"*_Module",
"*_Module$*",
"*Module_Provides*",
"Dagger*Component*",
"*ComposableSingletons$*",
"*_AssistedFactory_Impl*",
"*BuildConfig",
// Generated by Showkase
"*Ioelementandroid*PreviewKt$*",
"*Ioelementandroid*PreviewKt",
// Other
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
"*Node",
"*Node$*",
"*Presenter\$present\$*",
// Forked from compose
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
)
annotatedBy(
"androidx.compose.ui.tooling.preview.Preview",
"io.element.android.libraries.architecture.coverage.ExcludeFromCoverage",
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
)
}
}
defaults {
// add reports of both 'debug' and 'release' Android build variants to default reports
mergeWith("debug")
mergeWith("release")
verify {
onCheck = true
// General rule: minimum code coverage.
rule("Global minimum code coverage.") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
bound {
minValue = 65
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 25 and maxValue to 35.
maxValue = 75
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of Presenters is sufficient.
rule("Check code coverage of presenters") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*Presenter",
)
}
excludes {
classes(
"*Fake*Presenter",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
"*Presenter\$present\$*",
)
}
}
bound {
minValue = 85
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of States is sufficient.
rule("Check code coverage of states") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"^*State$",
)
}
excludes {
classes(
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
)
}
}
bound {
minValue = 90
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
rule("Check code coverage of views") {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
filters {
includes {
classes(
"*ViewKt",
)
}
}
bound {
// TODO Update this value, for now there are too many missing tests.
minValue = 0
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
}
}
}
}
androidReports("release") {}
}
}
fun Project.applyKoverPluginToAllSubProjects() = rootProject.allprojects {
if (project.path !in localAarProjects) {
apply(plugin = "org.jetbrains.kotlinx.kover")
}
}
fun Project.koverDependencies() {
project.rootProject.subprojects
.filter {
it.project.projectDir.resolve("build.gradle.kts").exists()
}
.map { it.path }
.sorted()
.filter {
it !in excludedKoverSubProjects
}
.forEach {
// println("Add $it to kover")
dependencies.add("kover", project(it))
}
}

5
plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts

@ -17,10 +17,12 @@ @@ -17,10 +17,12 @@
/**
* This will generate the plugin "io.element.android-compose-application" to use by app and samples modules
*/
import extension.koverDependencies
import extension.androidConfig
import extension.commonDependencies
import extension.composeConfig
import extension.composeDependencies
import extension.setupKover
import org.gradle.accessors.dm.LibrariesForLibs
val libs = the<LibrariesForLibs>()
@ -30,6 +32,8 @@ plugins { @@ -30,6 +32,8 @@ plugins {
id("com.autonomousapps.dependency-analysis")
}
setupKover()
android {
androidConfig(project)
composeConfig(libs)
@ -42,4 +46,5 @@ dependencies { @@ -42,4 +46,5 @@ dependencies {
commonDependencies(libs)
composeDependencies(libs)
coreLibraryDesugaring(libs.android.desugar)
koverDependencies()
}

7
plugins/src/main/kotlin/io.element.android-root.gradle.kts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
import extension.applyKoverPluginToAllSubProjects
plugins {
id("org.jetbrains.kotlinx.kover") apply false
}
applyKoverPluginToAllSubProjects()
Loading…
Cancel
Save