We have a multi-module Gradle project. All of the modules are immediate subdirectories of the root project directory.
In the modules, we run JUnit and generate coverage with Jacoco. In the root, we aggregate the coverage. This all works, but currently we have to enter the exclusions twice:
MODULE1:
apply plugin: "jacoco"
jacoco {
toolVersion = "0.8.5"
}
def static analysisExcludes() {
return [
"com/ourcompany/module1/*Config*",
"com/ourcompany/module1/endpoint/exception/**",
"com/ourcompany/module1/v2/**",
"com/ourcompany/module1/v3/**",
"src/generated/**",
"src/*test*/**",
"wrapper/dists/**"
]
}
def execData() {
return files(fileTree(buildDir).include("jacoco/*.exec"))
}
jacocoTestReport {
getExecutionData().setFrom(execData())
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: analysisExcludes())
}))
}
}
jacocoTestCoverageVerification {
getExecutionData().setFrom(execData());
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: analysisExcludes())
}))
}
violationRules {
rule {
limit {
minimum = 0.93
}
}
}
}
MODULE2:
apply plugin: "jacoco"
jacoco {
toolVersion = "0.8.5"
}
def static analysisExcludes() {
return [
"com/ourcompany/module2/*IgnoreMe*"
"src/generated/**",
"src/*test*/**",
"wrapper/dists/**"
]
}
def execData() {
return files(fileTree(buildDir).include("jacoco/*.exec"))
}
jacocoTestReport {
getExecutionData().setFrom(execData())
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: analysisExcludes())
}))
}
}
jacocoTestCoverageVerification {
getExecutionData().setFrom(execData());
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: analysisExcludes())
}))
}
violationRules {
rule {
limit {
minimum = 0.87
}
}
}
}
ROOT:
apply plugin: "jacoco"
jacoco {
toolVersion = "0.8.5"
}
// TODO: Eliminate double-entry bookkeeping by getting the excludes from the modules.
def static aggregatedAnalysisExcludes() {
return [
"**/com/ourcompany/module1/*Config*",
"**/com/ourcompany/module1/endpoint/exception/**",
"**/com/ourcompany/module1/v2/**",
"**/com/ourcompany/module1/v3/**",
"**/com/ourcompany/module2/*IgnoreMe*"
"**/src/generated/**",
"**/src/*test*/**",
"**/wrapper/dists/**"
]
}
def execData() {
return files(fileTree(rootDir).include("**/build/jacoco/*.exec"))
}
def includedClasses() {
return files(fileTree(dir: rootDir, include: "**/build/classes/**", exclude: aggregatedAnalysisExcludes()))
}
task jacocoAggregateReport(type: JacocoReport) {
getExecutionData().setFrom(execData());
afterEvaluate {
classDirectories.setFrom(includedClasses())
}
}
task jacocoAggregateTestCoverageVerification(type: JacocoCoverageVerification) {
getExecutionData().setFrom(execData());
afterEvaluate {
classDirectories.setFrom(includedClasses())
}
violationRules {
rule {
limit {
minimum = 0.91
}
}
}
}
task mergeJacocoExecData(type: JacocoMerge) {
setExecutionData(execData());
setDestinationFile(new File("build/jacoco/all.exec"))
}
apply plugin: "org.sonarqube"
def junitResultsDirs() {
def dirs = []
rootDir.eachDirRecurse { dir ->
if (dir.absolutePath.matches("^.*/build/test-results/(test|(component|integration)Test)\$")) {
dirs << dir
}
}
return dirs;
}
sonarqube {
properties {
property "sonar.projectName", "Multimodule Repo"
property "sonar.projectKey", "multimodule-repo"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.jacoco.reportPath", "build/jacoco/all.exec"
property "sonar.junit.reportsPath", junitResultsDirs()
property "sonar.issuesReport.html.enable", true
property "sonar.issuesReport.console.enable", true
property 'sonar.exclusions', aggregatedAnalysisExcludes()
}
}
We'd like to somehow iterate the modules to get their excludes, and combine them into the aggregated excludes, adding the "**/" prefixes that make the paths relative to the root.
In pseudo-code:
def aggregatedAnalysisExcludes() {
return rootDir.modules.excludes.withPrefixes("**/")
}
or:
def aggregatedAnalysisExcludes() {
def excludes = []
for (Module m : rootDir.modules) {
excludes << m.excludes.withPrefixes("**/")
}
return excludes
}
How do we write the code for real?
Note: Assuming there is a way to do what we want, the resulting list will contain duplicates of src/generated, src/test, and wrapper/dists. We tried creating lists with duplicates to see if that matters, and it does not. The excludes work correctly even when there are duplicates. But, if there's a clean declarative way to remove duplicates, that would be a nice addition to the solution.