A couple months ago, I tried to get Dape working with a Golang project and ran into a couple of problems that prevented me from being able to use it. Today I decided to revisit it, and I’m glad I did, because it was one of the last remaining reasons I had for ever needing to open VSCode.

First attempt at using Dape with a Golang project

Problem 1 - passing arguments

In the top-level of my repo, I created the file .dir-locals.el with the following content:

((go-mode . ((dape-configs .
        ((go-debug-main
          modes (go-mode go-ts-mode)
          command "dlv"
          command-args ("dap" "--listen" "127.0.0.1:55878" "--log-dest" "/tmp/dlv.log")
          command-cwd "/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency"
          host "127.0.0.1"
          port 55878
          :request "launch"
          :mode "debug"
          :type "go"
          :showLog "true"
          :program "/home/shanemcd/github/GoogleContainerTools/skaffold/cmd/skaffold/skaffold.go"
          :args ("build")))))))
 

I am using Google’s Skaffold here in my example, but any program that accepts command line arguments will serve the purpose.

With this config in place, I navigated to the program’s entrypoint (cmd/skaffold/skaffold.go) and was prompted with this:

The local variables list in /home/shanemcd/github/GoogleContainerTools/skaffold/
or .dir-locals.el contains values that may not be safe (*).

Do you want to apply it?  You can type
y  -- to apply the local variables list.
n  -- to ignore the local variables list.
!  -- to apply the local variables list, and permanently mark these
      values (*) as safe (in the future, they will be set automatically.)
i  -- to ignore the local variables list, and permanently mark these
      values (*) as ignored
+  -- to apply the local variables list, and trust all directory-local
      variables in this directory

  * dape-configs : ((go-debug-main modes (go-mode go-ts-mode) command "dlv" command-args ("dap" "--listen" "127.0.0.1:55878" "--log-dest" "/tmp/dlv.log") command-cwd "/var/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency" host "127.0.0.1" port 55878 :request "launch" :mode "debug" :type "go" :showLog "true" :program "/var/home/shanemcd/github/GoogleContainerTools/skaffold/cmd/skaffold/skaffold.go" :args ("build")))

After typing y and pressing Return. With the file open, I then ran M-x dape and saw the following output in the *dape-repl* buffer:

* Welcome to Dape REPL! *
Available Dape commands: debug, next, continue, pause, step, out, up, down, threads, stack, modules, sources, breakpoints, scope, watch, restart, kill, disconnect, quit
Empty input will rerun last command.

* Process launched /var/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency/__debug_bin1126826703 *
Type 'dlv help' for list of commands.
Hello
A tool that facilitates continuous development for Kubernetes applications.

  Find more information at: https://skaffold.dev/docs/getting-started/

End-to-end Pipelines:
  run                 Run a pipeline
  dev                 Run a pipeline in development mode
  debug               Run a pipeline in debug mode

Pipeline Building Blocks:
  build               Build the artifacts
  test                Run tests against your built application images
  deploy              Deploy pre-built artifacts
  delete              Delete any resources deployed by Skaffold
  render              Generate rendered Kubernetes manifests
  apply               Apply hydrated manifests to a cluster
  verify              Run verification tests against skaffold deployments

Getting Started With a New Project:
  init                Generate configuration for deploying an application

Other Commands:
  completion          Output shell completion for the given shell (bash, fish or zsh)
  config              Interact with the global Skaffold config file (defaults to `$HOME/.skaffold/config`)
  diagnose            Run a diagnostic on Skaffold
  exec                Execute a custom action
  fix                 Update old configuration to a newer schema version
  schema              List JSON schemas used to validate skaffold.yaml configuration
  survey              Opens a web browser to fill out the Skaffold survey
  version             Print the version information

Usage:
  skaffold [flags] [options]

Use "skaffold <command> --help" for more information about a given command.
Use "skaffold options" for a list of global command-line options (applies to all commands).

Well, that didn’t work as I had expected.

Reading over the dape README, I saw this under the C, C++ and Rust - lldb-dap heading:

