Skip to content

Instantly share code, notes, and snippets.

@sandipchitale
Last active January 14, 2019 07:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sandipchitale/fc68e2d2f6eeb411900125263165043e to your computer and use it in GitHub Desktop.
Save sandipchitale/fc68e2d2f6eeb411900125263165043e to your computer and use it in GitHub Desktop.
Gradle plugin to print Task Execution Graph
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.taskgraph.TaskExecutionPlan
import org.gradle.execution.taskgraph.TaskInfo
import java.lang.reflect.Field
/**
* 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)
/** Platform dependent line separator */
def ls = System.getProperty("line.separator")
@Override
void apply(Project project) {
project.extensions.create("printteg", PrintTegPluginExtension)
PrintTegPluginExtension 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)
// 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 { et ->
def seen = new HashSet<String>()
printGraph(printtegExt, dotGraph, ls, et, seen, '', DependencyKind.DEPENDENCY, '')
}
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 File getDestination(Project p) {
p.file(p.printteg.destination)
}
private TaskExecutionPlan getTEP(TaskExecutionGraph teg) {
Field f = teg.getClass().getDeclaredField("taskExecutionPlan")
f.setAccessible(true)
f.get(teg)
}
private Set<TaskInfo> getEntryTasks(TaskExecutionPlan tep) {
Field f = tep.getClass().getDeclaredField("entryTasks")
f.setAccessible(true)
Set<org.gradle.execution.taskgraph.TaskInfo> entryTasks = f.get(tep)
entryTasks
}
void printGraph(PrintTegPluginExtension printtegExt,
StringBuilder sb,
String ls,
TaskInfo entry,
HashSet<String> seen,
String indent,
DependencyKind dependencyKind,
String dependee) {
def ti = entry
def tproject = ti.task.project
def tname = ti.task.path
def tdesc = ti.task.description
def nodeKind = ti.dependencyPredecessors.empty ? NodeKind.START
: ti.dependencySuccessors.empty ? NodeKind.END : NodeKind.INNER
if (nodeKind == NodeKind.START && dependencyKind == DependencyKind.DEPENDENCY) {
sb.append(indent).append('&#8614;')
} else {
sb.append(indent).append('&#x2516')
}
switch(dependencyKind) {
case DependencyKind.MUST:
sb.append(' must run after');
break;
case DependencyKind.SHOULD:
sb.append(' should run after')
break;
}
sb.append(" $tname")
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 += ' '
ti.dependencySuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.DEPENDENCY, null)
}
ti.finalizers.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.FINALIZER, tname)
}
ti.mustSuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.MUST, tname)
}
ti.shouldSuccessors.each {succ ->
def sname = succ.task.path
printGraph(printtegExt, sb, ls, succ, seen, indent, DependencyKind.SHOULD, tname)
}
}
String edgeHash(String from, String to) {
return from.hashCode()*37 + to.hashCode()
}
enum NodeKind {
START, INNER, END
}
enum DependencyKind {
DEPENDENCY, MUST, SHOULD, FINALIZER
}
}
class PrintTegPluginExtension {
/** Enables the plugin for given project */
boolean enabled = true
/** Output file destination file */
String destination = 'build/reports/printteg.html'
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment