diff --git a/aggregates/build.gradle.kts b/aggregates/build.gradle.kts index b7e394b50bf..9306c05c5af 100644 --- a/aggregates/build.gradle.kts +++ b/aggregates/build.gradle.kts @@ -7,6 +7,8 @@ plugins { alias(libs.plugins.kotlin.compose.compiler) } +apply(from = "${project.rootDir}/jacoco/jacoco-kmp.gradle.kts") + kotlin { androidLibrary { namespace = "org.dhis2.mobile.aggregates" diff --git a/build.gradle.kts b/build.gradle.kts index a923bc4363b..e82b91eb020 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,7 +60,7 @@ allprojects { cacheChangingModulesFor(0, TimeUnit.SECONDS) eachDependency { if (requested.group == "org.jacoco") - useVersion("0.8.10") + useVersion(libs.versions.jacoco.get()) } } } diff --git a/commonskmm/build.gradle.kts b/commonskmm/build.gradle.kts index bd3c8f6b8e3..559754b8a4c 100644 --- a/commonskmm/build.gradle.kts +++ b/commonskmm/build.gradle.kts @@ -9,6 +9,8 @@ plugins { alias(libs.plugins.kotlin.serialization) } +apply(from = "${project.rootDir}/jacoco/jacoco-kmp.gradle.kts") + kotlin { compilerOptions { freeCompilerArgs.add("-Xcontext-parameters") diff --git a/jacoco/jacoco-kmp.gradle.kts b/jacoco/jacoco-kmp.gradle.kts new file mode 100644 index 00000000000..7bd1bdb3b60 --- /dev/null +++ b/jacoco/jacoco-kmp.gradle.kts @@ -0,0 +1,157 @@ +apply(plugin = "jacoco") + +// Check if this is a Kotlin Multiplatform project +val isKmpProject = plugins.hasPlugin("org.jetbrains.kotlin.multiplatform") +val isAndroidProject = plugins.hasPlugin("com.android.library") || plugins.hasPlugin("com.android.application") + +tasks.register("jacocoReport", JacocoReport::class) { + group = "Coverage" + description = "Generate XML/HTML code coverage reports" + + // Set source directories based on project type + if (isKmpProject) { + // For KMP projects, include all source sets + sourceDirectories.setFrom( + layout.projectDirectory.dir("src/commonMain/kotlin"), + layout.projectDirectory.dir("src/androidMain/kotlin"), + layout.projectDirectory.dir("src/commonTest/kotlin"), + layout.projectDirectory.dir("src/androidUnitTest/kotlin") + ) + } else { + // For Android projects + sourceDirectories.setFrom("${project.projectDir}/src/main/java") + } + + val excludes = mutableSetOf( + "android/databinding/**/*.class", + "**/android/databinding/*Binding.class", + "**/android/databinding/*", + "**/androidx/databinding/*", + "**/BR.*", + "**/R.class", + "**/R\$*.class", + "**/BuildConfig.*", + "**/Manifest*.*", + "**/*Test*.*", + "android/**/*.*", + "**/*MapperImpl*.*", + "**/*\$ViewInjector*.*", + "**/*\$ViewBinder*.*", + "**/BuildConfig.*", + "**/*Component*.*", + "**/*BR*.*", + "**/Manifest*.*", + "**/*\$Lambda\$*.*", + "**/*Companion*.*", + "**/*Module*.*", + "**/*Dagger*.*", + "**/*MembersInjector*.*", + "**/*_MembersInjector.class", + "**/*_Factory*.*", + "**/*_Provide*Factory*.*", + "**/*Extensions*.*", + "**/*\$Result.*", + "**/*\$Result\$*.*", + "**/*JsonAdapter.*", + "**/databinding/*.*", + "**/customviews/*.*", + "**/ui/*.class", + "**/*Activity.*", + "**/Activity*.*", + "**/*Activity*.*", + "**/*Fragment.*", + "**/Fragment*.*", + "**/*View.*", + "**/*Adapter.*", + "**/*Contract*.*", + "**/*Bindings*.*", + "**/AutoValue*.*", + "**/*\$*", + "**/*Navigator.*", + "**/*\$*\$*.*", + "**/animations/*.*", + "**/*Holder*.*", + "**/*Dialog*.*", + "**/*Service*.*", + "**/*Button*.*", + "**/SearchTEList.*", + "**/lambda\$*\$*.*" + ) + + // Set class directories based on project type + val classDirectoriesList = mutableListOf() + + if (isAndroidProject) { + // Android project class directories + val javaClassesApp = fileTree(layout.buildDirectory.dir("intermediates/javac/dhisDebug")){ + exclude(excludes) + } + val kotlinClassesApp = fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/dhisDebug")){ + exclude(excludes) + } + val javaClasses = fileTree(layout.buildDirectory.dir("intermediates/javac/debug")){ + exclude(excludes) + } + val kotlinClasses = fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")){ + exclude(excludes) + } + + classDirectoriesList.addAll(listOf(javaClassesApp, kotlinClassesApp, javaClasses, kotlinClasses)) + } + + if (isKmpProject) { + // KMP project class directories + val commonClasses = fileTree(layout.buildDirectory.dir("classes/kotlin/common")){ + exclude(excludes) + } + val androidClasses = fileTree(layout.buildDirectory.dir("classes/kotlin/android")){ + exclude(excludes) + } + val jvmClasses = fileTree(layout.buildDirectory.dir("classes/kotlin/jvm")){ + exclude(excludes) + } + + classDirectoriesList.addAll(listOf(commonClasses, androidClasses, jvmClasses)) + } + + classDirectories.setFrom(files(classDirectoriesList)) + + // Execution data - look for both .exec and .ec files + val unitTestsData = fileTree(layout.buildDirectory.dir("jacoco")) { + include("*.exec") + } + val androidTestsData = fileTree(layout.buildDirectory.dir("outputs/code_coverage")) { + include(listOf("**/*.ec")) + } + + executionData.setFrom(files(listOf(unitTestsData, androidTestsData))) + + // Add test task dependencies based on project type + if (isAndroidProject) { + val testTask = tasks.findByName("testDebugUnitTest") + val androidTestTask = tasks.findByName("connectedDebugAndroidTest") + + if (testTask != null) dependsOn(testTask) + if (androidTestTask != null) dependsOn(androidTestTask) + } + + if (isKmpProject) { + val commonTestTask = tasks.findByName("testDebugUnitTest") ?: tasks.findByName("test") + val androidTestTask = tasks.findByName("connectedDebugAndroidTest") ?: tasks.findByName("androidTest") + + if (commonTestTask != null) dependsOn(commonTestTask) + if (androidTestTask != null) dependsOn(androidTestTask) + } + + fun JacocoReportsContainer.reports() { + xml.required.set(true) + xml.outputLocation.set(layout.buildDirectory.file("coverage-report/jacocoTestReport.xml")) + + html.required.set(true) + html.outputLocation.set(layout.buildDirectory.dir("coverage-report")) + } + + reports { + reports() + } +} diff --git a/jacoco/jacoco.gradle.kts b/jacoco/jacoco.gradle.kts index cfc190089e9..6c8fbaea403 100644 --- a/jacoco/jacoco.gradle.kts +++ b/jacoco/jacoco.gradle.kts @@ -4,6 +4,18 @@ tasks.register("jacocoReport", JacocoReport::class) { group = "Coverage" description = "Generate XML/HTML code coverage reports for coverage.ec" + // Make sure tests are run before generating the report + // Only add dependencies if the tasks exist + val testTask = tasks.findByName("testDebugUnitTest") + val androidTestTask = tasks.findByName("connectedDebugAndroidTest") + + if (testTask != null) { + dependsOn(testTask) + } + if (androidTestTask != null) { + dependsOn(androidTestTask) + } + sourceDirectories.setFrom("${project.projectDir}/src/main/java") val excludes = mutableSetOf( @@ -62,18 +74,18 @@ tasks.register("jacocoReport", JacocoReport::class) { "**/lambda\$*\$*.*" ) - val javaClassesApp = fileTree("${buildDir}/intermediates/javac/dhisDebug"){ + val javaClassesApp = fileTree(layout.buildDirectory.dir("intermediates/javac/dhisDebug")){ exclude( excludes ) } - val kotlinClassesApp = fileTree("${buildDir}/tmp/kotlin-classes/dhisDebug"){ + val kotlinClassesApp = fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/dhisDebug")){ exclude(excludes) } - val javaClasses = fileTree("${buildDir}/intermediates/javac/debug"){ + val javaClasses = fileTree(layout.buildDirectory.dir("intermediates/javac/debug")){ exclude(excludes) } - val kotlinClasses = fileTree("${buildDir}/tmp/kotlin-classes/debug"){ + val kotlinClasses = fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")){ exclude(excludes) } @@ -88,10 +100,10 @@ tasks.register("jacocoReport", JacocoReport::class) { ) ) - val unitTestsData = fileTree("${buildDir}/jacoco") { + val unitTestsData = fileTree(layout.buildDirectory.dir("jacoco")) { include("*.exec") } - val androidTestsData = fileTree("${buildDir}/outputs/code_coverage") { + val androidTestsData = fileTree(layout.buildDirectory.dir("outputs/code_coverage")) { include(listOf("**/*.ec")) } @@ -106,10 +118,10 @@ tasks.register("jacocoReport", JacocoReport::class) { fun JacocoReportsContainer.reports() { xml.required.set(true) - xml.outputLocation.set(file("${buildDir}/coverage-report/jacocoTestReport.xml")) + xml.outputLocation.set(layout.buildDirectory.file("coverage-report/jacocoTestReport.xml")) html.required.set(true) - html.outputLocation.set(file("${buildDir}/coverage-report")) + html.outputLocation.set(layout.buildDirectory.dir("coverage-report")) } reports { @@ -117,12 +129,4 @@ tasks.register("jacocoReport", JacocoReport::class) { } } -/*android { - buildTypes { - getByName("debug") { - // jacoco test coverage reports both for - // androidTest and test source sets - testCoverageEnabled = false - } - } -}*/ + diff --git a/login/build.gradle.kts b/login/build.gradle.kts index 2f0a3fa1a72..122e1efb7f6 100644 --- a/login/build.gradle.kts +++ b/login/build.gradle.kts @@ -6,6 +6,8 @@ plugins { alias(libs.plugins.kotlin.serialization) } +apply(from = "${project.rootDir}/jacoco/jacoco-kmp.gradle.kts") + kotlin { compilerOptions { diff --git a/sync/build.gradle.kts b/sync/build.gradle.kts index ee7b03718fb..17930b38f1f 100644 --- a/sync/build.gradle.kts +++ b/sync/build.gradle.kts @@ -8,6 +8,8 @@ plugins { alias(libs.plugins.kotlin.serialization) } +apply(from = "${project.rootDir}/jacoco/jacoco-kmp.gradle.kts") + kotlin { compilerOptions { diff --git a/tracker/build.gradle.kts b/tracker/build.gradle.kts index 6c026c526b3..39e6bf88ed4 100644 --- a/tracker/build.gradle.kts +++ b/tracker/build.gradle.kts @@ -7,6 +7,8 @@ plugins { alias(libs.plugins.kotlin.compose.compiler) } +apply(from = "${project.rootDir}/jacoco/jacoco-kmp.gradle.kts") + kotlin { androidLibrary { namespace = "org.dhis2.mobile.tracker"