mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
More cleanup around AbstractExtensionService
This commit is contained in:
@@ -4,12 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable, 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 { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/platform/extensions/common/abstractExtensionService';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -17,6 +18,9 @@ import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/
|
||||
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.
|
||||
*/
|
||||
@@ -32,6 +36,14 @@ export interface IExtensionAPI {
|
||||
// _extensionAPIBrand: any;
|
||||
}
|
||||
|
||||
export abstract class ActivatedExtension {
|
||||
activationFailed: boolean;
|
||||
|
||||
constructor(activationFailed: boolean) {
|
||||
this.activationFailed = activationFailed;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostExtension extends ActivatedExtension {
|
||||
|
||||
module: IExtensionModule;
|
||||
@@ -152,6 +164,80 @@ export interface IExtensionContext {
|
||||
asAbsolutePath(relativePath: string): string;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -336,6 +422,191 @@ export class ExtHostExtensionService extends AbstractExtensionService<ExtHostExt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
function loadCommonJSModule<T>(modulePath: string): TPromise<T> {
|
||||
let r: T = null;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user