提交 85d03110 编写于 作者: I isidor

debug: introduce debugTaskRunner

上级 85112b01
......@@ -11,7 +11,6 @@ import * as errors from 'vs/base/common/errors';
import severity from 'vs/base/common/severity';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -22,18 +21,15 @@ import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expres
import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel';
import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions';
import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager';
import Constants from 'vs/workbench/contrib/markers/browser/constants';
import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { parse, getFirstFrame } from 'vs/base/common/console';
import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IAction } from 'vs/base/common/actions';
......@@ -42,12 +38,12 @@ import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug';
import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils';
import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner';
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint';
......@@ -55,23 +51,6 @@ const DEBUG_DATA_BREAKPOINTS_KEY = 'debug.databreakpoint';
const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint';
const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions';
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;
};
}
const enum TaskRunResult {
Failure,
Success
}
export class DebugService implements IDebugService {
_serviceBrand: undefined;
......@@ -81,6 +60,7 @@ export class DebugService implements IDebugService {
private readonly _onDidEndSession: Emitter<IDebugSession>;
private model: DebugModel;
private viewModel: ViewModel;
private taskRunner: DebugTaskRunner;
private configurationManager: ConfigurationManager;
private toDispose: IDisposable[];
private debugType: IContextKey<string>;
......@@ -107,8 +87,6 @@ export class DebugService implements IDebugService {
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IExtensionService private readonly extensionService: IExtensionService,
@IMarkerService private readonly markerService: IMarkerService,
@ITaskService private readonly taskService: ITaskService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService
......@@ -136,6 +114,7 @@ export class DebugService implements IDebugService {
this.toDispose.push(this.model);
this.viewModel = new ViewModel(contextKeyService);
this.taskRunner = this.instantiationService.createInstance(DebugTaskRunner);
this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.lifecycleService.onShutdown(this.dispose, this);
......@@ -299,7 +278,7 @@ export class DebugService implements IDebugService {
"Compound must have \"configurations\" attribute set in order to start multiple configurations."));
}
if (compound.preLaunchTask) {
const taskResult = await this.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask);
const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, this.showError);
if (taskResult === TaskRunResult.Failure) {
this.endInitializingState();
return false;
......@@ -411,7 +390,7 @@ export class DebugService implements IDebugService {
}
const workspace = launch ? launch.workspace : this.contextService.getWorkspace();
const taskResult = await this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask);
const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, this.showError);
if (taskResult === TaskRunResult.Success) {
return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options);
}
......@@ -548,7 +527,7 @@ export class DebugService implements IDebugService {
if (session.configuration.postDebugTask) {
try {
await this.runTask(session.root, session.configuration.postDebugTask);
await this.taskRunner.runTask(session.root, session.configuration.postDebugTask);
} catch (err) {
this.notificationService.error(err);
}
......@@ -587,8 +566,8 @@ export class DebugService implements IDebugService {
return Promise.resolve(TaskRunResult.Success);
}
await this.runTask(session.root, session.configuration.postDebugTask);
return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask);
await this.taskRunner.runTask(session.root, session.configuration.postDebugTask);
return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, this.showError);
};
const extensionDebugSession = getExtensionHostDebugSession(session);
......@@ -715,129 +694,6 @@ export class DebugService implements IDebugService {
return undefined;
}
//---- task management
private async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined): Promise<TaskRunResult> {
try {
const taskSummary = await this.runTask(root, taskId);
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 this.showError(err.message, [this.taskService.configureAction()]);
return TaskRunResult.Failure;
}
}
private 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);
});
}
//---- focus management
async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise<void> {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. 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 {
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,
) { }
async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined, onError: (msg: string, actions: IAction[]) => Promise<void>): Promise<TaskRunResult> {
try {
const taskSummary = await this.runTask(root, taskId);
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);
});
}
}
......@@ -25,7 +25,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
const $ = dom.$;
export class StartView extends ViewPane {
static ID = 'workbench.debug.startView';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册