Skip to content
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

Set cwd for dlv #80

Closed
fredrikaverpil opened this issue Apr 25, 2024 · 9 comments · Fixed by #81
Closed

Set cwd for dlv #80

fredrikaverpil opened this issue Apr 25, 2024 · 9 comments · Fixed by #81

Comments

@fredrikaverpil
Copy link
Contributor

fredrikaverpil commented Apr 25, 2024

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: #81 and here's a quick POC I threw together (needs cleaning up) to make my neotest adapter make use of this: fredrikaverpil/neotest-golang#3

@fredrikaverpil

This comment has been minimized.

@leoluz
Copy link
Owner

leoluz commented Apr 28, 2024

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?

@fredrikaverpil
Copy link
Contributor Author

fredrikaverpil commented Apr 29, 2024

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.

@leoluz
Copy link
Owner

leoluz commented Apr 30, 2024

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. In nvim-dap the adapter is configured with the dap.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 the dap.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: mfussenegger/nvim-dap#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?

@fredrikaverpil
Copy link
Contributor Author

fredrikaverpil commented Apr 30, 2024

@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;

  1. git clone https://github.com/fredrikaverpil/go-playground.git
  2. cd go-playground
  3. Start nvim in the repo root and open sub_project/main_test.go.
  4. Run nearest test with neotest (successful, at least if you're using neotest-golang).
  5. 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$" },
  }
  1. 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.

@leoluz
Copy link
Owner

leoluz commented May 2, 2024

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!

@leoluz
Copy link
Owner

leoluz commented May 2, 2024

created #85 based on this discussion.

@fredrikaverpil
Copy link
Contributor Author

@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.

@fredrikaverpil
Copy link
Contributor Author

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 🎉

serpro69 added a commit to serpro69/nvim-config that referenced this issue Sep 26, 2024
Tried to setup DAP for go following nvim-dap documentation and using
delve directly, as described in https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#go-using-delve-directly

However, I was struggling to get DAP working and was getting "Error on launch: Failed to launch" errors all the time,
when I discovered (via dap-ui) that the defaults of nvim-dap-go expects go.mod to be in the root of the project.

At the same time, the "native" neotest adapter for go seems to have the same behavior and
does not seem to support projects where go.mod is not in the root.

This was originally described here
leoluz/nvim-dap-go#80 (comment),
and there's an open issue for this in nvim-dap-go: leoluz/nvim-dap-go#85

There is, however, another adapter for go - fredrikaverpil/neotest-golang - which seems to be more flexible and does support projects
where go.mod is not in the root.

After replacing the adapter and adding configurations as per https://github.com/fredrikaverpil/neotest-golang/?tab=readme-ov-file#example-configuration-debugging,
I was finally able to get DAP working for go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants