dap-modeでEmacs上でgoをデバッグする

goでもなんでもいいんだけれど,最近Emacsでプログラムを書くときはLSPのお世話になることが非常に多い.ここまでEmacsでいろいろとできると,デバッグもやりたくなってくる.

LSPが流行るのと同時にDAPというのも流行っていて,LSPと同じようにエディタを問わずDebug Adapter Protocolを使えば,言語ごとのデバッグ支援が受けられるというものだ.そういうわけで,Emacsでもdap-modeというのがあり,これを使うことでEmacsでもIDEのようにデバッグができるようになる.

ちなみに,全面的にgoのデバッグをする話をするが,適切なextensionさえあれば他言語でも同じようにデバッグできるはずなので(そのためのAdapter Protocolだ),別言語の場合は言語ごとの設定だけを読み替えてくれれば良い.

dap-modeのインストール

Emacsdap-modeを入れる

github.com

packageでもいいしCaskでもel-getでもいいので,これを入れる.依存は結構あるので,入れるものは多い.

で,こんな設定を書いておく.

(use-package dap-mode
  :after (lsp-mode
          treemacs)
  :custom
  (dap-auto-configure-features '(sessions locals breakpoints expressions repl controls tooltip))
  :config
  (dap-mode 1)
  (dap-auto-configure-mode 1)
  (require 'dap-hydra)
  :bind
  ("C-c d" . dap-hydra/body)
  )

delveを入れる

DAPデバッグできるといっても,goのLSPであるgoplsとかはdebuggerまでは提供していない.DAPはProtocolなのでdebugger自体は言語側に必要になる. というわけで,go側にdebuggerとなる,delveを入れておく必要がある.

github.com

大抵のgo環境であればこれで足りる.

$ go get github.com/go-delve/delve/cmd/dlv

nodejsを入れる

これはdap-modeの都合なのだが,内部でvscode-goというvscode向けの拡張機能を利用している.こいつがjsで書かれており,Emacsdap-modeは内部でこれを利用してdelveを叩いてデバッグを行っている.というわけで,node.jsの実行環境が必要になる.

github.com

バージョン制約は特に書いてなかったので,適当にnodeが動く環境を用意すれば良いと思う.

dap-go-setup

ここまできたらEmacsを開いて,M-x dap-go-setup を叩く.これは初回だけ必要になるんだけど,内部ではこのタイミングでEmacsの設定ディレクトリ内の .extension/vscode/golang.go/extensionvscode-goをダウンロードしてきている.

デバッグしてみる

適当なmain.goを開いて,breakpointを仕掛けたい場所をクリックする.

f:id:h3poteto:20200924211002p:plain

こんなのが出れば成功. この状態でM-x dap-debugしてみれば良い.デバッグ設定を聞かれるので,Go Launch File Configurationを選ぶ.

goのプログラムが実行され,breakpointまで進んで,こんな画面になるはず.

f:id:h3poteto:20200924213058p:plain

デバッグの設定を生み出す

launch.jsonを作る

デバッグ対象が,例えばサーバープログラムとかで,main.go を単純にrunすればいいだけであれば特に困らないだろう.しかし,github.com/spf13/cobra とかを使って,CLIとかを作っていたりすると,サブコマンドを与えなければ希望するロジックに入ってくれなかったりする(デフォルトだとhelpだけが表示されたりね).その場合は,プログラムの起動コマンドを指定したくなるだろう. また,デバッグ対象がやたら深いディレクトリだったりして,そこからトップディレクトリのmain.goを呼び出してデバッグしたいような場合もあるだろう.この場合はプロジェクトディレクトリを指定したくなる.

こういう用途に答えるために,DAPデバッグ設定を読み込めるようになっている.

dap-mode/features.md at 73e94cc95060af70e19a5f21e88c99dfa2803fe3 · emacs-lsp/dap-mode · GitHub

All that needs to be done is to add a launch.json file at the project root and to run dap-debug

launch.json というファイル名で,プロジェクトルートに置いておくと,自動で読んでくれる.中身は,以下の説明の通り.

github.com

たとえば,cobraのサブコマンドとして,get というサブコマンドが必要だとしよう. その場合は,

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "MyGet",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "/home/h3poteto/src/github.com/h3poteto/package-name",
            "env": {},
            "args": "get"
        }
    ]
}

