diff --git a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts b/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts index 03f10d6cd5579..f7b315c035736 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts @@ -31,6 +31,9 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha $localShowMessage(severity: Severity, msg: string): void { this._extensionService._logOrShowMessage(severity, msg); } + $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + return this._extensionService._activateById(extensionId, activationEvent); + } $onWillActivateExtension(extensionId: ExtensionIdentifier): void { this._extensionService._onWillActivateExtension(extensionId); } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 04abd54e92b84..925871aefc4b8 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -68,6 +68,7 @@ export interface IInitData { environment: IEnvironment; workspace?: IWorkspaceData; resolvedExtensions: ExtensionIdentifier[]; + hostExtensions: ExtensionIdentifier[]; extensions: IExtensionDescription[]; telemetryInfo: ITelemetryInfo; logLevel: LogLevel; @@ -546,6 +547,7 @@ export interface MainThreadTaskShape extends IDisposable { export interface MainThreadExtensionServiceShape extends IDisposable { $localShowMessage(severity: Severity, msg: string): void; + $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string): Promise; $onWillActivateExtension(extensionId: ExtensionIdentifier): void; $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void; $onExtensionActivationFailed(extensionId: ExtensionIdentifier): void; @@ -749,7 +751,7 @@ export interface ExtHostExtensionServiceShape { $resolveAuthority(remoteAuthority: string): Promise; $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; - $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; diff --git a/src/vs/workbench/api/node/extHostExtensionActivator.ts b/src/vs/workbench/api/node/extHostExtensionActivator.ts index 102fd5bedced0..b6f508452883e 100644 --- a/src/vs/workbench/api/node/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/node/extHostExtensionActivator.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -161,6 +160,12 @@ export class EmptyExtension extends ActivatedExtension { } } +export class HostExtension extends ActivatedExtension { + constructor() { + super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); + } +} + export class FailedExtension extends ActivatedExtension { constructor(activationError: Error) { super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); @@ -170,7 +175,7 @@ export class FailedExtension extends ActivatedExtension { export interface IExtensionsActivatorHost { showMessage(severity: Severity, message: string): void; - actualActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise; + actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; } export class ExtensionActivatedByEvent { @@ -192,6 +197,7 @@ export class ExtensionsActivator { private readonly _registry: ExtensionDescriptionRegistry; private readonly _resolvedExtensionsSet: Set; + private readonly _hostExtensionsMap: Map; private readonly _host: IExtensionsActivatorHost; private readonly _activatingExtensions: Map>; private readonly _activatedExtensions: Map; @@ -200,10 +206,12 @@ export class ExtensionsActivator { */ private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; - constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { + constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { this._registry = registry; this._resolvedExtensionsSet = new Set(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); + this._hostExtensionsMap = new Map(); + hostExtensions.forEach((extensionId) => this._hostExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId)); this._host = host; this._activatingExtensions = new Map>(); this._activatedExtensions = new Map(); @@ -231,7 +239,7 @@ export class ExtensionsActivator { return NO_OP_VOID_PROMISE; } let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); - return this._activateExtensions(activateExtensions, reason).then(() => { + return this._activateExtensions(activateExtensions.map(e => e.identifier), reason).then(() => { this._alreadyActivatedEvents[activationEvent] = true; }); } @@ -242,14 +250,20 @@ export class ExtensionsActivator { throw new Error('Extension `' + extensionId + '` is not known'); } - return this._activateExtensions([desc], reason); + return this._activateExtensions([desc.identifier], reason); } /** * Handle semantics related to dependencies for `currentExtension`. * semantics: `redExtensions` must wait for `greenExtensions`. */ - private _handleActivateRequest(currentExtension: IExtensionDescription, greenExtensions: { [id: string]: IExtensionDescription; }, redExtensions: IExtensionDescription[]): void { + private _handleActivateRequest(currentExtensionId: ExtensionIdentifier, greenExtensions: { [id: string]: ExtensionIdentifier; }, redExtensions: ExtensionIdentifier[]): void { + if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentExtensionId))) { + greenExtensions[ExtensionIdentifier.toKey(currentExtensionId)] = currentExtensionId; + return; + } + + const currentExtension = this._registry.getExtensionDescription(currentExtensionId)!; let depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); let currentExtensionGetsGreenLight = true; @@ -261,61 +275,71 @@ export class ExtensionsActivator { continue; } - const depDesc = this._registry.getExtensionDescription(depId); + const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId)); + if (dep && !dep.activationFailed) { + // the dependency is already activated OK + continue; + } - if (!depDesc) { - // 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)); - const error = new Error(`Unknown dependency '${depId}'`); + 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)); + const error = new Error(`Dependency ${depId} failed to activate`); + (error).detail = dep.activationFailedError; this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; } - const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId)); - if (dep) { - if (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)); - const error = new Error(`Dependency ${depId} failed to activate`); - (error).detail = dep.activationFailedError; - this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); - return; - } - } else { + if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(depId))) { // must first wait for the dependency to activate currentExtensionGetsGreenLight = false; - greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc; + greenExtensions[ExtensionIdentifier.toKey(depId)] = this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!; + continue; } + + const depDesc = this._registry.getExtensionDescription(depId); + if (depDesc) { + // must first wait for the dependency to activate + currentExtensionGetsGreenLight = false; + greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc.identifier; + continue; + } + + // 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)); + const error = new Error(`Unknown dependency '${depId}'`); + this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); + return; } if (currentExtensionGetsGreenLight) { - greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtension; + greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtensionId; } else { - redExtensions.push(currentExtension); + redExtensions.push(currentExtensionId); } } - private _activateExtensions(extensionDescriptions: IExtensionDescription[], reason: ExtensionActivationReason): Promise { - // console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id)); - if (extensionDescriptions.length === 0) { + private _activateExtensions(extensionIds: ExtensionIdentifier[], reason: ExtensionActivationReason): Promise { + // console.log('_activateExtensions: ', extensionIds.map(p => p.value)); + if (extensionIds.length === 0) { return Promise.resolve(undefined); } - extensionDescriptions = extensionDescriptions.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p.identifier))); - if (extensionDescriptions.length === 0) { + extensionIds = extensionIds.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p))); + if (extensionIds.length === 0) { return Promise.resolve(undefined); } - let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null), - red: IExtensionDescription[] = []; + let greenMap: { [id: string]: ExtensionIdentifier; } = Object.create(null), + red: ExtensionIdentifier[] = []; - for (let i = 0, len = extensionDescriptions.length; i < len; i++) { - this._handleActivateRequest(extensionDescriptions[i], greenMap, red); + for (let i = 0, len = extensionIds.length; i < len; i++) { + this._handleActivateRequest(extensionIds[i], greenMap, red); } // Make sure no red is also green for (let i = 0, len = red.length; i < len; i++) { - const redExtensionKey = ExtensionIdentifier.toKey(red[i].identifier); + const redExtensionKey = ExtensionIdentifier.toKey(red[i]); if (greenMap[redExtensionKey]) { delete greenMap[redExtensionKey]; } @@ -336,8 +360,8 @@ export class ExtensionsActivator { }); } - private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - const extensionKey = ExtensionIdentifier.toKey(extensionDescription.identifier); + private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + const extensionKey = ExtensionIdentifier.toKey(extensionId); if (this._activatedExtensions.has(extensionKey)) { return Promise.resolve(undefined); @@ -348,9 +372,9 @@ export class ExtensionsActivator { return currentlyActivatingExtension; } - const newlyActivatingExtension = this._host.actualActivateExtension(extensionDescription, reason).then(undefined, (err) => { - this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionDescription.identifier.value, err.message)); - console.error('Activating extension `' + extensionDescription.identifier.value + '` failed: ', err.message); + 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)); + console.error('Activating extension `' + extensionId.value + '` failed: ', err.message); console.log('Here is the error stack: ', err.stack); // Treat the extension as being empty return new FailedExtension(err); diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 1f4a1e100b065..82228a9ec5e56 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { createApiFactory, initializeExtensionApi, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, IWorkspaceData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; -import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule } from 'vs/workbench/api/node/extHostExtensionActivator'; +import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule, HostExtension } from 'vs/workbench/api/node/extHostExtensionActivator'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; @@ -192,7 +192,11 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._registry = new ExtensionDescriptionRegistry(initData.extensions); this._storage = new ExtHostStorage(this._extHostContext); this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); - this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, { + + const hostExtensions = new Set(); + 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); @@ -208,7 +212,13 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } }, - actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise => { + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId); return this._activateExtension(extensionDescription, reason); } }); @@ -666,11 +676,14 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { ); } - public $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - return ( - this._barrier.wait() - .then(_ => this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent))) - ); + public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + await this._barrier.wait(); + if (!this._registry.getExtensionDescription(extensionId)) { + // unknown extension => ignore + return false; + } + await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); + return true; } public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 7e212550ceada..34e7cbf0a57db 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -440,6 +440,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { name: this._labelService.getWorkspaceLabel(workspace) }, resolvedExtensions: [], + hostExtensions: [], extensions: extensionDescriptions, telemetryInfo, logLevel: this._logService.getLevel(), diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts index 8329209bdd591..17316e8db3fbf 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts @@ -199,7 +199,7 @@ export class ExtensionHostProcessManager extends Disposable { return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService); } - public activate(extension: ExtensionIdentifier, activationEvent: string): Promise { + public activate(extension: ExtensionIdentifier, activationEvent: string): Promise { return this._extensionHostProcessProxy.then((proxy) => { return proxy.value.$activate(extension, activationEvent); }); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 306aa82dc6763..02e2314100359 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -823,6 +823,16 @@ export class ExtensionService extends Disposable implements IExtensionService { } } + public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + const results = await Promise.all( + this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) + ); + const activated = results.some(e => e); + if (!activated) { + throw new Error(`Unknown extension ${extensionId.value}`); + } + } + public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); } diff --git a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts index c4182d11adf7a..6ad4cac017d24 100644 --- a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts @@ -104,7 +104,7 @@ export class ExtensionDescriptionRegistry { this.addNode(from); this.addNode(to); if (this._arcs.has(from)) { - this._arcs.get(from).push(to); + this._arcs.get(from)!.push(to); } else { this._arcs.set(from, [to]); } @@ -112,7 +112,7 @@ export class ExtensionDescriptionRegistry { getArcs(id: string): string[] { if (this._arcs.has(id)) { - return this._arcs.get(id); + return this._arcs.get(id)!; } return []; } @@ -169,7 +169,7 @@ export class ExtensionDescriptionRegistry { } while (madeProgress); // The remaining nodes are bad and have loops - return nodes.map(id => descs.get(id)); + return nodes.map(id => descs.get(id)!); } public containsActivationEvent(activationEvent: string): boolean {