Last active
January 14, 2019 07:20
-
-
Save sandipchitale/fc68e2d2f6eeb411900125263165043e to your computer and use it in GitHub Desktop.
Gradle plugin to print Task Execution Graph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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('↦') | |
} 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(" $tname") | |
if (tdesc != null) { | |
sb.append(" - $tdesc") | |
} | |
switch(dependencyKind) { | |
case DependencyKind.FINALIZER: | |
sb.append(' finalizes ' + dependee) | |
break; | |
} | |
if (nodeKind == NodeKind.END) { | |
sb.append(' ⊣') | |
} | |
try { | |
if (seen.contains(tname)) { | |
sb.append(' ↑') | |
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