Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Aggregated Jacoco reports in a multi-project Gradle build
// ****************************************************************************
// The `org.kordamp.gradle.jacoco` plugin takes care of mane aspects shown here
// http://kordamp.org/kordamp-gradle-plugins/#_org_kordamp_gradle_jacoco
//
// The code shown here is obsolete. Use it at your own risk
// ****************************************************************************
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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

sleroy commented Dec 2, 2014

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

@Skamandros

This comment has been minimized.

Copy link

Skamandros commented Jun 22, 2015

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.

@mjdetullio

This comment has been minimized.

Copy link

mjdetullio commented Aug 26, 2015

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

@philbert

This comment has been minimized.

Copy link

philbert commented Aug 23, 2016

@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
    }
}
@dnltsk

This comment has been minimized.

Copy link

dnltsk commented Jan 6, 2018

thanks to @philbert and @nitin88 for posting their solutions. All my attempts do end up in a path may not be null or empty string. path='null' exception message from gradle. The command I am using is gradle clean test jacocoRootReport

@dnltsk

This comment has been minimized.

Copy link

dnltsk commented Jan 7, 2018

@philbert and @nitin88 I figured out that the jacocoRootTestReport and jacocoRootReport task doesn't work with code written in Kotlin. https://github.com/dnltsk/blog-2016-10-gradle-multi-module-coverage

@JLLeitschuh

This comment has been minimized.

Copy link

JLLeitschuh commented Feb 14, 2018

This is the solution that I got working for Junit 5 with the Gradle Kotlin DSL:

val jacocoTestResultTaskName = "jacocoJunit5TestReport"
subprojects {
    val junitPlatformTest: JavaExec by tasks // You don't need this if using Junt 4

    jacoco {
        toolVersion = "0.8.0"
        applyTo(junitPlatformTest) // You don't need this if using Junt 4
    }

    val sourceSets = java.sourceSets

    task<JacocoReport>(jacocoTestResultTaskName) { // For Junit4 I think this becomes `tasks.getting(JacocoReport::class) { ...`

        executionData(junitPlatformTest) // You don't need this if using Junt 4
        dependsOn(junitPlatformTest)

        sourceSets(sourceSets["main"])
        sourceDirectories = files(sourceSets["main"].allSource.srcDirs)
        classDirectories = files(sourceSets["main"].output)
        reports {
            html.isEnabled = true
            xml.isEnabled = true
            csv.isEnabled = false
        }
    }
}

task<JacocoReport>("jacocoRootReport") {
    val jacocoReportTasks =
        subprojects
            .map { it.tasks[jacocoTestResultTaskName] as JacocoReport } // For Junit 4 use `.flatMap { it.tasks.withType<JacocoReport>() }`
    dependsOn(jacocoReportTasks) // For Junit 4 this becomes `dependsOn(tasks["test"])`

    val executionData = jacocoReportTasks.map { it.executionData }
    executionData(*executionData.toTypedArray())

    // Pre-initialize these to empty collections to prevent NPE on += call below.
    additionalSourceDirs = files()
    sourceDirectories = files()
    classDirectories = files()

    subprojects.forEach { testedProject ->
        val sourceSets = testedProject.java.sourceSets
        this@task.additionalSourceDirs += files(sourceSets["main"].allSource.srcDirs)
        this@task.sourceDirectories += files(sourceSets["main"].allSource.srcDirs)
        this@task.classDirectories += files(sourceSets["main"].output)
    }

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

/**
 * Retrieves the [java][org.gradle.api.plugins.JavaPluginConvention] project convention.
 */
val Project.`java`: org.gradle.api.plugins.JavaPluginConvention
    get() = convention.getPluginByName("java")
@CarrierDirectmgao

This comment has been minimized.

Copy link

CarrierDirectmgao commented Aug 8, 2018

Like to share the code that I got working for Kotlin junit: 4.12

allprojects {
apply plugin: 'jacoco'

repositories {
    jcenter()
}

jacoco {
    toolVersion = '0.8.1'
}

}
subprojects {
apply plugin: 'kotlin'
apply plugin: "kotlin-spring"
apply plugin: 'io.spring.dependency-management'

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/libs-snapshot' }
    maven { url 'https://repo.spring.io/libs-milestone' }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.boot:spring-boot-dependencies:${spring_boot_version}"
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Finchley.M9'
    }
}

dependencies {
    compile "com.github.wnameless:json-flattener:0.5.0"
    compile "com.google.guava:guava:$guava_version"
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

    testCompile "junit:junit:4.12"
    testCompile "org.mockito:mockito-core:$mockito_version"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

jacocoTestReport {
    sourceDirectories = files(sourceSets.getByName("main").allSource.srcDirs)
    afterEvaluate {
        classDirectories = files(sourceSets.getByName("main").output.files.collect {
            fileTree(dir: it, exclude: ['**/*body*', '**/*inlined*'])
        })
    }
    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }
}

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

getSubprojects().forEach{ subproject ->
    def coverageFileLocation = "${subproject.buildDir}/jacoco/test.exec"
    if (new File(coverageFileLocation).exists()) {
        executionData += subproject.tasks.findByName("jacocoTestReport").property("executionData")
        sourceDirectories += subproject.tasks.findByName("jacocoTestReport").property("sourceDirectories")
        classDirectories += files(subproject.sourceSets.getByName("main").output.files.collect {
            fileTree(dir: it, exclude: ['**/*body*', '**/*inlined*'])
        })
    }
}

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

}

}

task wrapper(type: Wrapper) {
gradleVersion = '4.8.1' //version required
}

Note: Excluding the library classes created for library source file ServerRequestExtensions.kt, ServerResponseExtensions.kt, and Extensions.kt(which caused code climate failure with error message: could not find those files) by Kotlin compiler.

@twyatt

This comment has been minimized.

Copy link

twyatt commented Nov 1, 2018

For others that find there way to this helpful Gist, the gradle-android-junit-jacoco-plugin offers a simplified solution to many of the problems being addressed in this Gist's discussion thread.

@aalmiray

This comment has been minimized.

Copy link
Owner Author

aalmiray commented Mar 8, 2019

The org.kordamp.gradle.jacoco plugin takes care of many aspects shown here http://kordamp.org/kordamp-gradle-plugins/#_org_kordamp_gradle_jacoco

@karippery

This comment has been minimized.

Copy link

karippery commented Mar 11, 2019

in build.gradle.kts ".property("executionData") this part i can't translate ..

please help me to convert .kts file

    executionData += subproject.tasks.findByName("jacocoTestReport").property("executionData")
    sourceDirectories += subproject.tasks.findByName("jacocoTestReport").property("sourceDirectories")
@vlsi

This comment has been minimized.

Copy link

vlsi commented Mar 16, 2019

Here's my approach with Gradle 5.2.1 + Kotlin DSL.

Note: it allows Gradle to skip task creation unless required.

Note: doFirst should not be used for executionData configuration as it is not compatible with Gradle's incremental model.
In other words, the input parameters for the task should be configured at, well, configuration time otherwise Gradle would think the task is UP-TO-DATE.

fun reportsForHumans() = !(System.getenv()["CI"]?.toBoolean() ?: false)

val jacocoReport by tasks.registering(JacocoReport::class) {
    group = "Coverage reports"
    description = "Generates an aggregate report from all subprojects"
    executionData(jacocoMerge)
}

