My Visual Studio code extension allows users to deploy and debug applications on remote devices.
To do this I need to have the application "installed" in a local folder that will then be synchronized to the target using rsync.
Some languages/tools provide a simple and standardized way to do this (ex: dotnet publish, for .NET core apps), others don't.
To be flexible and let users choose the method they prefer, my extension relies on a task with a specific name to perform the operation. I have a resolveDebugConfiguration function that executes the task and then fills out the information inside debug connection to let user debug his app.
As long as this is a single task, this is not an issue. I can start it using vscode.tasks.executeTask and wait for its completion using OnEndTaskProcess. The task should be shell/process and a non-zero exit code means failure. I also check OnEndTask, to be sure that I don't miss other kinds of completition (ex: an invalid path in the cmd field or the user defined a custom task etc.).
Some users may want to have a more complex structure for this. For example having their deployment tasks depend on a build task, to ensure that the latest version is deployed, or perform additional operations in between, so I no longer have a single task, but a chain of tasks connected via dependson.
This is ok and still works...until it fails.
Or, better, until one of the dependency tasks fail.
In this case, I have no notification from OnEndTaskProcess or even OnEndTask and the tasks after the failed one remain inside vscode.tasks.taskExecutions list forever, it seems. So my resolveDebugConfiguration function never returns and vscode remains in the "starting debugger" state forever...
My code looks like this:
// retrieve task given its name
const tasks = await vscode.tasks.fetchTasks();
var deploy: vscode.Task | undefined = undefined;
for (var task of tasks) {
switch (task.name) {
case "deploy":
deploy = task;
break;
}
}
if (deploy === undefined) {
// error message telling user that he has to define a task named "deploy"
return null;
}
var emitter = new EventEmitter();
// the process event arrives before the generic terminate one (checked inside vscode sources)
vscode.tasks.onDidEndTaskProcess(e => {
if (e.execution.task.name === "deploy") {
emitter.emit("terminated", e.exitCode);
}
});
vscode.tasks.onDidEndTask(e => {
// check if task is still running, otherwise report an error
var taskexecutions = vscode.tasks.taskExecutions;
for (var taskexecution of taskexecutions) {
if (taskexecution.task.name === "deploy") {
return;
}
}
emitter.emit("terminated", -1);
});
try {
var execution = await vscode.tasks.executeTask(deploy);
}
catch (e) {
// catch execution exceptions and show a message to the user
return null;
}
var code = await new Promise<Number>((resolve, reject) => {
emitter.on("terminated", code => resolve(code));
});
if (code !== 0) {
// deploy task failed
return null;
}
// local deployment succeeded, move on...
Some of the tasks may take a long time, so using a timeout may be a solution worse than the problem.
It would be nice to have OnEndTask called even when a dependency fails, preventing the actual task from running, but this does not seem to happen.
I plan to open an issue on vscode github repo, but maybe someone has a solution that doesn't involves changes to the ide itself.