Skip to content

Instantly share code, notes, and snippets.

@sandipchitale
Created May 17, 2020 04:56
Show Gist options
  • Save sandipchitale/ecf6e4d5d489109a8ed62d2aabaa0541 to your computer and use it in GitHub Desktop.
Save sandipchitale/ecf6e4d5d489109a8ed62d2aabaa0541 to your computer and use it in GitHub Desktop.
Print Gradle Task Graph Plugin
apply plugin: PrintTaskExecGraphPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.execution.TaskExecutionGraph
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.execution.plan.ExecutionPlan
import org.gradle.execution.plan.TaskNode
import java.lang.reflect.Field
class PrintTaskExecGraphPluginExtension {
/** Enables the plugin for given project */
boolean enabled = true
/** Output file destination file */
String destination = 'build/reports/printteg.html'
}
/**
* Print Task Execution Graph
*
* <p>Gradle plugin printing Gradle task execution graphs.</p>
*
* @author Sandip Chitale
*/
class PrintTaskExecGraphPlugin implements Plugin<Project> {
static final Logger LOG = Logging.getLogger(PrintTaskExecGraphPlugin.class)
def destination
/** Platform dependent line separator */
def ls = System.getProperty("line.separator")
@Override
void apply(Project project) {
project.extensions.create("printteg", PrintTaskExecGraphPluginExtension)
PrintTaskExecGraphPluginExtension printtegExt = project.printteg
project.gradle.taskGraph.whenReady { g ->
if (printtegExt.enabled) {
// Access private variables of tasks graph
def tep = getTEP(g)
// Execution starts on these tasks
def entryTasks = getEntryTasks(tep)
// // Already processed edges
// def edges = [] as Set
// Create output buffer
def dotGraph = new StringBuilder(
'''<!DOCTYPE html>
<html>
<head>
<title>Task Execution Graph</title>
</head>
<body>
<pre>''').append(ls)
// Generate graph for each input
entryTasks.each { tn ->
def seen = [] as Set
printGraph(printtegExt, dotGraph, ls, tn, seen, '', DependencyKind.DEPENDENCY, '')
if (tn.task.project == project) {
if (tn.task.project.hasProperty('gradle-tasks')) {
println "{"
println " \"infoarchiveProjectPath\": \"${tn.task.project.getProjectDir().toString().replace('\\', '\\\\')}\","
println " \"tasks\": {"
def seenSize = seen.size();
for (int i = seenSize -1; i >= 0; i--) {
println " \"${seen[i].replace(':', '')}\": true,"
}
println " },"
println " \"runTasks\": \"\","
println " \"flags\": \"--refresh-dependencies --rerun-tasks\""
println "}"
}
}
}
dotGraph.append(ls)
// Finalize graph
dotGraph.append(
'''
</pre>
</body>
</html>
'''
).append(ls)
// Save graph
def outputFile = _getDestination(project)
outputFile.parentFile.mkdirs()
outputFile.write(dotGraph.toString())
LOG.info("PrintTEG: Dependency report written into $outputFile")
}
}
}
private void dryRun(TaskExecutionGraph taskExecutionGraph) {
taskExecutionGraph.allTasks.each { it.enabled = false }
}
private File _getDestination(Project p) {
p.file(p.printteg.destination)
}
private ExecutionPlan getTEP(TaskExecutionGraph teg) {
Field f = teg.class.getDeclaredField("executionPlan")
f.accessible = true
f.get(teg)
}
private Set<TaskNode> getEntryTasks(ExecutionPlan tep) {
Field f = tep.class.getDeclaredField("entryNodes")
f.accessible = true
Set<Node> entryNodes = f.get(tep)
entryNodes.findAll { it instanceof TaskNode }.collect { (TaskNode) it}
}
void printGraph(PrintTaskExecGraphPluginExtension printtegExt,
StringBuilder sb,
String ls,
TaskNode entry,
Set<Integer> seen,
String indent,
DependencyKind dependencyKind,
String dependee) {
def q = new LinkedList<TaskNode>()
def tn = entry
def tproject = tn.task.project
def tname = tn.task.path
def tidentity = tn.task.taskIdentity
def tgroup = tn.task.group
def tdesc = tn.task.description
def nodeKind = tn.dependencyPredecessors.empty ? NodeKind.START
: tn.dependencySuccessors.empty ? NodeKind.END : NodeKind.INNER
if (nodeKind == NodeKind.START && dependencyKind == DependencyKind.DEPENDENCY) {
sb.append(indent).append('&#8614; ')
} else {
sb.append(indent).append(' ')
}
switch(dependencyKind) {
case DependencyKind.MUST:
sb.append(' must run after ');
break;
case DependencyKind.SHOULD:
sb.append(' should run after ')
break;
}
sb.append("<b>${tname}</b>")
sb.append(' (')
if (tidentity != null) {
def tclass = tidentity.toString().replaceAll('^.+type=class ', '').replaceAll(', .+$', '')
sb.append("class: ${tclass}")
if (tidentity.projectPath != ':') {
sb.append(", project: ${tidentity.projectPath}")
}
}
if (tgroup != null) {
sb.append(", group: $tgroup")
}
sb.append(')')
if (tdesc != null) {
sb.append(" - $tdesc")
}
switch(dependencyKind) {
case DependencyKind.FINALIZER:
sb.append(' finalizes ' + dependee)
break;
}
if (nodeKind == NodeKind.END) {
sb.append(' &#8867;')
}
try {
if (seen.contains(tname)) {
sb.append(' &#x2191;')
return
}
} finally {
sb.append(ls)
}
seen.add(tname)
indent = indent.replace('&#x2516', '|') + ' &#x2516'
tn.dependencySuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.DEPENDENCY, null)
}
tn.finalizers.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.FINALIZER, tname)
}
tn.mustSuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.MUST, tname)
}
tn.shouldSuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.SHOULD, tname)
}
}
enum NodeKind {
START, INNER, END
}
enum DependencyKind {
DEPENDENCY, MUST, SHOULD, FINALIZER
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment