nvim-dap-go
nvim-dap-go copied to clipboard
Set cwd for dlv
Hi @leoluz and thanks for this very nice project! 👋
I've been having a lot of issues with neotest-go and I've also felt that it was really difficult to try and fix these problems in that codebase. Instead, I took the plunge and dove right down the rabbit hole and wrote my own neotest adapter for Go. I've successfully mitigated a bunch of the problems here: fredrikaverpil/neotest-golang.
Right now, I'm looking to enable debugging of a test which is part of a nested go project. This is doable with nvim-dap-go if we can just somehow pass in cwd
into the server config, like this:
local function setup_delve_adapter(dap, config)
local args = { "dap", "-l", "127.0.0.1:" .. config.delve.port }
vim.list_extend(args, config.delve.args)
dap.adapters.go = {
type = "server",
port = config.delve.port,
executable = {
command = config.delve.path,
args = args,
detached = config.delve.detached,
+ cwd = ...,
},
options = {
initialize_timeout_sec = config.delve.initialize_timeout_sec,
},
}
end
I've got this working locally ☝️.
It seems to me that the setup options could be extended to support a cwd
argument, similar to how it currently supports the detached
argument.
Then my neotest adapter can invoke require("dap-go").setup({ delve = { cwd = ... }})
prior to running require("neotest").run.run({ strategy = "dap" })
. Again, it looks like this could work here, from what I have locally right now.
What do you think about all this? It's not the most beautiful approach, but I really would like to build on top of nvim-dap-go and not have to fork off logic from your project.
Would you accept a PR where cwd
is another argument that can be passed into the dlv config, as outlined in the diff above?
EDIT: here's the PR: https://github.com/leoluz/nvim-dap-go/pull/81 and here's a quick POC I threw together (needs cleaning up) to make my neotest adapter make use of this: https://github.com/fredrikaverpil/neotest-golang/pull/3
A somewhat weird thing is I just noticed that nvim-dap attempts to read the cwd
from the adapter options here: https://github.com/mfussenegger/nvim-dap/blob/6ae8a14828b0f3bff1721a35a1dfd604b6a933bb/lua/dap.lua#L373
On my local machine, it's not necessary to register the cwd
there, but if we want nvim-dap
to pick up on this cwd
here, we also have to add it in two places:
local function setup_delve_adapter(dap, config)
local args = { "dap", "-l", "127.0.0.1:" .. config.delve.port }
vim.list_extend(args, config.delve.args)
dap.adapters.go = {
type = "server",
port = config.delve.port,
executable = {
command = config.delve.path,
args = args,
detached = config.delve.detached,
+ cwd = ...,
},
options = {
initialize_timeout_sec = config.delve.initialize_timeout_sec,
+ cwd = ...,
},
}
end
🤔 Hmmm, what's weird about this is that the debugger is launched just fine (in a nested Go sub-project) without declaring it on dap.adapters.go.options.cwd
.
EDIT: I opened a question on this in the nvim-dap discussions: https://github.com/mfussenegger/nvim-dap/discussions/1200
EDIT 2: never mind, this was answered in the nvim-dap discussion link above!
Hi! I am not opposed to add a configuration in nvim-dap-go to make it better collaborate with your plugin as long as it doesn't bring a big complexity to the project and is compatible with what we are trying to achieve. Your proposal is in sync with these terms and seems reasonable to me. However I'd like understand a bit better why this change is needed. You are changing the work directory used to start the delve process in DAP mode. In my understanding, this directory shouldn't matter as clients need to connect to the DAP server and request the binary or attach to a process. Can you please clarify why that is required and why your plugin needs to change that?
I am not opposed to add a configuration in nvim-dap-go to make it better collaborate with your plugin as long as it doesn't bring a big complexity to the project and is compatible with what we are trying to achieve.
Sounds very sane, and I support this kind of thinking too, not only with my open source projects. 👍😄
However I'd like understand a bit better why this change is needed.
Of course!
The short version is that if you work with monorepos and happen to have one or several Go projects (folder with go.mod) that is somewhere below the current working directory, go test
will not be able to execute without erroring:
$ pwd
/Users/fredrik/code/public/go-playground/nested_projects
$ tree
.
└── sub_project
├── go.mod
├── main.go
└── main_test.go
$ go test -v ./...
pattern ./...: directory prefix . does not contain main module or its selected dependencies
Since go test
does not support setting the cwd, you have to instead cd
into the go project:
$ cd sub_project
$ go test -v ./...
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok github.com/fredrikaverpil/go-playground/nested_projects/sub_project (cached)
So, in essence, if you launch neovim from your git monorepo root, neotest will not be able to execute the Go tests that resides in a Go project that is located further down the folder tree. I've mitigated this completely in the neotest-golang
adapter by setting the cwd
here: https://github.com/fredrikaverpil/neotest-golang/blob/ebef584bbeed51e71d4effc785f961a0cd1c226e/lua/neotest-golang/init.lua#L387
By setting the cwd here, tests will run fine even if neovim was launched from the monorepo root (and outside of the Go project). This is by the way how Visual Studio Code and Goland also works.
Now, on to DAP. I want to leverage nvim-dap-go as it defines how to configure and launch the delve server and I wish to debug a test by executing require("neotest").run.run({ strategy = "dap" })
in my monorepo. When I execute this command, I get DAP Error on launch: Failed to launch
. However, if I provide the cwd
(as I did in #81), I can debug the test just fine without any errors.
Since I'm providing the DAP strategy (here), it would've made a lot of sense to also provide the cwd for the adapter server here. But unfortunately, that's not how the DAP server config can be configured, it seems.
So all in all, this is the only way I've managed to get tests and tests debugging to work, when your neovim instance is started outside the actual Go project; to set the cwd
.
Thank you for the detailed explanation! 👏🏻 It is clear to me what you are trying to achieve now. While debugging code using DAP we have different "actors", if you will:
- DAP Client: is responsible for launching the application to be debugged (debugee), set breakpoints, inspect the state of the application, etc. However the client doesn't know how to do those things for every language. It mainly interacts with the DAP server (aka the debug adapter). In our case
nvim-dap
is the client. - Debug Adapter: is responsible to run and serve the DAP API and interface with the debugger which is language specific. In Go's case,
delve
does both: serve the DAP API and knows how to debug the code. Innvim-dap
the adapter is configured with thedap.adapters
table. - Debugee: Is the code to be debugged.
nvim-dap
needs to know the proper configuration in order to launch the debugee correctly. You configure the debugee in thedap.configurations
table.
That being said, it shouldn't matter in which folder you start the debug adapter process. What really matters is how the DAP client launch and provides the information about the debugee to the debug adapter. In #81 you are defining the cwd
of the debug adapter table which looks odd to me. My expectation is that you should provide a specific dap.configuration
for your project so nvim-dap
is able to properly launch your test.
There is a similar discussion here: https://github.com/mfussenegger/nvim-dap/issues/509. It seems that if you just set the program = "${workspaceFolder}/sub_project"
in your dap.configuration
it should work. Have you tried that already?
@leoluz yes, I tried ${workspaceFolder}/sub_project
, which gives me the DAP Error on launch: Failed to launch
.
Full DAP log
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:1172 ] "Starting debug adapter server executable" {
args = { "dap", "-l", "127.0.0.1:63475" },
command = "dlv",
detached = true
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:1305 ] "Debug adapter server executable started, listening on 63475"
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:1309 ] "Connecting to debug adapter" {
executable = {
args = { "dap", "-l", "127.0.0.1:63475" },
command = "dlv",
detached = true
},
options = {
initialize_timeout_sec = 20
},
port = 63475,
type = "server"
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:1686 ] "request" {
arguments = {
adapterID = "nvim-dap",
clientID = "neovim",
clientName = "neovim",
columnsStartAt1 = true,
linesStartAt1 = true,
locale = "en_US.UTF-8",
pathFormat = "path",
supportsProgressReporting = true,
supportsRunInTerminalRequest = true,
supportsStartDebuggingRequest = true,
supportsVariableType = true
},
command = "initialize",
seq = 1,
type = "request"
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:957 ] 1 {
body = {
supportsClipboardContext = true,
supportsConditionalBreakpoints = true,
supportsConfigurationDoneRequest = true,
supportsDelayedStackTraceLoading = true,
supportsDisassembleRequest = true,
supportsEvaluateForHovers = true,
supportsExceptionInfoRequest = true,
supportsFunctionBreakpoints = true,
supportsInstructionBreakpoints = true,
supportsLogPoints = true,
supportsSetVariable = true,
supportsSteppingGranularity = true
},
command = "initialize",
request_seq = 1,
seq = 0,
success = true,
type = "response"
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:1686 ] "request" {
arguments = {
args = { "-test.run", "^TestSum$" },
cwd = "/Users/fredrik/code/public/go-playground/nested_projects/sub_project",
mode = "test",
name = "Neotest-golang Debugger",
program = "/Users/fredrik/code/public/go-playground/nested_projects/sub_project",
request = "launch",
type = "go"
},
command = "launch",
seq = 2,
type = "request"
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:957 ] 1 {
body = {
category = "stderr",
output = "Build Error: go test -c -o /Users/fredrik/code/public/go-playground/nested_projects/__debug_bin3713934934 -gcflags all=-N -l /Users/fredrik/code/public/go-playground/nested_projects/sub_project\ngo: cannot find main module, but found .git/config in /Users/fredrik/code/public/go-playground\n\tto create a module there, run:\n\tcd .. && go mod init (exit status 1)\n"
},
event = "output",
seq = 0,
type = "event"
}
[ DEBUG ] 2024-04-30T07:26:12Z+0200 ] ...k/.local/share/fredrik/lazy/nvim-dap/lua/dap/session.lua:957 ] 1 {
body = {
error = {
format = "Failed to launch: Build error: Check the debug console for details.",
id = 3000,
showUser = false
}
},
command = "launch",
message = "Failed to launch",
request_seq = 2,
seq = 0,
success = false,
type = "response"
}
Debug console error, which details the problem:
Build Error: go test -c -o /Users/fredrik/code/public/go-playground/nested_projects/__debug_bin3713934934 -gcflags all=-N -l /Users/fredrik/code/public/go-playground/nested_projects/sub_project
go: cannot find main module, but found .git/config in /Users/fredrik/code/public/go-playground
to create a module there, run:
cd .. && go mod init (exit status 1)
This is the error ☝️ I get in all cases, except for when I set the cwd
for the delve debug adapter (nvim-dap-go).
If you'd like to try and repro this on your end;
-
git clone https://github.com/fredrikaverpil/go-playground.git
-
cd go-playground
- Start
nvim
in the repo root and opensub_project/main_test.go
. - Run nearest test with neotest (successful, at least if you're using neotest-golang).
- Set a breakpoint and debug the test with the following debugee config:
local dap_config = {
type = "go",
name = "Neotest-golang",
request = "launch",
mode = "test",
program = "${workspaceFolder}/sub_project", -- or "${fileDirname}"
args = { "-test.run", "^TestSum$" },
}
- Open the dap UI, see console, to see the error I posted above.
I have dap logging set to require("dap").set_log_level("TRACE")
but I don't think you need it to get the error in the dap console.
Ok, I think I got it. I took a look at the specification and the delve doc and contrary to what I said before, what actually launches the application is the adapter. In this case, it explains why starting the delve process in the same directory where the go.mod file is located fixes the problem with monorepos.
Thinking about nvim-dap-go use-cases, this configuration could be leveraged to allow debugging applications and test in mono-repos where the root dir doesn't have a go.mod file. It would be great to find a dynamic way to provide this path to the dap.adaptars.go.executable.cwd
attribute instead of expecting users to hardcode this config and having to change that value every time they switch to another project. I'll merge your PR for now and open an issue to track this improvement.
Thank you!
created https://github.com/leoluz/nvim-dap-go/issues/85 based on this discussion.
@leoluz Many thanks for taking your time to consider this change request 🥳
Don't hesitate to get in touch in case you discover a better way to deal with this use case, and I'll be happy to evaluate it together. I wholeheartedly agree it's somewhat backwards right now.
The neotest-golang adapter now has support for setting and resetting the nvim-dap-go configuration, and will be able to find and debug tests for the monorepo use case 🎉