Analyze DAGs

Use markup-graph as a general directed-graph model: build a graph with the DSL, then check it for cycles or compute an execution order with the bundled algorithms.

Prerequisites: markup-graph on the classpath (zero dependencies); optionally markup-dot-writer for visual output.

dependencies {
    implementation("org.markup-poet:markup-graph:0.2.0")
}

1. Model the dependencies as a digraph

Edges alone are enough — nodes referenced only by edges are created implicitly:

import org.markup.poet.dsl.graph.digraph
import org.markup.poet.dsl.graph.nodeIds
import org.markup.poet.dsl.graph.topologicalSortOrNull

val pipeline = digraph {
    edge("build", "test")
    edge("test", "deploy")
    edge("build", "lint")
}

pipeline.nodeIds   // [build, test, deploy, lint] — includes implicit edge-only nodes

2. Compute an execution order

topologicalSortOrNull() (Kahn’s algorithm) returns the node ids so that every edge points from an earlier to a later entry. Nodes with equal precedence keep declaration order, so the result is deterministic:

pipeline.topologicalSortOrNull()   // [build, test, lint, deploy]
pipeline.isAcyclic()               // true

3. Detect cycles

A null result means the graph contains a cycle:

import org.markup.poet.dsl.graph.isAcyclic

val cyclic = digraph {
    edge("a", "b")
    edge("b", "c")
    edge("c", "a")
}

cyclic.topologicalSortOrNull()   // null
cyclic.isAcyclic()               // false

Self-loops count as cycles too: digraph { edge("a", "a") }.isAcyclic() is false.

4. Only directed graphs qualify

Both algorithms require a directed graph; an undirected graph { } throws IllegalArgumentException:

import org.markup.poet.dsl.graph.graph

val undirected = graph {
    edge("a", "b")
}

try {
    undirected.topologicalSortOrNull()
} catch (e: IllegalArgumentException) {
    // "Topological sort requires a directed graph"
}

5. Debug cycles visually

When a sort comes back null on a large graph, write the same model as Graphviz DOT and render it — cycles are much easier to spot on a picture:

import org.markup.poet.dsl.write.dot.toDot

println(cyclic.toDot())
dot -Tsvg pipeline.dot -o pipeline.svg

See Graphs and Graphviz DOT for the full DOT workflow, and DOT mapping for the syntax mapping.