mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 18:46:34 -05:00
* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 * update distro * fix layering * update distro * fix tests
180 lines
7.9 KiB
TypeScript
180 lines
7.9 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as nls from 'vs/nls';
|
|
import severity from 'vs/base/common/severity';
|
|
import { Event } from 'vs/base/common/event';
|
|
import Constants from 'vs/workbench/contrib/markers/browser/constants';
|
|
import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService';
|
|
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
|
|
import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
|
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
|
import { IAction } from 'vs/base/common/actions';
|
|
import { withUndefinedAsNull } from 'vs/base/common/types';
|
|
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
|
import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
|
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
|
|
|
|
function once(match: (e: TaskEvent) => boolean, event: Event<TaskEvent>): Event<TaskEvent> {
|
|
return (listener, thisArgs = null, disposables?) => {
|
|
const result = event(e => {
|
|
if (match(e)) {
|
|
result.dispose();
|
|
return listener.call(thisArgs, e);
|
|
}
|
|
}, null, disposables);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
export const enum TaskRunResult {
|
|
Failure,
|
|
Success
|
|
}
|
|
|
|
export class DebugTaskRunner {
|
|
|
|
private canceled = false;
|
|
|
|
constructor(
|
|
@ITaskService private readonly taskService: ITaskService,
|
|
@IMarkerService private readonly markerService: IMarkerService,
|
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
|
@IPanelService private readonly panelService: IPanelService,
|
|
@IDialogService private readonly dialogService: IDialogService,
|
|
) { }
|
|
|
|
cancel(): void {
|
|
this.canceled = true;
|
|
}
|
|
|
|
async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined, onError: (msg: string, actions: IAction[]) => Promise<void>): Promise<TaskRunResult> {
|
|
try {
|
|
this.canceled = false;
|
|
const taskSummary = await this.runTask(root, taskId);
|
|
if (this.canceled) {
|
|
return TaskRunResult.Failure;
|
|
}
|
|
|
|
const errorCount = taskId ? this.markerService.getStatistics().errors : 0;
|
|
const successExitCode = taskSummary && taskSummary.exitCode === 0;
|
|
const failureExitCode = taskSummary && taskSummary.exitCode !== 0;
|
|
const onTaskErrors = this.configurationService.getValue<IDebugConfiguration>('debug').onTaskErrors;
|
|
if (successExitCode || onTaskErrors === 'debugAnyway' || (errorCount === 0 && !failureExitCode)) {
|
|
return TaskRunResult.Success;
|
|
}
|
|
if (onTaskErrors === 'showErrors') {
|
|
this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
|
|
return Promise.resolve(TaskRunResult.Failure);
|
|
}
|
|
|
|
const taskLabel = typeof taskId === 'string' ? taskId : taskId ? taskId.name : '';
|
|
const message = errorCount > 1
|
|
? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel)
|
|
: errorCount === 1
|
|
? nls.localize('preLaunchTaskError', "Error exists after running preLaunchTask '{0}'.", taskLabel)
|
|
: nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary ? taskSummary.exitCode : 0);
|
|
|
|
const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], {
|
|
checkbox: {
|
|
label: nls.localize('remember', "Remember my choice in user settings"),
|
|
},
|
|
cancelId: 2
|
|
});
|
|
|
|
if (result.choice === 2) {
|
|
return Promise.resolve(TaskRunResult.Failure);
|
|
}
|
|
const debugAnyway = result.choice === 0;
|
|
if (result.checkboxChecked) {
|
|
this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors');
|
|
}
|
|
if (debugAnyway) {
|
|
return TaskRunResult.Success;
|
|
}
|
|
|
|
this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
|
|
return Promise.resolve(TaskRunResult.Failure);
|
|
} catch (err) {
|
|
await onError(err.message, [this.taskService.configureAction()]);
|
|
return TaskRunResult.Failure;
|
|
}
|
|
}
|
|
|
|
async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise<ITaskSummary | null> {
|
|
if (!taskId) {
|
|
return Promise.resolve(null);
|
|
}
|
|
if (!root) {
|
|
return Promise.reject(new Error(nls.localize('invalidTaskReference', "Task '{0}' can not be referenced from a launch configuration that is in a different workspace folder.", typeof taskId === 'string' ? taskId : taskId.type)));
|
|
}
|
|
// run a task before starting a debug session
|
|
const task = await this.taskService.getTask(root, taskId);
|
|
if (!task) {
|
|
const errorMessage = typeof taskId === 'string'
|
|
? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId)
|
|
: nls.localize('DebugTaskNotFound', "Could not find the specified task.");
|
|
return Promise.reject(createErrorWithActions(errorMessage));
|
|
}
|
|
|
|
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340
|
|
let taskStarted = false;
|
|
const inactivePromise: Promise<ITaskSummary | null> = new Promise((c, e) => once(e => {
|
|
// When a task isBackground it will go inactive when it is safe to launch.
|
|
// But when a background task is terminated by the user, it will also fire an inactive event.
|
|
// This means that we will not get to see the real exit code from running the task (undefined when terminated by the user).
|
|
// Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this.
|
|
return (e.kind === TaskEventKind.Inactive
|
|
|| (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined))
|
|
&& e.taskId === task._id;
|
|
}, this.taskService.onDidStateChange)(e => {
|
|
taskStarted = true;
|
|
c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null);
|
|
}));
|
|
|
|
const promise: Promise<ITaskSummary | null> = this.taskService.getActiveTasks().then(async (tasks): Promise<ITaskSummary | null> => {
|
|
if (tasks.filter(t => t._id === task._id).length) {
|
|
// Check that the task isn't busy and if it is, wait for it
|
|
const busyTasks = await this.taskService.getBusyTasks();
|
|
if (busyTasks.filter(t => t._id === task._id).length) {
|
|
taskStarted = true;
|
|
return inactivePromise;
|
|
}
|
|
// task is already running and isn't busy - nothing to do.
|
|
return Promise.resolve(null);
|
|
}
|
|
once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => {
|
|
// Task is active, so everything seems to be fine, no need to prompt after 10 seconds
|
|
// Use case being a slow running task should not be prompted even though it takes more than 10 seconds
|
|
taskStarted = true;
|
|
});
|
|
const taskPromise = this.taskService.run(task);
|
|
if (task.configurationProperties.isBackground) {
|
|
return inactivePromise;
|
|
}
|
|
|
|
return taskPromise.then(withUndefinedAsNull);
|
|
});
|
|
|
|
return new Promise((c, e) => {
|
|
promise.then(result => {
|
|
taskStarted = true;
|
|
c(result);
|
|
}, error => e(error));
|
|
|
|
setTimeout(() => {
|
|
if (!taskStarted) {
|
|
const errorMessage = typeof taskId === 'string'
|
|
? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.")
|
|
: nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId));
|
|
e({ severity: severity.Error, message: errorMessage });
|
|
}
|
|
}, 10000);
|
|
});
|
|
}
|
|
}
|