To pass arguments, use :args ["arg1" "arg2" ..]

At first I did not try this because I assumed if it also applied to other languages it would have been called out explicitly, but sure enough, it worked after running dape and then appending my args:

go-debug-main :args ["build"]

I then tried to update my .dir-locals.el with the bracket notation:

diff --git a/.dir-locals.el b/.dir-locals.el
index 65e164b..9995c3a 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -11,4 +11,4 @@
          :type "go"
          :showLog "true"
          :program "/home/shanemcd/github/GoogleContainerTools/skaffold/cmd/skaffold/skaffold.go"
-          :args ("build")))))))
+          :args ["build"]))))))

… and it worked! 🎉 (but also🤦‍♂️)

* Welcome to Dape REPL! *
Available Dape commands: debug, next, continue, pause, step, out, up, down, threads, stack, modules, sources, breakpoints, scope, watch, restart, kill, disconnect, quit
Empty input will rerun last command.

* Process launched /var/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency/__debug_bin2338563526 build *
Type 'dlv help' for list of commands.
Generating tags...
 - app -> app:v2.15.0-4-g8c00b98d7
 - base -> base:v2.15.0-4-g8c00b98d7
Checking cache...
 - app: Not found. Building
 - base: Not found. Building
Starting build...
Building [base]...
Sending build context to Docker daemon  3.072kB

Step 1/3 : FROM alpine:3
 ---> aded1e1a5b37
Step 2/3 : COPY hello.txt .
 ---> Using cache
 ---> 5739d7557ae4
Step 3/3 : CMD ["./app"]
 ---> Using cache
 ---> 17979f81cffb
Successfully built 17979f81cffb
Successfully tagged base:v2.15.0-4-g8c00b98d7
The push refers to repository [docker.io/library/base]
23deea18e759: Preparing
08000c18d16d: Preparing
Process 160667 has exited with status 1
Detaching
* Session terminated *

Problem 2 - breakpoints not working

Now with the command invocation working, I proceeded with trying to set a breakpoint in the Skaffold code with dape-breakpoint-toggle C-x C-a b. Doing that and then re-running dape, I was surprised to see that the breakpoint did not hit as I was expecting.

After describing my problem to ChatGPT, it inferred from our previous chats that I was using Fedora Kinoite, and it pointed out right away that symlinks (Fedora Atomic Desktops link /home/ to /var/home) were known to cause problem in debuggers. After a quick search, that was easy to confirm after locating this note in the vscode-go docs. I was able to get around this by setting HOME to the full path when in my .dir-locals.el

diff --git a/.dir-locals.el b/.dir-locals.el
index 9995c3a..fba8c9f 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -3,12 +3,12 @@
          modes (go-mode go-ts-mode)
          command "dlv"
          command-args ("dap" "--listen" "127.0.0.1:55878" "--log-dest" "/tmp/dlv.log")
-          command-cwd "/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency"
+          command-cwd "/var/home/shanemcd/github/GoogleContainerTools/skaffold/examples/simple-artifact-dependency"
          host "127.0.0.1"
          port 55878
          :request "launch"
          :mode "debug"
          :type "go"
          :showLog "true"
-          :program "/home/shanemcd/github/GoogleContainerTools/skaffold/cmd/skaffold/skaffold.go"
+          :program "/var/home/shanemcd/github/GoogleContainerTools/skaffold/cmd/skaffold/skaffold.go"
          :args ["build"]))))))

And when launching emacs:

$ env HOME=/var/home/shanemcd emacs

Seeking a more long-term solution, I was able to add this to my emacs configuration, which handles setting HOME correctly whenever it is symlinked:

(when-let ((real-home (file-truename (getenv "HOME"))))
  (when (not (string= (getenv "HOME") real-home))
    (setenv "HOME" real-home)))

I still have a lot more to learn, but with this, I am at least able to set breakpoints in my Golang application, step through the code, and inspect variables. I hope this blog post proves to be useful for others, but if nothing else I will be able to copy + paste these configs the next time I lose them. 🙂