Skip to content

Commit 662ead2

Browse files
committed
Support breakpoint debugger integrated to IDEs
This commit adds the IDE-integrated breakpoint debugger based on walker. Now buildx provides DAP (Debug Adapter Protocol) API to IDEs so DAP-aware IDEs can call buildx and allow users to perform breakpoint-based debugging on the IDE's UI/UX. Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent 76d7ea4 commit 662ead2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+18563
-19
lines changed

commands/dap.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/docker/buildx/monitor/dap"
8+
"github.com/docker/cli/cli/command"
9+
"github.com/pkg/errors"
10+
"github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func addDAPCommands(cmd *cobra.Command, dockerCli command.Cli) {
15+
cmd.AddCommand(
16+
dapCmd(dockerCli),
17+
attachContainerCmd(dockerCli),
18+
)
19+
}
20+
21+
func dapCmd(dockerCli command.Cli) *cobra.Command {
22+
cmd := &cobra.Command{
23+
Use: "dap",
24+
Hidden: true,
25+
RunE: func(cmd *cobra.Command, args []string) error {
26+
logrus.SetOutput(os.Stderr)
27+
s, err := dap.NewServer(dockerCli, os.Stdin, os.Stdout)
28+
if err != nil {
29+
return err
30+
}
31+
if err := s.Serve(); err != nil {
32+
logrus.WithError(err).Warnf("failed to serve")
33+
}
34+
logrus.Info("finishing server")
35+
return nil
36+
},
37+
}
38+
return cmd
39+
}
40+
41+
func attachContainerCmd(dockerCli command.Cli) *cobra.Command {
42+
var setTtyRaw bool
43+
cmd := &cobra.Command{
44+
Use: fmt.Sprintf("%s [OPTIONS] rootdir", dap.AttachContainerCommand),
45+
Hidden: true,
46+
RunE: func(cmd *cobra.Command, args []string) error {
47+
if len(args) < 1 || args[0] == "" {
48+
return errors.Errorf("specify root dir: %+v", args)
49+
}
50+
return dap.AttachContainerIO(args[0], os.Stdin, os.Stdout, os.Stderr, setTtyRaw)
51+
},
52+
}
53+
flags := cmd.Flags()
54+
flags.BoolVar(&setTtyRaw, "set-tty-raw", false, "set tty raw")
55+
return cmd
56+
}

commands/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
100100
"builder",
101101
completion.BuilderNames(dockerCli),
102102
)
103+
addDAPCommands(cmd, dockerCli) // hidden command; we need it for emacs DAP support
103104
}
104105

