に参加して、VS Code の AI 支援開発について学びましょう。

デバッガー拡張機能

Visual Studio Code のデバッグアーキテクチャは、拡張機能の作成者が既存のデバッガーを VS Code に簡単に統合できるようにしながら、それらすべてと共通のユーザーインターフェースを持つことを可能にします。

VS Code には、組み込みのデバッガー拡張機能が 1 つ付属しています。それは、Node.js デバッガー拡張機能であり、VS Code がサポートする多くのデバッガー機能の優れたショーケースとなっています。

VS Code Debug Features

このスクリーンショットは、次のデバッグ機能を示しています。

  1. デバッグ構成管理。
  2. 開始/停止およびステップ実行のためのデバッグアクション。
  3. ソース、関数、条件付き、インラインブレークポイント、およびログポイント。
  4. マルチスレッドおよびマルチプロセスサポートを含むスタックトレース。
  5. ビューとホバーでの複雑なデータ構造のナビゲート。
  6. ホバーまたはソースにインラインで表示される変数。
  7. ウォッチ式の管理。
  8. オートコンプリート付きの対話型評価用のデバッグコンソール。

このドキュメントは、あらゆるデバッガーを VS Code で動作させることができるデバッガー拡張機能を作成するのに役立ちます。

VS Code のデバッグアーキテクチャ

VS Code は、デバッガーバックエンドと通信するために導入した抽象プロトコルに基づいて、汎用的な (言語に依存しない) デバッガー UI を実装しています。デバッガーは通常このプロトコルを実装しないため、デバッガーをプロトコルに「適応」させるための中間者が必要です。この中間者は通常、デバッガーと通信するスタンドアロンプロセスです。

VS Code Debug Architecture

この中間者をデバッグアダプター (略してDA) と呼び、DA と VS Code の間で使用される抽象プロトコルをデバッグアダプタープロトコル (略してDAP) と呼びます。デバッグアダプタープロトコルは VS Code とは独立しているため、独自のWeb サイトがあり、そこで導入と概要、詳細な仕様、および既知の実装とサポートツールのリストを見つけることができます。DAP の歴史と動機は、このブログ投稿で説明されています。

デバッグアダプターは VS Code から独立しており、他の開発ツールで使用できるため、拡張機能とコントリビューションポイントに基づいた VS Code の拡張機能アーキテクチャとは異なります。

このため、VS Code は debuggers というコントリビューションポイントを提供しており、特定のデバッグタイプ (例: Node.js デバッガーの場合は node) の下にデバッグアダプターをコントリビューションできます。ユーザーがそのタイプのデバッグセッションを開始すると、VS Code は登録された DA を起動します。

したがって、最も最小限の形式では、デバッガー拡張機能はデバッグアダプターの実装の宣言的な貢献に過ぎず、拡張機能は基本的に追加コードのないデバッグアダプターのパッケージングコンテナです。

VS Code Debug Architecture 2

より現実的なデバッガー拡張機能は、次の宣言的な項目の多くまたはすべてを VS Code に貢献します。

  • デバッガーがサポートする言語のリスト。VS Code はこれらの言語のブレークポイントを設定するための UI を有効にします。
  • デバッガーによって導入されたデバッグ構成属性の JSON スキーマ。VS Code はこのスキーマを使用して、launch.json エディターで構成を検証し、IntelliSense を提供します。JSON スキーマの構成要素 $refdefinition はサポートされていないことに注意してください。
  • VS Code によって作成される初期 launch.json のデフォルトのデバッグ構成。
  • ユーザーが launch.json ファイルに追加できるデバッグ構成スニペット。
  • デバッグ構成で使用できる変数の宣言。

詳細については、contributes.breakpoints および contributes.debuggers のリファレンスを参照してください。

上記の純粋な宣言的なコントリビューションに加えて、デバッグ拡張機能 API は次のコードベースの機能を提供します。

  • VS Code によって作成される初期 launch.json の動的に生成されるデフォルトのデバッグ構成。
  • 使用するデバッグアダプターを動的に決定します。
  • デバッグアダプターに渡される前にデバッグ構成を検証または変更します。
  • デバッグアダプターと通信します。
  • デバッグコンソールにメッセージを送信します。

