Cleanup in ExtHostExtensionService

This commit is contained in:
Alex Dima
2017-08-15 16:02:47 +02:00
parent f330bea3f5
commit 04b6de7655
6 changed files with 362 additions and 402 deletions

View File

@@ -604,7 +604,7 @@ class Extension<T> implements vscode.Extension<T> {
}
get exports(): T {
return <T>this._extensionService.get(this.id);
return <T>this._extensionService.getExtensionExports(this.id);
}
activate(): Thenable<T> {

View File

@@ -0,0 +1,241 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtensionDescriptionRegistry } from "vs/workbench/services/extensions/node/extensionDescriptionRegistry";
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = TPromise.as<void>(void 0);
export interface IExtensionMemento {
get<T>(key: string, defaultValue: T): T;
update(key: string, value: any): Thenable<boolean>;
}
export interface IExtensionContext {
subscriptions: IDisposable[];
workspaceState: IExtensionMemento;
globalState: IExtensionMemento;
extensionPath: string;
storagePath: string;
asAbsolutePath(relativePath: string): string;
}
/**
* Represents the source code (module) of an extension.
*/
export interface IExtensionModule {
activate(ctx: IExtensionContext): TPromise<IExtensionAPI>;
deactivate(): void;
}
/**
* Represents the API of an extension (return value of `activate`).
*/
export interface IExtensionAPI {
// _extensionAPIBrand: any;
}
export class ActivatedExtension {
activationFailed: boolean;
module: IExtensionModule;
exports: IExtensionAPI;
subscriptions: IDisposable[];
constructor(activationFailed: boolean, module: IExtensionModule, exports: IExtensionAPI, subscriptions: IDisposable[]) {
this.activationFailed = activationFailed;
this.module = module;
this.exports = exports;
this.subscriptions = subscriptions;
}
}
export class EmptyExtension extends ActivatedExtension {
constructor() {
super(false, { activate: undefined, deactivate: undefined }, undefined, []);
}
}
export class FailedExtension extends ActivatedExtension {
constructor() {
super(true, { activate: undefined, deactivate: undefined }, undefined, []);
}
}
export interface IExtensionsActivatorHost {
showMessage(severity: Severity, message: string): void;
actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension>;
}
export class ExtensionsActivator {
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _host: IExtensionsActivatorHost;
private readonly _activatingExtensions: { [extensionId: string]: TPromise<void>; };
private readonly _activatedExtensions: { [extensionId: string]: ActivatedExtension; };
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; };
constructor(registry: ExtensionDescriptionRegistry, host: IExtensionsActivatorHost) {
this._registry = registry;
this._host = host;
this._activatingExtensions = {};
this._activatedExtensions = {};
this._alreadyActivatedEvents = Object.create(null);
}
public isActivated(extensionId: string): boolean {
return hasOwnProperty.call(this._activatedExtensions, extensionId);
}
public getActivatedExtension(extensionId: string): ActivatedExtension {
if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
throw new Error('Extension `' + extensionId + '` is not known or not activated');
}
return this._activatedExtensions[extensionId];
}
public activateByEvent(activationEvent: string): TPromise<void> {
if (this._alreadyActivatedEvents[activationEvent]) {
return NO_OP_VOID_PROMISE;
}
let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
return this._activateExtensions(activateExtensions, 0).then(() => {
this._alreadyActivatedEvents[activationEvent] = true;
});
}
public activateById(extensionId: string): TPromise<void> {
let desc = this._registry.getExtensionDescription(extensionId);
if (!desc) {
throw new Error('Extension `' + extensionId + '` is not known');
}
return this._activateExtensions([desc], 0);
}
/**
* Handle semantics related to dependencies for `currentExtension`.
* semantics: `redExtensions` must wait for `greenExtensions`.
*/
private _handleActivateRequest(currentExtension: IExtensionDescription, greenExtensions: { [id: string]: IExtensionDescription; }, redExtensions: IExtensionDescription[]): void {
let depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
let currentExtensionGetsGreenLight = true;
for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
let depId = depIds[j];
let depDesc = this._registry.getExtensionDescription(depId);
if (!depDesc) {
// Error condition 1: unknown dependency
this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentExtension.id));
this._activatedExtensions[currentExtension.id] = new FailedExtension();
return;
}
if (hasOwnProperty.call(this._activatedExtensions, depId)) {
let dep = this._activatedExtensions[depId];
if (dep.activationFailed) {
// Error condition 2: a dependency has already failed activation
this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentExtension.id));
this._activatedExtensions[currentExtension.id] = new FailedExtension();
return;
}
} else {
// must first wait for the dependency to activate
currentExtensionGetsGreenLight = false;
greenExtensions[depId] = depDesc;
}
}
if (currentExtensionGetsGreenLight) {
greenExtensions[currentExtension.id] = currentExtension;
} else {
redExtensions.push(currentExtension);
}
}
private _activateExtensions(extensionDescriptions: IExtensionDescription[], recursionLevel: number): TPromise<void> {
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
if (extensionDescriptions.length === 0) {
return TPromise.as(void 0);
}
extensionDescriptions = extensionDescriptions.filter((p) => !hasOwnProperty.call(this._activatedExtensions, p.id));
if (extensionDescriptions.length === 0) {
return TPromise.as(void 0);
}
if (recursionLevel > 10) {
// More than 10 dependencies deep => most likely a dependency loop
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
// Error condition 3: dependency loop
this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].id));
this._activatedExtensions[extensionDescriptions[i].id] = new FailedExtension();
}
return TPromise.as(void 0);
}
let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null),
red: IExtensionDescription[] = [];
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
this._handleActivateRequest(extensionDescriptions[i], greenMap, red);
}
// Make sure no red is also green
for (let i = 0, len = red.length; i < len; i++) {
if (greenMap[red[i].id]) {
delete greenMap[red[i].id];
}
}
let green = Object.keys(greenMap).map(id => greenMap[id]);
// console.log('greenExtensions: ', green.map(p => p.id));
// console.log('redExtensions: ', red.map(p => p.id));
if (red.length === 0) {
// Finally reached only leafs!
return TPromise.join(green.map((p) => this._activateExtension(p))).then(_ => void 0);
}
return this._activateExtensions(green, recursionLevel + 1).then(_ => {
return this._activateExtensions(red, recursionLevel + 1);
});
}
private _activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
if (hasOwnProperty.call(this._activatedExtensions, extensionDescription.id)) {
return TPromise.as(void 0);
}
if (hasOwnProperty.call(this._activatingExtensions, extensionDescription.id)) {
return this._activatingExtensions[extensionDescription.id];
}
this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription).then(null, (err) => {
this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message));
console.error('Activating extension `' + extensionDescription.id + '` failed: ', err.message);
console.log('Here is the error stack: ', err.stack);
// Treat the extension as being empty
return new FailedExtension();
}).then((x: ActivatedExtension) => {
this._activatedExtensions[extensionDescription.id] = x;
delete this._activatingExtensions[extensionDescription.id];
});
return this._activatingExtensions[extensionDescription.id];
}
}

