VSCodeでPerlデバッグ


この記事はPerl Advent Calendar 2017の18日目の記事です。
昨日は @karupanerura さんで「コマンドラインツールとしてのperl」でした。

普段はPerlでの開発の場合、Mac+Vimで書いていたのですが、
最近、他言語での開発でVSCodeを使って、ターミナル、Git操作やデバッガなど組み込みのインターフェースのバランスの良さと、その軽快さに非常に驚きました。
そこで、改めてPerl用のプラグインなどを探してみて、デバッガ プラグイン「Perl Debug」を知りましたのでご紹介します。

なお、VSCode本体の紹介やインストールについては説明省略しています。
また、私の検証環境がMacなので、WindowsやLinux環境では若干動作が異なる可能性があります。

Perl Debug

Morten Henriksen氏作 (Github)

まずは、紹介Gif画像(開発リポジトリのreadme.mdより)を御覧ください。

しゅ、しゅごい。。。
あの素っ気ないperl debuggerが、なんて色気!

仕組みとしては、Perlに標準でバンドルされているデバッガ(perl5db)をVSCodeから起動して、標準入出力経由で操作しています。

対象環境

Mac, Linux, Windows(Strawberry perl, Activeperl) のperl5をサポートしています。
テストされたOSとperlバージョンの組み合わせはreadme.mdを参照ください。
perl5dbのインターフェースに依存しているので、少々バージョンに差異があっても動くかもしれません。

機能

以下をサポートしています。

  • ブレークポイント (条件付きブレークは未サポート)
  • 変数インスペクト
  • ウォッチ式
  • コールスタック
  • 変数値の書き換え
  • デバッグコンソールでの任意の式の評価
  • エディタ上でのホバーによる変数の値表示

インストール

  1. VSCodeサイドバーの拡張機能ボタン、または、メニューから[表示]>[拡張機能]を開く
  2. 検索窓に「perl」と入力。
    • デフォルトでは初期値として@sort:installs category:languagesと入力されているかもしれませんが、初期値を削除して「perl」とだけ入力してください。languagesカテゴリだと検索にヒットしないようですので。
  3. 検索結果の「Perl Debug」(作者はMorten Henriksen氏)をインストール

設定

Macでのlaunch.json設定例を以下に。
(Windowsの設定例は開発リポジトリのreadme.mdに例が記載されています。)

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Perl Debug",   // お好みの名前を
            "type": "perl",
            "request": "launch",
            "exec": "perl",         // perlコマンド(パスが通っていればフルパスでなくてよい)
            "execArgs": [],         // その他指定したいperlオプション
            "root": "${workspaceRoot}/",                        // 起動されるデバッガのワーキングディレクトリ
            "inc": ["${workspaceRoot}/lib"],                    // 追加で必要なPERL5LIBパス
            "program": "${workspaceRoot}/test.pl",              // デバッグ対象プログラム
            //"program": "${workspaceRoot}/${relativeFile}",    // VSCodeでその時アクティブなファイル
            "args": [],                                         // デバッグ対象プログラムのコマンドライン引数
            "stopOnEntry": true                                 // プログラムの最初のステートメントでbreak
        }
    ]
}

以下に幾つか補足します。

exec: plenvなどでプロジェクト毎に異なるバージョンのperlをインストールしている環境ではexecに適宜フルパスを指定してください。

execArgs: シンプルなプログラム起動の場合は空でOKです。後述[モジュールのプリロード]参照

inc: 開発プログラムで追加で参照するライブラリの基準パスを配列で指定してください。cartonなどでプロジェクト毎に異なるモジュールを利用しているケースでは、${workspaceRoot}/local/lib/perl5などとして追加すると良いでしょう。

ちなみに私は、plenv+cartonの環境で、carton -- exec zsh -lとして起動したシェルからVSCodeを起動することで、execやincなどで細かいパス指定を省略しています。

program, args: デバッグ対象のプログラムとそのコマンドライン引数を指定します。"${workspaceRoot}/${relativeFile}"という指定をした場合は、VSCodeで現在開いてアクティブなファイルを実行対象コマンドとみなしますが、逆に不便な場合が多いかも。

stopOnEntry: プログラムの最初のステートメントでbreakするオプションです。逆にfalseだと、VSCodeで指定した他のブレークポイントで止まってくれないようですので、実質的にはtrueにするしかないようです。

env: デバッガ起動時に設定したい環境変数を連想配列で指定。通常は空でOKです。環境変数でデバッガやプログラムの制御をしたい場合などに利用します。以下の例では、2つの環境変数をセットしています。

            "env": {
                "IS_DEBUG": "1",
                "FOO": "BAR"
            }

trace: 本プラグインのログを有効にするブール値。trueにすると、root指定したディレクトリ上にperl_debugger.logファイルが生成されます。

