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.
buildx/util/walker/breakpoints.go

222 lines
5.1 KiB
Go

package walker
import (
"context"
"fmt"
"sort"
"sync"
"sync/atomic"
"github.com/moby/buildkit/client/llb"
solverpb "github.com/moby/buildkit/solver/pb"
"github.com/pkg/errors"
)
// Breakpoint represents a breakpoint.
type Breakpoint interface {
IsTarget(ctx context.Context, st llb.State, contErr error) (yes bool, hitLocations []*solverpb.Range, err error)
IsMarked(line int64) bool
String() string
Init()
}
// Breakpoints manages a set of breakpoints.
type Breakpoints struct {
isBreakAllNode atomic.Bool
breakpoints map[string]Breakpoint
breakpointsMu sync.Mutex
breakpointIdx int64
}
// NewBreakpoints returns an empty set of breakpoints.
func NewBreakpoints() *Breakpoints {
return &Breakpoints{}
}
// Add adds a breakpoint with the specified key.
func (b *Breakpoints) Add(key string, bp Breakpoint) (string, error) {
b.breakpointsMu.Lock()
defer b.breakpointsMu.Unlock()
if b.breakpoints == nil {
b.breakpoints = make(map[string]Breakpoint)
}
if key == "" {
key = fmt.Sprintf("%d", atomic.AddInt64(&b.breakpointIdx, 1))
}
if _, ok := b.breakpoints[key]; ok {
return "", errors.Errorf("breakpoint %q already exists: %v", key, b)
}
b.breakpoints[key] = bp
return key, nil
}
// Clear removes the specified breakpoint.
func (b *Breakpoints) Clear(key string) {
b.breakpointsMu.Lock()
defer b.breakpointsMu.Unlock()
delete(b.breakpoints, key)
}
// ClearAll removes all breakpoints.
func (b *Breakpoints) ClearAll() {
b.breakpointsMu.Lock()
defer b.breakpointsMu.Unlock()
b.breakpoints = nil
atomic.StoreInt64(&b.breakpointIdx, 0)
}
// ForEach calls the callback on each breakpoint.
func (b *Breakpoints) ForEach(f func(key string, bp Breakpoint) bool) {
var keys []string
for k := range b.breakpoints {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if !f(k, b.breakpoints[k]) {
return
}
}
}
// BreakAllNode enables to configure to break on each node.
func (b *Breakpoints) BreakAllNode(v bool) {
b.isBreakAllNode.Store(v)
}
func (b *Breakpoints) isBreakpoint(ctx context.Context, st llb.State, handleErr error) (bool, map[string][]*solverpb.Range, error) {
if b.isBreakAllNode.Load() {
return true, nil, nil
}
b.breakpointsMu.Lock()
defer b.breakpointsMu.Unlock()
hits := make(map[string][]*solverpb.Range)
for k, bp := range b.breakpoints {
isBreak, bhits, err := bp.IsTarget(ctx, st, handleErr)
if err != nil {
return false, nil, err
}
if isBreak {
hits[k] = append(hits[k], bhits...)
}
}
if len(hits) > 0 {
return true, hits, nil
}
return false, nil, nil
}
// NewLineBreakpoint returns a breakpoint to break on the specified line.
func NewLineBreakpoint(line int64) Breakpoint {
return &lineBreakpoint{line}
}
type lineBreakpoint struct {
line int64
}
func (b *lineBreakpoint) Init() {}
func (b *lineBreakpoint) IsTarget(ctx context.Context, st llb.State, _ error) (yes bool, hitLocations []*solverpb.Range, err error) {
_, _, _, sources, err := st.Output().Vertex(ctx, nil).Marshal(ctx, nil)
if err != nil {
return false, nil, err
}
hits := make(map[solverpb.Range]struct{})
line := b.line
for _, loc := range sources {
for _, r := range loc.Ranges {
if int64(r.Start.Line) <= line && line <= int64(r.End.Line) {
hits[*r] = struct{}{}
}
}
}
if len(hits) > 0 {
var ret []*solverpb.Range
for r := range hits {
ret = append(ret, &r)
}
return true, ret, nil
}
return false, nil, nil
}
func (b *lineBreakpoint) IsMarked(line int64) bool {
return line == b.line
}
func (b *lineBreakpoint) String() string {
return fmt.Sprintf("line: %d", b.line)
}
// NewStopOnEntryBreakpoint returns a breakpoint that breaks at the first node.
func NewStopOnEntryBreakpoint() Breakpoint {
b := stopOnEntryBreakpoint(true)
return &b
}
type stopOnEntryBreakpoint bool
func (b *stopOnEntryBreakpoint) Init() {
*b = true
}
func (b *stopOnEntryBreakpoint) IsTarget(ctx context.Context, st llb.State, _ error) (yes bool, hitLocations []*solverpb.Range, err error) {
if *b {
*b = false // stop only once
return true, nil, nil
}
return false, nil, nil
}
func (b *stopOnEntryBreakpoint) IsMarked(line int64) bool {
return false
}
func (b *stopOnEntryBreakpoint) String() string {
return fmt.Sprintf("stop on entry")
}
// NewOnErrorBreakpoint returns a breakpoint that breaks when an error observed.
func NewOnErrorBreakpoint() Breakpoint {
return &onErrorBreakpoint{}
}
type onErrorBreakpoint struct{}
func (b *onErrorBreakpoint) Init() {}
func (b *onErrorBreakpoint) IsTarget(ctx context.Context, st llb.State, handleErr error) (yes bool, hitLocations []*solverpb.Range, err error) {
if handleErr == nil {
return false, nil, nil
}
_, _, _, sources, err := st.Output().Vertex(ctx, nil).Marshal(ctx, nil)
if err != nil {
return false, nil, err
}
hits := make(map[solverpb.Range]struct{})
for _, loc := range sources {
for _, r := range loc.Ranges {
hits[*r] = struct{}{}
}
}
var ret []*solverpb.Range
if len(hits) > 0 {
for r := range hits {
ret = append(ret, &r)
}
}
return true, ret, nil
}
func (b *onErrorBreakpoint) IsMarked(line int64) bool {
return false
}
func (b *onErrorBreakpoint) String() string {
return fmt.Sprintf("stop on error")
}