Path: blob/main/src/vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';6import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, ExtensionManagementError, ExtensionManagementErrorCode, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, IAllowedExtensionsService, VerifyExtensionSignatureConfigKey } from '../../../../platform/extensionManagement/common/extensionManagement.js';7import { URI } from '../../../../base/common/uri.js';8import { ExtensionType, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';9import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';10import { ILogService } from '../../../../platform/log/common/log.js';11import { toErrorMessage } from '../../../../base/common/errorMessage.js';12import { isNonEmptyArray } from '../../../../base/common/arrays.js';13import { CancellationToken } from '../../../../base/common/cancellation.js';14import { localize } from '../../../../nls.js';15import { IProductService } from '../../../../platform/product/common/productService.js';16import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';17import { IExtensionManagementServer } from '../common/extensionManagement.js';18import { Promises } from '../../../../base/common/async.js';19import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';20import { IFileService } from '../../../../platform/files/common/files.js';21import { RemoteExtensionManagementService } from '../common/remoteExtensionManagementService.js';22import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';23import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';24import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/remoteUserDataProfiles.js';25import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';26import { areApiProposalsCompatible } from '../../../../platform/extensions/common/extensionValidator.js';27import { isBoolean, isUndefined } from '../../../../base/common/types.js';2829export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService {3031constructor(32channel: IChannel,33private readonly localExtensionManagementServer: IExtensionManagementServer,34@IProductService productService: IProductService,35@IUserDataProfileService userDataProfileService: IUserDataProfileService,36@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,37@IRemoteUserDataProfilesService remoteUserDataProfilesService: IRemoteUserDataProfilesService,38@IUriIdentityService uriIdentityService: IUriIdentityService,39@ILogService private readonly logService: ILogService,40@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,41@IConfigurationService private readonly configurationService: IConfigurationService,42@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,43@IFileService private readonly fileService: IFileService,44@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,45) {46super(channel, productService, allowedExtensionsService, userDataProfileService, userDataProfilesService, remoteUserDataProfilesService, uriIdentityService);47}4849override async install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension> {50const local = await super.install(vsix, options);51await this.installUIDependenciesAndPackedExtensions(local);52return local;53}5455override async installFromGallery(extension: IGalleryExtension, installOptions: InstallOptions = {}): Promise<ILocalExtension> {56if (isUndefined(installOptions.donotVerifySignature)) {57const value = this.configurationService.getValue(VerifyExtensionSignatureConfigKey);58installOptions.donotVerifySignature = isBoolean(value) ? !value : undefined;59}60const local = await this.doInstallFromGallery(extension, installOptions);61await this.installUIDependenciesAndPackedExtensions(local);62return local;63}6465private async doInstallFromGallery(extension: IGalleryExtension, installOptions: InstallOptions): Promise<ILocalExtension> {66if (installOptions.downloadExtensionsLocally || this.configurationService.getValue('remote.downloadExtensionsLocally')) {67return this.downloadAndInstall(extension, installOptions);68}69try {70const clientTargetPlatform = await this.localExtensionManagementServer.extensionManagementService.getTargetPlatform();71return await super.installFromGallery(extension, { ...installOptions, context: { ...installOptions?.context, [EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]: clientTargetPlatform } });72} catch (error) {73switch (error.name) {74case ExtensionManagementErrorCode.Download:75case ExtensionManagementErrorCode.DownloadSignature:76case ExtensionManagementErrorCode.Gallery:77case ExtensionManagementErrorCode.Internal:78case ExtensionManagementErrorCode.Unknown:79try {80this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error));81return await this.downloadAndInstall(extension, installOptions);82} catch (e) {83this.logService.error(e);84throw e;85}86default:87this.logService.debug('Remote Install Error Name', error.name);88throw error;89}90}91}9293private async downloadAndInstall(extension: IGalleryExtension, installOptions: InstallOptions): Promise<ILocalExtension> {94this.logService.info(`Downloading the '${extension.identifier.id}' extension locally and install`);95const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);96installOptions = { ...installOptions, donotIncludePackAndDependencies: true };97const installed = await this.getInstalled(ExtensionType.User, undefined, installOptions.productVersion);98const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(compatible, CancellationToken.None);99if (workspaceExtensions.length) {100this.logService.info(`Downloading the workspace dependencies and packed extensions of '${compatible.identifier.id}' locally and install`);101for (const workspaceExtension of workspaceExtensions) {102await this.downloadCompatibleAndInstall(workspaceExtension, installed, installOptions);103}104}105return await this.downloadCompatibleAndInstall(compatible, installed, installOptions);106}107108private async downloadCompatibleAndInstall(extension: IGalleryExtension, installed: ILocalExtension[], installOptions: InstallOptions): Promise<ILocalExtension> {109const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);110this.logService.trace('Downloading extension:', compatible.identifier.id);111const location = await this.localExtensionManagementServer.extensionManagementService.download(compatible, installed.filter(i => areSameExtensions(i.identifier, compatible.identifier))[0] ? InstallOperation.Update : InstallOperation.Install, !!installOptions.donotVerifySignature);112this.logService.info('Downloaded extension:', compatible.identifier.id, location.path);113try {114const local = await super.install(location, { ...installOptions, keepExisting: true });115this.logService.info(`Successfully installed '${compatible.identifier.id}' extension`);116return local;117} finally {118try {119await this.fileService.del(location);120} catch (error) {121this.logService.error(error);122}123}124}125126private async checkAndGetCompatible(extension: IGalleryExtension, includePreRelease: boolean): Promise<IGalleryExtension> {127const targetPlatform = await this.getTargetPlatform();128let compatibleExtension: IGalleryExtension | null = null;129130if (extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {131compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;132}133134if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {135compatibleExtension = extension;136}137138if (!compatibleExtension) {139compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform);140}141142if (!compatibleExtension) {143const incompatibleApiProposalsMessages: string[] = [];144if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [], incompatibleApiProposalsMessages)) {145throw new ExtensionManagementError(localize('incompatibleAPI', "Can't install '{0}' extension. {1}", extension.displayName ?? extension.identifier.id, incompatibleApiProposalsMessages[0]), ExtensionManagementErrorCode.IncompatibleApi);146}147/** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */148if (!includePreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) {149throw new ExtensionManagementError(localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound);150}151throw new ExtensionManagementError(localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);152}153154return compatibleExtension;155}156157private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise<void> {158const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None);159const installed = await this.localExtensionManagementServer.extensionManagementService.getInstalled();160const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));161if (toInstall.length) {162this.logService.info(`Installing UI dependencies and packed extensions of '${local.identifier.id}' locally`);163await Promises.settled(toInstall.map(d => this.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));164}165}166167private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {168const result = new Map<string, IGalleryExtension>();169const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];170await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token);171return [...result.values()];172}173174private async getAllWorkspaceDependenciesAndPackedExtensions(extension: IGalleryExtension, token: CancellationToken): Promise<IGalleryExtension[]> {175const result = new Map<string, IGalleryExtension>();176result.set(extension.identifier.id.toLowerCase(), extension);177const manifest = await this.galleryService.getManifest(extension, token);178if (manifest) {179const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];180await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token);181}182result.delete(extension.identifier.id);183return [...result.values()];184}185186private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, uiExtension: boolean, token: CancellationToken): Promise<void> {187if (toGet.length === 0) {188return Promise.resolve();189}190191const extensions = await this.galleryService.getExtensions(toGet.map(id => ({ id })), token);192const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token)));193const extensionsManifests: IExtensionManifest[] = [];194for (let idx = 0; idx < extensions.length; idx++) {195const extension = extensions[idx];196const manifest = manifests[idx];197if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest) === uiExtension) {198result.set(extension.identifier.id.toLowerCase(), extension);199extensionsManifests.push(manifest);200}201}202toGet = [];203for (const extensionManifest of extensionsManifests) {204if (isNonEmptyArray(extensionManifest.extensionDependencies)) {205for (const id of extensionManifest.extensionDependencies) {206if (!result.has(id.toLowerCase())) {207toGet.push(id);208}209}210}211if (isNonEmptyArray(extensionManifest.extensionPack)) {212for (const id of extensionManifest.extensionPack) {213if (!result.has(id.toLowerCase())) {214toGet.push(id);215}216}217}218}219return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token);220}221}222223224