Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ dependencies {
testImplementation(projects.feature.account.fake)
testImplementation(projects.core.testing)
}

codeCoverage {
branchCoverage.set(9)
lineCoverage.set(25)
}
11 changes: 7 additions & 4 deletions app-k9mail/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ plugins {
id("thunderbird.quality.badging")
}

val testCoverageEnabled: Boolean by extra
if (testCoverageEnabled) {
apply(plugin = "jacoco")
}
val testCoverageEnabled = hasProperty("testCoverageEnabled")

android {
namespace = "com.fsck.k9"
Expand Down Expand Up @@ -100,6 +97,7 @@ android {

debug {
applicationIdSuffix = ".debug"

enableUnitTestCoverage = testCoverageEnabled
enableAndroidTestCoverage = testCoverageEnabled

Expand Down Expand Up @@ -179,3 +177,8 @@ dependencyGuard {
configuration("fossReleaseRuntimeClasspath")
configuration("fullReleaseRuntimeClasspath")
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(25)
}
13 changes: 9 additions & 4 deletions app-thunderbird/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ plugins {
id("thunderbird.quality.badging")
}

val testCoverageEnabled: Boolean by extra
if (testCoverageEnabled) {
apply(plugin = "jacoco")
}
val testCoverageEnabled = hasProperty("testCoverageEnabled")

android {
namespace = "net.thunderbird.android"
Expand Down Expand Up @@ -95,6 +92,9 @@ android {
applicationIdSuffix = ".debug"
versionNameSuffix = "-SNAPSHOT"

enableUnitTestCoverage = testCoverageEnabled
enableAndroidTestCoverage = testCoverageEnabled

isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
Expand Down Expand Up @@ -283,3 +283,8 @@ tasks.register("printConfigurations") {
}
}
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(25)
}
5 changes: 5 additions & 0 deletions app-ui-catalog/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ dependencies {
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.datetime)
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(0)
}
5 changes: 5 additions & 0 deletions backend/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ dependencies {
implementation(projects.feature.mail.folder.api)
api(projects.mail.common)
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(0)
}
5 changes: 5 additions & 0 deletions backend/demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ dependencies {
testImplementation(projects.mail.testing)
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(0)
}

tasks.register<UpdateDemoMailbox>("updateDemoMailbox") {
group = "demo"
description = "Update mailbox/contents.json from src/main/resources/mailbox contents."
Expand Down
5 changes: 5 additions & 0 deletions backend/imap/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ dependencies {
testImplementation(projects.backend.testing)
testImplementation(libs.mime4j.dom)
}

codeCoverage {
branchCoverage.set(45)
lineCoverage.set(44)
}
5 changes: 5 additions & 0 deletions backend/jmap/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ dependencies {
testImplementation(projects.backend.testing)
testImplementation(libs.okhttp.mockwebserver)
}

codeCoverage {
branchCoverage.set(33)
lineCoverage.set(42)
}
5 changes: 5 additions & 0 deletions backend/pop3/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ dependencies {

testImplementation(projects.mail.testing)
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(0)
}
5 changes: 5 additions & 0 deletions backend/testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ dependencies {
implementation(libs.junit)
implementation(libs.assertk)
}

codeCoverage {
branchCoverage.set(0)
lineCoverage.set(0)
}
6 changes: 5 additions & 1 deletion build-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ dependencies {
implementation(plugin(libs.plugins.dependency.check))
implementation(plugin(libs.plugins.detekt))
implementation(plugin(libs.plugins.spotless))
implementation(plugin(libs.plugins.kover))

// Make custom plugins in :plugins available to precompiled convention plugins by classpath
implementation(project(":plugins"))

implementation(libs.diff.utils)
compileOnly(libs.android.tools.common)
Expand All @@ -36,6 +40,6 @@ kotlin {
}
}

fun plugin(provider: Provider<PluginDependency>) = with(provider.get()) {
private fun plugin(provider: Provider<PluginDependency>) = with(provider.get()) {
"$pluginId:$pluginId.gradle.plugin:$version"
}
35 changes: 35 additions & 0 deletions build-plugin/plugins/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`kotlin-dsl`
}

group = "net.thunderbird.gradle.plugin"

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}

dependencies {
compileOnly(plugin(libs.plugins.kover))
}

gradlePlugin {
plugins {
register("QualityCodeCoverage") {
id = "net.thunderbird.gradle.plugin.quality.coverage"
implementationClass = "net.thunderbird.gradle.plugin.quality.coverage.CodeCoveragePlugin"
}
}
}

private fun plugin(provider: Provider<PluginDependency>) = with(provider.get()) {
"$pluginId:$pluginId.gradle.plugin:$version"
}
3 changes: 3 additions & 0 deletions build-plugin/plugins/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.thunderbird.gradle.plugin.quality.coverage

import org.gradle.api.provider.Property

internal const val DEFAULT_MIN_BRANCH_COVERAGE = 70
internal const val DEFAULT_MIN_LINE_COVERAGE = 75

interface CodeCoverageExtension {

/**
* Whether code coverage is disabled.
*/
val disabled: Property<Boolean>

/**
* Minimum required branch coverage in percent (0-100).
*/
val branchCoverage: Property<Int>

/**
* Minimum required line coverage in percent (0-100).
*/
val lineCoverage: Property<Int>

}

internal fun CodeCoverageExtension.initialize() {
disabled.convention(false)
branchCoverage.convention(DEFAULT_MIN_BRANCH_COVERAGE)
lineCoverage.convention(DEFAULT_MIN_LINE_COVERAGE)
}

internal fun CodeCoverageExtension.finalizeValueOnRead() {
disabled.finalizeValueOnRead()
branchCoverage.finalizeValueOnRead()
lineCoverage.finalizeValueOnRead()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package net.thunderbird.gradle.plugin.quality.coverage

import kotlinx.kover.gradle.plugin.KoverGradlePlugin
import kotlinx.kover.gradle.plugin.dsl.AggregationType
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import kotlinx.kover.gradle.plugin.dsl.KoverReportFiltersConfig
import kotlinx.kover.gradle.plugin.dsl.KoverVerificationRulesConfig
import net.thunderbird.gradle.plugin.quality.coverage.filter.androidFilter
import net.thunderbird.gradle.plugin.quality.coverage.filter.commonFilter
import net.thunderbird.gradle.plugin.quality.coverage.filter.composeFilter
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.create

/**
* A Gradle plugin that configures code coverage using the Kover plugin.
*
* It sets up default coverage thresholds and allows for customization via the [CodeCoverageExtension].
*
* The plugin can be globally disabled using a Gradle property or environment variable:
* - Gradle property: `-PcodeCoverageDisabled=true`
* - Environment variable: `CODE_COVERAGE_DISABLED=true`
*
* Example usage in a build script:
*
* ```kotlin
* plugins {
* id("net.thunderbird.gradle.plugin.quality.coverage")
* }
*
* codeCoverage {
* disabled.set(false) // Enable or disable coverage
* lineCoverage.set(80) // Set line coverage threshold
* branchCoverage.set(70) // Set branch coverage threshold
* }
*/
class CodeCoveragePlugin: Plugin<Project> {

override fun apply(target: Project) {
val extension = target.extensions.create<CodeCoverageExtension>("codeCoverage")

val gradleProperty = target.providers.gradleProperty("codeCoverageDisabled").map { it.toBoolean() }
val environmentProperty = target.providers.environmentVariable("CODE_COVERAGE_DISABLED")
.map { it.equals("true", ignoreCase = true) }
val disabledProvider = environmentProperty.orElse(gradleProperty).orElse(false)

extension.disabled.convention(disabledProvider)

extension.initialize()
extension.finalizeValueOnRead()

target.pluginManager.apply(KoverGradlePlugin::class)
target.configureKover(extension)
}

private fun Project.configureKover(coverageExtension: CodeCoverageExtension) {
extensions.configure<KoverProjectExtension>("kover") {
if (coverageExtension.disabled.get()) {
disable()
}

// See https://www.jacoco.org/jacoco/
useJacoco("0.8.14")

reports {
total {
filters {
commonFilter()
composeFilter()
androidFilter()
}
}

verify {
warningInsteadOfFailure.set(false)

applyVerificationRules(coverageExtension)
}
}
}
}

private fun KoverVerificationRulesConfig.applyVerificationRules(coverageExtension: CodeCoverageExtension) {
rule("branchCoveragePercentage") {
disabled.set(coverageExtension.disabled)
bound {
minValue.set(coverageExtension.branchCoverage)
coverageUnits.set(CoverageUnit.BRANCH)
aggregationForGroup.set(AggregationType.COVERED_PERCENTAGE)
}
}
rule("lineCoveragePercentage") {
disabled.set(coverageExtension.disabled)
bound {
minValue.set(coverageExtension.lineCoverage)
coverageUnits.set(CoverageUnit.LINE)
aggregationForGroup.set(AggregationType.COVERED_PERCENTAGE)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.thunderbird.gradle.plugin.quality.coverage.filter

import kotlinx.kover.gradle.plugin.dsl.KoverReportFiltersConfig

internal fun KoverReportFiltersConfig.androidFilter() {
excludes {
classes(
"*R$*",
"*BuildConfig",
"*Manifest*",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.thunderbird.gradle.plugin.quality.coverage.filter

import kotlinx.kover.gradle.plugin.dsl.KoverReportFiltersConfig

internal fun KoverReportFiltersConfig.commonFilter() {
excludes {
annotatedBy(
"*Generated*",
"**Generated**",
"*Generated",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.thunderbird.gradle.plugin.quality.coverage.filter

import kotlinx.kover.gradle.plugin.dsl.KoverReportFiltersConfig

internal fun KoverReportFiltersConfig.composeFilter() {
excludes {
annotatedBy(
"androidx.compose.ui.tooling.preview.Preview",
"androidx.compose.ui.tooling.preview.PreviewLightDark",
"app.k9mail.core.ui.compose.common.annotation.PreviewDevices",
"app.k9mail.core.ui.compose.common.annotation.PreviewDevicesWithBackground",
"app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape",
)
}
}
3 changes: 3 additions & 0 deletions build-plugin/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pluginManagement {
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

@Suppress("UnstableApiUsage")
repositories {
gradlePluginPortal()
google()
Expand All @@ -19,3 +20,5 @@ dependencyResolutionManagement {
}

rootProject.name = "build-plugin"

include(":plugins")
Loading
Loading