extHostExtensionActivator.ts 14.2 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import * as vscode from 'vscode';
A
Alex Dima 已提交
8
import { IDisposable } from 'vs/base/common/lifecycle';
9
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
10
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
11
import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions';
A
Alex Dima 已提交
12

R
Rob Lourens 已提交
13
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
A
Alex Dima 已提交
14 15 16 17 18

/**
 * Represents the source code (module) of an extension.
 */
export interface IExtensionModule {
J
Johannes Rieken 已提交
19
	activate?(ctx: vscode.ExtensionContext): Promise<IExtensionAPI>;
M
Matt Bierner 已提交
20
	deactivate?(): void;
A
Alex Dima 已提交
21 22 23 24 25 26 27 28 29
}

/**
 * Represents the API of an extension (return value of `activate`).
 */
export interface IExtensionAPI {
	// _extensionAPIBrand: any;
}

30 31 32 33 34 35 36
export type ExtensionActivationTimesFragment = {
	startup?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
	codeLoadingTime?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
	activateCallTime?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
	activateResolvedTime?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
};

37
export class ExtensionActivationTimes {
A
Alex Dima 已提交
38

M
Matt Bierner 已提交
39
	public static readonly NONE = new ExtensionActivationTimes(false, -1, -1, -1);
40

41
	public readonly startup: boolean;
42 43 44
	public readonly codeLoadingTime: number;
	public readonly activateCallTime: number;
	public readonly activateResolvedTime: number;
A
Alex Dima 已提交
45

46 47
	constructor(startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number) {
		this.startup = startup;
48 49 50 51 52 53 54 55
		this.codeLoadingTime = codeLoadingTime;
		this.activateCallTime = activateCallTime;
		this.activateResolvedTime = activateResolvedTime;
	}
}

export class ExtensionActivationTimesBuilder {

56
	private readonly _startup: boolean;
57 58 59 60 61 62 63
	private _codeLoadingStart: number;
	private _codeLoadingStop: number;
	private _activateCallStart: number;
	private _activateCallStop: number;
	private _activateResolveStart: number;
	private _activateResolveStop: number;

64 65
	constructor(startup: boolean) {
		this._startup = startup;
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
		this._codeLoadingStart = -1;
		this._codeLoadingStop = -1;
		this._activateCallStart = -1;
		this._activateCallStop = -1;
		this._activateResolveStart = -1;
		this._activateResolveStop = -1;
	}

	private _delta(start: number, stop: number): number {
		if (start === -1 || stop === -1) {
			return -1;
		}
		return stop - start;
	}

	public build(): ExtensionActivationTimes {
		return new ExtensionActivationTimes(
83
			this._startup,
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
			this._delta(this._codeLoadingStart, this._codeLoadingStop),
			this._delta(this._activateCallStart, this._activateCallStop),
			this._delta(this._activateResolveStart, this._activateResolveStop)
		);
	}

	public codeLoadingStart(): void {
		this._codeLoadingStart = Date.now();
	}

	public codeLoadingStop(): void {
		this._codeLoadingStop = Date.now();
	}

	public activateCallStart(): void {
		this._activateCallStart = Date.now();
	}

	public activateCallStop(): void {
		this._activateCallStop = Date.now();
	}

	public activateResolveStart(): void {
		this._activateResolveStart = Date.now();
	}

	public activateResolveStop(): void {
		this._activateResolveStop = Date.now();
	}
}

export class ActivatedExtension {

	public readonly activationFailed: boolean;
M
Matt Bierner 已提交
118
	public readonly activationFailedError: Error | null;
119 120
	public readonly activationTimes: ExtensionActivationTimes;
	public readonly module: IExtensionModule;
M
Matt Bierner 已提交
121
	public readonly exports: IExtensionAPI | undefined;
122 123 124 125
	public readonly subscriptions: IDisposable[];

	constructor(
		activationFailed: boolean,
M
Matt Bierner 已提交
126
		activationFailedError: Error | null,
127 128
		activationTimes: ExtensionActivationTimes,
		module: IExtensionModule,
M
Matt Bierner 已提交
129
		exports: IExtensionAPI | undefined,
130 131
		subscriptions: IDisposable[]
	) {
A
Alex Dima 已提交
132
		this.activationFailed = activationFailed;
133
		this.activationFailedError = activationFailedError;
134
		this.activationTimes = activationTimes;
A
Alex Dima 已提交
135 136 137 138 139 140 141
		this.module = module;
		this.exports = exports;
		this.subscriptions = subscriptions;
	}
}

export class EmptyExtension extends ActivatedExtension {
142
	constructor(activationTimes: ExtensionActivationTimes) {
143
		super(false, null, activationTimes, { activate: undefined, deactivate: undefined }, undefined, []);
A
Alex Dima 已提交
144 145 146
	}
}

147 148 149 150 151 152
export class HostExtension extends ActivatedExtension {
	constructor() {
		super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []);
	}
}

A
Alex Dima 已提交
153
export class FailedExtension extends ActivatedExtension {
154 155
	constructor(activationError: Error) {
		super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []);
A
Alex Dima 已提交
156 157 158 159
	}
}

