protected async installExtensions()

in patched-vscode/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts [197:414]


	protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> {
		const installExtensionResultsMap = new Map<string, InstallExtensionResult & { profileLocation: URI }>();
		const installingExtensionsMap = new Map<string, { task: IInstallExtensionTask; root: IInstallExtensionTask | undefined }>();
		const alreadyRequestedInstallations: Promise<any>[] = [];

		const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`;
		const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions, root: IInstallExtensionTask | undefined): void => {
			const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
			const key = `${getGalleryExtensionId(manifest.publisher, manifest.name)}-${options.profileLocation.toString()}`;
			installingExtensionsMap.set(key, { task: installExtensionTask, root });
			this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation });
			this.logService.info('Installing extension:', installExtensionTask.identifier.id, options.profileLocation.toString());
			// only cache gallery extensions tasks
			if (!URI.isUri(extension)) {
				this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] });
			}
		};

		try {
			// Start installing extensions
			for (const { manifest, extension, options } of extensions) {
				const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
				const installExtensionTaskOptions: InstallExtensionTaskOptions = {
					...options,
					installOnlyNewlyAddedFromExtensionPack: options.installOnlyNewlyAddedFromExtensionPack ?? !URI.isUri(extension) /* always true for gallery extensions */,
					isApplicationScoped,
					profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation(),
					productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date }
				};

				const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined;
				if (existingInstallExtensionTask) {
					this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id, installExtensionTaskOptions.profileLocation.toString());
					alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished());
				} else {
					createInstallExtensionTask(manifest, extension, installExtensionTaskOptions, undefined);
				}
			}

			// collect and start installing all dependencies and pack extensions
			await Promise.all([...installingExtensionsMap.values()].map(async ({ task }) => {
				if (task.options.donotIncludePackAndDependencies) {
					this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id);
				} else {
					try {
						const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, task.manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation, task.options.productVersion);
						const installed = await this.getInstalled(undefined, task.options.profileLocation, task.options.productVersion);
						const options: InstallExtensionTaskOptions = { ...task.options, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } };
						for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) {
							if (installingExtensionsMap.has(`${gallery.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) {
								continue;
							}
							const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(gallery, options.profileLocation));
							if (existingInstallingExtension) {
								if (this.canWaitForTask(task, existingInstallingExtension.task)) {
									const identifier = existingInstallingExtension.task.identifier;
									this.logService.info('Waiting for already requested installing extension', identifier.id, task.identifier.id, options.profileLocation.toString());
									existingInstallingExtension.waitingTasks.push(task);
									// add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension
									alreadyRequestedInstallations.push(
										Event.toPromise(
											Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier)))
										).then(results => {
											this.logService.info('Finished waiting for already requested installing extension', identifier.id, task.identifier.id, options.profileLocation.toString());
											const result = results.find(result => areSameExtensions(result.identifier, identifier));
											if (!result?.local) {
												// Extension failed to install
												throw new Error(`Extension ${identifier.id} is not installed`);
											}
										}));
								}
							} else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) {
								createInstallExtensionTask(manifest, gallery, options, task);
							}
						}
					} catch (error) {
						// Installing through VSIX
						if (URI.isUri(task.source)) {
							// Ignore installing dependencies and packs
							if (isNonEmptyArray(task.manifest.extensionDependencies)) {
								this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message);
							}
							if (isNonEmptyArray(task.manifest.extensionPack)) {
								this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message);
							}
						} else {
							this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id);
							throw error;
						}
					}
				}
			}));

			// Install extensions in parallel and wait until all extensions are installed / failed
			await this.joinAllSettled([...installingExtensionsMap.entries()].map(async ([key, { task }]) => {
				const startTime = new Date().getTime();
				let local: ILocalExtension;
				try {
					local = await task.run();
					await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)), ExtensionManagementErrorCode.PostInstall);
				} catch (e) {
					const error = toExtensionManagementError(e);
					if (!URI.isUri(task.source)) {
						reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', {
							extensionData: getGalleryExtensionTelemetryData(task.source),
							error,
							source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT]
						});
					}
					installExtensionResultsMap.set(key, { error, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: task.options.isApplicationScoped });
					this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error), task.options.profileLocation.toString());
					throw error;
				}
				if (!URI.isUri(task.source)) {
					const isUpdate = task.operation === InstallOperation.Update;
					const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
					reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {
						extensionData: getGalleryExtensionTelemetryData(task.source),
						verificationStatus: task.verificationStatus,
						duration: new Date().getTime() - startTime,
						durationSinceUpdate,
						source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT]
					});
					// In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX.
					if (isWeb && task.operation !== InstallOperation.Update) {
						try {
							await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install);
						} catch (error) { /* ignore */ }
					}
				}
				installExtensionResultsMap.set(key, { local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: local.isApplicationScoped });
			}));

			if (alreadyRequestedInstallations.length) {
				await this.joinAllSettled(alreadyRequestedInstallations);
			}
			return [...installExtensionResultsMap.values()];
		} catch (error) {
			const getAllDepsAndPacks = (extension: ILocalExtension, profileLocation: URI, allDepsOrPacks: string[]) => {
				const depsOrPacks = [];
				if (extension.manifest.extensionDependencies?.length) {
					depsOrPacks.push(...extension.manifest.extensionDependencies);
				}
				if (extension.manifest.extensionPack?.length) {
					depsOrPacks.push(...extension.manifest.extensionPack);
				}
				for (const id of depsOrPacks) {
					if (allDepsOrPacks.includes(id.toLowerCase())) {
						continue;
					}
					allDepsOrPacks.push(id.toLowerCase());
					const installed = installExtensionResultsMap.get(`${id.toLowerCase()}-${profileLocation.toString()}`);
					if (installed?.local) {
						allDepsOrPacks = getAllDepsAndPacks(installed.local, profileLocation, allDepsOrPacks);
					}
				}
				return allDepsOrPacks;
			};
			const getErrorResult = (task: IInstallExtensionTask) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, error });

			const rollbackTasks: IUninstallExtensionTask[] = [];
			for (const [key, { task, root }] of installingExtensionsMap) {
				const result = installExtensionResultsMap.get(key);
				if (!result) {
					task.cancel();
					installExtensionResultsMap.set(key, getErrorResult(task));
				}
				// If the extension is installed by a root task and the root task is failed, then uninstall the extension
				else if (result.local && root && !installExtensionResultsMap.get(`${root.identifier.id.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local) {
					rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation }));
					installExtensionResultsMap.set(key, getErrorResult(task));
				}
			}
			for (const [key, { task }] of installingExtensionsMap) {
				const result = installExtensionResultsMap.get(key);
				if (!result?.local) {
					continue;
				}
				if (task.options.donotIncludePackAndDependencies) {
					continue;
				}
				const depsOrPacks = getAllDepsAndPacks(result.local, task.options.profileLocation, [result.local.identifier.id.toLowerCase()]).slice(1);
				if (depsOrPacks.some(depOrPack => installingExtensionsMap.has(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`) && !installExtensionResultsMap.get(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local)) {
					rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation }));
					installExtensionResultsMap.set(key, getErrorResult(task));
				}
			}

			if (rollbackTasks.length) {
				await Promise.allSettled(rollbackTasks.map(async rollbackTask => {
					try {
						await rollbackTask.run();
						this.logService.info('Rollback: Uninstalled extension', rollbackTask.extension.identifier.id);
					} catch (error) {
						this.logService.warn('Rollback: Error while uninstalling extension', rollbackTask.extension.identifier.id, getErrorMessage(error));
					}
				}));
			}

			throw error;
		} finally {
			// Finally, remove all the tasks from the cache
			for (const { task } of installingExtensionsMap.values()) {
				if (task.source && !URI.isUri(task.source)) {
					this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.options.profileLocation));
				}
			}
			if (installExtensionResultsMap.size) {
				const results = [...installExtensionResultsMap.values()];
				for (const result of results) {
					if (result.local) {
						this.logService.info(`Extension installed successfully:`, result.identifier.id, result.profileLocation.toString());
					}
				}
				this._onDidInstallExtensions.fire(results);
			}
		}
	}