View File

@@ -4,70 +4,20 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { dispose } from 'vs/base/common/lifecycle';
import { join } from 'path';
import { mkdirp, dirExists } from 'vs/base/node/pfs';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtensionDescriptionRegistry } from 'vs/platform/extensions/common/abstractExtensionService';
import { ExtensionDescriptionRegistry } from "vs/workbench/services/extensions/node/extensionDescriptionRegistry";
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = TPromise.as<void>(void 0);
/**
* Represents the source code (module) of an extension.
*/
export interface IExtensionModule {
activate(ctx: IExtensionContext): TPromise<IExtensionAPI>;
deactivate(): void;
}
/**
* Represents the API of an extension (return value of `activate`).
*/
export interface IExtensionAPI {
// _extensionAPIBrand: any;
}
export abstract class ActivatedExtension {
activationFailed: boolean;
constructor(activationFailed: boolean) {
this.activationFailed = activationFailed;
}
}
export class ExtHostExtension extends ActivatedExtension {
module: IExtensionModule;
exports: IExtensionAPI;
subscriptions: IDisposable[];
constructor(activationFailed: boolean, module: IExtensionModule, exports: IExtensionAPI, subscriptions: IDisposable[]) {
super(activationFailed);
this.module = module;
this.exports = exports;
this.subscriptions = subscriptions;
}
}
export class ExtHostEmptyExtension extends ExtHostExtension {
constructor() {
super(false, { activate: undefined, deactivate: undefined }, undefined, []);
}
}
export interface IExtensionMemento {
get<T>(key: string, defaultValue: T): T;
update(key: string, value: any): Thenable<boolean>;
}
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule } from "vs/workbench/api/node/extHostExtensionActivator";
import { Barrier } from "vs/workbench/services/extensions/node/barrier";
class ExtensionMemento implements IExtensionMemento {
@@ -155,112 +105,85 @@ class ExtensionStoragePath {
}
}
export interface IExtensionContext {
subscriptions: IDisposable[];
workspaceState: IExtensionMemento;
globalState: IExtensionMemento;
extensionPath: string;
storagePath: string;
asAbsolutePath(relativePath: string): string;
}
export class ExtHostExtensionService {
export abstract class AbstractExtensionService<T extends ActivatedExtension> {
public _serviceBrand: any;
private _onReady: TPromise<boolean>;
private _onReadyC: (v: boolean) => void;
private _isReady: boolean;
protected _registry: ExtensionDescriptionRegistry;
protected _manager: ExtensionsManager<T>;
constructor(isReadyByDefault: boolean) {
if (isReadyByDefault) {
this._isReady = true;
this._onReady = TPromise.as(true);
this._onReadyC = (v: boolean) => { /*no-op*/ };
} else {
this._isReady = false;
this._onReady = new TPromise<boolean>((c, e, p) => {
this._onReadyC = c;
}, () => {
console.warn('You should really not try to cancel this ready promise!');
});
}
this._registry = new ExtensionDescriptionRegistry();
this._manager = new ExtensionsManager(this._registry, {
showMessage: (severity: Severity, message: string): void => {
this._showMessage(severity, message);
},
createFailedExtension: (): T => {
return this._createFailedExtension();
},
actualActivateExtension: (extensionDescription: IExtensionDescription): TPromise<T> => {
return this._actualActivateExtension(extensionDescription);
}
});
}
protected _triggerOnReady(): void {
this._isReady = true;
this._onReadyC(true);
}
public onReady(): TPromise<boolean> {
return this._onReady;
}
public isActivated(extensionId: string): boolean {
return this._manager.isActivated(extensionId);
}
public activateByEvent(activationEvent: string): TPromise<void> {
if (this._isReady) {
return this._manager.activateByEvent(activationEvent);
} else {
return this._onReady.then(() => this._manager.activateByEvent(activationEvent));
}
}
public activateById(extensionId: string): TPromise<void> {
if (this._isReady) {
return this._manager.activateById(extensionId);
} else {
return this._onReady.then(() => this._manager.activateById(extensionId));
}
}
protected abstract _showMessage(severity: Severity, message: string): void;
protected abstract _createFailedExtension(): T;
protected abstract _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<T>;
}
export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExtension> {
private _threadService: IThreadService;
private _storage: ExtHostStorage;
private _storagePath: ExtensionStoragePath;
private _proxy: MainProcessExtensionServiceShape;
private _telemetryService: ITelemetryService;
private readonly _barrier: Barrier;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _threadService: IThreadService;
private readonly _telemetryService: ITelemetryService;
private readonly _storage: ExtHostStorage;
private readonly _storagePath: ExtensionStoragePath;
private readonly _proxy: MainProcessExtensionServiceShape;
private _activator: ExtensionsActivator;
/**
* This class is constructed manually because it is a service, so it doesn't use any ctor injection
*/
constructor(initData: IInitData, threadService: IThreadService, telemetryService: ITelemetryService) {
super(false);
this._registry.registerExtensions(initData.extensions);
this._barrier = new Barrier();
this._registry = new ExtensionDescriptionRegistry(initData.extensions);
this._threadService = threadService;
this._telemetryService = telemetryService;
this._storage = new ExtHostStorage(threadService);
this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
this._proxy = this._threadService.get(MainContext.MainProcessExtensionService);
this._telemetryService = telemetryService;
this._activator = null;
// initialize API first
// initialize API first (i.e. do not release barrier until the API is initialized)
const apiFactory = createApiFactory(initData, threadService, this, this._telemetryService);
initializeExtensionApi(this, apiFactory).then(() => this._triggerOnReady());
initializeExtensionApi(this, apiFactory).then(() => {
this._activator = new ExtensionsActivator(this._registry, {
showMessage: (severity: Severity, message: string): void => {
this._proxy.$localShowMessage(severity, message);
switch (severity) {
case Severity.Error:
console.error(message);
break;
case Severity.Warning:
console.warn(message);
break;
default:
console.log(message);
}
},
actualActivateExtension: (extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> => {
return this._activateExtension(extensionDescription);
}
});
this._barrier.open();
});
}
public onExtensionAPIReady(): TPromise<boolean> {
return this._barrier.wait();
}
public isActivated(extensionId: string): boolean {
if (this._barrier.isOpen()) {
return this._activator.isActivated(extensionId);
}
return false;
}
public activateByEvent(activationEvent: string): TPromise<void> {
if (this._barrier.isOpen()) {
return this._activator.activateByEvent(activationEvent);
} else {
return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent));
}
}
public activateById(extensionId: string): TPromise<void> {
if (this._barrier.isOpen()) {
return this._activator.activateById(extensionId);
} else {
return this._barrier.wait().then(() => this._activator.activateById(extensionId));
}
}
public getAllExtensionDescriptions(): IExtensionDescription[] {
@@ -271,31 +194,26 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
return this._registry.getExtensionDescription(extensionId);
}
public $localShowMessage(severity: Severity, msg: string): void {
switch (severity) {
case Severity.Error:
console.error(msg);
break;
case Severity.Warning:
console.warn(msg);
break;
default:
console.log(msg);
public getExtensionExports(extensionId: string): IExtensionAPI {
if (this._barrier.isOpen()) {
return this._activator.getActivatedExtension(extensionId).exports;
} else {
return null;
}
}
public get(extensionId: string): IExtensionAPI {
return this._manager.getActivatedExtension(extensionId).exports;
}
public deactivate(extensionId: string): TPromise<void> {
let result: TPromise<void> = TPromise.as(void 0);
if (!this._manager.isActivated(extensionId)) {
if (!this._barrier.isOpen()) {
return result;
}
let extension = this._manager.getActivatedExtension(extensionId);
if (!this._activator.isActivated(extensionId)) {
return result;
}
let extension = this._activator.getActivatedExtension(extensionId);
if (!extension) {
return result;
}
@@ -322,15 +240,40 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
return result;
}
// -- overwriting AbstractExtensionService
// --- impl
protected _showMessage(severity: Severity, msg: string): void {
this._proxy.$localShowMessage(severity, msg);
this.$localShowMessage(severity, msg);
private _activateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> {
return this._doActivateExtension(extensionDescription).then((activatedExtension) => {
this._proxy.$onExtensionActivated(extensionDescription.id);
return activatedExtension;
}, (err) => {
this._proxy.$onExtensionActivationFailed(extensionDescription.id);
throw err;
});
}
protected _createFailedExtension() {
return new ExtHostExtension(true, { activate: undefined, deactivate: undefined }, undefined, []);
private _doActivateExtension(extensionDescription: IExtensionDescription): TPromise<ActivatedExtension> {
let event = getTelemetryActivationEvent(extensionDescription);
this._telemetryService.publicLog('activatePlugin', event);
if (!extensionDescription.main) {
// Treat the extension as being empty => NOT AN ERROR CASE
return TPromise.as(new EmptyExtension());
}
return TPromise.join<any>([
loadCommonJSModule(extensionDescription.main),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1]);
}, (errors: any[]) => {
// Avoid failing with an array of errors, fail with a single error
if (errors[0]) {
return TPromise.wrapError<ActivatedExtension>(errors[0]);
}
if (errors[1]) {
return TPromise.wrapError<ActivatedExtension>(errors[1]);
}
return undefined;
});
}
private _loadExtensionContext(extensionDescription: IExtensionDescription): TPromise<IExtensionContext> {
@@ -354,43 +297,7 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
});
}
protected _actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
return this._doActualActivateExtension(extensionDescription).then((activatedExtension) => {
this._proxy.$onExtensionActivated(extensionDescription.id);
return activatedExtension;
}, (err) => {
this._proxy.$onExtensionActivationFailed(extensionDescription.id);
throw err;
});
}
private _doActualActivateExtension(extensionDescription: IExtensionDescription): TPromise<ExtHostExtension> {
let event = getTelemetryActivationEvent(extensionDescription);
this._telemetryService.publicLog('activatePlugin', event);
if (!extensionDescription.main) {
// Treat the extension as being empty => NOT AN ERROR CASE
return TPromise.as(new ExtHostEmptyExtension());
}
return this.onReady().then(() => {
return TPromise.join<any>([
loadCommonJSModule(extensionDescription.main),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return ExtHostExtensionService._callActivate(<IExtensionModule>values[0], <IExtensionContext>values[1]);
}, (errors: any[]) => {
// Avoid failing with an array of errors, fail with a single error
if (errors[0]) {
return TPromise.wrapError<ExtHostExtension>(errors[0]);
}
if (errors[1]) {
return TPromise.wrapError<ExtHostExtension>(errors[1]);
}
return undefined;
});
});
}
private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ExtHostExtension> {
private static _callActivate(extensionModule: IExtensionModule, context: IExtensionContext): TPromise<ActivatedExtension> {
// Make sure the extension's surface is not undefined
extensionModule = extensionModule || {
activate: undefined,
@@ -398,7 +305,7 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
};
return this._callActivateOptional(extensionModule, context).then((extensionExports) => {
return new ExtHostExtension(false, extensionModule, extensionExports, context.subscriptions);
return new ActivatedExtension(false, extensionModule, extensionExports, context.subscriptions);
});
}
@@ -418,192 +325,7 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
// -- called by main thread
public $activateByEvent(activationEvent: string): TPromise<void> {
return this._manager.activateByEvent(activationEvent);
}
}
export interface IExtensionsManagerHost<T extends ActivatedExtension> {
showMessage(severity: Severity, message: string): void;
createFailedExtension(): T;
actualActivateExtension(extensionDescription: IExtensionDescription): TPromise<T>;
}
export interface IActivatedExtensionMap<T extends ActivatedExtension> {
[extensionId: string]: T;
}
interface IActivatingExtensionMap {
[extensionId: string]: TPromise<void>;
}
export class ExtensionsManager<T extends ActivatedExtension> {
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _host: IExtensionsManagerHost<T>;
private readonly _activatingExtensions: IActivatingExtensionMap;
private readonly _activatedExtensions: IActivatedExtensionMap<T>;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; };
constructor(registry: ExtensionDescriptionRegistry, host: IExtensionsManagerHost<T>) {
this._registry = registry;
this._host = host;
this._activatingExtensions = {};
this._activatedExtensions = {};
this._alreadyActivatedEvents = Object.create(null);
}
public isActivated(extensionId: string): boolean {
return hasOwnProperty.call(this._activatedExtensions, extensionId);
}
public getActivatedExtension(extensionId: string): T {
if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) {
throw new Error('Extension `' + extensionId + '` is not known or not activated');
}
return this._activatedExtensions[extensionId];
}
public setActivatedExtension(extensionId: string, value: T): void {
this._activatedExtensions[extensionId] = value;
}
public activateByEvent(activationEvent: string): TPromise<void> {
if (this._alreadyActivatedEvents[activationEvent]) {
return NO_OP_VOID_PROMISE;
}
let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
return this._activateExtensions(activateExtensions, 0).then(() => {
this._alreadyActivatedEvents[activationEvent] = true;
});
}
public activateById(extensionId: string): TPromise<void> {
let desc = this._registry.getExtensionDescription(extensionId);
if (!desc) {
throw new Error('Extension `' + extensionId + '` is not known');
}
return this._activateExtensions([desc], 0);
}
/**
* Handle semantics related to dependencies for `currentExtension`.
* semantics: `redExtensions` must wait for `greenExtensions`.
*/
private _handleActivateRequest(currentExtension: IExtensionDescription, greenExtensions: { [id: string]: IExtensionDescription; }, redExtensions: IExtensionDescription[]): void {
let depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
let currentExtensionGetsGreenLight = true;
for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
let depId = depIds[j];
let depDesc = this._registry.getExtensionDescription(depId);
if (!depDesc) {
// Error condition 1: unknown dependency
this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentExtension.id));
this._activatedExtensions[currentExtension.id] = this._host.createFailedExtension();
return;
}
if (hasOwnProperty.call(this._activatedExtensions, depId)) {
let dep = this._activatedExtensions[depId];
if (dep.activationFailed) {
// Error condition 2: a dependency has already failed activation
this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentExtension.id));
this._activatedExtensions[currentExtension.id] = this._host.createFailedExtension();
return;
}
} else {
// must first wait for the dependency to activate
currentExtensionGetsGreenLight = false;
greenExtensions[depId] = depDesc;
}
}
if (currentExtensionGetsGreenLight) {
greenExtensions[currentExtension.id] = currentExtension;
} else {
redExtensions.push(currentExtension);
}
}
private _activateExtensions(extensionDescriptions: IExtensionDescription[], recursionLevel: number): TPromise<void> {
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
if (extensionDescriptions.length === 0) {
return TPromise.as(void 0);
}
extensionDescriptions = extensionDescriptions.filter((p) => !hasOwnProperty.call(this._activatedExtensions, p.id));
if (extensionDescriptions.length === 0) {
return TPromise.as(void 0);
}
if (recursionLevel > 10) {
// More than 10 dependencies deep => most likely a dependency loop
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
// Error condition 3: dependency loop
this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].id));
this._activatedExtensions[extensionDescriptions[i].id] = this._host.createFailedExtension();
}
return TPromise.as(void 0);
}
let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null),
red: IExtensionDescription[] = [];
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
this._handleActivateRequest(extensionDescriptions[i], greenMap, red);
}
// Make sure no red is also green
for (let i = 0, len = red.length; i < len; i++) {
if (greenMap[red[i].id]) {
delete greenMap[red[i].id];
}
}
let green = Object.keys(greenMap).map(id => greenMap[id]);
// console.log('greenExtensions: ', green.map(p => p.id));
// console.log('redExtensions: ', red.map(p => p.id));
if (red.length === 0) {
// Finally reached only leafs!
return TPromise.join(green.map((p) => this._activateExtension(p))).then(_ => void 0);
}
return this._activateExtensions(green, recursionLevel + 1).then(_ => {
return this._activateExtensions(red, recursionLevel + 1);
});
}
public _activateExtension(extensionDescription: IExtensionDescription): TPromise<void> {
if (hasOwnProperty.call(this._activatedExtensions, extensionDescription.id)) {
return TPromise.as(void 0);
}
if (hasOwnProperty.call(this._activatingExtensions, extensionDescription.id)) {
return this._activatingExtensions[extensionDescription.id];
}
this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription).then(null, (err) => {
this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message));
console.error('Activating extension `' + extensionDescription.id + '` failed: ', err.message);
console.log('Here is the error stack: ', err.stack);
// Treat the extension as being empty
return this._host.createFailedExtension();
}).then((x: T) => {
this._activatedExtensions[extensionDescription.id] = x;
delete this._activatingExtensions[extensionDescription.id];
});
return this._activatingExtensions[extensionDescription.id];
return this.activateByEvent(activationEvent);
}
}