The JaCoCo results from all subprojects shall be combined.
- Don't make any assumptions about where files are located in the build folders.
- Refer to the sources of truth for getting at needed information.
- Don't make any assumptions about which source sets are being tested. It might be
main
, but it might not. - Handle subprojects that don't JaCoCo.
- Handle Test tasks that don't have any tests, or don't produce a .exec file for some other reason.
- Correctly declare inputs and outputs for up-to-date checking.
- Groovy DSL
- The code below was written for Gradle 6.1.1.
def getProjectList() {
// These projects are considered. Replace with a different list as needed.
subprojects + project
}
task jacocoMerge(type: JacocoMerge) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = 'Merge the JaCoCo data files from all subprojects into one'
project.afterEvaluate { // do it at the end of the config phase to be sure all information is present
FileCollection execFiles = project.objects.fileCollection() // an empty FileCollection
getProjectList().each { Project subproject ->
if (subproject.pluginManager.hasPlugin('jacoco')) {
def testTasks = subproject.tasks.withType(Test)
dependsOn(testTasks) // ensure that .exec files are actually present
testTasks.each { Test task ->
// The JacocoTaskExtension is the source of truth for the location of the .exec file.
JacocoTaskExtension extension = task.getExtensions().findByType(JacocoTaskExtension.class);
if (extension != null) {
execFiles.from extension.getDestinationFile()
}
}
}
}
executionData = execFiles
}
doFirst {
// .exec files might be missing if a project has no tests. Filter in execution phase.
executionData = executionData.filter { it.canRead() }
}
}
def getReportTasks(JacocoReport pRootTask) {
getProjectList().collect {
it.tasks.withType(JacocoReport).findAll { it != pRootTask }
}.flatten()
}
task jacocoRootReport(type: JacocoReport, dependsOn: tasks.jacocoMerge) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = 'Generates an aggregate report from all subprojects'
logger.lifecycle 'Using aggregated file: ' + tasks.jacocoMerge.destinationFile
executionData.from tasks.jacocoMerge.destinationFile
project.afterEvaluate {
// The JacocoReport tasks are the source of truth for class files and sources.
def reportTasks = getReportTasks(tasks.jacocoRootReport)
classDirectories.from project.files({
reportTasks.collect {it.classDirectories}.findAll {it != null}
})
sourceDirectories.from project.files({
reportTasks.collect {it.sourceDirectories}.findAll {it != null}
})
}
}
Hope this helps someone! Let me know in the comments how the code could be better.
The above solution combines many great comments and suggestions from these pages:
Gradle docs has a working version of this:
https://docs.gradle.org/6.5.1/samples/sample_jvm_multi_project_with_code_coverage.html