github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/release/dependency/graph.go (about) 1 package dependency 2 3 import ( 4 "fmt" 5 6 "golang.org/x/exp/maps" 7 ) 8 9 // Graph is dependencies graph. K stands for map keys type (e.g. string names), N for data type. 10 type Graph[K comparable, N any] struct { 11 Nodes map[K]*Node[N] 12 dependencies []GraphDependency[K] 13 } 14 15 // NewGraph returns empty graph. 16 func NewGraph[K comparable, N any]() *Graph[K, N] { 17 return &Graph[K, N]{ 18 Nodes: make(map[K]*Node[N]), 19 dependencies: make([]GraphDependency[K], 0), 20 } 21 } 22 23 func (graph *Graph[K, N]) NewNode(key K, data N) error { 24 if _, ok := graph.Nodes[key]; ok { 25 return fmt.Errorf("key %v already exists", key) 26 } 27 28 node := newNode(data) 29 graph.Nodes[key] = node 30 31 return nil 32 } 33 34 // AddDependency adds lazy dependency. It will be evaluated only in `Build` method. 35 func (graph *Graph[K, N]) AddDependency(dependant, dependency K) { 36 graph.dependencies = append(graph.dependencies, newDependency(dependant, dependency)) 37 } 38 39 func (graph *Graph[K, N]) Reverse() (*Graph[K, N], error) { 40 newDependenciesGraph := NewGraph[K, N]() 41 42 for key, node := range graph.Nodes { 43 err := newDependenciesGraph.NewNode(key, node.Data) 44 if err != nil { 45 return nil, err 46 } 47 } 48 49 for _, dep := range graph.dependencies { 50 newDependenciesGraph.AddDependency(dep.Dependency(), dep.Dependant()) 51 } 52 53 err := newDependenciesGraph.Build() 54 if err != nil { 55 return nil, err 56 } 57 58 return newDependenciesGraph, nil 59 } 60 61 func (graph *Graph[K, N]) Build() error { 62 for _, dep := range graph.dependencies { 63 dependant, ok := graph.Nodes[dep.Dependant()] 64 if !ok { 65 return fmt.Errorf("dependant key %v does not exist", dep.Dependant()) 66 } 67 dependency, ok := graph.Nodes[dep.Dependency()] 68 if !ok { 69 return fmt.Errorf("dependency key %v does not exist", dep.Dependency()) 70 } 71 72 dependant.addDependency(dependency) 73 } 74 75 if err := graph.detectCycle(); err != nil { 76 return err 77 } 78 79 return nil 80 } 81 82 func (graph *Graph[K, N]) detectCycle() error { 83 visited := make(map[*Node[N]]int) 84 85 for _, node := range graph.Nodes { 86 err := graph.dfs(node, visited) 87 if err != nil { 88 return err 89 } 90 } 91 92 return nil 93 } 94 95 // dfs is Depth First Search. 96 func (graph *Graph[K, N]) dfs(node *Node[N], visited map[*Node[N]]int) error { 97 // This means that during recursion we hit node that is already being dfs'd 98 if visited[node] == -1 { 99 return fmt.Errorf("graph loop detected (starts with %v)", node) 100 } 101 102 if visited[node] == 1 { 103 return nil 104 } 105 106 visited[node] = -1 107 108 for _, dep := range node.dependencies { 109 err := graph.dfs(dep, visited) 110 if err != nil { 111 return err 112 } 113 } 114 115 visited[node] = 1 116 117 return nil 118 } 119 120 func (graph *Graph[K, N]) runChan(ch chan<- *Node[N]) { 121 nodes := maps.Clone(graph.Nodes) 122 123 for len(nodes) > 0 { 124 for key, node := range nodes { 125 switch { 126 // In case some release failed because it's dependency failed 127 case node.IsDone(): 128 delete(nodes, key) 129 case node.IsReady(): 130 ch <- node 131 delete(nodes, key) 132 } 133 } 134 } 135 136 close(ch) 137 } 138 139 // Run returns channel for data and runs goroutine that handles dependency graph 140 // and populates channel with ready to install releases. 141 func (graph *Graph[K, N]) Run() <-chan *Node[N] { 142 ch := make(chan *Node[N], len(graph.Nodes)) 143 go graph.runChan(ch) 144 145 return ch 146 } 147 148 type GraphDependency[K comparable] [2]K 149 150 func newDependency[K comparable](dependant, dependency K) GraphDependency[K] { 151 return GraphDependency[K]{dependant, dependency} 152 } 153 154 func (dep GraphDependency[K]) Dependant() K { 155 return dep[0] 156 } 157 158 func (dep GraphDependency[K]) Dependency() K { 159 return dep[1] 160 }