| 1 | package steward |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "github.com/dominikbraun/graph" |
| 7 | "github.com/soverenio/vanilla/throw" |
| 8 | |
| 9 | "go.bigb.es/auxilia/collect" |
| 10 | "go.bigb.es/auxilia/internal/graphutil" |
| 11 | "go.bigb.es/auxilia/internal/logutil" |
| 12 | ) |
| 13 | |
| 14 | type assetTreeNode struct { |
| 15 | filled bool |
| 16 | isOverride bool |
| 17 | position int |
| 18 | } |
| 19 | |
| 20 | func (n assetTreeNode) Empty() bool { |
| 21 | return !n.filled |
| 22 | } |
| 23 | |
| 24 | func getTreeNode(manager *Manager, svc *serviceAsset) assetTreeNode { |
| 25 | for pos, override := range manager.overrides { |
| 26 | if override == svc { |
| 27 | return assetTreeNode{ |
| 28 | filled: true, |
| 29 | isOverride: true, |
| 30 | position: pos, |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | for pos, component := range manager.components { |
| 36 | if component == svc { |
| 37 | return assetTreeNode{ |
| 38 | filled: true, |
| 39 | isOverride: false, |
| 40 | position: pos, |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | return assetTreeNode{filled: false} |
| 46 | } |
| 47 | |
| 48 | func nullHasher[T comparable](node T) T { |
| 49 | return node |
| 50 | } |
| 51 | |
| 52 | func (m *Manager) generateTree() graph.Graph[assetTreeNode, assetTreeNode] { |
| 53 | tree := graph.New(nullHasher[assetTreeNode], graph.Tree(), graph.Directed()) |
| 54 | |
| 55 | for i := range m.overrides { |
| 56 | err := tree.AddVertex(assetTreeNode{ |
| 57 | isOverride: true, |
| 58 | position: i, |
| 59 | filled: true, |
| 60 | }) |
| 61 | |
| 62 | if err != nil { |
| 63 | panic(throw.IllegalState()) |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | for i := range m.components { |
| 68 | err := tree.AddVertex(assetTreeNode{ |
| 69 | isOverride: false, |
| 70 | position: i, |
| 71 | filled: true, |
| 72 | }) |
| 73 | |
| 74 | if err != nil { |
| 75 | panic(throw.IllegalState()) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | // TODO: rewrite dependency searches (dependency may be in parent), now it's continue |
| 80 | for _, component := range append(m.overrides, m.components...) { |
| 81 | componentID := getTreeNode(m, component) |
| 82 | if componentID.Empty() { |
| 83 | panic(throw.IllegalState()) |
| 84 | } |
| 85 | |
| 86 | for _, dependency := range component.dependencies { |
| 87 | dependencyID := getTreeNode(m, dependency) |
| 88 | if dependencyID.Empty() { |
| 89 | continue |
| 90 | } |
| 91 | |
| 92 | switch err := tree.AddEdge(componentID, dependencyID); err { |
| 93 | case nil: |
| 94 | case graph.ErrEdgeAlreadyExists: |
| 95 | default: |
| 96 | panic(throw.W(err, throw.IllegalState().Error())) |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | return tree |
| 102 | } |
| 103 | |
| 104 | func assetFromNode(m *Manager, node assetTreeNode) *serviceAsset { |
| 105 | if node.isOverride { |
| 106 | return m.overrides[node.position] |
| 107 | } |
| 108 | |
| 109 | return m.components[node.position] |
| 110 | } |
| 111 | |
| 112 | func (m *Manager) buildExecutionOrder(ctx context.Context) ([]*serviceAsset, error) { |
| 113 | tree := m.generateTree() |
| 114 | |
| 115 | for _, node := range append(m.overrides, m.components...) { |
| 116 | switch { |
| 117 | case node.dependents != nil: |
| 118 | continue // has dependents |
| 119 | case !node.root && node.ignoreUnused: |
| 120 | continue // it's not root and it can be unused |
| 121 | case !node.root: |
| 122 | logutil.FromContext(ctx).Error("empty dependents asset without root option", "asset", node.Name()) |
| 123 | |
| 124 | continue |
| 125 | } |
| 126 | |
| 127 | if err := graph.DFS(tree, getTreeNode(m, node), func(node assetTreeNode) bool { |
| 128 | asset := assetFromNode(m, node) |
| 129 | asset.roots = append(asset.roots, node) |
| 130 | |
| 131 | return false |
| 132 | }); err != nil { |
| 133 | return nil, err |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | ordered, err := graphutil.ChronologicalSort(tree) |
| 138 | if err != nil { |
| 139 | return nil, err |
| 140 | } |
| 141 | |
| 142 | return collect.ConvertList( |
| 143 | collect.FromSlice(ordered).Filter(func(node assetTreeNode) bool { |
| 144 | asset := assetFromNode(m, node) |
| 145 | |
| 146 | if asset.roots == nil { |
| 147 | if !asset.ignoreUnused { |
| 148 | logutil.FromContext(ctx).Warn("skipping initialization of asset without root attachment", "asset", asset.Name()) |
| 149 | } |
| 150 | |
| 151 | return false |
| 152 | } |
| 153 | return true |
| 154 | }), |
| 155 | func(node assetTreeNode) *serviceAsset { return assetFromNode(m, node) }, |
| 156 | ).Collect(), nil |
| 157 | } |
| 158 | |