このドキュメントの残りの部分では、デバッガー拡張機能を開発する方法について説明します。

モックデバッグ拡張機能

このチュートリアルでは、ゼロからデバッグアダプターを作成するのは少し大変なので、教育用の「デバッグアダプター スターターキット」として作成したシンプルな DA から始めます。これは、実際のデバッガーと通信するのではなく、モックするため、「Mock Debug」と呼ばれています。Mock Debug はデバッガーをシミュレートし、ステップ実行、続行、ブレークポイント、例外、変数アクセスをサポートしますが、実際のデバッガーには接続されていません。

mock-debug の開発セットアップに入る前に、まず VS Code Marketplace から事前に構築されたバージョンをインストールして試してみましょう。

  • 拡張機能ビューに切り替えて、「mock」と入力して Mock Debug 拡張機能を検索し、
  • 拡張機能を「インストール」して「リロード」します。

Mock Debug を試すには

  • 新しい空のフォルダー mock test を作成し、VS Code で開きます。
  • ファイル readme.md を作成し、任意のテキストを数行入力します。
  • 実行とデバッグビュー (⇧⌘D (Windows、Linux Ctrl+Shift+D)) に切り替えて、launch.json ファイルを作成リンクを選択します。
  • VS Code は、デフォルトの起動構成を作成するために「デバッガー」を選択させます。「Mock Debug」を選択してください。
  • 緑色の開始ボタンを押し、次にEnterを押して、提案されたファイルreadme.mdを確定します。

デバッグセッションが開始され、readme ファイルを「ステップ実行」したり、ブレークポイントを設定してヒットさせたり、例外に遭遇したりすることができます (行に exception という単語がある場合)。

Mock Debugger running

Mock Debug を独自の開発の出発点として使用する前に、まず事前にビルドされたバージョンをアンインストールすることをお勧めします。

  • 拡張機能ビューに切り替えて、Mock Debug 拡張機能の歯車アイコンをクリックします。
  • 「アンインストール」アクションを実行し、ウィンドウを「リロード」します。

Mock Debug の開発セットアップ

次に、Mock Debug のソースを取得し、VS Code 内で開発を開始しましょう。

git clone https://github.com/microsoft/vscode-mock-debug.git
cd vscode-mock-debug
yarn

VS Code でプロジェクトフォルダー vscode-mock-debug を開きます。

パッケージの中身は?

  • package.json は mock-debug 拡張機能のマニフェストです。
    • mock-debug 拡張機能の貢献をリストアップしています。
    • compile および watch スクリプトは、TypeScript ソースを out フォルダーにトランスパイルし、その後のソース変更を監視するために使用されます。
    • 依存関係 vscode-debugprotocolvscode-debugadapter、および vscode-debugadapter-testsupport は、Node ベースのデバッグアダプターの開発を簡素化する NPM モジュールです。
  • src/mockRuntime.ts は、シンプルなデバッグ API を持つ*モック*ランタイムです。
  • ランタイムをデバッグアダプタープロトコルに*適合させる*コードは src/mockDebug.ts にあります。ここでは、DAP のさまざまなリクエストのハンドラーを見つけることができます。
  • デバッガー拡張機能の実装はデバッグアダプター内にあるため、拡張機能コード (つまり、拡張機能ホストプロセスで実行されるコード) はまったく必要ありません。しかし、Mock Debug には小さな src/extension.ts があります。これは、デバッガー拡張機能の拡張機能コードで何ができるかを示すためです。

次に、Extension 起動構成を選択し、F5 を押して Mock Debug 拡張機能をビルドして起動します。最初に、TypeScript ソースが out フォルダーに完全にトランスパイルされます。完全なビルド後、変更をトランスパイルする*ウォッチャータスク*が開始されます。

