タスクプロバイダー
ユーザーは通常、Visual Studio Code において タスク を tasks.json ファイルで定義します。しかし、ソフトウェア開発の過程には、VS Code 拡張機能の「タスクプロバイダー」によって自動的に検出できるタスクもいくつか存在します。VS Code で Tasks: Run Task コマンドが実行されると、すべてのアクティブなタスクプロバイダーが、ユーザーが実行可能なタスクを提供します。tasks.json ファイルでは、特定のフォルダーやワークスペースに対してユーザーが手動でタスクを定義できますが、タスクプロバイダーはワークスペースの詳細を検出し、それに対応する VS Code タスクを自動的に作成できます。たとえば、タスクプロバイダーは make や Rakefile のような特定のビルドファイルが存在するかを確認し、ビルドタスクを作成するといったことが可能です。このトピックでは、拡張機能がどのようにタスクを自動検出し、エンドユーザーに提供するかを説明します。
このガイドでは、Rakefile で定義されたタスクを自動検出するタスクプロバイダーの構築方法を学びます。完全なソースコードは以下から入手できます: https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample
タスク定義
システム内のタスクを一意に識別するために、タスクを提供する拡張機能は、タスクを識別するプロパティを定義する必要があります。Rake の例では、タスク定義は次のようになります。
"taskDefinitions": [
{
"type": "rake",
"required": [
"task"
],
"properties": {
"task": {
"type": "string",
"description": "The Rake task to customize"
},
"file": {
"type": "string",
"description": "The Rake file that provides the task. Can be omitted."
}
}
}
]
これは rake タスクのタスク定義をコントリビュート(寄与)します。タスク定義には task と file という 2 つの属性があります。task は Rake タスクの名前であり、file はそのタスクを含む Rakefile を指します。task プロパティは必須ですが、file プロパティはオプションです。file 属性を省略した場合、ワークスペースフォルダーのルートにある Rakefile が使用されます。
When 節 (When clause)
タスク定義には、オプションで when プロパティを持たせることができます。when プロパティは、その種類のタスクが利用可能になる条件を指定します。when プロパティは、VS Code 内の他の場所で when プロパティが機能するのと同様に機能します。タスク定義を作成する際は、以下のコンテキストを常に考慮する必要があります。
shellExecutionSupported: VS Code がShellExecutionタスクを実行できる場合に true となります(VS Code がデスクトップアプリケーションとして実行されている場合や、Dev Containers などのリモート拡張機能を使用している場合など)。processExecutionSupported: VS Code がProcessExecutionタスクを実行できる場合に true となります(VS Code がデスクトップアプリケーションとして実行されている場合や、Dev Containers などのリモート拡張機能を使用している場合など)。現時点では常にshellExecutionSupportedと同じ値になります。customExecutionSupported: VS Code がCustomExecutionを実行できる場合に true となります。これは常に true です。
タスクプロバイダー
拡張機能がコード補完をサポートできるようにする言語プロバイダーと同様に、拡張機能はタスクプロバイダーを登録して、利用可能なすべてのタスクを計算させることができます。これは、次のコードスニペットに示すように vscode.tasks 名前空間を使用して行われます。
import * as vscode from 'vscode';
let rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
const taskProvider = vscode.tasks.registerTaskProvider('rake', {
provideTasks: () => {
if (!rakePromise) {
rakePromise = getRakeTasks();
}
return rakePromise;
},
resolveTask(_task: vscode.Task): vscode.Task | undefined {
const task = _task.definition.task;
// A Rake task consists of a task and an optional file as specified in RakeTaskDefinition
// Make sure that this looks like a Rake task by checking that there is a task.
if (task) {
// resolveTask requires that the same definition object be used.
const definition: RakeTaskDefinition = <any>_task.definition;
return new vscode.Task(
definition,
_task.scope ?? vscode.TaskScope.Workspace,
definition.task,
'rake',
new vscode.ShellExecution(`rake ${definition.task}`)
);
}
return undefined;
}
});
provideTasks と同様に、resolveTask メソッドは VS Code によって呼び出され、拡張機能からタスクを取得します。resolveTask は provideTasks の代わりに呼び出すことができ、これを実装するプロバイダーのパフォーマンスを向上させることを目的としています。たとえば、ユーザーが拡張機能によって提供されたタスクを実行するキーバインディングを持っている場合、VS Code が provideTasks を呼び出してすべてのタスクを待機するよりも、そのタスクプロバイダーに対して resolveTask を呼び出し、該当する 1 つのタスクのみを素早く取得する方が効率的です。ユーザーが個々のタスクプロバイダーをオフにできるように設定を設けることが推奨されており、一般的です。特定のプロバイダーのタスク取得が遅いとユーザーが気付いた場合、プロバイダーをオフにする可能性があります。その場合でも、ユーザーは tasks.json 内でそのプロバイダーのタスクを参照し続けるかもしれません。resolveTask が実装されていない場合、tasks.json 内のタスクが作成されなかったという警告が表示されます。resolveTask があれば、拡張機能は tasks.json で定義されたタスクに対してタスクを提供し続けることができます。
getRakeTasks の実装は以下の処理を行います。
- 各ワークスペースフォルダーに対して
rake -AT -f Rakefileコマンドを使用し、Rakefileで定義されているすべての Rake タスクをリストアップします。 - stdio 出力を解析します。
- リストされた各タスクに対して、
vscode.Taskの実装を作成します。
Rake タスクのインスタンス化には package.json ファイルで定義されたタスク定義が必要であるため、VS Code は以下のような TypeScript インターフェースを使用して構造を定義します。
interface RakeTaskDefinition extends vscode.TaskDefinition {
/**
* The task name
*/
task: string;
/**
* The rake file containing the task
*/
file?: string;
}
最初のワークスペースフォルダーにある compile というタスクから出力が得られると仮定すると、対応するタスクの作成は次のようになります。
let task = new vscode.Task(
{ type: 'rake', task: 'compile' },
vscode.workspace.workspaceFolders[0],
'compile',
'rake',
new vscode.ShellExecution('rake compile')
);
出力にリストされた各タスクに対して、上記パターンを使用して対応する VS Code タスクが作成され、getRakeTasks の呼び出しからすべてのタスクの配列が返されます。
ShellExecution は、OS 固有のシェルで rake compile コマンドを実行します(例:Windows では PowerShell、Ubuntu では bash など)。タスクがプロセスを直接実行する必要がある場合(シェルを生成しない場合)、vscode.ProcessExecution を使用できます。ProcessExecution には、拡張機能がプロセスに渡される引数を完全に制御できるという利点があります。ShellExecution を使用すると、シェルコマンドの解釈(bash でのワイルドカード展開など)が利用されます。ShellExecution を単一のコマンドラインで作成する場合、コマンド内の適切な引用符やエスケープ(空白を扱うためなど)を拡張機能側で保証する必要があります。
CustomExecution
一般的に、ShellExecution や ProcessExecution はシンプルであるため、これらを使用するのが最善です。しかし、実行間で多くの状態を保持する必要があるタスクや、独立したスクリプトやプロセスとしてうまく機能しないタスク、または出力の広範な処理が必要なタスクには、CustomExecution が適している場合があります。CustomExecution は通常、複雑なビルドシステムで使用されます。CustomExecution には、タスクの実行時に呼び出されるコールバックのみがあります。これにより、タスクで実行できることの柔軟性が高まりますが、同時に、タスクプロバイダーがプロセス管理や必要な出力解析に責任を持つ必要があることを意味します。また、タスクプロバイダーは Pseudoterminal を実装し、それを CustomExecution コールバックから返す責任も負います。
return new vscode.Task(
definition,
vscode.TaskScope.Workspace,
`${flavor} ${flags.join(' ')}`,
CustomBuildTaskProvider.CustomBuildScriptType,
new vscode.CustomExecution(
async (): Promise<vscode.Pseudoterminal> => {
// When the task is executed, this callback will run. Here, we setup for running the task.
return new CustomBuildTaskTerminal(
this.workspaceRoot,
flavor,
flags,
() => this.sharedState,
(state: string) => (this.sharedState = state)
);
}
)
);
Pseudoterminal の実装を含む完全な例は、以下から確認できます: https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample/src/customTaskProvider.ts