export interface IExtensionsActivatorHost {
160
	onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void;
161
	actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
A
Alex Dima 已提交
162 163
}

A
Alex Dima 已提交
164 165 166
export class ExtensionActivatedByEvent {
	constructor(
		public readonly startup: boolean,
167
		public readonly extensionId: ExtensionIdentifier,
A
Alex Dima 已提交
168 169 170 171 172 173
		public readonly activationEvent: string
	) { }
}

export class ExtensionActivatedByAPI {
	constructor(
174 175
		public readonly startup: boolean,
		public readonly extensionId: ExtensionIdentifier
A
Alex Dima 已提交
176 177 178 179 180
	) { }
}

export type ExtensionActivationReason = ExtensionActivatedByEvent | ExtensionActivatedByAPI;

181 182
type ActivationIdAndReason = { id: ExtensionIdentifier, reason: ExtensionActivationReason };

A
Alex Dima 已提交
183 184 185
export class ExtensionsActivator {

	private readonly _registry: ExtensionDescriptionRegistry;
A
Alex Dima 已提交
186
	private readonly _resolvedExtensionsSet: Set<string>;
187
	private readonly _hostExtensionsMap: Map<string, ExtensionIdentifier>;
A
Alex Dima 已提交
188
	private readonly _host: IExtensionsActivatorHost;
189 190
	private readonly _activatingExtensions: Map<string, Promise<void>>;
	private readonly _activatedExtensions: Map<string, ActivatedExtension>;
A
Alex Dima 已提交
191 192 193 194 195
	/**
	 * 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; };

196
	constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) {
A
Alex Dima 已提交
197
		this._registry = registry;
A
Alex Dima 已提交
198 199
		this._resolvedExtensionsSet = new Set<string>();
		resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId)));
200 201
		this._hostExtensionsMap = new Map<string, ExtensionIdentifier>();
		hostExtensions.forEach((extensionId) => this._hostExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId));
A
Alex Dima 已提交
202
		this._host = host;
203 204
		this._activatingExtensions = new Map<string, Promise<void>>();
		this._activatedExtensions = new Map<string, ActivatedExtension>();
A
Alex Dima 已提交
205 206 207
		this._alreadyActivatedEvents = Object.create(null);
	}

208 209
	public isActivated(extensionId: ExtensionIdentifier): boolean {
		const extensionKey = ExtensionIdentifier.toKey(extensionId);
210 211

		return this._activatedExtensions.has(extensionKey);
A
Alex Dima 已提交
212 213
	}

214 215
	public getActivatedExtension(extensionId: ExtensionIdentifier): ActivatedExtension {
		const extensionKey = ExtensionIdentifier.toKey(extensionId);
216

217 218
		const activatedExtension = this._activatedExtensions.get(extensionKey);
		if (!activatedExtension) {
219
			throw new Error('Extension `' + extensionId.value + '` is not known or not activated');
A
Alex Dima 已提交
220
		}
221
		return activatedExtension;
A
Alex Dima 已提交
222 223
	}

224
	public activateByEvent(activationEvent: string, startup: boolean): Promise<void> {
A
Alex Dima 已提交
225 226 227
		if (this._alreadyActivatedEvents[activationEvent]) {
			return NO_OP_VOID_PROMISE;
		}
228
		const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
229 230 231 232
		return this._activateExtensions(activateExtensions.map(e => ({
			id: e.identifier,
			reason: new ExtensionActivatedByEvent(startup, e.identifier, activationEvent)
		}))).then(() => {
A
Alex Dima 已提交
233 234 235 236
			this._alreadyActivatedEvents[activationEvent] = true;
		});
	}

237
	public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
238
		const desc = this._registry.getExtensionDescription(extensionId);
A
Alex Dima 已提交
239 240 241 242
		if (!desc) {
			throw new Error('Extension `' + extensionId + '` is not known');
		}

243 244 245 246
		return this._activateExtensions([{
			id: desc.identifier,
			reason
		}]);
A
Alex Dima 已提交
247 248 249 250 251 252
	}

	/**
	 * Handle semantics related to dependencies for `currentExtension`.
	 * semantics: `redExtensions` must wait for `greenExtensions`.
	 */
253 254 255
	private _handleActivateRequest(currentActivation: ActivationIdAndReason, greenExtensions: { [id: string]: ActivationIdAndReason; }, redExtensions: ActivationIdAndReason[]): void {
		if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentActivation.id))) {
			greenExtensions[ExtensionIdentifier.toKey(currentActivation.id)] = currentActivation;
256 257 258
			return;
		}

259
		const currentExtension = this._registry.getExtensionDescription(currentActivation.id)!;
260
		const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
A
Alex Dima 已提交
261 262
		let currentExtensionGetsGreenLight = true;

