github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/model/target_graph.go (about) 1 package model 2 3 import "fmt" 4 5 type TargetGraph struct { 6 // Targets in topological order. 7 sortedTargets []TargetSpec 8 byID map[TargetID]TargetSpec 9 } 10 11 func NewTargetGraph(targets []TargetSpec) (TargetGraph, error) { 12 sortedTargets, err := TopologicalSort(targets) 13 if err != nil { 14 return TargetGraph{}, err 15 } 16 17 return TargetGraph{ 18 sortedTargets: sortedTargets, 19 byID: MakeTargetMap(sortedTargets), 20 }, nil 21 } 22 23 // In Tilt, Manifests should always be DAGs with a single root node 24 // (the deploy target). This is just a quick sanity check to make sure 25 // that's true, because many of our graph-traversal algorithms won't work if 26 // it's not true. 27 func (g TargetGraph) IsSingleSourceDAG() bool { 28 seenIDs := make(map[TargetID]bool, len(g.sortedTargets)) 29 lastIdx := len(g.sortedTargets) - 1 30 for i := lastIdx; i >= 0; i-- { 31 t := g.sortedTargets[i] 32 id := t.ID() 33 isLastTarget := i == lastIdx 34 if !isLastTarget && !seenIDs[id] { 35 return false 36 } 37 38 for _, depID := range t.DependencyIDs() { 39 seenIDs[depID] = true 40 } 41 } 42 return true 43 } 44 45 // Visit t and its transitive dependencies in post-order (aka depth-first) 46 func (g TargetGraph) VisitTree(root TargetSpec, visit func(dep TargetSpec) error) error { 47 visitedIDs := make(map[TargetID]bool) 48 49 // pre-declare the variable, so that this function can recurse 50 var helper func(current TargetSpec) error 51 helper = func(current TargetSpec) error { 52 deps, err := g.DepsOf(current) 53 if err != nil { 54 return err 55 } 56 57 for _, dep := range deps { 58 if visitedIDs[dep.ID()] { 59 continue 60 } 61 62 err := helper(dep) 63 if err != nil { 64 return err 65 } 66 } 67 68 err = visit(current) 69 if err != nil { 70 return err 71 } 72 visitedIDs[current.ID()] = true 73 return nil 74 } 75 76 return helper(root) 77 } 78 79 // Return the direct dependency targets. 80 func (g TargetGraph) DepsOf(t TargetSpec) ([]TargetSpec, error) { 81 depIDs := t.DependencyIDs() 82 result := make([]TargetSpec, len(depIDs)) 83 for i, depID := range depIDs { 84 dep, ok := g.byID[depID] 85 if !ok { 86 return nil, fmt.Errorf("Dep %q not found in graph", depID) 87 } 88 result[i] = dep 89 } 90 return result, nil 91 } 92 93 // Is this image directly deployed a container? 94 func (g TargetGraph) IsDeployedImage(iTarget ImageTarget) bool { 95 id := iTarget.ID() 96 for _, t := range g.sortedTargets { 97 switch t := t.(type) { 98 case K8sTarget, DockerComposeTarget: 99 // Returns true if a K8s or DC target directly depends on this image. 100 for _, depID := range t.DependencyIDs() { 101 if depID == id { 102 return true 103 } 104 } 105 } 106 } 107 return false 108 } 109 110 func (g TargetGraph) Images() []ImageTarget { 111 return ExtractImageTargets(g.sortedTargets) 112 } 113 114 // Returns all the images in the graph that are directly deployed to a container. 115 func (g TargetGraph) DeployedImages() []ImageTarget { 116 result := []ImageTarget{} 117 for _, iTarget := range g.Images() { 118 if g.IsDeployedImage(iTarget) { 119 result = append(result, iTarget) 120 } 121 } 122 return result 123 }