ghidra/gradleScripts/jacoco.gradle
2019-03-26 13:46:51 -04:00

209 lines
7.9 KiB
Groovy

// Used for jacocoBranchReport task. Cmd line param to specify branch origin. Defaults to master.
def jacoco_origin = project.hasProperty('jacoco.origin') ? project.getProperty('jacoco.origin') : "master"
import groovy.io.FileType;
if (project.jacocoEnabled) {
def String jacocoRootExecPath = "$buildDir/jacoco/jacocoMerge.exec"
def numFoundExecutionFiles = 0 // number of jacoco data files found in subprojects
delete new File(jacocoRootExecPath) // If the merged exec file (output from jacocoMerge) exists,
// jacocoReport & jacocoBranchReport tasks are skipped and
// the report is not generated.
// So always delete the merged file before the determination
// to skip a task is made.
List excludesList = generateExcludesList()
/*********************************************************************************
* Task to merge multiple jacoco execution data files into one.
*********************************************************************************/
task jacocoMerge(type: JacocoMerge) {
description = 'Task to merge multiple jacoco execution data files into one.'
destinationFile = new File(jacocoRootExecPath)
// Make this collection of execution data files empty during the configuration phase.
// There may be new exec files generated during the execution phase
// (ex: gradle test jacocoReport). So gather up these files in the execution phase
// via doFirst below.
executionData = project.files([])
// Before Task runs, update executionData by searching for files in each subproject.
doFirst {
logger.debug("jacocoMerge: Searching in " + subprojects.size() + " subproject(s)")
subprojects.each { p ->
logger.debug("jacocoMerge: Searching $p.name subproject in directory: $p.buildDir/jacoco/")
File jacocoExecDir = new File("$p.buildDir/jacoco/")
if (jacocoExecDir.exists()) {
jacocoExecDir.eachFileRecurse (FileType.FILES) { file ->
numFoundExecutionFiles++
logger.debug("jacocoMerge: Adding $p.name: $file")
executionData file
}
}
}
println "jacocoMerge: Added $numFoundExecutionFiles execution data files to $destinationFile"
}
}
/*********************************************************************************
* Task to create a jacoco report based on changes from current branch and origin.
* Default origin is 'master'. Specify -Pjacoco.origin=value to change the value of origin.
*********************************************************************************/
task jacocoBranchReport(type: JacocoReport, group: 'Coverage reports') {
description = 'Generates a Jacoco report based on changes from current branch and origin.'
dependsOn ":jacocoMerge"
executionData new File(jacocoRootExecPath)
// Get current branch name
String[] cmd = ["/bin/bash", "-c", "git rev-parse --abbrev-ref HEAD"]
ProcessBuilder builder = new ProcessBuilder();
builder.command(cmd);
Process process = builder.start();
def branchName = process.in.text
process.waitFor();
branchName = branchName.trim()
logger.debug("jacocoBranchReport: Current branchName is $branchName")
// Find commit in origin before branching. See: https://stackoverflow.com/q/1527234
cmd = ["/bin/bash", "-c", "diff -u <(git rev-list --first-parent $branchName) <(git rev-list --first-parent $jacoco_origin) | sed -ne 's/^ //p' | head -1"]
builder = new ProcessBuilder();
builder.command(cmd);
process = builder.start();
def lastRevision = process.in.text
process.waitFor();
lastRevision = lastRevision.trim()
logger.debug("jacocoBranchReport: last revision before branching from $jacoco_origin is $lastRevision")
// Find the files that were changed in the branch.
builder = new ProcessBuilder();
cmd = ["/bin/bash", "-c", "git diff --name-only $lastRevision"]
builder.command(cmd);
process = builder.start();
def filesChanged = process.in.text
process.waitFor();
logger.debug("jacocoBranchReport: files changed are:" + filesChanged)
List filesToInclude = new ArrayList<String>()
filesChanged.split().each{ fileName ->
// Filter out files not in src/main/java and create an inclusion pattern.
if(fileName.endsWith(".java") && fileName.contains("/src/main/java/")) {
String fqName = fileName.split("/src/main/java/")[1]
fqName = fqName.replace(".java", ".class")
filesToInclude.add(fqName)
}
}
sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
classDirectories = files(subprojects.sourceSets.main.output)
logger.debug("jacocoBranchReport: Files to include: " + filesToInclude)
// Only include these src/main/java files in the report
if (filesToInclude.size() > 0) {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it,
include: filesToInclude.toArray(new String[filesToInclude.size()]))
})
}
// Turn on html reports, 'doFirst' may disable this later on.
reports {
html.enabled = true
xml.enabled = false
}
// Output info before execution.
doFirst {
println "jacocoBranchReport: Found $filesToInclude.size Java files to filter on branch '$branchName' and revision $lastRevision from origin '$jacoco_origin'"
println "jacocoBranchReport: Number of jacoco execution data files found from jacocoMerge: $numFoundExecutionFiles"
// Turn off reports if no files to report or no jacoco data files found. Otherwise the jacoco task will create empty report.
if (filesToInclude.size() == 0 || numFoundExecutionFiles == 0) {
reports {
html.enabled = false
xml.enabled = false
}
println "jacocoBranchReport: Empty filter or no jacoco execution data found. Not writing report."
} else {
println "jacocoBranchReport: Writing report to file://$reports.html.destination/index.html"
}
}
}
/*********************************************************************************
* Task to generate an aggregate jacoco report from all subprojects
*********************************************************************************/
task jacocoReport(type: JacocoReport, group: 'Coverage reports') {
description = 'Generates an aggregate Jacoco report from all subprojects'
dependsOn ":jacocoMerge"
executionData new File(jacocoRootExecPath)
sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
classDirectories = files(subprojects.sourceSets.main.output)
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it, exclude: excludesList)
})
reports {
html.enabled = true
xml.enabled = false
html.destination = new File(project.ext.reportDir + "/jacocoReport")
}
doFirst {
if (numFoundExecutionFiles == 0) {
println "jacocoReport: No execution data files found."
println "jacocoReport: No report written to $reports.html.destination.absolutePath."
reports {
html.enabled = false
xml.enabled = false
}
} else {
println "jacocoReport: Writing report to $reports.html.destination.absolutePath"
}
}
}
}
/*********************************************************************************
* Generate the Jacoco excludes list from file (this will strip out comments and
* whitespace).
*
* This uses 'gradleScripts/jacoco.excludes.src.txt' to generate list of
* class exclusions for the 'jacocoReport' task.
*
*********************************************************************************/
def String[] generateExcludesList() {
File inputFile = new File(rootProject.projectDir, "gradleScripts/jacoco.excludes.src.txt")
def lines = inputFile.readLines()
.findAll({ line ->
!shouldIgnoreLine(line)
})
.collect()
println "Returning ${lines.size()} exclusion line(s) for jacocoReport."
return lines
}
/* An ignorable line is one that is only whitespace or that starts with a comment marker */
def shouldIgnoreLine(line) {
if (line.startsWith('#')){
return true
}
if (line.startsWith("//")) {
return true
}
if (line.trim().isEmpty()) {
return true
}
return false
}