105106
func rootFlags(options *rootOptions, flags *pflag.FlagSet) {

docs/guides/breakpoints.md

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Breakpoint debugger (experimental)
2+
3+
## Debugger on the monitor
4+
5+
You can use `--invoke=debug-step` to use breakpoint debugger.
6+
7+
This doesn't perform the build but immediately launches the debug monitor.
8+
You can continue the build on the monitor in a step-by-step manner.
9+
`show` command shows the source file with breakpoint information.
10+
11+
```
12+
$ BUILDX_EXPERIMENTAL=1 buildx build --invoke=debug-step /tmp/ctx3
13+
INFO: connecting to buildx server
14+
INFO: no buildx server found; launching...
15+
[+] Building 3.2s (5/5) FINISHED
16+
=> [internal] booting buildkit 0.6s
17+
=> => starting container buildx_buildkit_testbuilder0 0.6s
18+
=> [internal] load build definition from Dockerfile 0.0s
19+
=> => transferring dockerfile: 87B 0.0s
20+
=> [internal] load .dockerignore 0.0s
21+
=> => transferring context: 2B 0.0s
22+
=> [internal] load metadata for docker.io/library/busybox:latest 2.3s
23+
=> [auth] library/busybox:pull token for registry-1.docker.io 0.0s
24+
Launching interactive container. Press Ctrl-a-c to switch to monitor console
25+
/ # Switched IO
26+
(buildx) show
27+
Filename: "Dockerfile"
28+
1| FROM busybox
29+
2| RUN echo hi > /hi
30+
3| RUN echo aaaa > /a
31+
```
32+
33+
You can start the build using `continue` command.
34+
`=>` shows the position where the build is pausing.
35+
36+
```
37+
(buildx) continue
38+
...
39+
Break at [stopOnEntry]
40+
Filename: "Dockerfile"
41+
=> 1| FROM busybox
42+
2| RUN echo hi > /hi
43+
3| RUN echo aaaa > /a
44+
```
45+
46+
You can set a breakpoint using `break` command.
47+
The position of the breakpoint is shown by `*` on the `show` command output.
48+
49+
```
50+
(buildx) break 3
51+
(buildx) show
52+
Filename: "Dockerfile"
53+
=> 1| FROM busybox
54+
2| RUN echo hi > /hi
55+
* 3| RUN echo aaaa > /a
56+
```
57+
58+
`continue` resumes the build and stops when it encounters a breakpoint.
59+
You can switch to the shell using `C-a-c` and inspect the filesystem.
60+
61+
```
62+
(buildx) continue
63+
[+] Building 0.0s (2/2) FINISHED
64+
=> CACHED [1/3] FROM docker.io/library/busybox@sha256:7b3ccabffc97de872a 0.0s
65+
=> => resolve docker.io/library/busybox@sha256:7b3ccabffc97de872a30dfd23 0.0s
66+
=> CACHED [2/3] RUN echo hi > /hi 0.0s
67+
[+] Building 0.0s (3/3) FINISHED
68+
=> CACHED [1/3] FROM docker.io/library/busybox@sha256:7b3ccabffc97de872a 0.0s
69+
=> => resolve docker.io/library/busybox@sha256:7b3ccabffc97de872a30dfd23 0.0s
70+
=> CACHED [3/3] RUN echo aaaa > /a 0.0s
71+
=> CACHED [2/3] RUN echo hi > /hi 0.0s
72+
Break at [1]
73+
Filename: "Dockerfile"
74+
1| FROM busybox
75+
2| RUN echo hi > /hi
76+
*=> 3| RUN echo aaaa > /a
77+
78+
(buildx) Switched IO
79+
80+
/ # cat /a
81+
aaaa
82+
```
83+
84+
- Implemented commands
85+
- `show`: shows the Dockerfile
86+
- `break`: set a breakpoint at the specified line
87+
- `breakpoints`: list key-value pairs of available breakpoints
88+
- `clear`: clear the breakpoint specified by the key
89+
- `clearall`: clear all breakpoints
90+
- `next`: proceed to the next line
91+
- `continue`: resume the build until the next breakpoint
92+
93+
## Debugger on IDEs
94+
95+
The breakpoint debugger can be used on IDEs.
96+
The monitor provides DAP API for IDEs so that the breakpoint debugger can be controlled via the editor's UI.
97+
98+
### VS Code
99+
100+
You can use `vscode-buildx-debugger` extension.
101+
Buildx needs to be available via PATH environtmen variable.
102+
103+
Install the VSIX file to VS Code as the following:
104+
105+
```
106+
code --install-extension vscode-buildx-debugger-${VERSION}.vsix
107+
```
108+
109+
You can lanuch the debugger by putting `launch.json` in `.vscode` in the project directory.
110+
111+
```
112+
{
113+
"version": "0.2.0",
114+
"configurations": [
115+
{
116+
"type": "dockerfile",
117+
"request": "launch",
118+
"name": "Debug Dockerfile",
119+
"program": "${workspaceFolder}/Dockerfile",
120+
"stopOnEntry": true
121+
}
122+
}
123+
```
124+
125+
![VSCode DAP](./images/vscode-dap.png)
126+
127+
### Emacs
128+
129+
- Requirements
130+
- [dap-mode](https://github.com/emacs-lsp/dap-mode)
131+
- configuration guide: https://emacs-lsp.github.io/dap-mode/page/configuration/
132+
133+
The following config enables emacs to use buildx as a breakpoint debugger for Dockerfile.
134+
Buildx needs to be available via PATH environtmen variable.
135+
136+
```
137+
(require 'dap-mode)
138+
(require 'dap-utils)
139+
140+
(dap-register-debug-provider "dockerfile" 'dap-dockerfile--populate-default-args)
141+
142+
(defun dap-dockerfile--populate-default-args (conf)
143+
"Populate CONF with the default arguments."
144+
(-> conf
145+
(dap--put-if-absent :program buffer-file-name)
146+
(dap--put-if-absent :dap-server-path (list "buildx" "dap"))))
147+
148+
(dap-register-debug-template "Dockerfile Debug Configuration"
149+
(list :type "dockerfile"
150+
:request "launch"
151+
:stopOnEntry t
152+
:name "Debug Dockerfile"
153+
:environment-variables '(("BUILDX_EXPERIMENTAL" . "1"))
154+
))
155+
```
156+
157+
To launch the debugger, `M-x dap-debug` then select `Dockerfile Debug Configuration` template.
158+
159+
dap-mode also supports `launch.json` of VS Code.
160+
dap-mode looks up `launch.json` under `(lsp-workspace-root)` and loads it.
161+
Refer to the [dap-mode document](https://github.com/emacs-lsp/dap-mode/blob/5e449c864107e08353fd21c44897c4d480190d94/docs/page/features.md#launchjson-support) for details.
162+
163+
![Emacs DAP](./images/emacs-dap.png)
164+
165+
### Neovim
166+
167+
- Requirements
168+
- Neovim (>= 0.6)
169+
170+
The following [packer.nvim](https://github.com/wbthomason/packer.nvim) config enables neovim to use buildx as a breakpoint debugger for Dockerfile.
171+
Add it to your packer.nvim config location (e.g. `~/.config/nvim/init.lua`, `~/.config/nvim/lua/plugins.lua`, etc.).
172+
Buildx needs to be available via PATH environtmen variable.
173+
174+
```
175+
require'packer'.startup(function()
176+
use "mfussenegger/nvim-dap"
177+
end)
178+
179+
local dap = require("dap")
180+
181+
dap.adapters.dockerfile = {
182+
type = 'executable';
183+
command = '/path/to/buildx';
184+
args = { 'dap' };
185+
options = {
186+
env = {
187+
'BUILDX_EXPERIMENTAL=1'
188+
},
189+
};
190+
}
191+
192+
dap.configurations.dockerfile = {
193+
{
194+
type = "dockerfile",
195+
name = "Dockerfile Configuration",
196+
request = "launch",
197+
stopOnEntry = true,
198+
program = "${file}",
199+
},
200+
}
201+
```
202+
203+
nvim-dap also supports a subset of `launch.json` of VS Code.
204+
`:lua require('dap.ext.vscode').load_launchjs()` searches `launch.json` under the current working directory and loads it.
205+
Refer to the [nvim-dap document](https://github.com/mfussenegger/nvim-dap/blob/f4a3be57f61893cffa1e22aa5e1e7bded495fcf2/doc/dap.txt#L231-L283) for details.
206+
207+
See also [`:help dap.txt`](https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt) of nvim-dap for available commands.
208+
209+
![nvim DAP](./images/nvim-dap.png)
210+
211+
### Launch Configuration
212+
213+
In the launch configuration (e.g. `launch.json` on VS Code), the following properties are provided.
214+
215+
- `program` *string* **REQUIRED** : Absolute path to Dockerfile.
216+
- `stopOnEntry` *boolean* : Automatically stop after launch. (default: `true`)
217+
- `target` *string* : Target build stage to build.
218+
- `build-args` *array* : Build-time variables.
219+
- `ssh` *array* : Allow forwarding SSH agent to the build. Format: `default|<id>[=<socket>|<key>[,<key>]]`
220+
- `secrets` *array* : Expose secret value to the build. Format: `id=secretname,src=filepath`
221+
- `root` *string* : Root directory for controller server.
222+
- `controller-mode` *string* : Mode to launch the controller (`local` vs `remote`(default))
223+
- `server-config` *string* : Path to the controller server configuration file.
224+
225+
Common and mandatory properties are the following (see [VS Code documentation](https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes) for details).
226+
227+
- `type` : Type of debugger to use. Must be `dockerfile`.
228+
- `request` : The request type. `launch` is only supported as of now.
229+
- `name` : The reader-friendly name of this configuration.
230+
231+
### Repl commands
232+
233+
#### exec
234+
235+
```
236+
NAME:
237+
buildx exec - Execute command in the step
238+
239+
USAGE:
240+
exec [OPTIONS] [ARGS...]
241+
242+
If ARGS isn't provided, "/bin/sh" is used by default.
243+
244+
245+
OPTIONS:
246+
--image value Execute command in the debugger image. Implies "rollback" flag.
247+
--result-mount-path value Mountpoint to mount the rootfs of the current step. Ignored if --image isn't specified. (default: "/debugroot")
248+
--init-state Execute commands in an initial state of that step
249+
--tty, -t Allocate tty (enabled by default)
250+
-i Enable stdin (FIXME: must be set with tty) (enabled by default)
251+
--env value, -e value Set environment variables
252+
--workdir value, -w value Working directory inside the container
253+
--rollback Kill running processes and recreate the debugging container
254+
```
255+
256+
#### ps
257+
258+
```
259+
NAME:
260+
buildx ps - List attachable processes.
261+
262+
USAGE:
263+
ps
264+
```
265+
266+
### attach
267+
268+
```
269+
NAME:
270+
buildx attach - Attach to a processes.
271+
272+
USAGE:
273+
attach PID
274+
```
275+
276+
#### help
277+
278+
```
279+
NAME:
280+
- Shows a list of commands or help for one command
281+
282+
USAGE:
283+
[command]
284+
```
285+
286+
### Known Limitations
287+
288+
Following lists the current known limitations that should be eliminated in the futural version.
289+
290+
- `Step Into` and `Step Out` in Debug toolbar is unsupported. To inspect instructions deeper, you can use [`exec`](#exec) REPL command instead.
291+
- Logpoint, conditional, function and data breakpoints aren't supported.
292+
- Setting value to a variable isn't supported.
293+
- `attach` request type in launch configuration isn't supported.
294+

docs/guides/images/emacs-dap.png

53.3 KB
Loading

docs/guides/images/nvim-dap.png

66.8 KB
Loading

docs/guides/images/vscode-dap.png

89.4 KB
Loading

0 commit comments

Comments
 (0)