작업 공급자
사용자는 일반적으로 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의 두 가지 속성이 있습니다. task는 Rake 작업의 이름이며, file은 작업이 포함된 Rakefile을 가리킵니다. task 속성은 필수이며, file 속성은 선택 사항입니다. file 속성이 생략되면 작업 영역 폴더의 루트에 있는 Rakefile이 사용됩니다.
When 절
작업 정의에는 선택적으로 when 속성이 있을 수 있습니다. when 속성은 이 유형의 작업이 사용 가능한 조건을 지정합니다. when 속성은 VS Code의 다른 곳에서 when 속성이 있는 곳과 동일한 방식으로 작동합니다. 작업 정의를 만들 때 항상 고려해야 할 컨텍스트는 다음과 같습니다.
shellExecutionSupported: VS Code가ShellExecution작업을 실행할 수 있을 때 (예: VS Code가 데스크톱 애플리케이션으로 실행되거나 Dev Containers와 같은 원격 확장 중 하나를 사용하는 경우).processExecutionSupported: VS Code가ProcessExecution작업을 실행할 수 있을 때 (예: VS Code가 데스크톱 애플리케이션으로 실행되거나 Dev Containers와 같은 원격 확장 중 하나를 사용하는 경우). 현재shellExecutionSupported와 동일한 값을 가집니다.customExecutionSupported: VS Code가CustomExecution을 실행할 수 있을 때. 이는 항상 참입니다.
작업 제공자
코드 완료를 지원하는 확장 기능과 유사하게, 확장은 모든 사용 가능한 작업을 계산하기 위해 작업 제공자를 등록할 수 있습니다. 이는 다음 코드 조각에 표시된 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를 호출하여 모든 작업을 제공하기 위해 provideTasks를 호출하고 기다리는 대신 빠르게 하나의 작업을 얻는 것이 좋습니다. 개별 작업 제공자를 끄도록 하는 설정을 갖는 것이 좋은 관행이므로 이는 일반적입니다. 사용자는 특정 제공자의 작업이 가져오는 속도가 느리다는 것을 알게 되어 해당 제공자를 끌 수 있습니다. 이 경우 사용자는 여전히 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에서 확인할 수 있습니다.