dap: return error from evaluate command in repl context

In the repl context, we will now return the error instead of directly
printing it. We also suppress reporting errors from cobra. The logic
flow has also been changed to prevent returning errors from cobra unless
there was something related to the command line invocation so usage will
only be printed when a command was typed wrong and it will not show up
for every error.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2025-07-23 13:51:03 -05:00
parent a1ca46e85e
commit fea53ad1f8
+58 -31
View File
@@ -29,43 +29,70 @@ func (d *Adapter[C]) Evaluate(ctx Context, req *dap.EvaluateRequest, resp *dap.E
return nil return nil
} }
var t *thread var retErr error
if req.Arguments.FrameId > 0 { cmd := d.replCommands(ctx, req, resp, &retErr)
if t = d.getThreadByFrameID(req.Arguments.FrameId); t == nil {
return errors.Errorf("no thread with frame id %d", req.Arguments.FrameId)
}
} else {
if t = d.getFirstThread(); t == nil {
return errors.New("no paused thread")
}
}
cmd := d.replCommands(ctx, t, resp)
cmd.SetArgs(args) cmd.SetArgs(args)
cmd.SetErr(d.Out()) cmd.SetErr(d.Out())
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
fmt.Fprintf(d.Out(), "ERROR: %+v\n", err) // This error should only happen if there was something command
// related that malfunctioned as it will also print usage.
// Normal errors should set retErr from replCommands.
return err
} }
return nil return retErr
} }
func (d *Adapter[C]) replCommands(ctx Context, t *thread, resp *dap.EvaluateResponse) *cobra.Command { func (d *Adapter[C]) replCommands(ctx Context, req *dap.EvaluateRequest, resp *dap.EvaluateResponse, retErr *error) *cobra.Command {
rootCmd := &cobra.Command{} rootCmd := &cobra.Command{
SilenceErrors: true,
}
execCmd := &cobra.Command{ execCmd, execOpts := replCmd(ctx, "exec", resp, retErr, d.execCmd)
Use: "exec", execCmd.PreRun = func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error { execOpts.FrameID = req.Arguments.FrameId
if !d.supportsExec {
return errors.New("cannot exec without runInTerminal client capability")
}
return t.Exec(ctx, args, resp)
},
} }
rootCmd.AddCommand(execCmd) rootCmd.AddCommand(execCmd)
return rootCmd return rootCmd
} }
func (t *thread) Exec(ctx Context, args []string, eresp *dap.EvaluateResponse) (retErr error) { type execOptions struct {
FrameID int
}
func (d *Adapter[C]) execCmd(ctx Context, args []string, flags execOptions) (string, error) {
if !d.supportsExec {
return "", errors.New("cannot exec without runInTerminal client capability")
}
var t *thread
if flags.FrameID > 0 {
if t = d.getThreadByFrameID(flags.FrameID); t == nil {
return "", errors.Errorf("no thread with frame id %d", flags.FrameID)
}
} else {
if t = d.getFirstThread(); t == nil {
return "", errors.New("no paused thread")
}
}
return t.Exec(ctx, args)
}
func replCmd[Flags any, RetVal any](ctx Context, name string, resp *dap.EvaluateResponse, retErr *error, fn func(ctx Context, args []string, flags Flags) (RetVal, error)) (*cobra.Command, *Flags) {
flags := new(Flags)
return &cobra.Command{
Use: name,
Run: func(cmd *cobra.Command, args []string) {
v, err := fn(ctx, args, *flags)
if err != nil {
*retErr = err
return
}
resp.Body.Result = fmt.Sprint(v)
},
}, flags
}
func (t *thread) Exec(ctx Context, args []string) (message string, retErr error) {
cfg := &build.InvokeConfig{Tty: true} cfg := &build.InvokeConfig{Tty: true}
if len(cfg.Entrypoint) == 0 && len(cfg.Cmd) == 0 { if len(cfg.Entrypoint) == 0 && len(cfg.Cmd) == 0 {
cfg.Entrypoint = []string{"/bin/sh"} // launch shell by default cfg.Entrypoint = []string{"/bin/sh"} // launch shell by default
@@ -75,7 +102,7 @@ func (t *thread) Exec(ctx Context, args []string, eresp *dap.EvaluateResponse) (
ctr, err := build.NewContainer(ctx, t.rCtx, cfg) ctr, err := build.NewContainer(ctx, t.rCtx, cfg)
if err != nil { if err != nil {
return err return "", err
} }
defer func() { defer func() {
if retErr != nil { if retErr != nil {
@@ -85,7 +112,7 @@ func (t *thread) Exec(ctx Context, args []string, eresp *dap.EvaluateResponse) (
dir, err := os.MkdirTemp("", "buildx-dap-exec") dir, err := os.MkdirTemp("", "buildx-dap-exec")
if err != nil { if err != nil {
return err return "", err
} }
defer func() { defer func() {
if retErr != nil { if retErr != nil {
@@ -96,7 +123,7 @@ func (t *thread) Exec(ctx Context, args []string, eresp *dap.EvaluateResponse) (
socketPath := filepath.Join(dir, "s.sock") socketPath := filepath.Join(dir, "s.sock")
l, err := net.Listen("unix", socketPath) l, err := net.Listen("unix", socketPath)
if err != nil { if err != nil {
return err return "", err
} }
go func() { go func() {
@@ -121,11 +148,11 @@ func (t *thread) Exec(ctx Context, args []string, eresp *dap.EvaluateResponse) (
resp := ctx.Request(req) resp := ctx.Request(req)
if !resp.GetResponse().Success { if !resp.GetResponse().Success {
return errors.New(resp.GetResponse().Message) return "", errors.New(resp.GetResponse().Message)
} }
eresp.Body.Result = fmt.Sprintf("Started process attached to %s.", socketPath) message = fmt.Sprintf("Started process attached to %s.", socketPath)
return nil return message, nil
} }
func (t *thread) runExec(l net.Listener, ctr *build.Container, cfg *build.InvokeConfig) { func (t *thread) runExec(l net.Listener, ctr *build.Container, cfg *build.InvokeConfig) {