You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
package walker
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/moby/buildkit/client/llb"
|
|
solverpb "github.com/moby/buildkit/solver/pb"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// BreakContext contains information about the current breakpoint
|
|
type BreakContext struct {
|
|
// State is the current LLB state
|
|
State llb.State
|
|
|
|
// Cursors are current cursor locations
|
|
Cursors []solverpb.Range // walker should pass the hit range to the hander.
|
|
|
|
// Hits are all breakpoints hit.
|
|
Hits map[string][]*solverpb.Range // walker should pass this to the handler.
|
|
|
|
// Definition is the definition of the current LLB.
|
|
Definition *solverpb.Definition
|
|
|
|
// Breakpoints is a set of registered breakpoints
|
|
Breakpoints *Breakpoints
|
|
}
|
|
|
|
// BreakHandlerFunc is a callback function to be call on each break
|
|
type BreakHandlerFunc func(ctx context.Context, bCtx *BreakContext) error
|
|
|
|
// OnVertexHandlerFunc is a callback function called on each vertex.
|
|
type OnVertexHandlerFunc func(ctx context.Context, st llb.State) error
|
|
|
|
// Walker walks an LLB tree from the leaves to the root. Can be controlled using breakpoints.
|
|
type Walker struct {
|
|
breakHandler BreakHandlerFunc
|
|
breakpoints *Breakpoints
|
|
closed bool
|
|
cursors map[solverpb.Range]int
|
|
cursorsMu sync.Mutex
|
|
|
|
mu sync.Mutex
|
|
|
|
inputsMu sync.Mutex
|
|
|
|
onVertexHandler OnVertexHandlerFunc
|
|
}
|
|
|
|
// NewWalker returns a walker configured with the breakpoints and breakpoint handler.
|
|
// onVertexHandlerFunc is called on each vertex including non breakpoints.
|
|
func NewWalker(bps *Breakpoints, breakHandler BreakHandlerFunc, onVertexHandler OnVertexHandlerFunc) *Walker {
|
|
bps.ForEach(func(key string, bp Breakpoint) bool {
|
|
bp.Init()
|
|
return true
|
|
})
|
|
return &Walker{
|
|
breakHandler: breakHandler,
|
|
breakpoints: bps,
|
|
onVertexHandler: onVertexHandler,
|
|
}
|
|
}
|
|
|
|
// Close closes the walker.
|
|
func (w *Walker) Close() {
|
|
w.closed = true
|
|
w.breakpoints = nil
|
|
}
|
|
|
|
// GetCursors returns positions where the walker is currently looking at.
|
|
func (w *Walker) GetCursors() (res []solverpb.Range) {
|
|
w.cursorsMu.Lock()
|
|
defer w.cursorsMu.Unlock()
|
|
for r, i := range w.cursors {
|
|
if i > 0 {
|
|
res = append(res, r)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// BreakAllNode configures whether the walker breaks on each vertex.
|
|
func (w *Walker) BreakAllNode(v bool) {
|
|
w.breakpoints.BreakAllNode(v)
|
|
}
|
|
|
|
// Walk starts walking the specified LLB from the leaves to the root.
|
|
// func (w *Walker) Walk(ctx context.Context, st llb.State) error {
|
|
func (w *Walker) Walk(ctx context.Context, def *solverpb.Definition) error {
|
|
defOp, err := llb.NewDefinitionOp(def)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return w.walk(ctx, def, llb.NewState(defOp), 0)
|
|
}
|
|
|
|
func (w *Walker) inputsOfState(ctx context.Context, st llb.State) []llb.Output {
|
|
w.inputsMu.Lock()
|
|
defer w.inputsMu.Unlock()
|
|
return st.Output().Vertex(ctx, nil).Inputs()
|
|
}
|
|
|
|
func (w *Walker) walk(ctx context.Context, orgDef *solverpb.Definition, st llb.State, depth int) error {
|
|
eg, egCtx := errgroup.WithContext(ctx)
|
|
for _, o := range w.inputsOfState(ctx, st) {
|
|
o := o
|
|
eg.Go(func() error {
|
|
return w.walk(egCtx, orgDef, llb.NewState(o), depth+1)
|
|
})
|
|
}
|
|
if err := eg.Wait(); err != nil {
|
|
return err
|
|
}
|
|
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
def, err := st.Marshal(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dgst, _, _, _, err := st.Output().Vertex(ctx, nil).Marshal(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var ranges []solverpb.Range
|
|
if def.Source != nil {
|
|
if locs, ok := def.Source.Locations[dgst.String()]; ok {
|
|
for _, loc := range locs.Locations {
|
|
for _, r := range loc.Ranges {
|
|
ranges = append(ranges, *r)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
w.addCursor(ranges)
|
|
defer func() {
|
|
w.removeCursor(ranges)
|
|
|
|
}()
|
|
if w.closed {
|
|
return errors.Errorf("walker closed")
|
|
}
|
|
handleErr := w.onVertexHandler(ctx, st)
|
|
isBreak, hits, err := w.breakpoints.isBreakpoint(ctx, st, handleErr)
|
|
if err != nil {
|
|
return err
|
|
|
|
}
|
|
if isBreak {
|
|
err = w.breakHandler(ctx, &BreakContext{
|
|
State: st,
|
|
Cursors: w.GetCursors(),
|
|
Hits: hits,
|
|
Definition: orgDef,
|
|
Breakpoints: w.breakpoints,
|
|
})
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return handleErr
|
|
}
|
|
|
|
func (w *Walker) addCursor(ranges []solverpb.Range) {
|
|
w.cursorsMu.Lock()
|
|
defer w.cursorsMu.Unlock()
|
|
for _, r := range ranges {
|
|
if w.cursors == nil {
|
|
w.cursors = make(map[solverpb.Range]int)
|
|
}
|
|
w.cursors[r]++
|
|
}
|
|
}
|
|
|
|
func (w *Walker) removeCursor(ranges []solverpb.Range) {
|
|
w.cursorsMu.Lock()
|
|
defer w.cursorsMu.Unlock()
|
|
if w.cursors == nil {
|
|
return
|
|
}
|
|
for _, r := range ranges {
|
|
w.cursors[r] = w.cursors[r] - 1
|
|
if w.cursors[r] == 0 {
|
|
delete(w.cursors, r)
|
|
}
|
|
}
|
|
}
|