ソースのトランスパイル後、Mock Debug 拡張機能がデバッグモードで実行されている状態で、「[Extension Development Host]」というラベルの付いた新しい VS Code ウィンドウが表示されます。そのウィンドウから、readme.md ファイルを含む mock test プロジェクトを開き、'F5' でデバッグセッションを開始し、ステップ実行します。

Debugging Extension and Server

拡張機能をデバッグモードで実行しているため、src/extension.ts にブレークポイントを設定してヒットさせることができますが、前述のように、拡張機能で実行される興味深いコードはあまりありません。興味深いコードは、別のプロセスであるデバッグアダプターで実行されます。

デバッグアダプター自体をデバッグするには、デバッグモードで実行する必要があります。これは、デバッグアダプターをサーバーモードで実行し、VS Code がそれに接続するように構成することで最も簡単に実現できます。VS Code の vscode-mock-debug プロジェクトで、ドロップダウンメニューから起動構成Serverを選択し、緑色の開始ボタンを押します。

拡張機能のデバッグセッションがすでにアクティブであったため、VS Code デバッガー UI はマルチセッションモードに入ります。これは、CALL STACK ビューにExtensionServerという2つのデバッグセッションの名前が表示されることで示されます。

Debugging Extension and Server

これで、拡張機能と DA の両方を同時にデバッグできるようになりました。ここに至るより速い方法は、両方のセッションを自動的に起動する Extension + Server 起動構成を使用することです。

拡張機能と DA をデバッグするための代替の、さらに簡単なアプローチは、以下にあります。

ファイル src/mockDebug.ts のメソッド launchRequest(...) の先頭にブレークポイントを設定し、最後に、モックテストの起動構成にポート 4711debugServer 属性を追加して、モックデバッガーが DA サーバーに接続するように構成します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "mock",
      "request": "launch",
      "name": "mock test",
      "program": "${workspaceFolder}/readme.md",
      "stopOnEntry": true,
      "debugServer": 4711
    }
  ]
}

このデバッグ構成を起動すると、VS Code はモックデバッグアダプターを別のプロセスとして起動せず、すでに実行中のサーバーのローカルポート 4711 に直接接続し、launchRequest のブレークポイントにヒットするはずです。

このセットアップで、Mock Debug を簡単に編集、トランスパイル、デバッグできるようになりました。

しかし、ここからが本当の作業の始まりです。src/mockDebug.tssrc/mockRuntime.ts にあるデバッグアダプターのモック実装を、「実際の」デバッガーまたはランタイムと通信するコードに置き換える必要があります。これには、デバッグアダプタープロトコルを理解し、実装することが含まれます。これに関する詳細は、こちらで見つけることができます。

デバッガー拡張機能の package.json の構造

デバッグアダプターのデバッガー固有の実装を提供するだけでなく、デバッガー拡張機能は、さまざまなデバッグ関連のコントリビューションポイントに貢献する package.json を必要とします。

では、Mock Debug の package.json を詳しく見てみましょう。

すべての VS Code 拡張機能と同様に、package.json は拡張機能の基本的なプロパティであるnamepublisher、およびversionを宣言します。categoriesフィールドを使用して、VS Code 拡張機能マーケットプレースで拡張機能を見つけやすくします。

{
  "name": "mock-debug",
  "displayName": "Mock Debug",
  "version": "0.24.0",
  "publisher": "...",
  "description": "Starter extension for developing debug adapters for VS Code.",
  "author": {
    "name": "...",
    "email": "..."
  },
  "engines": {
    "vscode": "^1.17.0",
    "node": "^7.9.0"
  },
  "icon": "images/mock-debug-icon.png",
  "categories": ["Debuggers"],

  "contributes": {
    "breakpoints": [{ "language": "markdown" }],
    "debuggers": [
      {
        "type": "mock",
        "label": "Mock Debug",

        "program": "./out/mockDebug.js",
        "runtime": "node",

        "configurationAttributes": {
          "launch": {
            "required": ["program"],
            "properties": {
              "program": {
                "type": "string",
                "description": "Absolute path to a text file.",
                "default": "${workspaceFolder}/${command:AskForProgramName}"
              },
              "stopOnEntry": {
                "type": "boolean",
                "description": "Automatically stop after launch.",
                "default": true
              }
            }
          }
        },

        "initialConfigurations": [
          {
            "type": "mock",
            "request": "launch",
            "name": "Ask for file name",
            "program": "${workspaceFolder}/${command:AskForProgramName}",
            "stopOnEntry": true
          }
        ],

        "configurationSnippets": [
          {
            "label": "Mock Debug: Launch",
            "description": "A new configuration for launching a mock debug program",
            "body": {
              "type": "mock",
              "request": "launch",
              "name": "${2:Launch Program}",
              "program": "^\"\\${workspaceFolder}/${1:Program}\""
            }
          }
        ],

        "variables": {
          "AskForProgramName": "extension.mock-debug.getProgramName"
        }
      }
    ]
  },

  "activationEvents": ["onDebug", "onCommand:extension.mock-debug.getProgramName"]
}

次に、デバッグ拡張機能に固有のコントリビューションを含むcontributesセクションを見てみましょう。

まず、breakpointsコントリビューションポイントを使用して、ブレークポイント設定が有効になる言語をリストします。これがないと、Markdown ファイルにブレークポイントを設定することはできません。

次に、debuggersセクションです。ここでは、デバッグタイプmockの下に1つのデバッガーが導入されます。ユーザーは、起動構成でこのタイプを参照できます。オプションの属性labelを使用して、UI に表示する際にデバッグタイプに適切な名前を付けることができます。

デバッグ拡張機能がデバッグアダプターを使用するため、そのコードへの相対パスがprogram属性として与えられます。拡張機能を自己完結型にするために、アプリケーションは拡張機能フォルダー内に存在する必要があります。慣例として、このアプリケーションはoutまたはbinという名前のフォルダー内に保持しますが、別の名前を使用することもできます。

VS Codeは様々なプラットフォームで動作するため、DAプログラムが異なるプラットフォームもサポートしていることを確認する必要があります。そのためには、以下の選択肢があります。

  1. プログラムがプラットフォームに依存しない方法で実装されている場合、たとえば、サポートされているすべてのプラットフォームで利用可能なランタイム上で実行されるプログラムとして実装されている場合、runtime属性を介してこのランタイムを指定できます。現在、VS Code は node および mono ランタイムをサポートしています。上記のモックデバッグアダプターはこのアプローチを使用しています。

  2. DA の実装が異なるプラットフォームで異なる実行ファイルを必要とする場合、program 属性は次のように特定のプラットフォームに対して修飾できます。

    "debuggers": [{
        "type": "gdb",
        "windows": {
            "program": "./bin/gdbDebug.exe",
        },
        "osx": {
            "program": "./bin/gdbDebug.sh",
        },
        "linux": {
            "program": "./bin/gdbDebug.sh",
        }
    }]
    
  3. 両方のアプローチを組み合わせることも可能です。以下の例は、macOSとLinuxではランタイムが必要ですがWindowsでは必要ないMonoアプリケーションとして実装されたMono DAからのものです。

    "debuggers": [{
        "type": "mono",
        "program": "./bin/monoDebug.exe",
        "osx": {
            "runtime": "mono"
        },
        "linux": {
            "runtime": "mono"
        }
    }]
    

configurationAttributes は、このデバッガーで利用できる launch.json 属性のスキーマを宣言します。このスキーマは、launch.json の検証、および起動構成の編集時の IntelliSense とホバーヘルプのサポートに使用されます。

initialConfigurations は、このデバッガーのデフォルトの launch.json の初期内容を定義します。この情報は、プロジェクトに launch.json がなく、ユーザーがデバッグセッションを開始したり、実行とデバッグビューでlaunch.json ファイルを作成リンクを選択したりする場合に使用されます。この場合、VS Code はユーザーにデバッグ環境を選択させ、対応する launch.json を作成します。

Debugger Quickpick

launch.json の初期内容を package.json で静的に定義する代わりに、DebugConfigurationProvider を実装することで初期構成を動的に計算できます (詳細については、以下のDebugConfigurationProvider の使用セクションを参照してください)。

configurationSnippetsは、launch.jsonを編集する際にIntelliSenseに表示される起動構成スニペットを定義します。慣例として、スニペットのlabel属性にデバッグ環境名を接頭辞として付けることで、多くのスニペット提案のリストで提示されたときに明確に識別できるようにします。

variables の貢献は、「変数」を「コマンド」にバインドします。これらの変数は、${command:xyz} 構文を使用して起動構成で使用でき、デバッグセッションが開始されると、バインドされたコマンドから返された値に置き換えられます。

コマンドの実装は拡張機能にあり、UI のない単純な式から、拡張機能 API で利用可能な UI 機能に基づいた洗練された機能まで多岐にわたります。Mock Debug は、変数 AskForProgramName をコマンド extension.mock-debug.getProgramName にバインドします。src/extension.ts にあるこのコマンドの実装では、showInputBox を使用してユーザーにプログラム名を入力させます。

vscode.commands.registerCommand('extension.mock-debug.getProgramName', config => {
  return vscode.window.showInputBox({
    placeHolder: 'Please enter the name of a markdown file in the workspace folder',
    value: 'readme.md'
  });
});

この変数は、起動構成の任意の文字列値で ${command:AskForProgramName} として使用できるようになりました。

DebugConfigurationProvider の使用

package.json のデバッグ貢献の静的な性質では不十分な場合、DebugConfigurationProvider を使用して、デバッグ拡張機能の以下の側面を動的に制御できます。

  • 新しく作成された launch.json の初期デバッグ構成は、たとえばワークスペースで利用可能なコンテキスト情報に基づいて動的に生成できます。
  • 起動構成は、新しいデバッグセッションを開始するために使用される前に、解決 (または変更) できます。これにより、ワークスペースで利用可能な情報に基づいてデフォルト値を入力できます。2つの解決メソッドが存在します。resolveDebugConfiguration は、変数が起動構成で置換される前に呼び出され、resolveDebugConfigurationWithSubstitutedVariables は、すべての変数が置換された後に呼び出されます。前者は、検証ロジックがデバッグ構成に追加の変数を挿入する場合に使用する必要があります。後者は、検証ロジックがすべてのデバッグ構成属性の最終値にアクセスする必要がある場合に使用する必要があります。

src/extension.tsMockConfigurationProvider は、resolveDebugConfiguration を実装して、launch.json が存在しないが、アクティブなエディターで Markdown ファイルが開いている場合にデバッグセッションが開始されるケースを検出します。これは、ユーザーがエディターでファイルを開いていて、launch.json を作成せずにデバッグしたいという典型的なシナリオです。

デバッグ構成プロバイダーは、vscode.debug.registerDebugConfigurationProvider を介して、通常は拡張機能の activate 関数で特定のデバッグタイプに登録されます。DebugConfigurationProvider が十分に早く登録されるようにするには、デバッグ機能が使用されるとすぐに拡張機能がアクティブ化される必要があります。これは、package.jsononDebug イベントの拡張機能アクティベーションを構成することで簡単に実現できます。

"activationEvents": [
    "onDebug",
    // ...
],

このすべてをキャッチする onDebug は、デバッグ機能が使用されるとすぐにトリガーされます。これは、拡張機能の起動コストが低い (つまり、起動シーケンスに多くの時間を費やさない) 限り問題なく機能します。デバッグ拡張機能の起動コストが高い場合 (たとえば、言語サーバーを起動するため)、onDebug アクティベーションイベントは、比較的早期にトリガーされ、特定のデバッグタイプを考慮しないため、他のデバッグ拡張機能に悪影響を与える可能性があります。