263
		for (let j = 0, lenJ = depIds.length; j < lenJ; j++) {
A
Alex Dima 已提交
264 265 266 267 268 269 270
			const depId = depIds[j];

			if (this._resolvedExtensionsSet.has(ExtensionIdentifier.toKey(depId))) {
				// This dependency is already resolved
				continue;
			}

271 272 273 274 275
			const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId));
			if (dep && !dep.activationFailed) {
				// the dependency is already activated OK
				continue;
			}
A
Alex Dima 已提交
276

277 278
			if (dep && dep.activationFailed) {
				// Error condition 2: a dependency has already failed activation
279
				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));
280 281
				const error = new Error(`Dependency ${depId} failed to activate`);
				(<any>error).detail = dep.activationFailedError;
282
				this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
283 284
				return;
			}
A
Alex Dima 已提交
285

286
			if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(depId))) {
287 288
				// must first wait for the dependency to activate
				currentExtensionGetsGreenLight = false;
289 290 291 292
				greenExtensions[ExtensionIdentifier.toKey(depId)] = {
					id: this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!,
					reason: currentActivation.reason
				};
293
				continue;
A
Alex Dima 已提交
294
			}
295 296 297 298 299

			const depDesc = this._registry.getExtensionDescription(depId);
			if (depDesc) {
				// must first wait for the dependency to activate
				currentExtensionGetsGreenLight = false;
300 301 302 303
				greenExtensions[ExtensionIdentifier.toKey(depId)] = {
					id: depDesc.identifier,
					reason: currentActivation.reason
				};
304 305 306 307
				continue;
			}

			// Error condition 1: unknown dependency
308
			this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependencyError(depId));
309 310 311
			const error = new Error(`Unknown dependency '${depId}'`);
			this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error));
			return;
A
Alex Dima 已提交
312 313 314
		}

		if (currentExtensionGetsGreenLight) {
315
			greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentActivation;
A
Alex Dima 已提交
316
		} else {
317
			redExtensions.push(currentActivation);
A
Alex Dima 已提交
318 319 320
		}
	}

321 322 323
	private _activateExtensions(extensions: ActivationIdAndReason[]): Promise<void> {
		// console.log('_activateExtensions: ', extensions.map(p => p.id.value));
		if (extensions.length === 0) {
R
Rob Lourens 已提交
324
			return Promise.resolve(undefined);
A
Alex Dima 已提交
325 326
		}

327 328
		extensions = extensions.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p.id)));
		if (extensions.length === 0) {
R
Rob Lourens 已提交
329
			return Promise.resolve(undefined);
A
Alex Dima 已提交
330 331
		}

332 333
		const greenMap: { [id: string]: ActivationIdAndReason; } = Object.create(null),
			red: ActivationIdAndReason[] = [];
A
Alex Dima 已提交
334

335 336
		for (let i = 0, len = extensions.length; i < len; i++) {
			this._handleActivateRequest(extensions[i], greenMap, red);
A
Alex Dima 已提交
337 338 339 340
		}

		// Make sure no red is also green
		for (let i = 0, len = red.length; i < len; i++) {
341
			const redExtensionKey = ExtensionIdentifier.toKey(red[i].id);
342 343
			if (greenMap[redExtensionKey]) {
				delete greenMap[redExtensionKey];
A
Alex Dima 已提交
344 345 346
			}
		}

347
		const green = Object.keys(greenMap).map(id => greenMap[id]);
A
Alex Dima 已提交
348

349 350
		// console.log('greenExtensions: ', green.map(p => p.id.value));
		// console.log('redExtensions: ', red.map(p => p.id.value));
A
Alex Dima 已提交
351 352 353

		if (red.length === 0) {
			// Finally reached only leafs!
354
			return Promise.all(green.map((p) => this._activateExtension(p.id, p.reason))).then(_ => undefined);
A
Alex Dima 已提交
355 356
		}

357 358
		return this._activateExtensions(green).then(_ => {
			return this._activateExtensions(red);
A
Alex Dima 已提交
359 360 361
		});
	}

362 363
	private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
		const extensionKey = ExtensionIdentifier.toKey(extensionId);
364 365

		if (this._activatedExtensions.has(extensionKey)) {
R
Rob Lourens 已提交
366
			return Promise.resolve(undefined);
A
Alex Dima 已提交
367 368
		}

369 370 371
		const currentlyActivatingExtension = this._activatingExtensions.get(extensionKey);
		if (currentlyActivatingExtension) {
			return currentlyActivatingExtension;
A
Alex Dima 已提交
372 373
		}

374
		const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => {
375
			this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message));
376
			console.error('Activating extension `' + extensionId.value + '` failed: ', err.message);
A
Alex Dima 已提交
377 378
			console.log('Here is the error stack: ', err.stack);
			// Treat the extension as being empty
379
			return new FailedExtension(err);
A
Alex Dima 已提交
380
		}).then((x: ActivatedExtension) => {
381 382
			this._activatedExtensions.set(extensionKey, x);
			this._activatingExtensions.delete(extensionKey);
383
		});
A
Alex Dima 已提交
384

385 386
		this._activatingExtensions.set(extensionKey, newlyActivatingExtension);
		return newlyActivatingExtension;
A
Alex Dima 已提交
387 388
	}
}