という設定を書く. programmain.goが存在するディレクトリを,絶対パスで記述する必要がある(相対パスではダメだった).

これを用意した上で,M-x dap-debug すると,デバッグ設定選択画面に MyGetが表示されるようになる.ので,それを選べばこの設定で起動してくれる.

Emacs側に定義する

launch.jsonを作ってもいいんだけど,これをプロジェクト設定ごとに生み出したり,そもそもソースのディレクトリを汚すのはイマイチとも思う.

そういう人向けに,dap-register-debug-templateが用意されており,Emacsの設定側で追加しておくこともできる. リンクはJavaの設定のものだが,goでも↑と同じ内容を設定すれば問題ない.

emacs-lsp.github.io

都度生成する

それもいまいちと思う場合は,この関数を都度呼び出せるようにすれば良い.幸い,インタラクティブに設定を生成してくれる関数を書いている方がいるので,こういうのを作ると便利かもしれない.

ladicle.com

ちなみにこちらの関数では,デバッグ時にargsの指定はできないようなので(テストのときは入ってる),上記のようにサブコマンドを叩きたい場合は,argsの項目を追加している.

トラブルシューティング

dap-go-setupに失敗する

M-x dap-go-setup 時点で,

internal/modules/cjs/loader.js:979
  throw err;
  ^

Error: Cannot find module '/Users/h3poteto/.emacs.d/.extension/vscode/golang.go/extension/out/src/debugAdapter/goDebug.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:976:15)
    at Function.Module._load (internal/modules/cjs/loader.js:859:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}

Process Go Debug stderr finished

というようなエラーが出る.これは既にIssue化されており,

github.com

vscode-go側がアップデートして,ファイル名が変更になっている.

2020年9月23日現在,

$ pwd
/home/h3poteto/.emacs.d/.extension/vscode/golang.go/extension
[h3poteto]:~/.emacs.d/.extension/vscode/golang.go/extension
$ ls -lha dist
total 4.3M
drwxr-xr-x 2 h3poteto h3poteto 4.0K  9月 10 00:07 .
drwxr-xr-x 7 h3poteto h3poteto 4.0K  9月 10 00:07 ..
-rw-r--r-- 1 h3poteto h3poteto 228K  9月  2 22:02 debugAdapter2.js
-rw-r--r-- 1 h3poteto h3poteto 1.1M  9月  2 22:02 debugAdapter.js
-rw-r--r-- 1 h3poteto h3poteto 3.0M  9月  2 22:02 goMain.js

こうなっており,そもそもディレクトからして違う.

これに対しては,PullRequestも出ており,

github.com

debugAdapter.js を使えば問題ないらしい.

というわけで,

  :config
  (custom-set-variables '(dap-go-debug-program `("node"
                                                 ,(f-join dap-go-debug-path "extension/dist/debugAdapter.js"))))

みたいなのをuse-package内に追加してやる必要がある. これでdap-go-setup は成功した.

各ウィンドウサイズが気に入らない

f:id:h3poteto:20200924211109p:plain

こんな感じで,replの画面が見えないほどに占拠されるのが嫌だ.というわけでこの辺を設定でいじっている.

dap-modeのuse-packageに

  :config
  (custom-set-variables `(dap-output-window-max-height 50))

こんなのを追加している.

いい感じのサイズになる.

f:id:h3poteto:20200924211023p:plain

他の画面についても,この辺の変数でいじくれるようになっている.

dap-mode/dap-ui.el at 73e94cc95060af70e19a5f21e88c99dfa2803fe3 · emacs-lsp/dap-mode · GitHub