Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Aggregated Jacoco reports in a multi-project Gradle build
allprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
repositories {
jcenter()
}
jacoco {
toolVersion = '0.7.1.201405082137'
}
}
subprojects {
dependencies {
testCompile 'junit:junit:4.11'
}
jacocoTestReport {
additionalSourceDirs = files(sourceSets.main.allSource.srcDirs)
sourceDirectories = files(sourceSets.main.allSource.srcDirs)
classDirectories = files(sourceSets.main.output)
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
}
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
dependsOn = subprojects.test
additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
classDirectories = files(subprojects.sourceSets.main.output)
executionData = files(subprojects.jacocoTestReport.executionData)
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
}
huxi commented Jul 23, 2014

jacocoRootReport doesn't work if some subprojects don't have any tests at all because this causes the onlyIf of JacocoReport to be false.

My workaround looks like this:

task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
    dependsOn = subprojects.test
    additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
    sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
    classDirectories = files(subprojects.sourceSets.main.output)
    executionData = files(subprojects.jacocoTestReport.executionData)
    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }
    onlyIf = {
        true
    }
    doFirst {
        executionData = files(executionData.findAll {
            it.exists()
        })
    }
}

I think this is actually a bug in JacocoReport. The onlyIf should be removed and the task should only actually use existing files.

sleroy commented Dec 2, 2014

Thank you for initial solution and the feedback, I was stucked with the same problem.

Thanks, this gist helped me a lot. If it's possible to determine the set of Java projects with tests, one can also use a custom project set instead of subprojects.

A problem I ran into was these examples didn't work with custom source sets, since those source set closures are not yet evaluated when configuring JaCoCo. In addition, some subprojects were not Java projects. Here's my solution:

In root build.gradle:

allprojects {
    repositories {
        jcenter()
    }
}

apply plugin: 'jacoco'

task jacocoTestReport(type: JacocoReport) {
    sourceDirectories = files()
    classDirectories = files()
    executionData = files()

    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }

    // Work-around to allow us to build list of executionData files in doFirst
    onlyIf = {
        true
    }

    /*
     * Builds list of source dirs, class dirs, and executionData files
     * when task is run, not at script evaluation time
     */
    doFirst {
        subprojects.findAll { subproject ->
            subproject.pluginManager.hasPlugin('java')
        }.each { subproject ->
            additionalSourceDirs files((Set<File>) subproject.sourceSets.main.allJava.srcDirs)
            additionalClassDirs ((FileCollection) subproject.sourceSets.main.output)
            if (subproject.pluginManager.hasPlugin('jacoco')) {
                executionData subproject.tasks.jacocoTestReport.executionData
            }
        }

        executionData = files(executionData.findAll {
            it.exists()
        })
    }
}

task build {
    dependsOn tasks.jacocoTestReport
}

Root java-common.gradle:

// Common settings for projects that already apply the 'java' plugin

tasks.withType(JavaCompile) {
    options.debug = true
}

apply plugin: 'jacoco'

// Must be after sourceSets closure
jacocoTestReport {
    dependsOn tasks.test

    //noinspection GroovyAssignabilityCheck
    sourceSets sourceSets.main
    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }
}

tasks.check.dependsOn tasks.jacocoTestReport
rootProject.tasks.jacocoTestReport.dependsOn tasks.test

In each Java module's build.gradle:

apply plugin: 'java'

sourceSets {
    // customizations
}

apply from: rootProject.file('java-common.gradle')

And since the JacocoReport task type doesn't support "groups" in the report structure, I also wrote a completely custom task that uses the Ant report task directly. On the report index this will break up each module instead of clumping all the packages together. You can use this in place of the other example.

In root build.gradle, along with same per-project settings mentioned above:

allprojects {
    repositories {
        jcenter()
    }
}

apply plugin: 'jacoco'

task jacocoTestReport << {
    FileCollection executionData = files()

    subprojects.findAll { subproject ->
        subproject.pluginManager.hasPlugin('java') && subproject.pluginManager.hasPlugin('jacoco')
    }.each { subproject ->
        executionData += subproject.tasks.jacocoTestReport.executionData
    }

    executionData = files(executionData.findAll {
        it.exists()
    })

    ant.taskdef(name: 'jacocoReport', classname: 'org.jacoco.ant.ReportTask',
            classpath: configurations.jacocoAnt.asPath)

    ant.jacocoReport {
        executiondata {
            executionData.addToAntBuilder(ant, 'resources')
        }
        structure(name: project.name) {
            subprojects.findAll { subproject ->
                subproject.pluginManager.hasPlugin('java')
            }.each { subproject ->
                group(name: subproject.name) {
                    classfiles {
                        ((FileCollection) subproject.sourceSets.main.output).filter {
                            it.exists()
                        }.addToAntBuilder(ant, 'resources')
                    }
                    sourcefiles {
                        files((Set<File>) subproject.sourceSets.main.allJava.srcDirs).filter {
                            it.exists()
                        }.addToAntBuilder(ant, 'resources')
                    }
                }
            }
        }
        html(destdir: "${buildDir}/reports/jacoco/${name}/html")
        xml(destfile: "${buildDir}/reports/jacoco/${name}/${name}.xml")
        // Uncomment for CSV
        //csv(destfile: "${buildDir}/reports/jacoco/${name}/${name}.csv")
    }
}

task build {
    dependsOn tasks.jacocoTestReport
}

If you want the group names to go by path instead of just name, you can use subproject.projectDir.path.substring(projectDir.path.length() + 1) in place of subproject.name.

nitin88 commented Oct 20, 2015

Hi,

I am looking a way to exclude classes from aggregated report. Any help is welcome!

task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
    dependsOn = subprojects.test
    additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
    sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
    classDirectories = files(subprojects.sourceSets.main.output)
    executionData = files(subprojects.jacocoTestReport.executionData)  //how to exclude some package/classes com.test.**
    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }
    onlyIf = {
        true
    }
    doFirst {
        executionData = files(executionData.findAll {
            it.exists()
        })
    }
}
nz1845 commented Dec 1, 2015

I was able to exclude classes for example by adding this to the task

afterEvaluate {
        classDirectories = files(classDirectories.files.collect {
            fileTree(dir: it,
                    exclude: ['*com/package/a*',
                              '*com/package/b*',
                              '*com/something*'])
        })
    }
vjkm commented Dec 19, 2015

Hi All

I have a problem using this
As I am using AndroidTest folder to place our tests and we are using the task connectedCheck

Nothing as sujjested above is working!!!

Can someone help me out!

Thanks
vkm

@nitin88 I discovered this code here which works for me:

// aggregates jacoco results from all subprojects and core project and generate a report
task jacocoRootTestReport(type: JacocoReport) {

    sourceSets sourceSets.main

    def jacocoTestFiles = ["$buildDir/jacoco/test.exec"]
    subprojects.each { p ->
        def coverageFileLocation = "$p.buildDir/jacoco/test.exec"
        if (new File(coverageFileLocation).exists()) {
            jacocoTestFiles << coverageFileLocation
        }
    }

    logger.info('Aggregating next JaCoCo Coverage Files: {}', jacocoTestFiles)
    executionData files(jacocoTestFiles)

    reports {
        xml.enabled false
        csv.enabled false
        html.enabled true
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment