タスクプロバイダー
通常、ユーザーはVisual Studio Codeでタスクをtasks.jsonファイルで定義します。しかし、ソフトウェア開発中には、VS Code拡張機能がタスクプロバイダーによって自動的に検出できるタスクもいくつかあります。VS Codeからタスク: タスクの実行コマンドを実行すると、すべてのアクティブなタスクプロバイダーがユーザーが実行できるタスクを提供します。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プロパティを含めることができます。whenプロパティは、このタイプのタスクが利用可能になる条件を指定します。whenプロパティは、whenプロパティがあるVS Codeの他の場所と同じように機能します。タスク定義を作成する際には、次のコンテキストを常に考慮する必要があります。
shellExecutionSupported: VS Codeがデスクトップアプリケーションとして実行されている場合、またはDev Containersなどのリモート拡張機能を使用している場合など、VS CodeがShellExecutionタスクを実行できる場合にTrueです。processExecutionSupported: VS Codeがデスクトップアプリケーションとして実行されている場合、またはDev Containersなどのリモート拡張機能を使用している場合など、VS CodeがProcessExecutionタスクを実行できる場合にTrueです。現在、常に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がそのタスクプロバイダーに対してresolveTaskを呼び出して1つのタスクをすばやく取得する方が、provideTasksを呼び出して拡張機能がすべてのタスクを提供するのを待つよりも良いでしょう。ユーザーが個々のタスクプロバイダーをオフにできる設定を持つことは良い習慣であり、これは一般的です。ユーザーは特定のプロバイダーからのタスクの取得が遅いことに気づき、プロバイダーをオフにするかもしれません。この場合でも、ユーザーはtasks.jsonでこのプロバイダーからのいくつかのタスクを参照するかもしれません。resolveTaskが実装されていない場合、tasks.jsonで定義されたタスクが作成されなかったという警告が表示されます。resolveTaskを使用すると、拡張機能はtasks.jsonで定義されたタスクに対してタスクを提供できます。
getRakeTasksの実装は以下を実行します。
- 各ワークスペースフォルダーに対して
rake -AT -f Rakefileコマンドを使用して、Rakefileで定義されたすべてのrakeタスクをリストします。 - 標準入出力出力を解析します。
- リストされたすべてのタスクに対して、
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にあります。