allprojects {
    plugins.withType<JacocoPlugin> { // this is executed for each project that has Jacoco plugin applied
        the<JacocoPluginExtension>().toolVersion = "0.8.3"

        val testTasks = tasks.withType<Test>()
        testTasks.configureEach {
            extensions.configure<JacocoTaskExtension> {
                // We don't want to collect coverage for third-party classes
                includes?.add("com.acme.*")
            }
        }

        jacocoReport {
            // Note: this creates a lazy collection
            // Some of the projects might fail to create a file (e.g. no tests or no coverage),
            // So we check for file existence. Otherwise JacocoReport would fail
            val execFiles = files(testTasks).filter { it.exists() && it.name.endsWith(".exec") }
            executionData(execFiles)
        }

        tasks.withType<JacocoReport>().configureEach {
            reports {
                html.isEnabled = reportsForHumans()
                xml.isEnabled = !reportsForHumans()
            }
        }
        // Add each project to combined report
        configure<SourceSetContainer> {
            val mainCode = main.get()
            jacocoReport {
                additionalSourceDirs.from(mainCode.allJava.srcDirs)
                sourceDirectories.from(mainCode.allSource.srcDirs)
                // IllegalStateException: Can't add different class with same name: module-info
                // https://github.com/jacoco/jacoco/issues/858
                classDirectories.from(mainCode.output.asFileTree.matching {
                    exclude("module-info.class")
                })
            }
        }
    }
}
@slavik57

This comment has been minimized.

Copy link

slavik57 commented Apr 21, 2019

I tried the suggestion of @huxi and got the same error as @dnltsk got: path may not be null or empty string. path='null'
After a bit of fiddling around I managed to solve this error by adding the Jacoco plugin for all the projects instead of only subprojects:

allprojects {
    ...
    apply plugin: 'jacoco'
    jacoco {
        toolVersion = "0.8.3"
    }
}

Then I got an error Cannot resolve external dependency org.jacoco:org.jacoco.ant:0.8.3 because no repositories are defined.
Then I moved the repositories section also to the allprojects scope instead of only the subprojects

After doing all of that it worked like a charm.

The final root build.gradle file:

allprojects {
    ...
    apply plugin: 'jacoco'
    jacoco {
        toolVersion = "0.8.3"
    }

    repositories {
        ...
        jcenter()
        mavenLocal()
    }
}

subprojects {
    ...

    def jacocoExecutionDataFiles = fileTree(buildDir).include("/jacoco/*.exec")
    jacocoTestReport {
        executionData = jacocoExecutionDataFiles
    }
}
...
task jacocoRootReport(type: JacocoReport) {
    description = 'Generates an aggregate report from all subprojects'

    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)
}
@gbzarelli

This comment has been minimized.

Copy link

gbzarelli commented Aug 23, 2019

I found this plugin:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.palantir:jacoco-coverage:<version>'
    }
}

apply plugin: 'com.palantir.jacoco-full-report'

GITHUB PROJECT

Just run your check next jacocoFullReport It's merge the report pages in your build project home folder;

For merge test.exec files I use these method:

def allTestCoverageFile = "$buildDir/jacoco/all-test.exec"

task jacocoMergeTest(type: JacocoMerge) {
    destinationFile = file(allTestCoverageFile)
    executionData = project.fileTree(dir: '.', include: '**/build/jacoco/test.exec')
}
@joshuadavis

This comment has been minimized.

Copy link

joshuadavis commented Sep 8, 2019

I found this plugin:
GITHUB PROJECT

Unfortunately, that plugin is deprecated. As per the Gihub project page:

Note: This plugin is considered obsolete as of Gradle 3.4 which supports coverage enforcement as part of the core JaCoCo plugin.

@edin-m

This comment has been minimized.

Copy link

edin-m commented Nov 29, 2019

For gradle 6.0.1 & jacoco 0.8.5 this works

jacocoTestReport {   
    reports {
        xml.enabled false
        csv.enabled false
        html.destination file("${buildDir}/jacocoHtml")
    }

    // needed for report - if you use source sets it'll bring in all deps into the report
    def classTree = fileTree(dir: 'build/classes/java/main', include: 'com/myapp/**')
    // maybe omit classes from the report
    classTree.exclude '**/*App.class'
    classDirectories.from = classTree

    // needed for source highlight
    sourceDirectories.from = files(sourceSets.main.allSource.srcDirs)
}
@fieldju

