goでもなんでもいいんだけれど,最近Emacsでプログラムを書くときはLSPのお世話になることが非常に多い.ここまでEmacsでいろいろとできると,デバッグもやりたくなってくる.
LSPが流行るのと同時にDAPというのも流行っていて,LSPと同じようにエディタを問わずDebug Adapter Protocolを使えば,言語ごとのデバッグ支援が受けられるというものだ.そういうわけで,Emacsでもdap-modeというのがあり,これを使うことでEmacsでもIDEのようにデバッグができるようになる.
ちなみに,全面的にgoのデバッグをする話をするが,適切なextensionさえあれば他言語でも同じようにデバッグできるはずなので(そのためのAdapter Protocolだ),別言語の場合は言語ごとの設定だけを読み替えてくれれば良い.
dap-modeのインストール
Emacsにdap-modeを入れる
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を入れておく必要がある.
大抵のgo環境であればこれで足りる.
$ go get github.com/go-delve/delve/cmd/dlv
nodejsを入れる
これはdap-modeの都合なのだが,内部でvscode-goというvscode向けの拡張機能を利用している.こいつがjsで書かれており,Emacsのdap-modeは内部でこれを利用してdelveを叩いてデバッグを行っている.というわけで,node.jsの実行環境が必要になる.
バージョン制約は特に書いてなかったので,適当にnodeが動く環境を用意すれば良いと思う.
dap-go-setup
ここまできたらEmacsを開いて,M-x dap-go-setup
を叩く.これは初回だけ必要になるんだけど,内部ではこのタイミングでEmacsの設定ディレクトリ内の .extension/vscode/golang.go/extension
に vscode-goをダウンロードしてきている.
デバッグしてみる
適当なmain.goを開いて,breakpointを仕掛けたい場所をクリックする.
こんなのが出れば成功.
この状態でM-x dap-debug
してみれば良い.デバッグ設定を聞かれるので,Go Launch File Configuration
を選ぶ.
goのプログラムが実行され,breakpointまで進んで,こんな画面になるはず.
デバッグの設定を生み出す
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
というファイル名で,プロジェクトルートに置いておくと,自動で読んでくれる.中身は,以下の説明の通り.
たとえば,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" } ] }
という設定を書く. program
はmain.go
が存在するディレクトリを,絶対パスで記述する必要がある(相対パスではダメだった).
これを用意した上で,M-x dap-debug
すると,デバッグ設定選択画面に MyGet
が表示されるようになる.ので,それを選べばこの設定で起動してくれる.
Emacs側に定義する
launch.jsonを作ってもいいんだけど,これをプロジェクト設定ごとに生み出したり,そもそもソースのディレクトリを汚すのはイマイチとも思う.
そういう人向けに,dap-register-debug-template
が用意されており,Emacsの設定側で追加しておくこともできる.
リンクはJavaの設定のものだが,goでも↑と同じ内容を設定すれば問題ない.
都度生成する
それもいまいちと思う場合は,この関数を都度呼び出せるようにすれば良い.幸い,インタラクティブに設定を生成してくれる関数を書いている方がいるので,こういうのを作ると便利かもしれない.
ちなみにこちらの関数では,デバッグ時に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化されており,
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も出ており,
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
は成功した.
各ウィンドウサイズが気に入らない
こんな感じで,replの画面が見えないほどに占拠されるのが嫌だ.というわけでこの辺を設定でいじっている.
dap-modeのuse-packageに
:config (custom-set-variables `(dap-output-window-max-height 50))
こんなのを追加している.
いい感じのサイズになる.
他の画面についても,この辺の変数でいじくれるようになっている.
dap-mode/dap-ui.el at 73e94cc95060af70e19a5f21e88c99dfa2803fe3 · emacs-lsp/dap-mode · GitHub