Improve extension activation failure messages

This commit is contained in:
Sandeep Somavarapu
2019-03-15 10:19:21 +01:00
parent 48612205e1
commit d6058d77c0
5 changed files with 92 additions and 27 deletions

View File

@@ -7,22 +7,38 @@ import { SerializedError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/node/extHost.protocol';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, ExtensionActivationError, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
private readonly _extensionService: ExtensionService;
private readonly _notificationService: INotificationService;
private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService;
private readonly _windowService: IWindowService;
constructor(
extHostContext: IExtHostContext,
@IExtensionService extensionService: IExtensionService
@IExtensionService extensionService: IExtensionService,
@INotificationService notificationService: INotificationService,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IWindowService windowService: IWindowService
) {
if (extensionService instanceof ExtensionService) {
this._extensionService = extensionService;
}
this._notificationService = notificationService;
this._extensionsWorkbenchService = extensionsWorkbenchService;
this._windowService = windowService;
}
public dispose(): void {
@@ -49,6 +65,64 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
console.error(`[${extensionId}]${error.message}`);
console.error(error.stack);
}
$onExtensionActivationFailed(extensionId: ExtensionIdentifier): void {
async $onExtensionActivationError(extensionId: ExtensionIdentifier, activationError: ExtensionActivationError): Promise<void> {
if (typeof activationError === 'string') {
this._extensionService._logOrShowMessage(Severity.Error, activationError);
} else {
this._handleMissingDependency(extensionId, activationError.dependencies[0]);
}
}
private async _handleMissingDependency(extensionId: ExtensionIdentifier, missingDependency: string): Promise<void> {
const extension = await this._extensionService.getExtension(extensionId.value);
const local = await this._extensionsWorkbenchService.queryLocal();
const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingDependency }))[0];
if (installedDependency) {
await this._handleMissingInstalledDependency(extension, installedDependency);
} else {
await this._handleMissingNotInstalledDependency(extension, missingDependency);
}
}
private async _handleMissingInstalledDependency(extension: IExtensionDescription, missingInstalledDependency: IExtension): Promise<void> {
const extName = extension.displayName || extension.name;
if (missingInstalledDependency.enablementState === EnablementState.Enabled || missingInstalledDependency.enablementState === EnablementState.WorkspaceEnabled) {
this._notificationService.notify({
severity: Severity.Error,
message: localize('reload window', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not loaded. Would you like to reload the window to load the extension?", extName, missingInstalledDependency.displayName),
actions: {
primary: [new Action('reload', localize('reload', "Reload Window"), '', true, () => this._windowService.reloadWindow())]
}
});
} else {
this._notificationService.notify({
severity: Severity.Error,
message: localize('disabledDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is disabled. Would you like to enable the extension and reload the window?", extName, missingInstalledDependency.displayName),
actions: {
primary: [new Action('enable', localize('enable dep', "Enable and Reload"), '', true,
() => this._extensionsWorkbenchService.setEnablement([missingInstalledDependency], missingInstalledDependency.enablementState === EnablementState.Disabled ? EnablementState.Enabled : EnablementState.WorkspaceEnabled)
.then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))]
}
});
}
}
private async _handleMissingNotInstalledDependency(extension: IExtensionDescription, missingDependency: string): Promise<void> {
const extName = extension.displayName || extension.name;
const dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] })).firstPage[0];
if (dependencyExtension) {
this._notificationService.notify({
severity: Severity.Error,
message: localize('uninstalledDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName),
actions: {
primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true,
() => this._extensionsWorkbenchService.install(dependencyExtension)
.then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))]
}
});
} else {
this._notificationService.error(localize('unknownDep', "Cannot activate extension '{0}' because it depends on an unknown extension '{1}'.", extName, missingDependency));
}
}
}

View File

@@ -36,7 +36,7 @@ import { ITreeItem, IRevealOptions } from 'vs/workbench/common/views';
import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionDescription, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
import { IRPCProtocol, createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
@@ -575,7 +575,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
$activateExtension(extensionId: ExtensionIdentifier, activationEvent: string | null): Promise<void>;
$onWillActivateExtension(extensionId: ExtensionIdentifier): void;
$onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string | null): void;
$onExtensionActivationFailed(extensionId: ExtensionIdentifier): void;
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
}

View File

@@ -5,9 +5,9 @@
import * as nls from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionActivationError, MissingDependenciesError } from 'vs/workbench/services/extensions/common/extensions';
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -173,8 +173,7 @@ export class FailedExtension extends ActivatedExtension {
}
export interface IExtensionsActivatorHost {
showMessage(severity: Severity, message: string): void;
onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void;
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
}
@@ -283,7 +282,7 @@ export class ExtensionsActivator {
if (dep && dep.activationFailed) {
// Error condition 2: a dependency has already failed activation
this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId));
this._host.onExtensionActivationError(currentExtension.identifier, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId));
const error = new Error(`Dependency ${depId} failed to activate`);
(<any>error).detail = dep.activationFailedError;
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
@@ -306,7 +305,7 @@ export class ExtensionsActivator {
}
// Error condition 1: unknown dependency
this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not installed or disabled. Please install or enable '{1}' and reload the window.", currentExtension.displayName || currentExtension.identifier.value, depId));
this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependenciesError([depId]));
const error = new Error(`Unknown dependency '${depId}'`);
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
return;
@@ -373,7 +372,7 @@ export class ExtensionsActivator {
}
const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => {
this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message));
this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message));
console.error('Activating extension `' + extensionId.value + '` failed: ', err.message);
console.log('Here is the error stack: ', err.stack);
// Treat the extension as being empty

View File

@@ -9,7 +9,6 @@ import { originalFSPath } from 'vs/base/common/resources';
import { Barrier } from 'vs/base/common/async';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { ILogService } from 'vs/platform/log/common/log';
@@ -20,7 +19,7 @@ import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionA
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionDescription, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@@ -208,19 +207,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId)));
this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, initData.hostExtensions, {
showMessage: (severity: Severity, message: string): void => {
this._mainThreadExtensionsProxy.$localShowMessage(severity, message);
switch (severity) {
case Severity.Error:
console.error(message);
break;
case Severity.Warning:
console.warn(message);
break;
default:
console.log(message);
}
onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => {
this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error);
},
actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
@@ -383,7 +371,6 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes);
return activatedExtension;
}, (err) => {
this._mainThreadExtensionsProxy.$onExtensionActivationFailed(extensionDescription.identifier);
this._logExtensionActivationTimes(extensionDescription, reason, 'failure');
throw err;
});