This comment has been minimized.

Copy link

fieldju commented Dec 16, 2019

As of gradle 6.x

task generateMergedReport(type: JacocoReport) {
  dependsOn = subprojects.test
  additionalSourceDirs.setFrom files(subprojects.sourceSets.main.allSource.srcDirs)
  sourceDirectories.setFrom files(subprojects.sourceSets.main.allSource.srcDirs)
  classDirectories.setFrom files(subprojects.sourceSets.main.output)
  executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/test.exec')
  reports {
    xml.enabled false
    csv.enabled false
    html.enabled true
  }
}

sourceDirectories = blah -> sourceDirectories.setFrom blah etc

This will fix

Cannot set the value of read-only property 'executionData' for task ':generateMergedReport' of type org.gradle.testing.jacoco.tasks.JacocoReport.
@marklagendijk

This comment has been minimized.

Copy link

marklagendijk commented Jan 6, 2020

For gradle 6.0.x with kotlin format:

task<JacocoReport>("jacocoRootReport") {
    dependsOn(subprojects.map { it.tasks.withType<Test>() })
    dependsOn(subprojects.map { it.tasks.withType<JacocoReport>() })
    additionalSourceDirs.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].allSource.srcDirs })
    sourceDirectories.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].allSource.srcDirs })
    classDirectories.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].output })
    executionData.setFrom(project.fileTree(".") {
        include("**/build/jacoco/test.exec")
    })
    reports {
        xml.isEnabled = false
        csv.isEnabled = false
        html.isEnabled = true
        html.destination = file("${buildDir}/reports/jacoco/html")
    }
}
@deanorderly

This comment has been minimized.

Copy link

deanorderly commented Feb 1, 2020

shucks, none of these worked for gradle 5.6 and we can't upgrade to gradle 6 yet due to other breaks. We are in gradle 5.6 for protobuf plugin too so we can't go back. only the merge is broken. We either get a merged report that says 0 code coverage or it breaks with test.exec not exists. we used to do this way

onlyIf = {
    true
}
doFirst {
    executionData = files(executionData.findAll {
        it.exists()
    })
}

but that's no longer working. Our code worked in gradle 5.3. Here is our latest try on branch 'fixJacoco'...

https://github.com/deanhiller/webpieces/blob/fixJacoco/build.gradle

@wojciechwojcik

This comment has been minimized.

Copy link

wojciechwojcik commented Feb 11, 2020

@deanorderly I had the same problem and found clean and simple solution here:
gradle/gradle#5898 (comment)
gradle/gradle#5898 (comment)

Basically you do

task jacocoRootReport(type: JacocoReport) {
     executionData.from = files(subprojects.jacocoTestReport.executionData).filter { f -> f.exists() }
}

jacocoTestReport {
    executionData.from = fileTree(buildDir).include("/jacoco/*.exec")
}

Tested with gradle 5.6.4

@deanorderly

This comment has been minimized.

Copy link

deanorderly commented Mar 5, 2020

@tsjensen

This comment has been minimized.

Copy link

tsjensen commented Mar 5, 2020

Thanks for the many comments in this Gist, it's been very helpful! I fiddled with it for a while, and combined some info found in other places, to come up with something that worked for me: Gist (with explanations). Some features:

  • Doesn't make assumptions about where files are located in the build folders.
  • Refers to the sources of truth for getting at needed information.
  • Doesn't make assumptions about which source sets are being tested. It might be main, but it might not.
  • Handles subprojects that don't JaCoCo.
  • Handles Test tasks that don't have any tests, or don't produce a .exec file for some other reason.

Hope it helps someone!

@deanorderly

This comment has been minimized.

Copy link

deanorderly commented Mar 5, 2020

@dlehammer

This comment has been minimized.

Copy link

dlehammer commented Mar 9, 2020

I've had success with the following sourceSets configuration approach for multi-module:

