github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/buildcontrol/build_control.go (about) 1 package buildcontrol 2 3 import ( 4 "time" 5 6 "github.com/windmilleng/tilt/internal/store" 7 "github.com/windmilleng/tilt/pkg/model" 8 ) 9 10 // NOTE(maia): we eventually want to move the BuildController into its own package 11 // (as we do with all subscribers), but for now, just move the underlying functions 12 // so they can be used from elsewhere. 13 14 // Algorithm to choose a manifest to build next. 15 func NextTargetToBuild(state store.EngineState) *store.ManifestTarget { 16 // Don't build anything if there are pending config file changes. 17 // We want the Tiltfile to re-run first. 18 if len(state.PendingConfigFileChanges) > 0 { 19 return nil 20 } 21 22 targets := RemoveTargetsWaitingOnDependencies(state, state.Targets()) 23 24 // If any of the manifest targets haven't been built yet, build them now. 25 unbuilt := FindTargetsNeedingInitialBuild(targets) 26 27 if len(unbuilt) > 0 { 28 ret := NextUnbuiltTargetToBuild(unbuilt) 29 return ret 30 } 31 32 // Next prioritize builds that crashed and need a rebuilt to have up-to-date code. 33 for _, mt := range targets { 34 if mt.State.NeedsRebuildFromCrash { 35 return mt 36 } 37 } 38 39 // Next prioritize builds that have been manually triggered. 40 if len(state.TriggerQueue) > 0 { 41 mn := state.TriggerQueue[0] 42 mt, ok := state.ManifestTargets[mn] 43 if ok { 44 return mt 45 } 46 } 47 48 return EarliestPendingAutoTriggerTarget(targets) 49 } 50 51 func NextManifestNameToBuild(state store.EngineState) model.ManifestName { 52 mt := NextTargetToBuild(state) 53 if mt == nil { 54 return "" 55 } 56 return mt.Manifest.Name 57 } 58 59 func isWaitingOnDependencies(state store.EngineState, mt *store.ManifestTarget) bool { 60 // dependencies only block the first build, so if this manifest has ever built, ignore dependencies 61 if mt.State.StartedFirstBuild() { 62 return false 63 } 64 65 for _, mn := range mt.Manifest.ResourceDependencies { 66 ms, ok := state.ManifestState(mn) 67 if !ok || ms == nil || ms.RuntimeState == nil || !ms.RuntimeState.HasEverBeenReady() { 68 return true 69 } 70 } 71 72 return false 73 } 74 75 func RemoveTargetsWaitingOnDependencies(state store.EngineState, mts []*store.ManifestTarget) []*store.ManifestTarget { 76 var ret []*store.ManifestTarget 77 for _, mt := range mts { 78 if !isWaitingOnDependencies(state, mt) { 79 ret = append(ret, mt) 80 } 81 } 82 83 return ret 84 } 85 86 // Helper function for ordering targets that have never been built before. 87 func NextUnbuiltTargetToBuild(unbuilt []*store.ManifestTarget) *store.ManifestTarget { 88 // unresourced YAML goes first 89 unresourced := FindUnresourcedYAML(unbuilt) 90 if unresourced != nil { 91 return unresourced 92 } 93 94 // Local resources come before all cluster resources (b/c LR's may 95 // change things on disk that cluster resources then pull in). 96 localTargets := FindLocalTargets(unbuilt) 97 if len(localTargets) > 0 { 98 return localTargets[0] 99 } 100 101 // If this is Kubernetes, unbuilt resources go first. 102 // (If this is Docker Compose, we want to trust the ordering 103 // that docker-compose put things in.) 104 deployOnlyK8sTargets := FindDeployOnlyK8sManifestTargets(unbuilt) 105 if len(deployOnlyK8sTargets) > 0 { 106 return deployOnlyK8sTargets[0] 107 } 108 109 return unbuilt[0] 110 } 111 112 func FindUnresourcedYAML(targets []*store.ManifestTarget) *store.ManifestTarget { 113 for _, target := range targets { 114 if target.Manifest.ManifestName() == model.UnresourcedYAMLManifestName { 115 return target 116 } 117 } 118 return nil 119 } 120 121 func FindDeployOnlyK8sManifestTargets(targets []*store.ManifestTarget) []*store.ManifestTarget { 122 result := []*store.ManifestTarget{} 123 for _, target := range targets { 124 if target.Manifest.IsK8s() && len(target.Manifest.ImageTargets) == 0 { 125 result = append(result, target) 126 } 127 } 128 return result 129 } 130 131 func FindLocalTargets(targets []*store.ManifestTarget) []*store.ManifestTarget { 132 result := []*store.ManifestTarget{} 133 for _, target := range targets { 134 if target.Manifest.IsLocal() { 135 result = append(result, target) 136 } 137 } 138 return result 139 } 140 141 // Go through all the manifests, and check: 142 // 1) all pending file changes, and 143 // 2) all pending manifest changes 144 // The earliest one is the one we want. 145 // 146 // If no targets are pending, return nil 147 func EarliestPendingAutoTriggerTarget(targets []*store.ManifestTarget) *store.ManifestTarget { 148 var choice *store.ManifestTarget 149 earliest := time.Now() 150 151 for _, mt := range targets { 152 ok, newTime := mt.State.HasPendingChangesBefore(earliest) 153 if ok { 154 if !mt.Manifest.TriggerMode.AutoOnChange() { 155 // Don't trigger update of a manual manifest just b/c if has 156 // pending changes; must come through the TriggerQueue, above. 157 continue 158 } 159 choice = mt 160 earliest = newTime 161 } 162 } 163 164 return choice 165 } 166 167 func FindTargetsNeedingInitialBuild(targets []*store.ManifestTarget) []*store.ManifestTarget { 168 result := []*store.ManifestTarget{} 169 for _, target := range targets { 170 if !target.State.StartedFirstBuild() && target.Manifest.TriggerMode.AutoInitial() { 171 result = append(result, target) 172 } 173 } 174 return result 175 }