dap: add debug adapter implementation
Adds a simple implementation of the debug adapter that supports the very basics of a debug adapter. It supports the launch request, the configuration done request, the creation of threads, stopping, resuming, and disconnecting from server. It does not support custom breakpoints, stack traces, or variable inspection yet. These are planned to be added in the future. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
+22
-29
@@ -313,9 +313,12 @@ func toRepoOnly(in string) (string, error) {
|
||||
return strings.Join(out, ","), nil
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
Evaluate func(ctx context.Context, c gateway.Client, res *gateway.Result) error
|
||||
}
|
||||
type (
|
||||
EvaluateFunc func(ctx context.Context, name string, c gateway.Client, res *gateway.Result) error
|
||||
Handler struct {
|
||||
Evaluate EvaluateFunc
|
||||
}
|
||||
)
|
||||
|
||||
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
||||
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
|
||||
@@ -510,12 +513,25 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
|
||||
rKey := resultKey(dp.driverIndex, k)
|
||||
results.Set(rKey, res)
|
||||
|
||||
forceEval := false
|
||||
if children := childTargets[rKey]; len(children) > 0 {
|
||||
if err := waitForChildren(ctx, bh, c, res, results, children); err != nil {
|
||||
// wait for the child targets to register their LLB before evaluating
|
||||
_, err := results.Get(ctx, children...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if bh != nil && bh.Evaluate != nil {
|
||||
if err := bh.Evaluate(ctx, c, res); err != nil {
|
||||
forceEval = true
|
||||
}
|
||||
|
||||
// invoke custom evaluate handler if it is present
|
||||
if bh != nil && bh.Evaluate != nil {
|
||||
if err := bh.Evaluate(ctx, k, c, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if forceEval {
|
||||
if err := res.EachRef(func(ref gateway.Reference) error {
|
||||
return ref.Evaluate(ctx)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -1199,29 +1215,6 @@ func solve(ctx context.Context, c gateway.Client, req gateway.SolveRequest) (*ga
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func waitForChildren(ctx context.Context, bh *Handler, c gateway.Client, res *gateway.Result, results *waitmap.Map, children []string) error {
|
||||
// wait for the child targets to register their LLB before evaluating
|
||||
_, err := results.Get(ctx, children...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we need to wait until the child targets have completed before we can release
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.Go(func() error {
|
||||
if bh != nil && bh.Evaluate != nil {
|
||||
return bh.Evaluate(ctx, c, res)
|
||||
}
|
||||
return res.EachRef(func(ref gateway.Reference) error {
|
||||
return ref.Evaluate(ctx)
|
||||
})
|
||||
})
|
||||
eg.Go(func() error {
|
||||
_, err := results.Get(ctx, children...)
|
||||
return err
|
||||
})
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func catchFrontendError(retErr, frontendErr *error) {
|
||||
*frontendErr = *retErr
|
||||
if errors.Is(*retErr, ErrRestart) {
|
||||
|
||||
+24
-12
@@ -14,18 +14,18 @@ import (
|
||||
)
|
||||
|
||||
type InvokeConfig struct {
|
||||
Entrypoint []string
|
||||
Cmd []string
|
||||
NoCmd bool
|
||||
Env []string
|
||||
User string
|
||||
NoUser bool
|
||||
Cwd string
|
||||
NoCwd bool
|
||||
Tty bool
|
||||
Rollback bool
|
||||
Initial bool
|
||||
SuspendOn SuspendOn
|
||||
Entrypoint []string `json:"entrypoint,omitempty"`
|
||||
Cmd []string `json:"cmd,omitempty"`
|
||||
NoCmd bool `json:"noCmd,omitempty"`
|
||||
Env []string `json:"env,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
NoUser bool `json:"noUser,omitempty"`
|
||||
Cwd string `json:"cwd,omitempty"`
|
||||
NoCwd bool `json:"noCwd,omitempty"`
|
||||
Tty bool `json:"tty,omitempty"`
|
||||
Rollback bool `json:"rollback,omitempty"`
|
||||
Initial bool `json:"initial,omitempty"`
|
||||
SuspendOn SuspendOn `json:"suspendOn,omitempty"`
|
||||
}
|
||||
|
||||
func (cfg *InvokeConfig) NeedsDebug(err error) bool {
|
||||
@@ -43,6 +43,18 @@ func (s SuspendOn) DebugEnabled(err error) bool {
|
||||
return err != nil || s == SuspendAlways
|
||||
}
|
||||
|
||||
func (s *SuspendOn) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "error":
|
||||
*s = SuspendError
|
||||
case "always":
|
||||
*s = SuspendAlways
|
||||
default:
|
||||
return errors.Errorf("unknown suspend name: %s", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
cancelOnce sync.Once
|
||||
containerCancel func(error)
|
||||
|
||||
+13
-20
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// NewResultHandle stores a gateway client, gateway result, and the error from
|
||||
// NewResultHandle stores a gateway client, gateway reference, and the error from
|
||||
// an evaluate call if it is present.
|
||||
//
|
||||
// This ResultHandle can be used to execute additional build steps in the same
|
||||
@@ -24,9 +24,10 @@ import (
|
||||
// failures and successes.
|
||||
//
|
||||
// If the returned ResultHandle is not nil, the caller must call Done() on it.
|
||||
func NewResultHandle(ctx context.Context, c gateway.Client, res *gateway.Result, err error) *ResultHandle {
|
||||
func NewResultHandle(ctx context.Context, c gateway.Client, ref gateway.Reference, meta map[string][]byte, err error) *ResultHandle {
|
||||
rCtx := &ResultHandle{
|
||||
res: res,
|
||||
ref: ref,
|
||||
meta: meta,
|
||||
gwClient: c,
|
||||
}
|
||||
if err != nil && !errors.As(err, &rCtx.solveErr) {
|
||||
@@ -37,8 +38,9 @@ func NewResultHandle(ctx context.Context, c gateway.Client, res *gateway.Result,
|
||||
|
||||
// ResultHandle is a build result with the client that built it.
|
||||
type ResultHandle struct {
|
||||
res *gateway.Result
|
||||
ref gateway.Reference
|
||||
solveErr *errdefs.SolveError
|
||||
meta map[string][]byte
|
||||
gwClient gateway.Client
|
||||
|
||||
doneOnce sync.Once
|
||||
@@ -74,9 +76,9 @@ func (r *ResultHandle) NewContainer(ctx context.Context, cfg *InvokeConfig) (gat
|
||||
}
|
||||
|
||||
func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
|
||||
if r.res != nil && r.solveErr == nil {
|
||||
if r.ref != nil && r.solveErr == nil {
|
||||
logrus.Debugf("creating container from successful build")
|
||||
ccfg, err := containerConfigFromResult(r.res, cfg)
|
||||
ccfg, err := containerConfigFromResult(r.ref, cfg)
|
||||
if err != nil {
|
||||
return containerCfg, err
|
||||
}
|
||||
@@ -94,9 +96,9 @@ func (r *ResultHandle) getContainerConfig(cfg *InvokeConfig) (containerCfg gatew
|
||||
|
||||
func (r *ResultHandle) getProcessConfig(cfg *InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) (_ gateway.StartRequest, err error) {
|
||||
processCfg := newStartRequest(stdin, stdout, stderr)
|
||||
if r.res != nil && r.solveErr == nil {
|
||||
if r.ref != nil && r.solveErr == nil {
|
||||
logrus.Debugf("creating container from successful build")
|
||||
if err := populateProcessConfigFromResult(&processCfg, r.res, cfg); err != nil {
|
||||
if err := populateProcessConfigFromResult(&processCfg, r.meta, cfg); err != nil {
|
||||
return processCfg, err
|
||||
}
|
||||
} else {
|
||||
@@ -108,20 +110,11 @@ func (r *ResultHandle) getProcessConfig(cfg *InvokeConfig, stdin io.ReadCloser,
|
||||
return processCfg, nil
|
||||
}
|
||||
|
||||
func containerConfigFromResult(res *gateway.Result, cfg *InvokeConfig) (*gateway.NewContainerRequest, error) {
|
||||
func containerConfigFromResult(ref gateway.Reference, cfg *InvokeConfig) (*gateway.NewContainerRequest, error) {
|
||||
if cfg.Initial {
|
||||
return nil, errors.Errorf("starting from the container from the initial state of the step is supported only on the failed steps")
|
||||
}
|
||||
|
||||
ps, err := exptypes.ParsePlatforms(res.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, ok := res.FindRef(ps.Platforms[0].ID)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no reference found")
|
||||
}
|
||||
|
||||
return &gateway.NewContainerRequest{
|
||||
Mounts: []gateway.Mount{
|
||||
{
|
||||
@@ -133,8 +126,8 @@ func containerConfigFromResult(res *gateway.Result, cfg *InvokeConfig) (*gateway
|
||||
}, nil
|
||||
}
|
||||
|
||||
func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Result, cfg *InvokeConfig) error {
|
||||
imgData := res.Metadata[exptypes.ExporterImageConfigKey]
|
||||
func populateProcessConfigFromResult(req *gateway.StartRequest, meta map[string][]byte, cfg *InvokeConfig) error {
|
||||
imgData := meta[exptypes.ExporterImageConfigKey]
|
||||
var img *ocispecs.Image
|
||||
if len(imgData) > 0 {
|
||||
img = &ocispecs.Image{}
|
||||
|
||||
Reference in New Issue
Block a user