コストのかかるデバッグ拡張機能のより良いアプローチは、より細かいアクティベーションイベントを使用することです。

  • onDebugInitialConfigurations は、DebugConfigurationProviderprovideDebugConfigurations メソッドが呼び出される直前に発生します。
  • onDebugResolve:type は、指定されたタイプに対する DebugConfigurationProviderresolveDebugConfiguration または resolveDebugConfigurationWithSubstitutedVariables メソッドが呼び出される直前に発生します。

経験則: デバッグ拡張機能のアクティベーションが安価な場合は、onDebug を使用します。高価な場合は、DebugConfigurationProvider が対応するメソッド provideDebugConfigurations および/または resolveDebugConfiguration を実装しているかどうかに応じて、onDebugInitialConfigurations および/または onDebugResolve を使用します。

デバッガー拡張機能を公開する

デバッガー拡張機能を作成したら、マーケットプレイスに公開できます。

  • package.json 内の属性を更新して、デバッガー拡張機能の名前と目的を反映させます。
  • 拡張機能の公開で説明されているように、マーケットプレイスにアップロードします。

デバッガー拡張機能を開発するための別の方法

これまで見てきたように、デバッガー拡張機能の開発には通常、拡張機能とデバッグアダプターの両方を2つの並行セッションでデバッグすることが含まれます。前述のように、VS Codeはこれをうまくサポートしていますが、拡張機能とデバッグアダプターが1つのプログラムで、1つのデバッグセッションでデバッグできる場合、開発はより簡単になるでしょう。

このアプローチは、デバッグアダプターが TypeScript/JavaScript で実装されている限り、実際には簡単に実現できます。基本的な考え方は、デバッグアダプターを拡張機能内で直接実行し、セッションごとに新しい外部デバッグアダプターを起動する代わりに、VS Code がそれに接続するようにすることです。

このため、VS Code はデバッグアダプターの作成と実行方法を制御するための拡張機能 API を提供しています。DebugAdapterDescriptorFactory には、デバッグセッションが開始され、デバッグアダプターが必要になったときに VS Code によって呼び出される createDebugAdapterDescriptor メソッドがあります。このメソッドは、デバッグアダプターの実行方法を記述する記述子オブジェクト (DebugAdapterDescriptor) を返さなければなりません。

現在、VS Code はデバッグアダプターを実行する 3 つの異なる方法をサポートしており、その結果、3 つの異なる記述子タイプを提供しています。

  • DebugAdapterExecutable: このオブジェクトは、パスとオプションの引数およびランタイムを持つ外部実行可能ファイルとしてデバッグアダプターを記述します。実行可能ファイルは、デバッグアダプタープロトコルを実装し、stdin/stdout を介して通信する必要があります。これは VS Code のデフォルトの操作モードであり、DebugAdapterDescriptorFactory が明示的に登録されていない場合、VS Code は package.json の対応する値でこの記述子を自動的に使用します。
  • DebugAdapterServer: このオブジェクトは、特定のローカルポートまたはリモートポートを介して通信するサーバーとして実行されているデバッグアダプターを記述します。vscode-debugadapter npm モジュールに基づくデバッグアダプターの実装は、このサーバーモードを自動的にサポートします。
  • DebugAdapterInlineImplementation: このオブジェクトは、vscode.DebugAdapter インターフェイスを実装する JavaScript または TypeScript オブジェクトとしてデバッグアダプターを記述します。vscode-debugadapter npm モジュールのバージョン 1.38-pre.4 以降に基づくデバッグアダプターの実装は、このインターフェイスを自動的に実装します。

Mock Debugは、3種類のDebugAdapterDescriptorFactoriesと、それらが「mock」デバッグタイプに登録される方法の例を示しています。使用する実行モードは、グローバル変数runModeexternalserver、またはinlineのいずれかの値に設定することで選択できます。

開発においては、inline モードと server モードは特に便利です。これらにより、拡張機能とデバッグアダプターを単一のプロセス内でデバッグできるからです。

© . This site is unofficial and not affiliated with Microsoft.