build: add plugin for code coverage reporting

This commit is contained in:
Oscar Mira 2025-01-30 13:53:35 +01:00
parent 301f1efc1c
commit d05a056698
No known key found for this signature in database
GPG Key ID: B371B98C5DC32237
8 changed files with 192 additions and 4 deletions

View File

@ -0,0 +1,28 @@
plugins {
`kotlin-dsl`
}
kotlin {
jvmToolchain(21)
}
dependencies {
compileOnly(libs.android.gradle.plugin)
compileOnly(libs.kotlin.gradle.plugin)
}
tasks {
validatePlugins {
enableStricterValidation = true
failOnWarning = true
}
}
gradlePlugin {
plugins {
register("androidLibraryJacoco") {
id = libs.plugins.sdk.android.library.jacoco.get().pluginId
implementationClass = "AndroidLibraryJacocoPlugin"
}
}
}

View File

@ -0,0 +1,14 @@
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
class AndroidLibraryJacocoPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
apply(plugin = "jacoco")
configureJacoco(extensions.getByType<LibraryAndroidComponentsExtension>())
}
}
}

View File

@ -0,0 +1,92 @@
import com.android.build.api.artifact.ScopedArtifact
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ScopedArtifacts
import com.android.build.api.variant.SourceDirectories
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.register
import org.gradle.testing.jacoco.tasks.JacocoReport
import java.util.Locale
private val coverageExclusions = listOf(
// Android
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
)
private fun String.capitalize() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
/**
* Creates a new task that generates a combined coverage report with data from local and
* instrumented tests.
*
* `create{variant}CombinedCoverageReport`
*
* Note that coverage data must exist before running the task. This allows us to run device
* tests on CI using a different Github Action or an external device farm.
*/
internal fun Project.configureJacoco(
androidComponentsExtension: AndroidComponentsExtension<*, *, *>,
) {
androidComponentsExtension.onVariants { variant ->
val myObjFactory = project.objects
val buildDir = layout.buildDirectory.get().asFile
val allJars: ListProperty<RegularFile> = myObjFactory.listProperty(RegularFile::class.java)
val allDirectories: ListProperty<Directory> =
myObjFactory.listProperty(Directory::class.java)
val reportTask =
tasks.register(
"create${variant.name.capitalize()}CombinedCoverageReport",
JacocoReport::class,
) {
classDirectories.setFrom(
allJars,
allDirectories.map { dirs ->
dirs.map { dir ->
myObjFactory.fileTree().setDir(dir).exclude(coverageExclusions)
}
},
)
reports {
html.required = true
xml.required = true
}
fun SourceDirectories.Flat?.toFilePaths(): Provider<List<String>> = this
?.all
?.map { directories -> directories.map { it.asFile.path } }
?: provider { emptyList() }
sourceDirectories.setFrom(
files(
variant.sources.java.toFilePaths(),
variant.sources.kotlin.toFilePaths()
),
)
executionData.setFrom(
project.fileTree("$buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest")
.matching { include("**/*.exec") },
project.fileTree("$buildDir/outputs/code_coverage/${variant.name}AndroidTest")
.matching { include("**/*.ec") },
)
}
variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT)
.use(reportTask)
.toGet(
ScopedArtifact.CLASSES,
{ _ -> allJars },
{ _ -> allDirectories },
)
}
}

View File

@ -0,0 +1,28 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
}
}
dependencyResolutionManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android(\\..*)?")
includeGroupByRegex("com\\.google(\\..*)?")
includeGroupByRegex("androidx?(\\..*)?")
}
}
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
include(":plugins")
rootProject.name = "build-logic"

View File

@ -9,7 +9,7 @@ plugins {
}
allprojects {
group = "im.molly"
group = "im.molly.monero.sdk"
}
tasks {

View File

@ -13,6 +13,7 @@ okhttp = "4.10.0"
room = "2.6.1"
[libraries]
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }
@ -27,6 +28,7 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "
androidx-ui = { module = "androidx.compose.ui:ui" }
androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines"}
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
okhttp = { module = "com.squareup.okhttp3:okhttp" }
@ -40,3 +42,6 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
# Plugins defined by this project
sdk-android-library-jacoco = { id ="sdk.android.library.jacoco" }

View File

@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.sdk.android.library.jacoco)
}
kotlin {
@ -38,6 +39,11 @@ android {
}
buildTypes {
getByName("debug") {
enableUnitTestCoverage = true
enableAndroidTestCoverage = true
}
getByName("release") {
isMinifyEnabled = false

View File

@ -1,24 +1,39 @@
pluginManagement {
includeBuild("build-logic")
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
google()
google {
content {
includeGroupByRegex("com\\.android(\\..*)?")
includeGroupByRegex("com\\.google(\\..*)?")
includeGroupByRegex("androidx?(\\..*)?")
}
}
mavenCentral()
}
versionCatalogs {
// "libs" is predefined by Gradle
create("testLibs") {
from(files("gradle/test-libs.versions.toml"))
}
}
}
check(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) {
"""
This project requires JDK 21+ but it is currently using JDK ${JavaVersion.current()}.
Java Home: [${System.getProperty("java.home")}]
https://developer.android.com/build/jdks#jdk-config-in-studio
""".trimIndent()
}
includeProject("lib", "lib/android")
includeProject("demo", "demo/android")