From 17c2e1dc3568ed1fa0e77d1b4f8b4b9be2640cbc Mon Sep 17 00:00:00 2001 From: "Jonathan A. Sternberg" Date: Fri, 5 Sep 2025 16:01:37 -0500 Subject: [PATCH] dap: collect references for inputs to ensure the file explorer works on an error The debug adapter will now look at the error and collect the input mounts from the exec operation and translate them to a gateway reference to be able to access the filesystem in the file explorer. When we are attempting to find the real operation that failed and succeed, we now re-solve the dependencies (which should already have been solved correctly to begin with) so that we can have the proper reference and can access the filesystem contents. Signed-off-by: Jonathan A. Sternberg --- dap/thread.go | 118 ++++++++++++++++++++++++++++------------------- dap/variables.go | 31 ++++++++++--- 2 files changed, 94 insertions(+), 55 deletions(-) diff --git a/dap/thread.go b/dap/thread.go index f1a0d40af..42a65c8f1 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 8ede60ce6..25840f451 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 +}