diff --git a/commands/dap.go b/commands/dap.go index 5817251924ae..8beddb1af9f2 100644 --- a/commands/dap.go +++ b/commands/dap.go @@ -4,6 +4,7 @@ import ( "context" "github.com/docker/buildx/dap" + "github.com/docker/buildx/dap/common" "github.com/docker/buildx/util/cobrautil" "github.com/docker/buildx/util/ioset" "github.com/docker/buildx/util/progress" @@ -20,31 +21,18 @@ func dapCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { } cobrautil.MarkCommandExperimental(cmd) - flags := cmd.Flags() - flags.StringVar(&options.OnFlag, "on", "error", "When to pause the adapter ([always, error])") - - cobrautil.MarkFlagsExperimental(flags, "on") - dapBuildCmd := buildCmd(dockerCli, rootOpts, &options) dapBuildCmd.Args = cobra.RangeArgs(0, 1) cmd.AddCommand(dapBuildCmd) return cmd } -type dapOptions struct { - // OnFlag is a flag to configure the timing of launching the debugger. - OnFlag string -} +type dapOptions struct{} func (d *dapOptions) New(in ioset.In) (debuggerInstance, error) { - invokeConfig, err := parseInvokeConfig("", d.OnFlag) - if err != nil { - return nil, err - } - conn := dap.NewConn(in.Stdin, in.Stdout) return &adapterProtocolDebugger{ - Adapter: dap.New[LaunchConfig](invokeConfig), + Adapter: dap.New[LaunchConfig](), conn: conn, }, nil } @@ -53,6 +41,7 @@ type LaunchConfig struct { Dockerfile string `json:"dockerfile,omitempty"` ContextPath string `json:"contextPath,omitempty"` Target string `json:"target,omitempty"` + common.Config } type adapterProtocolDebugger struct { diff --git a/dap/adapter.go b/dap/adapter.go index 9ef7950b4ee0..ce5991ba0c9b 100644 --- a/dap/adapter.go +++ b/dap/adapter.go @@ -11,19 +11,20 @@ import ( "sync/atomic" "github.com/docker/buildx/build" + "github.com/docker/buildx/dap/common" "github.com/google/go-dap" gateway "github.com/moby/buildkit/frontend/gateway/client" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) -type Adapter[T any] struct { +type Adapter[C LaunchConfig] struct { srv *Server eg *errgroup.Group - cfg build.InvokeConfig + cfg common.Config initialized chan struct{} - started chan launchResponse[T] + started chan launchResponse[C] configuration chan struct{} evaluateReqCh chan *evaluateRequest @@ -36,24 +37,21 @@ type Adapter[T any] struct { idPool *idPool } -func New[T any](cfg *build.InvokeConfig) *Adapter[T] { - d := &Adapter[T]{ +func New[C LaunchConfig]() *Adapter[C] { + d := &Adapter[C]{ initialized: make(chan struct{}), - started: make(chan launchResponse[T], 1), + started: make(chan launchResponse[C], 1), configuration: make(chan struct{}), evaluateReqCh: make(chan *evaluateRequest), threads: make(map[int]*thread), nextThreadID: 1, idPool: new(idPool), } - if cfg != nil { - d.cfg = *cfg - } d.srv = NewServer(d.dapHandler()) return d } -func (d *Adapter[T]) Start(ctx context.Context, conn Conn) (T, error) { +func (d *Adapter[C]) Start(ctx context.Context, conn Conn) (C, error) { d.eg, _ = errgroup.WithContext(ctx) d.eg.Go(func() error { return d.srv.Serve(ctx, conn) @@ -65,10 +63,11 @@ func (d *Adapter[T]) Start(ctx context.Context, conn Conn) (T, error) { if !ok { resp.Error = context.Canceled } + d.cfg = resp.Config.GetConfig() return resp.Config, resp.Error } -func (d *Adapter[T]) Stop() error { +func (d *Adapter[C]) Stop() error { if d.eg == nil { return nil } @@ -96,7 +95,7 @@ func (d *Adapter[T]) Stop() error { return err } -func (d *Adapter[T]) Initialize(c Context, req *dap.InitializeRequest, resp *dap.InitializeResponse) error { +func (d *Adapter[C]) Initialize(c Context, req *dap.InitializeRequest, resp *dap.InitializeResponse) error { close(d.initialized) // Set capabilities. @@ -104,36 +103,36 @@ func (d *Adapter[T]) Initialize(c Context, req *dap.InitializeRequest, resp *dap return nil } -type launchResponse[T any] struct { - Config T +type launchResponse[C any] struct { + Config C Error error } -func (d *Adapter[T]) Launch(c Context, req *dap.LaunchRequest, resp *dap.LaunchResponse) error { +func (d *Adapter[C]) Launch(c Context, req *dap.LaunchRequest, resp *dap.LaunchResponse) error { defer close(d.started) - var cfg T + var cfg C if err := json.Unmarshal(req.Arguments, &cfg); err != nil { - d.started <- launchResponse[T]{Error: err} + d.started <- launchResponse[C]{Error: err} return err } d.start(c) - d.started <- launchResponse[T]{Config: cfg} + d.started <- launchResponse[C]{Config: cfg} return nil } -func (d *Adapter[T]) Disconnect(c Context, req *dap.DisconnectRequest, resp *dap.DisconnectResponse) error { +func (d *Adapter[C]) Disconnect(c Context, req *dap.DisconnectRequest, resp *dap.DisconnectResponse) error { close(d.evaluateReqCh) return nil } -func (d *Adapter[T]) start(c Context) { +func (d *Adapter[C]) start(c Context) { c.Go(d.launch) } -func (d *Adapter[T]) Continue(c Context, req *dap.ContinueRequest, resp *dap.ContinueResponse) error { +func (d *Adapter[C]) Continue(c Context, req *dap.ContinueRequest, resp *dap.ContinueResponse) error { d.threadsMu.RLock() t := d.threads[req.Arguments.ThreadId] d.threadsMu.RUnlock() @@ -142,7 +141,7 @@ func (d *Adapter[T]) Continue(c Context, req *dap.ContinueRequest, resp *dap.Con return nil } -func (d *Adapter[T]) Next(c Context, req *dap.NextRequest, resp *dap.NextResponse) error { +func (d *Adapter[C]) Next(c Context, req *dap.NextRequest, resp *dap.NextResponse) error { d.threadsMu.RLock() t := d.threads[req.Arguments.ThreadId] d.threadsMu.RUnlock() @@ -151,7 +150,7 @@ func (d *Adapter[T]) Next(c Context, req *dap.NextRequest, resp *dap.NextRespons return nil } -func (d *Adapter[T]) SetBreakpoints(c Context, req *dap.SetBreakpointsRequest, resp *dap.SetBreakpointsResponse) error { +func (d *Adapter[C]) SetBreakpoints(c Context, req *dap.SetBreakpointsRequest, resp *dap.SetBreakpointsResponse) error { // TODO: implement breakpoints for range req.Arguments.Breakpoints { // Fail to create all breakpoints that were requested. @@ -163,13 +162,13 @@ func (d *Adapter[T]) SetBreakpoints(c Context, req *dap.SetBreakpointsRequest, r return nil } -func (d *Adapter[T]) ConfigurationDone(c Context, req *dap.ConfigurationDoneRequest, resp *dap.ConfigurationDoneResponse) error { +func (d *Adapter[C]) ConfigurationDone(c Context, req *dap.ConfigurationDoneRequest, resp *dap.ConfigurationDoneResponse) error { d.configuration <- struct{}{} close(d.configuration) return nil } -func (d *Adapter[T]) launch(c Context) { +func (d *Adapter[C]) launch(c Context) { // Send initialized event. c.C() <- &dap.InitializedEvent{ Event: dap.Event{ @@ -198,7 +197,7 @@ func (d *Adapter[T]) launch(c Context) { started := c.Go(func(c Context) { defer d.deleteThread(c, t) defer close(req.errCh) - req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta, req.inputs) + req.errCh <- t.Evaluate(c, req.c, req.ref, req.meta, req.inputs, d.cfg) }) if !started { @@ -209,7 +208,7 @@ func (d *Adapter[T]) launch(c Context) { } } -func (d *Adapter[T]) newThread(ctx Context, name string) (t *thread) { +func (d *Adapter[C]) newThread(ctx Context, name string) (t *thread) { d.threadsMu.Lock() id := d.nextThreadID t = &thread{ @@ -232,14 +231,14 @@ func (d *Adapter[T]) newThread(ctx Context, name string) (t *thread) { return t } -func (d *Adapter[T]) getThread(id int) (t *thread) { +func (d *Adapter[C]) getThread(id int) (t *thread) { d.threadsMu.Lock() t = d.threads[id] d.threadsMu.Unlock() return t } -func (d *Adapter[T]) deleteThread(ctx Context, t *thread) { +func (d *Adapter[C]) deleteThread(ctx Context, t *thread) { d.threadsMu.Lock() delete(d.threads, t.id) d.threadsMu.Unlock() @@ -262,7 +261,7 @@ type evaluateRequest struct { errCh chan<- error } -func (d *Adapter[T]) EvaluateResult(ctx context.Context, name string, c gateway.Client, res *gateway.Result, inputs build.Inputs) error { +func (d *Adapter[C]) EvaluateResult(ctx context.Context, name string, c gateway.Client, res *gateway.Result, inputs build.Inputs) error { eg, _ := errgroup.WithContext(ctx) if res.Ref != nil { eg.Go(func() error { @@ -279,7 +278,7 @@ func (d *Adapter[T]) EvaluateResult(ctx context.Context, name string, c gateway. return eg.Wait() } -func (d *Adapter[T]) evaluateRef(ctx context.Context, name string, c gateway.Client, ref gateway.Reference, meta map[string][]byte, inputs build.Inputs) error { +func (d *Adapter[C]) evaluateRef(ctx context.Context, name string, c gateway.Client, ref gateway.Reference, meta map[string][]byte, inputs build.Inputs) error { errCh := make(chan error, 1) // Send a solve request to the launch routine @@ -307,7 +306,7 @@ func (d *Adapter[T]) evaluateRef(ctx context.Context, name string, c gateway.Cli } } -func (d *Adapter[T]) Threads(c Context, req *dap.ThreadsRequest, resp *dap.ThreadsResponse) error { +func (d *Adapter[C]) Threads(c Context, req *dap.ThreadsRequest, resp *dap.ThreadsResponse) error { d.threadsMu.RLock() defer d.threadsMu.RUnlock() @@ -321,7 +320,7 @@ func (d *Adapter[T]) Threads(c Context, req *dap.ThreadsRequest, resp *dap.Threa return nil } -func (d *Adapter[T]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap.StackTraceResponse) error { +func (d *Adapter[C]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap.StackTraceResponse) error { t := d.getThread(req.Arguments.ThreadId) if t == nil { return errors.Errorf("no such thread: %d", req.Arguments.ThreadId) @@ -331,7 +330,7 @@ func (d *Adapter[T]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap return nil } -func (d *Adapter[T]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceResponse) error { +func (d *Adapter[C]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceResponse) error { fname := req.Arguments.Source.Path dt, ok := d.sourceMap.Get(fname) @@ -343,7 +342,7 @@ func (d *Adapter[T]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceR return nil } -func (d *Adapter[T]) evaluate(ctx context.Context, name string, c gateway.Client, res *gateway.Result, opt build.Options) error { +func (d *Adapter[C]) evaluate(ctx context.Context, name string, c gateway.Client, res *gateway.Result, opt build.Options) error { errCh := make(chan error, 1) started := d.srv.Go(func(ctx Context) { @@ -362,13 +361,13 @@ func (d *Adapter[T]) evaluate(ctx context.Context, name string, c gateway.Client } } -func (d *Adapter[T]) Handler() build.Handler { +func (d *Adapter[C]) Handler() build.Handler { return build.Handler{ Evaluate: d.evaluate, } } -func (d *Adapter[T]) dapHandler() Handler { +func (d *Adapter[C]) dapHandler() Handler { return Handler{ Initialize: d.Initialize, Launch: d.Launch, @@ -383,15 +382,15 @@ func (d *Adapter[T]) dapHandler() Handler { } } -func (d *Adapter[T]) Out() io.Writer { - return &adapterWriter[T]{d} +func (d *Adapter[C]) Out() io.Writer { + return &adapterWriter[C]{d} } -type adapterWriter[T any] struct { - *Adapter[T] +type adapterWriter[C LaunchConfig] struct { + *Adapter[C] } -func (d *adapterWriter[T]) Write(p []byte) (n int, err error) { +func (d *adapterWriter[C]) Write(p []byte) (n int, err error) { started := d.srv.Go(func(c Context) { <-d.initialized diff --git a/dap/adapter_test.go b/dap/adapter_test.go index 58f09bbe40cc..215c1dc85805 100644 --- a/dap/adapter_test.go +++ b/dap/adapter_test.go @@ -7,13 +7,14 @@ import ( "testing" "time" + "github.com/docker/buildx/dap/common" "github.com/google/go-dap" "github.com/stretchr/testify/assert" "golang.org/x/sync/errgroup" ) func TestLaunch(t *testing.T) { - adapter, conn, client := NewTestAdapter[any](t) + adapter, conn, client := NewTestAdapter[common.Config](t) ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, context.DeadlineExceeded) defer cancel() @@ -68,7 +69,7 @@ func TestLaunch(t *testing.T) { eg.Wait() } -func NewTestAdapter[T any](t *testing.T) (*Adapter[T], Conn, *Client) { +func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *Client) { t.Helper() rd1, wr1 := io.Pipe() @@ -84,7 +85,7 @@ func NewTestAdapter[T any](t *testing.T) (*Adapter[T], Conn, *Client) { clientConn.Close() }) - adapter := New[T](nil) + adapter := New[C]() t.Cleanup(func() { adapter.Stop() }) client := NewClient(clientConn) diff --git a/dap/common/config.go b/dap/common/config.go new file mode 100644 index 000000000000..20cdac171295 --- /dev/null +++ b/dap/common/config.go @@ -0,0 +1,9 @@ +package common + +type Config struct { + StopOnEntry bool `json:"stopOnEntry,omitempty"` +} + +func (c Config) GetConfig() Config { + return c +} diff --git a/dap/config.go b/dap/config.go new file mode 100644 index 000000000000..fba8f154df7d --- /dev/null +++ b/dap/config.go @@ -0,0 +1,7 @@ +package dap + +import "github.com/docker/buildx/dap/common" + +type LaunchConfig interface { + GetConfig() common.Config +} diff --git a/dap/thread.go b/dap/thread.go index c29b65709c10..18ee91f18f0e 100644 --- a/dap/thread.go +++ b/dap/thread.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/docker/buildx/build" + "github.com/docker/buildx/dap/common" "github.com/google/go-dap" "github.com/moby/buildkit/client/llb" gateway "github.com/moby/buildkit/frontend/gateway/client" @@ -67,13 +68,17 @@ const ( stepNext ) -func (t *thread) Evaluate(ctx Context, c gateway.Client, ref gateway.Reference, meta map[string][]byte, inputs build.Inputs) error { +func (t *thread) Evaluate(ctx Context, c gateway.Client, ref gateway.Reference, meta map[string][]byte, inputs build.Inputs, cfg common.Config) error { if err := t.init(ctx, c, ref, meta, inputs); err != nil { return err } defer t.reset() - step := stepNext + step := stepContinue + if cfg.StopOnEntry { + step = stepNext + } + for { pos, err := t.seekNext(ctx, step) diff --git a/docs/reference/buildx_dap.md b/docs/reference/buildx_dap.md index 669aba0bf4dc..3eebe3a4f1bb 100644 --- a/docs/reference/buildx_dap.md +++ b/docs/reference/buildx_dap.md @@ -12,11 +12,10 @@ Start debug adapter protocol compatible debugger (EXPERIMENTAL) ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:-----------------------------------------------------------| -| `--builder` | `string` | | Override the configured builder instance | -| `-D`, `--debug` | `bool` | | Enable debug logging | -| `--on` | `string` | `error` | When to pause the adapter ([always, error]) (EXPERIMENTAL) | +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:-----------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `-D`, `--debug` | `bool` | | Enable debug logging |