build(coverage): Add plugins for global coverage and common coverage configuration.

This commit is contained in:
Mateusz Armatys
2023-05-09 12:10:17 +00:00
committed by Neil Marietta
parent e730fe635a
commit 97384ce56a
14 changed files with 283 additions and 75 deletions
+12
View File
@@ -34,6 +34,7 @@ plugins {
id("publish-core-libraries")
id("me.proton.core.gradle-plugins.tests")
id("me.proton.core.gradle-plugins.jacoco")
id("me.proton.core.gradle-plugins.coverage-config")
alias(libs.plugins.benManes.versions.gradle)
alias(libs.plugins.kotlin.binaryCompatibilityValidator)
alias(libs.plugins.kotlin.gradle)
@@ -56,6 +57,17 @@ repositories {
mavenCentral()
}
protonCoverage {
excludes.add {
classes(
"me.proton.core.accountmanager.data.db.AccountManagerDatabaseMigrations*",
"*Database_Impl*",
"*Database\$Companion*", // DB migrations
"*LogTag"
)
}
}
fun isNonStable(version: String): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) }
val regex = "^[0-9,.v-]+(-r)?$".toRegex()
+5
View File
@@ -0,0 +1,5 @@
plugins {
id("me.proton.core.gradle-plugins.global-coverage")
}
publishOption.shouldBePublishedAsLib = false
+17 -1
View File
@@ -35,10 +35,26 @@ Use internally in core project to orchestrate dependencies and apply android/kot
- Plugin id: `me.proton.core.gradle-plugins.coverage`
- Published on MavenCentral.
Apply the plugin to each module for which the code coverage is needed.
Apply the plugin to each (non-root) project for which the code coverage is needed.
Additional settings can be configured via `protonCoverage` extension,
e.g. setting custom minimum coverage levels, excluding additional files.
## Global coverage plugin
- Plugin id: `me.proton.core.gradle-plugins.global-coverage`
- Published on MavenCentral.
The plugin is intended to be applied to a separate project, that's only used to
generate a global coverage report or perform coverage percentage verification.
## Common coverage config plugin
- Plugin id: `me.proton.core.gradle-plugins.coverage-config`
- Published on MavenCentral.
Apply the plugin on the root project.
Use the `protonCoverage` extension to configure common coverage settings.
Those settings will be picked up the all the submodules that
use the Coverage plugin (`me.proton.core.gradle-plugins.coverage`).
## Detekt plugin
- Plugin id: `me.proton.core.gradle-plugins.detekt`
- Published on MavenCentral.
+14 -2
View File
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2023 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,6 +44,18 @@ gradlePlugin {
description = "Plugin to generate coverage reports compatible with GitLab"
implementationClass = "me.proton.core.gradle.plugins.coverage.ProtonCoveragePlugin"
}
create("globalCoveragePlugin") {
id = "me.proton.core.gradle-plugins.global-coverage"
displayName = "Proton global coverage plugin"
description = "Plugin to generate global coverage reports compatible with GitLab"
implementationClass = "me.proton.core.gradle.plugins.coverage.ProtonGlobalCoveragePlugin"
}
create("coverageCommonConfigPlugin") {
id = "me.proton.core.gradle-plugins.coverage-config"
displayName = "Proton coverage config plugin"
description = "Plugin to configure common coverage settings"
implementationClass = "me.proton.core.gradle.plugins.coverage.ProtonCoverageCommonConfigPlugin"
}
}
testSourceSets(functionalTest)
@@ -0,0 +1,19 @@
package me.proton.core.gradle.plugins.coverage
import org.gradle.api.Project
private val koverTaskNameRegex = Regex("(.*:)?kover.*")
private val tasksTaskNameRegex = Regex("(.*:)?tasks")
/** Optional optimization.
* Avoid further configuration if we're not trying to run a `kover` task,
* or display the project's tasks.
* Example:
* > ./gradlew koverHtmlReport # <- The kover plugin will be configured
* > ./gradlew tasks # <- The kover plugin will be configured
* > ./gradlew someOtherTask # <- The kover plugin will NOT be configured
*/
internal fun Project.shouldSkipPluginApplication(): Boolean =
!gradle.startParameter.taskNames.any {
it.matches(koverTaskNameRegex) || it.matches(tasksTaskNameRegex)
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.gradle.plugins.coverage
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
/**
* The plugin should be applied on a root project.
* It can be used to provide a common configuration for code coverage.
* The configuration will be picked up by the submodules which use
* the [ProtonCoveragePlugin].
*/
public class ProtonCoverageCommonConfigPlugin : Plugin<Project> {
override fun apply(target: Project) {
if (target != target.rootProject) error("${this::class.simpleName} should be applied on the root project.")
val ext = target.extensions.create<ProtonCoverageExtension>("protonCoverage")
target.afterEvaluate {
ext.finalizeValuesOnRead()
}
}
}
@@ -19,6 +19,7 @@
package me.proton.core.gradle.plugins.coverage
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilter
import org.gradle.api.Project
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
@@ -67,6 +68,27 @@ public interface ProtonCoverageExtension {
public val minLineCoveragePercentage: Property<Int>
}
internal fun ProtonCoverageExtension.applyConventionsFrom(project: Project) {
project.extensions.findByType(ProtonCoverageExtension::class.java)?.let { otherExt ->
require(otherExt != this) { "Cannot apply the convention from the same object." }
applyConventionsFrom(otherExt)
}
}
internal fun ProtonCoverageExtension.applyConventionsFrom(other: ProtonCoverageExtension) {
other.androidBuildVariant.orNull?.let { androidBuildVariant.convention(it) }
other.disabled.orNull?.let { disabled.convention(it) }
other.enableAllRules.orNull?.let { enableAllRules.convention(it) }
other.enableAndroidRules.orNull?.let { enableAndroidRules.convention(it) }
other.enableDaggerRules.orNull?.let { enableDaggerRules.convention(it) }
other.enableKotlinParcelizeRules.orNull?.let { enableKotlinParcelizeRules.convention(it) }
other.enableKotlinSerializationRules.orNull?.let { enableKotlinSerializationRules.convention(it) }
other.enableRoomDbRules.orNull?.let { enableRoomDbRules.convention(it) }
other.excludes.orNull?.let { excludes.convention(it) }
other.minBranchCoveragePercentage.orNull?.let { minBranchCoveragePercentage.convention(it) }
other.minLineCoveragePercentage.orNull?.let { minLineCoveragePercentage.convention(it) }
}
internal fun ProtonCoverageExtension.applyGeneralConventions() {
disabled.convention(false)
excludes.convention(emptyList())
@@ -25,6 +25,11 @@ import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
import kotlinx.kover.gradle.plugin.dsl.KoverVerifyReportConfig
import kotlinx.kover.gradle.plugin.dsl.MetricType
import me.proton.core.gradle.plugins.coverage.rules.androidRules
import me.proton.core.gradle.plugins.coverage.rules.daggerRules
import me.proton.core.gradle.plugins.coverage.rules.kotlinParcelizeRules
import me.proton.core.gradle.plugins.coverage.rules.kotlinSerializationRules
import me.proton.core.gradle.plugins.coverage.rules.roomDbRules
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
@@ -38,11 +43,14 @@ private const val DEFAULT_XML_REPORT_FILE = "reports/kover/report.xml"
internal const val PROTON_COVERAGE_EXT: String = "protonCoverage"
/**
* The plugin can be applied on a non-root project.
* It can be used to generate coverage HTML/XML report, and verify minimum coverage percentages.
*/
public class ProtonCoveragePlugin : Plugin<Project> {
private val koverTaskNameRegex = Regex("(.*:)?kover.*")
private val tasksTaskNameRegex = Regex("(.*:)?tasks")
override fun apply(target: Project) {
if (target == target.rootProject) error("${this::class.simpleName} should not be applied on the root project.")
val ext = target.extensions.create<ProtonCoverageExtension>(PROTON_COVERAGE_EXT)
if (target.shouldSkipPluginApplication()) {
@@ -50,6 +58,7 @@ public class ProtonCoveragePlugin : Plugin<Project> {
}
ext.applyGeneralConventions()
ext.applyConventionsFrom(target.rootProject)
ext.finalizeValuesOnRead()
target.afterEvaluate {
@@ -59,31 +68,11 @@ public class ProtonCoveragePlugin : Plugin<Project> {
}
}
/** Optional optimization.
* Avoid further configuration if we're not trying to run a `kover` task,
* or display the project's tasks.
* Example:
* > ./gradlew koverHtmlReport # <- The kover plugin will be configured
* > ./gradlew tasks # <- The kover plugin will be configured
* > ./gradlew someOtherTask # <- The kover plugin will NOT be configured
*/
private fun Project.shouldSkipPluginApplication(): Boolean =
!gradle.startParameter.taskNames.any {
it.matches(koverTaskNameRegex) || it.matches(tasksTaskNameRegex)
}
private fun Project.onAfterEvaluate(ext: ProtonCoverageExtension) {
extensions.configure<KoverProjectExtension> {
useKoverTool()
}
if (ext.enableAllRules.orNull == true) {
ext.enableAndroidRules.convention(true)
ext.enableDaggerRules.convention(true)
ext.enableKotlinParcelizeRules.convention(true)
ext.enableKotlinSerializationRules.convention(true)
ext.enableRoomDbRules.convention(true)
}
if (hasAndroidPlugin()) {
ext.androidBuildVariant.convention(DEFAULT_ANDROID_BUILD_VARIANT)
ext.enableAndroidRules.convention(true)
@@ -98,6 +87,13 @@ public class ProtonCoveragePlugin : Plugin<Project> {
if (plugins.hasPlugin(PluginIds.kotlinSerialization)) {
ext.enableKotlinSerializationRules.convention(true)
}
if (ext.enableAllRules.orNull == true) {
ext.enableAndroidRules.convention(true)
ext.enableDaggerRules.convention(true)
ext.enableKotlinParcelizeRules.convention(true)
ext.enableKotlinSerializationRules.convention(true)
ext.enableRoomDbRules.convention(true)
}
configureKoverExtension(ext)
}
@@ -152,53 +148,4 @@ public class ProtonCoveragePlugin : Plugin<Project> {
)
}
}
private fun KoverReportFilters.androidRules() {
excludes {
classes(
"*Activity",
"*Fragment",
"*.BuildConfig",
"*.R",
"*.R$*"
)
}
}
private fun KoverReportFilters.daggerRules() {
excludes {
annotatedBy(
"dagger.internal.DaggerGenerated",
"dagger.Module",
"javax.annotation.processing.Generated"
)
packages("hilt_aggregated_deps")
}
}
private fun KoverReportFilters.kotlinSerializationRules() {
excludes {
annotatedBy(
"kotlinx.serialization.SerialName",
"kotlinx.serialization.Serializable"
)
classes("*\$\$serializer")
}
}
private fun KoverReportFilters.kotlinParcelizeRules() {
excludes {
annotatedBy("kotlinx.parcelize.Parcelize")
}
}
private fun KoverReportFilters.roomDbRules() {
excludes {
annotatedBy(
"androidx.room.Dao",
"androidx.room.Entity"
)
classes("*Database\$Companion*") // For DB migrations
}
}
}
@@ -0,0 +1,51 @@
package me.proton.core.gradle.plugins.coverage
import kotlinx.kover.gradle.plugin.KoverGradlePlugin
import kotlinx.kover.gradle.plugin.dsl.KoverNames
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
/**
* The plugin can be applied on a separate project.
* It can generate a combined coverage report from all subprojects
* which use the [ProtonCoveragePlugin].
*/
public class ProtonGlobalCoveragePlugin : Plugin<Project> {
override fun apply(target: Project) {
if (!target.shouldSkipPluginApplication()) {
configureGlobalCoverageReports(target)
}
target.plugins.apply(ProtonCoveragePlugin::class.java)
}
private fun configureGlobalCoverageReports(target: Project) {
val rootProject = target.rootProject
rootProject.subprojects {
if (project == target) return@subprojects
target.evaluationDependsOn(project.path)
}
target.afterEvaluate {
target.plugins.apply("org.gradle.java-library")
target.plugins.apply("org.jetbrains.kotlin.jvm")
target.plugins.apply(KoverGradlePlugin::class.java)
target.extensions.configure<ProtonCoverageExtension> {
enableAllRules.set(true)
}
rootProject.subprojects {
if (project == target) return@subprojects
if (project.plugins.hasPlugin(ProtonCoveragePlugin::class.java) &&
project.extensions.findByType(ProtonCoverageExtension::class.java)?.disabled?.get() != true
) {
target.dependencies.add(KoverNames.DEPENDENCY_CONFIGURATION_NAME, project)
}
}
}
}
}
@@ -0,0 +1,20 @@
package me.proton.core.gradle.plugins.coverage.rules
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
internal fun KoverReportFilters.androidRules() {
excludes {
annotatedBy("androidx.compose.runtime.Composable")
classes(
"*Activity",
"*Activity\$*",
"*Binding",
"*.BuildConfig",
"*ComposableSingletons\$*",
"*Fragment",
"*Fragment\$*",
"*.R",
"*.R$*"
)
}
}
@@ -0,0 +1,24 @@
package me.proton.core.gradle.plugins.coverage.rules
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
internal fun KoverReportFilters.daggerRules() {
excludes {
annotatedBy(
"dagger.hilt.android.AndroidEntryPoint",
"dagger.internal.DaggerGenerated",
"dagger.Binds",
"dagger.Module",
"dagger.Provides",
"javax.annotation.processing.Generated"
)
classes(
"*Hilt_*",
"*_HiltModules_*",
"*_Provide*Factory",
"*_Provide*Factory\$*",
"*_Factory"
)
packages("hilt_aggregated_deps")
}
}
@@ -0,0 +1,9 @@
package me.proton.core.gradle.plugins.coverage.rules
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
internal fun KoverReportFilters.kotlinParcelizeRules() {
excludes {
annotatedBy("kotlinx.parcelize.Parcelize")
}
}
@@ -0,0 +1,13 @@
package me.proton.core.gradle.plugins.coverage.rules
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
internal fun KoverReportFilters.kotlinSerializationRules() {
excludes {
annotatedBy(
"kotlinx.serialization.SerialName",
"kotlinx.serialization.Serializable"
)
classes("*\$\$serializer")
}
}
@@ -0,0 +1,18 @@
package me.proton.core.gradle.plugins.coverage.rules
import kotlinx.kover.gradle.plugin.dsl.KoverReportFilters
internal fun KoverReportFilters.roomDbRules() {
excludes {
annotatedBy(
"androidx.room.Dao",
"androidx.room.Database",
"androidx.room.Entity",
"javax.annotation.processing.Generated"
)
classes(
"*Dao_Impl",
"*Dao_Impl\$*"
)
}
}