mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 02:28:34 +01:00
Reroute activation for hosted extensions
This commit is contained in:
@@ -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<void>;
|
||||
$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<ResolvedAuthority>;
|
||||
$startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise<void>;
|
||||
$activateByEvent(activationEvent: string): Promise<void>;
|
||||
$activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void>;
|
||||
$activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise<boolean>;
|
||||
|
||||
$deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
|
||||
|
||||
|
||||
@@ -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<ActivatedExtension>;
|
||||
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
|
||||
}
|
||||
|
||||
export class ExtensionActivatedByEvent {
|
||||
@@ -192,6 +197,7 @@ export class ExtensionsActivator {
|
||||
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _resolvedExtensionsSet: Set<string>;
|
||||
private readonly _hostExtensionsMap: Map<string, ExtensionIdentifier>;
|
||||
private readonly _host: IExtensionsActivatorHost;
|
||||
private readonly _activatingExtensions: Map<string, Promise<void>>;
|
||||
private readonly _activatedExtensions: Map<string, ActivatedExtension>;
|
||||
@@ -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<string>();
|
||||
resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId)));
|
||||
this._hostExtensionsMap = new Map<string, ExtensionIdentifier>();
|
||||
hostExtensions.forEach((extensionId) => this._hostExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId));
|
||||
this._host = host;
|
||||
this._activatingExtensions = new Map<string, Promise<void>>();
|
||||
this._activatedExtensions = new Map<string, ActivatedExtension>();
|
||||
@@ -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`);
|
||||
(<any>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`);
|
||||
(<any>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<void> {
|
||||
// console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id));
|
||||
if (extensionDescriptions.length === 0) {
|
||||
private _activateExtensions(extensionIds: ExtensionIdentifier[], reason: ExtensionActivationReason): Promise<void> {
|
||||
// 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<void> {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extensionDescription.identifier);
|
||||
private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
|
||||
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);
|
||||
|
||||
@@ -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<string>();
|
||||
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<ActivatedExtension> => {
|
||||
actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
|
||||
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<void> {
|
||||
return (
|
||||
this._barrier.wait()
|
||||
.then(_ => this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)))
|
||||
);
|
||||
public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise<boolean> {
|
||||
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<void> {
|
||||
|
||||
Reference in New Issue
Block a user