-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decouple debugger from TUI #286
Conversation
a88ab13
to
6904b0a
Compare
a243559
to
907ef49
Compare
1970425
to
aa2b833
Compare
4884444
to
927946b
Compare
565cec5
to
bfadb54
Compare
codegen/gateway_exec.go
Outdated
ssh []llbutil.SSHOption | ||
) | ||
|
||
f, err := os.Create("/tmp/tee") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this leftover debugging?
Writing to a fixed path in /tmp
is dangerous - for example, a different user can symlink /tmp/tee
-> /etc/passwd
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! It was from debugging.
codegen/debugger_test.go
Outdated
} | ||
|
||
func cleanup(value string) string { | ||
return fmt.Sprintf("%s\n", strings.TrimSpace(dedent.Dedent(value))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: strings.TrimSpace(dedent.Dedent(value)) + "\n"
is simpler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outside scope of this PR imo, but I'm still thinking about how to share test utility but that would mean testing functions in a non-testing package. Maybe use internal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be fine to create a utility package that's only imported by tests.
codegen/debug/linespec_test.go
Outdated
) | ||
|
||
func cleanup(value string) string { | ||
return fmt.Sprintf("%s\n", strings.TrimSpace(dedent.Dedent(value))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: strings.TrimSpace(dedent.Dedent(value)) + "\n"
is simpler
cmd/hlb/command/run.go
Outdated
} else { | ||
g.Go(func() error { | ||
pr, pw := io.Pipe() | ||
is := steer.NewInputSteerer(info.Stdin, pw) | ||
return debug.TUIFrontend(ctx, dbgr, is, pr, info.Stdout, info.Stderr) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to have the caller pass this in instead of having Run
default to the TUI frontend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind.
ErrOutput solver.Console | ||
Output io.Writer | ||
DefaultPlatform string // format: osname/osarch | ||
Tree bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a future followup, we might want to move this outside of Run
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't like Tree tbh, and it will have to go away eventually when we move codegen into a gateway.BuildFunc
.
codegen/debugger.go
Outdated
} | ||
|
||
// Breakpoints can be either a call expr or within a func lit. | ||
if with.Expr.CallExpr != nil && with.Expr.CallExpr.Breakpoint() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
if with.Expr.CallExpr == nil || !with.Expr.CallExpr.Breakpoint() {
if with.Expr.FuncLit == nil {
return
}
breakpoint := false
...
}
}
codegen/debugger_test.go
Outdated
d.Terminate() | ||
|
||
select { | ||
case <-time.After(100 * time.Millisecond): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this long enough to be reliable? I'd make it a few seconds just in case. It shouldn't affect test runtime except in the failure case.
codegen/debug/tui.go
Outdated
diagnostic.DisplayError(s.Ctx, stdout, spans, s.Err, true) | ||
} else { | ||
for _, span := range diagnostic.Spans(s.Err) { | ||
fmt.Fprintf(stdout, "%s\n", span.Pretty(ctx)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: fmt.Fprintln(stdout, span.Pretty(ctx))
if err != nil { | ||
printError(stderr, s, err) | ||
} | ||
goto prompt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really mind the goto, but another way to do this would be to set a flag when we want to run the part of the loop before prompt
. Then most commands would automatically skip that part, except the ones that set the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also goto execute
, I just think there's unnecessary nesting and this is simpler to read.
codegen/debug/tui.go
Outdated
|
||
bps := dbgr.Breakpoints() | ||
if i > len(bps) { | ||
return fmt.Errorf("no breakpoint with id %d\n", i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the newline intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No
openllb#286 changed FileBuffer to return a newly constructed SourceMap on each call to the SourceMap method. This causes a dramatic increase in the gRPC request size, because It turns out that BuildKit deduplicates source maps based on pointer address: https://github.com/moby/buildkit/blob/d21254e7f74b49a84c3fbe1b13260cb23954cf92/client/llb/sourcemap.go#L57 Reuse the same SourceMap structure when nothing has changed.
Features
codegen.Debugger
doesn't know about stdin, stdout or input steering.debug.TUIFrontend
only needs to deal with user interactions.Implementation notes
ast.StopNode
which are nodes that debugger should potentially halt on. These are:*ast.FuncSignature
,*ast.CallStmt
and*ast.CallExpr
.(ast.StopNode).Subject()
returns the node that should be the subject of a breakpoint, since we shouldn't highlight the entire command which may be multi-line.fs
oroption::run
builtin, but rather keyword that is ignored by checker & codegen. The debugger parses these to create breakpoints.*codegen.State
to see if they live inside the same block scope. Or you can quickly access its parent*ast.Module
.shell-on-error
removed in favor of the debugger being able to catch*errdefs.SolveError
as a regular runtime exception. Users can choose to reverse step from there, exec into the exception state as many times as they like. In the future, when debug attach is available, consider additional TUI commands to exec into the same process, or start multiple processes.(*RunInfo).ControlDebugger
is a new callback that gives programmatic access to thecodegen.Debugger
interface. Seecodegen/debugger_tests.go
to see how this can be used.debug.ParseLinespec
based on delve linespec: https://github.com/go-delve/delve/blob/master/Documentation/cli/locspec.mdDesign
rev step
,rev next
,rev continue
,restart
). Instead of trying to run codegen backwards, the debugger continues to block on the last yield and simulates reversal by playing back from its snapshots. To the user, it looks and feels like codegen running backwards.sync.Mutex
. It is locked when the debugger is created and only unlocked when waiting for instruction. When a client instructs how to continue, the mutex is locked again.(codegen.Debugger).GetState
is blocked until the debugger is available to accept instructions, leading to a clean interface in the TUI (and DAP server later on).How
errdefs.SolveError
is handlederrdefs.SolveError
are returned after codegen is complete and the solve request is sent off to BuildKit.errdefs.SolveError
is a snapshot just like any other yield from codegen, thecodegen.Debugger
interface only has oneExec
method. Internally it distinguishes based on the snapshot'sErr
containing anerrdefs.SolveError
to choose betweenExecWithFS
andExecWithSolveErr
, but clients of the debugger need not worry about the difference.