github.com/hashicorp/packer@v1.14.3/internal/dag/dag.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package dag 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 11 "github.com/hashicorp/hcl/v2" 12 ) 13 14 // AcyclicGraph is a specialization of Graph that cannot have cycles. 15 type AcyclicGraph struct { 16 Graph 17 } 18 19 // WalkFunc is the callback used for walking the graph. 20 type WalkFunc func(Vertex) hcl.Diagnostics 21 22 // DepthWalkFunc is a walk function that also receives the current depth of the 23 // walk as an argument 24 type DepthWalkFunc func(Vertex, int) error 25 26 func (g *AcyclicGraph) DirectedGraph() Grapher { 27 return g 28 } 29 30 // Validate validates the DAG. A DAG is valid if it has no cycles or self-referencing vertex. 31 func (g *AcyclicGraph) Validate() error { 32 // Look for cycles of more than 1 component 33 var err error 34 cycles := g.Cycles() 35 if len(cycles) > 0 { 36 for _, cycle := range cycles { 37 cycleStr := make([]string, len(cycle)) 38 for j, vertex := range cycle { 39 cycleStr[j] = VertexName(vertex) 40 } 41 42 err = errors.Join(err, fmt.Errorf( 43 "Cycle: %s", strings.Join(cycleStr, ", "))) 44 } 45 } 46 47 // Look for cycles to self 48 for _, e := range g.Edges() { 49 if e.Source() == e.Target() { 50 err = errors.Join(err, fmt.Errorf( 51 "Self reference: %s", VertexName(e.Source()))) 52 } 53 } 54 55 return err 56 } 57 58 // Cycles reports any cycles between graph nodes. 59 // Self-referencing nodes are not reported, and must be detected separately. 60 func (g *AcyclicGraph) Cycles() [][]Vertex { 61 var cycles [][]Vertex 62 for _, cycle := range StronglyConnected(&g.Graph) { 63 if len(cycle) > 1 { 64 cycles = append(cycles, cycle) 65 } 66 } 67 return cycles 68 } 69 70 type walkType uint64 71 72 const ( 73 depthFirst walkType = 1 << iota 74 breadthFirst 75 downOrder 76 upOrder 77 ) 78 79 // ReverseTopologicalOrder returns a topological sort of the given graph, with 80 // target vertices ordered before the sources of their edges. The nodes are not 81 // sorted, and any valid order may be returned. This function will panic if it 82 // encounters a cycle. 83 func (g *AcyclicGraph) ReverseTopologicalOrder() []Vertex { 84 return g.topoOrder(downOrder) 85 } 86 87 func (g *AcyclicGraph) topoOrder(order walkType) []Vertex { 88 // Use a dfs-based sorting algorithm, similar to that used in 89 // TransitiveReduction. 90 sorted := make([]Vertex, 0, len(g.vertices)) 91 92 // tmp track the current working node to check for cycles 93 tmp := map[Vertex]bool{} 94 95 // perm tracks completed nodes to end the recursion 96 perm := map[Vertex]bool{} 97 98 var visit func(v Vertex) 99 100 visit = func(v Vertex) { 101 if perm[v] { 102 return 103 } 104 105 if tmp[v] { 106 panic("cycle found in dag") 107 } 108 109 tmp[v] = true 110 var next Set 111 switch { 112 case order&downOrder != 0: 113 next = g.downEdgesNoCopy(v) 114 case order&upOrder != 0: 115 next = g.upEdgesNoCopy(v) 116 default: 117 panic(fmt.Sprintln("invalid order", order)) 118 } 119 120 for _, u := range next { 121 visit(u) 122 } 123 124 tmp[v] = false 125 perm[v] = true 126 sorted = append(sorted, v) 127 } 128 129 for _, v := range g.Vertices() { 130 visit(v) 131 } 132 133 return sorted 134 }