GraphWorkspaceMojo.java
package network.ike.plugin.ws;
import network.ike.workspace.Subproject;
import network.ike.workspace.Dependency;
import network.ike.workspace.WorkspaceGraph;
import org.apache.maven.api.plugin.MojoException;
import org.apache.maven.api.plugin.annotations.Mojo;
import org.apache.maven.api.plugin.annotations.Parameter;
import java.util.List;
/**
* Print the workspace dependency graph.
*
* <p>Displays all subprojects in topological order with their
* dependencies. Optionally outputs DOT format for Graphviz rendering.
*
* <pre>{@code
* mvn ike:graph
* mvn ike:graph -Dformat=dot
* }</pre>
*/
@Mojo(name = "graph", projectRequired = false, aggregator = true)
public class GraphWorkspaceMojo extends AbstractWorkspaceMojo {
/**
* Output format: "text" (default) or "dot" (Graphviz DOT).
*/
@Parameter(property = "format", defaultValue = "text")
String format;
/** Creates this goal instance. */
public GraphWorkspaceMojo() {}
@Override
protected WorkspaceReportSpec runGoal() throws MojoException {
WorkspaceGraph graph = loadGraph();
if ("dot".equalsIgnoreCase(format)) {
printDot(graph);
} else {
printText(graph);
}
// Append the GraphViz dependency graph to the report.
// IKE-DIAGRAMS.md mandates GraphViz for dependency graphs;
// Mermaid is discouraged (IKE-Network/ike-issues#406).
return new WorkspaceReportSpec(WsGoal.GRAPH,
DotGraphSupport.buildDotReportBlock(graph));
}
private void printText(WorkspaceGraph graph) {
getLog().info("");
getLog().info(header("Dependency Graph"));
getLog().info("══════════════════════════════════════════════════════════════");
getLog().info("");
List<String> sorted = graph.topologicalSort();
for (int i = 0; i < sorted.size(); i++) {
String name = sorted.get(i);
Subproject sub = graph.manifest().subprojects().get(name);
getLog().info(String.format(" %2d. %s", i + 1, name));
if (!sub.dependsOn().isEmpty()) {
for (int j = 0; j < sub.dependsOn().size(); j++) {
Dependency dep = sub.dependsOn().get(j);
boolean last = (j == sub.dependsOn().size() - 1);
String connector = last ? "└─" : "├─";
getLog().info(String.format(" %s %s (%s)",
connector, dep.subproject(), dep.relationship()));
// Show transitive dependencies
Subproject depComp = graph.manifest().subprojects()
.get(dep.subproject());
if (depComp != null && !depComp.dependsOn().isEmpty()) {
String prefix = last ? " " : " │ ";
printTransitiveDeps(graph, depComp, prefix, name);
}
}
}
}
getLog().info("");
getLog().info(" " + sorted.size() + " components in dependency order.");
getLog().info("");
}
/**
* Recursively print transitive dependencies with tree indentation.
*
* @param graph the workspace graph
* @param sub the subproject whose dependencies to print
* @param prefix indentation prefix for this level
* @param root the root subproject name (to prevent cycles)
*/
private void printTransitiveDeps(WorkspaceGraph graph, Subproject sub,
String prefix, String root) {
for (int i = 0; i < sub.dependsOn().size(); i++) {
Dependency dep = sub.dependsOn().get(i);
// Prevent infinite recursion if there's a cycle
if (dep.subproject().equals(root)) continue;
boolean last = (i == sub.dependsOn().size() - 1);
String connector = last ? "└─" : "├─";
getLog().info(String.format("%s%s %s (%s)",
prefix, connector, dep.subproject(), dep.relationship()));
Subproject depComp = graph.manifest().subprojects()
.get(dep.subproject());
if (depComp != null && !depComp.dependsOn().isEmpty()) {
String childPrefix = prefix + (last ? " " : "│ ");
printTransitiveDeps(graph, depComp, childPrefix, root);
}
}
}
private void printDot(WorkspaceGraph graph) {
for (String line
: DotGraphSupport.dotFromGraph(graph).split("\n")) {
getLog().info(line);
}
}
}