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  }