diff --git a/dap/thread.go b/dap/thread.go index f1a0d40af085..42a65c8f14b9 100644 --- a/dap/thread.go +++ b/dap/thread.go @@ -49,7 +49,6 @@ type thread struct { // Attributes set when a thread is paused. cancel context.CancelCauseFunc // invoked when the thread is resumed rCtx *build.ResultHandle - curPos digest.Digest stackTrace []int32 } @@ -74,15 +73,16 @@ func (t *thread) Evaluate(ctx Context, c gateway.Client, headRef gateway.Referen } var ( - ref gateway.Reference - next = t.entrypoint - err error + ref gateway.Reference + mounts map[string]gateway.Reference + next = t.entrypoint + err error ) for next != nil { event := t.needsDebug(next, action, err) if event.Reason != "" { select { - case action = <-t.pause(ctx, ref, err, next, event): + case action = <-t.pause(ctx, ref, err, mounts, next, event): // do nothing here case <-ctx.Done(): return context.Cause(ctx) @@ -96,7 +96,7 @@ func (t *thread) Evaluate(ctx Context, c gateway.Client, headRef gateway.Referen if action == stepContinue { t.setBreakpoints(ctx) } - ref, next, err = t.seekNext(ctx, next, action) + ref, next, mounts, err = t.seekNext(ctx, next, action) } return nil } @@ -285,26 +285,17 @@ func (t *thread) needsDebug(cur *step, step stepType, err error) (e dap.StoppedE return } -func (t *thread) pause(c Context, ref gateway.Reference, err error, pos *step, event dap.StoppedEventBody) <-chan stepType { +func (t *thread) pause(c Context, ref gateway.Reference, err error, mounts map[string]gateway.Reference, pos *step, event dap.StoppedEventBody) <-chan stepType { t.mu.Lock() defer t.mu.Unlock() if t.paused != nil { return t.paused } - t.paused = make(chan stepType, 1) - if err != nil { - var solveErr *errdefs.SolveError - if errors.As(err, &solveErr) { - if dt, err := solveErr.Op.MarshalVT(); err == nil { - t.curPos = digest.FromBytes(dt) - } - } - } ctx, cancel := context.WithCancelCause(c) - t.collectStackTrace(ctx, pos, ref) + t.collectStackTrace(ctx, pos, mounts) t.cancel = cancel if ref != nil || err != nil { @@ -435,7 +426,7 @@ func (t *thread) setBreakpoints(ctx Context) { t.bps = t.breakpointMap.Intersect(ctx, t.def.Source, t.sourcePath) } -func (t *thread) seekNext(ctx Context, from *step, action stepType) (gateway.Reference, *step, error) { +func (t *thread) seekNext(ctx Context, from *step, action stepType) (gateway.Reference, *step, map[string]gateway.Reference, error) { // If we're at the end, return no digest to signal that // we should conclude debugging. var target *step @@ -452,12 +443,12 @@ func (t *thread) seekNext(ctx Context, from *step, action stepType) (gateway.Ref return t.seek(ctx, target) } -func (t *thread) seek(ctx Context, target *step) (ref gateway.Reference, result *step, err error) { +func (t *thread) seek(ctx Context, target *step) (ref gateway.Reference, result *step, mounts map[string]gateway.Reference, err error) { if target != nil { if target.dgst != "" { - ref, err = t.solve(ctx, target.dgst) + ref, err = t.solve(ctx, target.dgst, 0) if err != nil { - return ref, nil, err + return ref, nil, nil, err } } @@ -468,31 +459,15 @@ func (t *thread) seek(ctx Context, target *step) (ref gateway.Reference, result if ref != nil { if err = ref.Evaluate(ctx); err != nil { - // If this is not a solve error, do not return the - // reference and target step. - var solveErr *errdefs.SolveError - if errors.As(err, &solveErr) { - if dt, err := solveErr.Op.MarshalVT(); err == nil { - // Find the error digest. - errDgst := digest.FromBytes(dt) - - // Iterate from the first step to find the one - // we failed on. - result = t.entrypoint - for result != nil { - next := result.in - if next != nil && next.dgst == errDgst { - break - } - result = next - } - } - } else { - return nil, nil, err + result, mounts = t.rewind(ctx, err) + if result == nil { + return nil, nil, nil, err } + } else { + mounts = map[string]gateway.Reference{"/": ref} } } - return ref, result, err + return ref, result, mounts, err } func (t *thread) continueDigest(from *step) *step { @@ -523,13 +498,15 @@ func (t *thread) continueDigest(from *step) *step { return next(from) } -func (t *thread) solve(ctx context.Context, target digest.Digest) (gateway.Reference, error) { +func (t *thread) solve(ctx context.Context, target digest.Digest, index int64) (gateway.Reference, error) { if target == t.head { return t.ref, nil } head := &pb.Op{ - Inputs: []*pb.Input{{Digest: string(target)}}, + Inputs: []*pb.Input{ + {Digest: string(target), Index: index}, + }, } dt, err := head.MarshalVT() if err != nil { @@ -567,12 +544,12 @@ func (t *thread) releaseState() { t.variables.Reset() } -func (t *thread) collectStackTrace(ctx context.Context, pos *step, ref gateway.Reference) { +func (t *thread) collectStackTrace(ctx context.Context, pos *step, mounts map[string]gateway.Reference) { for pos != nil { frame := pos.frame - frame.ExportVars(ctx, ref, t.variables) + frame.ExportVars(ctx, mounts, t.variables) t.stackTrace = append(t.stackTrace, int32(frame.Id)) - pos, ref = pos.out, nil + pos, mounts = pos.out, nil } } @@ -587,3 +564,48 @@ func (t *thread) hasFrame(id int) bool { _, ok := t.frames[int32(id)] return ok } + +func (t *thread) rewind(ctx context.Context, err error) (result *step, mounts map[string]gateway.Reference) { + var solveErr *errdefs.SolveError + if !errors.As(err, &solveErr) { + // If this is not a solve error, do not return the + // reference and target step. + return nil, nil + } + + dt, err := solveErr.Op.MarshalVT() + if err != nil { + return nil, nil + } + + // Find the error digest. + errDgst := digest.FromBytes(dt) + + // Iterate from the first step to find the one + // we failed on. + result = t.entrypoint + for result != nil { + next := result.in + if next != nil && next.dgst == errDgst { + break + } + result = next + } + + if exec, ok := solveErr.Op.GetOp().(*pb.Op_Exec); ok { + mounts = make(map[string]gateway.Reference, len(exec.Exec.Mounts)) + for _, m := range exec.Exec.Mounts { + if m.Input < 0 { + continue + } + + // TODO: Use the correct input index for this. + input := solveErr.Op.Inputs[m.Input] + target, index := digest.Digest(input.Digest), input.Index + if ref, err := t.solve(ctx, target, index); err == nil { + mounts[m.Dest] = ref + } + } + } + return result, mounts +} diff --git a/dap/variables.go b/dap/variables.go index 8ede60ce61bc..25840f451c97 100644 --- a/dap/variables.go +++ b/dap/variables.go @@ -67,10 +67,10 @@ func (f *frame) fillLocation(def *llb.Definition, loc *pb.Locations, ws string, } } -func (f *frame) ExportVars(ctx context.Context, ref gateway.Reference, refs *variableReferences) { +func (f *frame) ExportVars(ctx context.Context, mounts map[string]gateway.Reference, refs *variableReferences) { f.fillVarsFromOp(f.op, refs) - if ref != nil { - f.fillVarsFromResult(ctx, ref, refs) + if len(mounts) > 0 { + f.fillVarsFromResult(ctx, mounts, refs) } } @@ -186,18 +186,23 @@ func execOpVars(exec *pb.ExecOp, refs *variableReferences) dap.Variable { } } -func (f *frame) fillVarsFromResult(ctx context.Context, ref gateway.Reference, refs *variableReferences) { +func (f *frame) fillVarsFromResult(ctx context.Context, mounts map[string]gateway.Reference, refs *variableReferences) { f.scopes = append(f.scopes, dap.Scope{ Name: "File Explorer", PresentationHint: "locals", VariablesReference: refs.New(func() []dap.Variable { - return fsVars(ctx, ref, "/", refs) + return fsVars(ctx, mounts, "/", refs) }), Expensive: true, }) } -func fsVars(ctx context.Context, ref gateway.Reference, path string, vars *variableReferences) []dap.Variable { +func fsVars(ctx context.Context, mounts map[string]gateway.Reference, path string, vars *variableReferences) []dap.Variable { + path, ref := lookupPath(path, mounts) + if ref == nil { + return nil + } + files, err := ref.ReadDir(ctx, gateway.ReadDirRequest{ Path: path, }) @@ -228,7 +233,7 @@ func fsVars(ctx context.Context, ref gateway.Reference, path string, vars *varia return statVars(file) }), } - return append([]dap.Variable{dvar}, fsVars(ctx, ref, fullpath, vars)...) + return append([]dap.Variable{dvar}, fsVars(ctx, mounts, fullpath, vars)...) }) fv.Value = "" } else { @@ -436,3 +441,15 @@ func betterLocation(r *pb.Range, f *frame, next *step) bool { // Doesn't seem to be a better location. return false } + +func lookupPath(path string, mounts map[string]gateway.Reference) (remainder string, ref gateway.Reference) { + var prefix string + for p, r := range mounts { + if len(p) > len(prefix) && strings.HasPrefix(path, p) { + prefix = p + remainder, _ = filepath.Rel(prefix, p) + ref = r + } + } + return "/" + remainder, ref +}