github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/build/pipeline_state.go (about) 1 package build 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/tilt-dev/tilt/pkg/logger" 10 ) 11 12 type PipelineState struct { 13 totalPipelineStepCount int 14 curBuildStep int 15 curPipelineStart time.Time 16 pipelineSteps []PipelineStep 17 c Clock 18 } 19 20 type PipelineStep struct { 21 Name string // for logging 22 StartTime time.Time 23 Duration time.Duration // not populated until end of the step 24 } 25 26 type Clock interface { 27 Now() time.Time 28 } 29 30 type realClock struct{} 31 32 func (realClock) Now() time.Time { return time.Now() } 33 34 func ProvideClock() Clock { 35 return realClock{} 36 } 37 38 const buildStepOutputPrefix = " " 39 40 func NewPipelineState(ctx context.Context, totalStepCount int, c Clock) *PipelineState { 41 return &PipelineState{ 42 totalPipelineStepCount: totalStepCount, 43 pipelineSteps: []PipelineStep{}, 44 curPipelineStart: c.Now(), 45 c: c, 46 } 47 } 48 49 // NOTE(maia): this func should always be deferred in a closure, so that the `err` arg 50 // is bound at the time of calling rather than at the time of deferring. I.e., do: 51 // 52 // defer func() { ps.End(ctx, err) }() 53 // 54 // and NOT: 55 // 56 // defer ps.End(ctx, err) 57 func (ps *PipelineState) End(ctx context.Context, err error) { 58 ps.curBuildStep = 0 59 60 if err != nil { 61 return 62 } 63 64 l := logger.Get(ctx) 65 66 elapsed := ps.c.Now().Sub(ps.curPipelineStart) 67 68 for i, step := range ps.pipelineSteps { 69 l.Infof("%sStep %d - %.2fs (%s)", buildStepOutputPrefix, i+1, step.Duration.Seconds(), step.Name) 70 } 71 72 t := logger.Blue(l).Sprintf("%.2fs", elapsed.Seconds()) 73 l.Infof("%sDONE IN: %s \n", buildStepOutputPrefix, t) 74 } 75 76 func (ps *PipelineState) curPipelineIndex() int { 77 // human-readable i.e. 1-indexed 78 return len(ps.pipelineSteps) 79 } 80 81 func (ps *PipelineState) curPipelineStep() PipelineStep { 82 if len(ps.pipelineSteps) == 0 { 83 return PipelineStep{} 84 } 85 return ps.pipelineSteps[len(ps.pipelineSteps)-1] 86 } 87 88 func (ps *PipelineState) StartPipelineStep(ctx context.Context, format string, a ...interface{}) { 89 l := logger.Get(ctx) 90 stepName := fmt.Sprintf(format, a...) 91 ps.pipelineSteps = append(ps.pipelineSteps, PipelineStep{ 92 Name: stepName, 93 StartTime: ps.c.Now(), 94 }) 95 line := logger.Blue(l).Sprintf("STEP %d/%d", ps.curPipelineIndex(), ps.totalPipelineStepCount) 96 l.Infof("%s — %s", line, stepName) 97 ps.curBuildStep = 1 98 } 99 100 func (ps *PipelineState) EndPipelineStep(ctx context.Context) { 101 elapsed := ps.c.Now().Sub(ps.curPipelineStep().StartTime) 102 logger.Get(ctx).Infof("") 103 ps.pipelineSteps[len(ps.pipelineSteps)-1].Duration = elapsed 104 } 105 106 func (ps *PipelineState) StartBuildStep(ctx context.Context, format string, a ...interface{}) { 107 l := logger.Get(ctx) 108 l.Infof("%s%s", buildStepOutputPrefix, fmt.Sprintf(format, a...)) 109 ps.curBuildStep++ 110 } 111 112 func (ps *PipelineState) Printf(ctx context.Context, format string, a ...interface{}) { 113 l := logger.Get(ctx) 114 if ps.curBuildStep == 0 { 115 l.Infof(format, a...) 116 } else { 117 message := fmt.Sprintf(format, a...) 118 message = strings.ReplaceAll(message, "\n", "\n"+buildStepOutputPrefix) 119 l.Infof("%s%s", buildStepOutputPrefix, message) 120 } 121 } 122 123 func (ps *PipelineState) AttachLogger(ctx context.Context) context.Context { 124 l := logger.Get(ctx) 125 if ps.curBuildStep == 0 { 126 return ctx 127 } else { 128 return logger.WithLogger(ctx, 129 logger.NewPrefixedLogger(buildStepOutputPrefix, l)) 130 } 131 }