task coverageReport(type: JacocoReport) {
  ...
  SourceSet[] allMainSourceSets = ([sourceSets.main] + subprojects*.sourceSets.main) as SourceSet[]
  sourceSets allMainSourceSets
  ...
}

Thanks for the original gist that inspired me :)

EDIT: just remembered, verified with Gradle v5.6.4 & 6.2.1.

@ashwini-desai

This comment has been minimized.

Copy link

ashwini-desai commented Mar 11, 2020

I finally could get aggregated report by the following approach:
Root build.gradle

subprojects {
    apply(plugin: 'org.jetbrains.kotlin.jvm')

    repositories {
        jcenter()
        mavenCentral()
   }
}

task codeCoverageReport(type: JacocoReport) {

    // Gather execution data from all subprojects
    executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")

    // Add all relevant sourcesets from the subprojects
    subprojects.each {
        sourceSets it.sourceSets.main
    }

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

// always run the tests before generating the report
codeCoverageReport.dependsOn {
    subprojects*.test
}
@jimshowalter

This comment has been minimized.

Copy link

jimshowalter commented Mar 19, 2020

We have a working solution that even publishes to sonar, but one part of it requires double-entry bookkeeping. Maybe someone here can figure out the answer? https://stackoverflow.com/questions/60728028/how-can-i-aggregate-excludes-from-modules-into-the-root-excludes.

@jimshowalter

This comment has been minimized.

Copy link

jimshowalter commented Mar 22, 2020

Figured it out. The only remaining issue is a (String[]) cast that shouldn't be needed.

@CarlosM4rtinez

This comment has been minimized.

Copy link

CarlosM4rtinez commented Apr 18, 2020

th

As of gradle 6.x

task generateMergedReport(type: JacocoReport) {
  dependsOn = subprojects.test
  additionalSourceDirs.setFrom files(subprojects.sourceSets.main.allSource.srcDirs)
  sourceDirectories.setFrom files(subprojects.sourceSets.main.allSource.srcDirs)
  classDirectories.setFrom files(subprojects.sourceSets.main.output)
  executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/test.exec')
  reports {
    xml.enabled false
    csv.enabled false
    html.enabled true
  }
}

sourceDirectories = blah -> sourceDirectories.setFrom blah etc

This will fix

Cannot set the value of read-only property 'executionData' for task ':generateMergedReport' of type org.gradle.testing.jacoco.tasks.JacocoReport.

thanks!!!

@cdpatelgit

This comment has been minimized.

Copy link

cdpatelgit commented May 22, 2020

Hi, Can anyone share example for same scenario multimodule jacoco coverage with Gradle 2.5/
Thanks in advance

@NikolayMetchev

This comment has been minimized.

Copy link

NikolayMetchev commented May 27, 2020

It turns out the Gradle people came up with a working solution:
https://docs.gradle.org/6.4.1/samples/sample_jvm_multi_project_with_code_coverage.html

@adomokos

This comment has been minimized.

Copy link

adomokos commented Jun 1, 2020

It turns out the Gradle people came up with a working solution:
https://docs.gradle.org/6.4.1/samples/sample_jvm_multi_project_with_code_coverage.html

This solution does not provide an aggregated result. This worked for me:

plugins {
    java
    base
    jacoco
    ...
}

// Jacoco Test Coverage Report
jacoco {
    toolVersion = "0.8.5"
}

task<JacocoReport>("jacocoRootReport") {
    dependsOn(subprojects.map { it.tasks.withType<Test>() })
    dependsOn(subprojects.map { it.tasks.withType<JacocoReport>() })
    additionalSourceDirs.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].allSource.srcDirs })
    sourceDirectories.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].allSource.srcDirs })
    classDirectories.setFrom(subprojects.map { it.the<SourceSetContainer>()["main"].output })
    executionData.setFrom(project.fileTree(".") { include("**/build/jacoco/test.exec") })
    reports {
        xml.isEnabled = false
        csv.isEnabled = false
        html.isEnabled = true
        html.destination = file("${buildDir}/reports/jacoco/html")
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.