設定値に${...}という変数が利用されていますが、詳細は VSCodeのドキュメントを参照ください。
${workspaceRoot}/${relativeFile}は結局${file}と同じこと、とか、${env.NAME}で環境変数が参照できる、とか、たった今知りました。。

応用: Mojolicousでの利用

PSGIプログラム指定方法

MojoliciousなどをPSGIアプリケーションとして起動させたい場合は、前述の設定のprogramにはPSGIサーバを、argsにそのオプションを指定します。例は以下のようになります。

            //...
            "program": "${workspaceRoot}/local/bin/plackup",
            "args": ["--port=3000", "script/myapp"],          // 起動オプション
            //...

モジュールのプリロード

しかし、前述のprogram, args設定だけではまだ期待通りにブレークポイントで止まってくれないかもしれません。

PSGIアプリケーションではPSGIサーバが動的にアプリケーションのモジュールをロードするため、デバッガ起動直後では、サーバのことは知っているけど、その上に乗っかるアプリケーションのことは知らない、という状態となります。
perl5db(標準のPerlデバッガ)ではこのようなケースのために、未コンパイルのモジュールでbreakするためのb postponeコマンドやb compileコマンドがありますが、これらは[ファイルパス]:[行番号]の形式での指定ができません。
一方、VSCodeとPerl Debugプラグインは、エディタで指定したブレークポイントの情報をファイル名と行番号として把握しており、これをperl5dbにb <ファイル名>:<行番号>形式のコマンドでブレークポイントを設定しています。
このギャップのために、ブレークポイントが期待通りに設定されないことになります。

対策として、breakしたいモジュールをプリロードする方法があります。
以下の例では、launch.jsonのexecArgs設定で-Mオプションを利用したperlのモジュールロードをしています。

            //...
            "exec": "perl",
            "execArgs": ["-MMyApp::Foo", "-MMyApp::Bar"], // MyApp::Foo, MyApp::Barモジュールにbreakしたい場合
            "program": "${workspaceRoot}/local/bin/plackup",
            "args": ["--port=3000", "script/myapp"],
            //...

プロセスfork

forkするようなプログラムの場合、本プラグインはタスクプロセス(perlプロセス)の停止/再起動の制御がうまくできないようです。
そのため、listenポートを掴んだままバックグラウンドでプロセスが生き残ったまま、ということになりがちですので、perlの起動前に前回の残骸プロセスをkillするようにラップしたコマンドをexecとして設定するとよいかもしれません。(後述[デバッガの拡張]参照)

Mojoliciousのcommandをインプリメントする場合などは、forkしないのでこのような心配は不要です。

応用: デバッガの拡張

本プラグインは設定ファイルの値で以下のようなイメージで外部プロセスを起動します。
※詳細はデバッグコンソール上の出力ログで確認できます。
<exec> <execArgs> -I<inc> -d <program> <args>

設定で例えば、execパラメタにperl、programパラメタにtest.plを指定した場合のイメージは以下のようになります。
perl -d test.pl

本プラグインは標準のperl5dbを想定してコマンド発行、応答のパースをしていますが、
execの設定でperlをラップした自作コマンドを指定することで、
前処理/後処理の追加やperl5dbをカスタムしたバージョンを使ったり、リモート環境のデバッグを行うなどの応用ができそうです。

以下は、plackupのバックグラウンドプロセスが残っていたらkillする前処理を入れたラッパの例です。
このシェルスクリプトをperlwrapperというファイルにでも保存し、execの設定で指定します。

perlwrapper
#!/bin/sh
killall plackup > /dev/null 2>&1
perl "$@"

その他

デバッグコンソールでは、入力がそのままperlデバッガ(perl5db)に渡ります。
その為、変数値の再設定や、x <任意の式>コマンドなどで任意の式を評価するなどが可能です。
しかし、c(継続)、n(ステップオーバー)、s(ステップイン)などの処理が進むコマンドを実行すると、VSCodeは進行したことを把握できていないので、エディタ画面の表示は更新されません。

私の環境やデバッグ対象プログラムによるのかもしれませんが、Mojoliciousのデバッグをすると非常に動作が重く、ステップ実行がもっさりするので、結局デバッグコンソールで直接perl5dbコマンドを叩いています。
どうも変数インスペクトの処理で時間がかかっているように見えますが、詳細はわかっていません。

IntelliJ IDEA

IntelliJ IDEAなどJetBrains社製品をお使いの方は、@AnnPin さんの以下の素晴らしい記事をご参照ください。
こちらは、IntelliJ IDEAのプラグイン側と合わせて作られたDevel::Camelcadedbというソケット通信にも対応している専用のデバッガを用いています。

2017年 Perl5 との戦いに生き残るための最高の開発環境を手に入れる
2017年 Perl5 との戦いに生き残るための最高の開発環境を手に入れる - リモートデバッグ 編

最後に

皆様の開発の一助になればと。

明日は @magnolia_k_ さんです。
楽しみですね。