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.
222 lines
5.1 KiB
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")
|
|
}
|