Created
May 17, 2020 04:56
-
-
Save sandipchitale/ecf6e4d5d489109a8ed62d2aabaa0541 to your computer and use it in GitHub Desktop.
Print Gradle Task Graph Plugin
This file contains hidden or 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.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('↦ ') | |
} 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(' ⊣') | |
} | |
try { | |
if (seen.contains(tname)) { | |
sb.append(' ↑') | |
return | |
} | |
} finally { | |
sb.append(ls) | |
} | |
seen.add(tname) | |
indent = indent.replace('┖', '|') + ' ┖' | |
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