Merge pull request #3307 from jsternberg/dap-variables
dap: implement variable references
This commit is contained in:
@@ -216,6 +216,7 @@ func (d *Adapter[C]) newThread(ctx Context, name string) (t *thread) {
|
||||
sourceMap: &d.sourceMap,
|
||||
breakpointMap: d.breakpointMap,
|
||||
idPool: d.idPool,
|
||||
variables: newVariableReferences(),
|
||||
}
|
||||
d.threads[t.id] = t
|
||||
d.nextThreadID++
|
||||
@@ -240,6 +241,11 @@ func (d *Adapter[C]) getThread(id int) (t *thread) {
|
||||
|
||||
func (d *Adapter[C]) deleteThread(ctx Context, t *thread) {
|
||||
d.threadsMu.Lock()
|
||||
if t := d.threads[t.id]; t != nil {
|
||||
if t.variables != nil {
|
||||
t.variables.Reset()
|
||||
}
|
||||
}
|
||||
delete(d.threads, t.id)
|
||||
d.threadsMu.Unlock()
|
||||
|
||||
@@ -252,6 +258,18 @@ func (d *Adapter[C]) deleteThread(ctx Context, t *thread) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Adapter[T]) getThreadByFrameID(id int) (t *thread) {
|
||||
d.threadsMu.RLock()
|
||||
defer d.threadsMu.RUnlock()
|
||||
|
||||
for _, t := range d.threads {
|
||||
if t.hasFrame(id) {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type evaluateRequest struct {
|
||||
name string
|
||||
c gateway.Client
|
||||
@@ -330,6 +348,35 @@ func (d *Adapter[C]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Adapter[C]) Scopes(c Context, req *dap.ScopesRequest, resp *dap.ScopesResponse) error {
|
||||
t := d.getThreadByFrameID(req.Arguments.FrameId)
|
||||
if t == nil {
|
||||
return errors.Errorf("no such frame id: %d", req.Arguments.FrameId)
|
||||
}
|
||||
|
||||
resp.Body.Scopes = t.Scopes(req.Arguments.FrameId)
|
||||
for i, s := range resp.Body.Scopes {
|
||||
resp.Body.Scopes[i].VariablesReference = (t.id << 24) | s.VariablesReference
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Adapter[C]) Variables(c Context, req *dap.VariablesRequest, resp *dap.VariablesResponse) error {
|
||||
tid := req.Arguments.VariablesReference >> 24
|
||||
|
||||
t := d.getThread(tid)
|
||||
if t == nil {
|
||||
return errors.Errorf("no such thread: %d", tid)
|
||||
}
|
||||
|
||||
varRef := req.Arguments.VariablesReference & ((1 << 24) - 1)
|
||||
resp.Body.Variables = t.Variables(varRef)
|
||||
for i, ref := range resp.Body.Variables {
|
||||
resp.Body.Variables[i].VariablesReference = (tid << 24) | ref.VariablesReference
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Adapter[C]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceResponse) error {
|
||||
fname := req.Arguments.Source.Path
|
||||
|
||||
@@ -378,6 +425,8 @@ func (d *Adapter[C]) dapHandler() Handler {
|
||||
Disconnect: d.Disconnect,
|
||||
Threads: d.Threads,
|
||||
StackTrace: d.StackTrace,
|
||||
Scopes: d.Scopes,
|
||||
Variables: d.Variables,
|
||||
Source: d.Source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ type Handler struct {
|
||||
Restart HandlerFunc[*dap.RestartRequest, *dap.RestartResponse]
|
||||
Threads HandlerFunc[*dap.ThreadsRequest, *dap.ThreadsResponse]
|
||||
StackTrace HandlerFunc[*dap.StackTraceRequest, *dap.StackTraceResponse]
|
||||
Scopes HandlerFunc[*dap.ScopesRequest, *dap.ScopesResponse]
|
||||
Variables HandlerFunc[*dap.VariablesRequest, *dap.VariablesResponse]
|
||||
Evaluate HandlerFunc[*dap.EvaluateRequest, *dap.EvaluateResponse]
|
||||
Source HandlerFunc[*dap.SourceRequest, *dap.SourceResponse]
|
||||
}
|
||||
|
||||
@@ -125,6 +125,10 @@ func (s *Server) handleMessage(c Context, m dap.Message) (dap.ResponseMessage, e
|
||||
return s.h.Threads.Do(c, req)
|
||||
case *dap.StackTraceRequest:
|
||||
return s.h.StackTrace.Do(c, req)
|
||||
case *dap.ScopesRequest:
|
||||
return s.h.Scopes.Do(c, req)
|
||||
case *dap.VariablesRequest:
|
||||
return s.h.Variables.Do(c, req)
|
||||
case *dap.EvaluateRequest:
|
||||
return s.h.Evaluate.Do(c, req)
|
||||
case *dap.SourceRequest:
|
||||
|
||||
+50
-53
@@ -26,6 +26,7 @@ type thread struct {
|
||||
idPool *idPool
|
||||
sourceMap *sourceMap
|
||||
breakpointMap *breakpointMap
|
||||
variables *variableReferences
|
||||
|
||||
// Inputs to the evaluate call.
|
||||
c gateway.Client
|
||||
@@ -48,11 +49,10 @@ type thread struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// Attributes set when a thread is paused.
|
||||
rCtx *build.ResultHandle
|
||||
curPos digest.Digest
|
||||
|
||||
// Lazy attributes that are set when a thread is paused.
|
||||
stackTrace []dap.StackFrame
|
||||
rCtx *build.ResultHandle
|
||||
curPos digest.Digest
|
||||
stackTrace []int32
|
||||
frames map[int32]*frame
|
||||
}
|
||||
|
||||
type region struct {
|
||||
@@ -154,6 +154,7 @@ func (t *thread) pause(c Context, err error, event dap.StoppedEventBody) <-chan
|
||||
}
|
||||
}
|
||||
}
|
||||
t.collectStackTrace()
|
||||
|
||||
event.ThreadId = t.id
|
||||
c.C() <- &dap.StoppedEvent{
|
||||
@@ -178,18 +179,7 @@ func (t *thread) resume(step stepType) {
|
||||
if t.paused == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.rCtx != nil {
|
||||
t.rCtx.Done()
|
||||
t.rCtx = nil
|
||||
}
|
||||
|
||||
if t.stackTrace != nil {
|
||||
for _, frame := range t.stackTrace {
|
||||
t.idPool.Put(int64(frame.Id))
|
||||
}
|
||||
t.stackTrace = nil
|
||||
}
|
||||
t.releaseState()
|
||||
|
||||
t.paused <- step
|
||||
close(t.paused)
|
||||
@@ -207,10 +197,23 @@ func (t *thread) StackTrace() []dap.StackFrame {
|
||||
return []dap.StackFrame{}
|
||||
}
|
||||
|
||||
if t.stackTrace == nil {
|
||||
t.stackTrace = t.makeStackTrace()
|
||||
frames := make([]dap.StackFrame, len(t.stackTrace))
|
||||
for i, id := range t.stackTrace {
|
||||
frames[i] = t.frames[id].StackFrame
|
||||
}
|
||||
return t.stackTrace
|
||||
return frames
|
||||
}
|
||||
|
||||
func (t *thread) Scopes(frameID int) []dap.Scope {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
frame := t.frames[int32(frameID)]
|
||||
return frame.Scopes()
|
||||
}
|
||||
|
||||
func (t *thread) Variables(id int) []dap.Variable {
|
||||
return t.variables.Get(id)
|
||||
}
|
||||
|
||||
func (t *thread) getLLBState(ctx Context) error {
|
||||
@@ -502,15 +505,16 @@ func (t *thread) solve(ctx context.Context, target digest.Digest) (gateway.Refer
|
||||
return res.SingleRef()
|
||||
}
|
||||
|
||||
func (t *thread) newStackFrame() dap.StackFrame {
|
||||
return dap.StackFrame{
|
||||
Id: int(t.idPool.Get()),
|
||||
func (t *thread) releaseState() {
|
||||
if t.rCtx != nil {
|
||||
t.rCtx.Done()
|
||||
t.rCtx = nil
|
||||
}
|
||||
t.stackTrace = nil
|
||||
t.frames = nil
|
||||
}
|
||||
|
||||
func (t *thread) makeStackTrace() []dap.StackFrame {
|
||||
var frames []dap.StackFrame
|
||||
|
||||
func (t *thread) collectStackTrace() {
|
||||
region := t.regionsByDigest[t.curPos]
|
||||
r := t.regions[region]
|
||||
|
||||
@@ -519,45 +523,38 @@ func (t *thread) makeStackTrace() []dap.StackFrame {
|
||||
digests = digests[:index+1]
|
||||
}
|
||||
|
||||
t.frames = make(map[int32]*frame)
|
||||
for i := len(digests) - 1; i >= 0; i-- {
|
||||
dgst := digests[i]
|
||||
|
||||
frame := t.newStackFrame()
|
||||
frame := &frame{}
|
||||
frame.Id = int(t.idPool.Get())
|
||||
|
||||
if meta, ok := t.def.Metadata[dgst]; ok {
|
||||
fillStackFrameMetadata(&frame, meta)
|
||||
frame.setNameFromMeta(meta)
|
||||
}
|
||||
if loc, ok := t.def.Source.Locations[string(dgst)]; ok {
|
||||
t.fillStackFrameLocation(&frame, loc)
|
||||
frame.fillLocation(t.def, loc, t.sourcePath)
|
||||
}
|
||||
frames = append(frames, frame)
|
||||
|
||||
if op := t.ops[dgst]; op != nil {
|
||||
frame.fillVarsFromOp(op, t.variables)
|
||||
}
|
||||
t.stackTrace = append(t.stackTrace, int32(frame.Id))
|
||||
t.frames[int32(frame.Id)] = frame
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
func fillStackFrameMetadata(frame *dap.StackFrame, meta llb.OpMetadata) {
|
||||
if name, ok := meta.Description["llb.customname"]; ok {
|
||||
frame.Name = name
|
||||
} else if cmd, ok := meta.Description["com.docker.dockerfile.v1.command"]; ok {
|
||||
frame.Name = cmd
|
||||
}
|
||||
// TODO: should we infer the name from somewhere else?
|
||||
}
|
||||
func (t *thread) hasFrame(id int) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
func (t *thread) fillStackFrameLocation(frame *dap.StackFrame, loc *pb.Locations) {
|
||||
for _, l := range loc.Locations {
|
||||
for _, r := range l.Ranges {
|
||||
frame.Line = int(r.Start.Line)
|
||||
frame.Column = int(r.Start.Character)
|
||||
frame.EndLine = int(r.End.Line)
|
||||
frame.EndColumn = int(r.End.Character)
|
||||
|
||||
info := t.def.Source.Infos[l.SourceIndex]
|
||||
frame.Source = &dap.Source{
|
||||
Path: filepath.Join(t.sourcePath, info.Filename),
|
||||
}
|
||||
return
|
||||
}
|
||||
if t.paused == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := t.frames[int32(id)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func pop[S ~[]E, E any](s *S) E {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
dap.StackFrame
|
||||
scopes []dap.Scope
|
||||
}
|
||||
|
||||
func (f *frame) setNameFromMeta(meta llb.OpMetadata) {
|
||||
if name, ok := meta.Description["llb.customname"]; ok {
|
||||
f.Name = name
|
||||
} else if cmd, ok := meta.Description["com.docker.dockerfile.v1.command"]; ok {
|
||||
f.Name = cmd
|
||||
}
|
||||
// TODO: should we infer the name from somewhere else?
|
||||
}
|
||||
|
||||
func (f *frame) fillLocation(def *llb.Definition, loc *pb.Locations, ws string) {
|
||||
for _, l := range loc.Locations {
|
||||
for _, r := range l.Ranges {
|
||||
f.Line = int(r.Start.Line)
|
||||
f.Column = int(r.Start.Character)
|
||||
f.EndLine = int(r.End.Line)
|
||||
f.EndColumn = int(r.End.Character)
|
||||
|
||||
info := def.Source.Infos[l.SourceIndex]
|
||||
f.Source = &dap.Source{
|
||||
Path: filepath.Join(ws, info.Filename),
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frame) fillVarsFromOp(op *pb.Op, refs *variableReferences) {
|
||||
f.scopes = []dap.Scope{
|
||||
{
|
||||
Name: "Arguments",
|
||||
PresentationHint: "arguments",
|
||||
VariablesReference: refs.New(func() []dap.Variable {
|
||||
var vars []dap.Variable
|
||||
if op.Platform != nil {
|
||||
vars = append(vars, platformVars(op.Platform, refs))
|
||||
}
|
||||
|
||||
switch op := op.Op.(type) {
|
||||
case *pb.Op_Exec:
|
||||
vars = append(vars, execOpVars(op.Exec, refs))
|
||||
}
|
||||
return vars
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func platformVars(platform *pb.Platform, refs *variableReferences) dap.Variable {
|
||||
return dap.Variable{
|
||||
Name: "platform",
|
||||
Value: fmt.Sprintf("%s/%s", platform.OS, platform.Architecture),
|
||||
VariablesReference: refs.New(func() []dap.Variable {
|
||||
vars := []dap.Variable{
|
||||
{
|
||||
Name: "architecture",
|
||||
Value: platform.Architecture,
|
||||
},
|
||||
{
|
||||
Name: "os",
|
||||
Value: platform.OS,
|
||||
},
|
||||
}
|
||||
|
||||
if platform.Variant != "" {
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: "variant",
|
||||
Value: platform.Variant,
|
||||
})
|
||||
}
|
||||
|
||||
if platform.OSVersion != "" {
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: "osversion",
|
||||
Value: platform.OSVersion,
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func execOpVars(exec *pb.ExecOp, refs *variableReferences) dap.Variable {
|
||||
return dap.Variable{
|
||||
Name: "exec",
|
||||
Value: strings.Join(exec.Meta.Args, " "),
|
||||
VariablesReference: refs.New(func() []dap.Variable {
|
||||
vars := []dap.Variable{
|
||||
{
|
||||
Name: "args",
|
||||
Value: brief(strings.Join(exec.Meta.Args, " ")),
|
||||
VariablesReference: refs.New(func() []dap.Variable {
|
||||
vars := make([]dap.Variable, 0, len(exec.Meta.Args))
|
||||
for i, arg := range exec.Meta.Args {
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: strconv.Itoa(i),
|
||||
Value: arg,
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "env",
|
||||
Value: brief(strings.Join(exec.Meta.Env, " ")),
|
||||
VariablesReference: refs.New(func() []dap.Variable {
|
||||
vars := make([]dap.Variable, 0, len(exec.Meta.Env))
|
||||
for _, envstr := range exec.Meta.Env {
|
||||
parts := strings.SplitN(envstr, "=", 2)
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: parts[0],
|
||||
Value: parts[1],
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
if exec.Meta.Cwd != "" {
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: "workdir",
|
||||
Value: exec.Meta.Cwd,
|
||||
})
|
||||
}
|
||||
|
||||
if exec.Meta.User != "" {
|
||||
vars = append(vars, dap.Variable{
|
||||
Name: "user",
|
||||
Value: exec.Meta.User,
|
||||
})
|
||||
}
|
||||
return vars
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frame) Scopes() []dap.Scope {
|
||||
return f.scopes
|
||||
}
|
||||
|
||||
type variableReferences struct {
|
||||
refs map[int32]func() []dap.Variable
|
||||
nextID atomic.Int32
|
||||
mask int32
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func newVariableReferences() *variableReferences {
|
||||
v := new(variableReferences)
|
||||
v.Reset()
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *variableReferences) New(fn func() []dap.Variable) int {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
id := v.nextID.Add(1) | v.mask
|
||||
v.refs[id] = sync.OnceValue(fn)
|
||||
return int(id)
|
||||
}
|
||||
|
||||
func (v *variableReferences) Get(id int) []dap.Variable {
|
||||
v.mu.RLock()
|
||||
fn := v.refs[int32(id)]
|
||||
v.mu.RUnlock()
|
||||
|
||||
var vars []dap.Variable
|
||||
if fn != nil {
|
||||
vars = fn()
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
vars = []dap.Variable{}
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
func (v *variableReferences) Reset() {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
v.refs = make(map[int32]func() []dap.Variable)
|
||||
v.nextID.Store(0)
|
||||
}
|
||||
|
||||
func brief(s string) string {
|
||||
if len(s) >= 64 {
|
||||
return s[:60] + " ..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user