github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/pkg/model/target.go (about) 1 package model 2 3 import ( 4 "fmt" 5 6 "github.com/pkg/errors" 7 ) 8 9 // An abstract build graph of targets and their dependencies. 10 // Each target should have a unique ID. 11 12 type TargetType string 13 type TargetName string 14 15 func (n TargetName) String() string { return string(n) } 16 17 const ( 18 // Deployed k8s entities 19 TargetTypeK8s TargetType = "k8s" 20 21 // Image builds 22 TargetTypeImage TargetType = "image" 23 24 // Docker-compose service build and deploy 25 // TODO(nick): Currently, build and deploy are represented as a single target. 26 // In the future, we might have a separate build target and deploy target. 27 TargetTypeDockerCompose TargetType = "docker-compose" 28 29 // Runs a local command when triggered (manually or via changed dep) 30 TargetTypeLocal TargetType = "local" 31 32 // Aggregation of multiple targets into one UI view. 33 // TODO(nick): Currently used as the type for both Manifest and YAMLManifest, though 34 // we expect YAMLManifest to go away. 35 TargetTypeManifest TargetType = "manifest" 36 37 // Changes that affect all targets, rebuilding the target graph. 38 TargetTypeConfigs TargetType = "configs" 39 ) 40 41 type TargetID struct { 42 Type TargetType 43 Name TargetName 44 } 45 46 func (id TargetID) Empty() bool { 47 return id.Type == "" || id.Name == "" 48 } 49 50 func (id TargetID) String() string { 51 if id.Empty() { 52 return "" 53 } 54 return fmt.Sprintf("%s:%s", id.Type, id.Name) 55 } 56 57 func TargetIDSet(tids []TargetID) map[TargetID]bool { 58 res := make(map[TargetID]bool) 59 for _, id := range tids { 60 res[id] = true 61 } 62 return res 63 } 64 65 type TargetSpec interface { 66 ID() TargetID 67 68 // Check to make sure the spec is well-formed. 69 // All TargetSpecs should throw an error in the case where the ID is empty. 70 Validate() error 71 72 DependencyIDs() []TargetID 73 } 74 75 type TargetStatus interface { 76 TargetID() TargetID 77 LastBuild() BuildRecord 78 } 79 80 type Target interface { 81 Spec() TargetSpec 82 Status() TargetStatus 83 } 84 85 // De-duplicate target ids, maintaining the same order. 86 func DedupeTargetIDs(ids []TargetID) []TargetID { 87 result := make([]TargetID, 0, len(ids)) 88 dupes := make(map[TargetID]bool, len(ids)) 89 for _, id := range ids { 90 if !dupes[id] { 91 dupes[id] = true 92 result = append(result, id) 93 } 94 } 95 return result 96 } 97 98 // Map all the targets by their target ID. 99 func MakeTargetMap(targets []TargetSpec) map[TargetID]TargetSpec { 100 result := make(map[TargetID]TargetSpec, len(targets)) 101 for _, target := range targets { 102 result[target.ID()] = target 103 } 104 return result 105 } 106 107 // Create a topologically sorted list of targets. Returns an error 108 // if the targets can't be topologically sorted. (e.g., there's a cycle). 109 func TopologicalSort(targets []TargetSpec) ([]TargetSpec, error) { 110 targetMap := MakeTargetMap(targets) 111 result := make([]TargetSpec, 0, len(targets)) 112 inResult := make(map[TargetID]bool, len(targets)) 113 searching := make(map[TargetID]bool, len(targets)) 114 115 var ensureInResult func(id TargetID) error 116 ensureInResult = func(id TargetID) error { 117 if inResult[id] { 118 return nil 119 } 120 if searching[id] { 121 return fmt.Errorf("Found a cycle at target: %s", id.Name) 122 } 123 searching[id] = true 124 125 current, ok := targetMap[id] 126 if !ok { 127 return fmt.Errorf("Missing target dependency: %s", id.Name) 128 } 129 130 for _, depID := range current.DependencyIDs() { 131 err := ensureInResult(depID) 132 if err != nil { 133 return err 134 } 135 } 136 result = append(result, current) 137 inResult[id] = true 138 return nil 139 } 140 141 for _, target := range targets { 142 err := ensureInResult(target.ID()) 143 if err != nil { 144 return nil, errors.Wrap(err, "Internal error") 145 } 146 } 147 return result, nil 148 }