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);
}
}
}