From 522d13322c9c680d3c24777f81ff99a4454644a3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2024 15:57:53 +0100 Subject: [PATCH] Proper fix for #236537 - Support installEveryWhere option by installing the extension only on servers on which it is not installed --- .../browser/extensionsWorkbenchService.ts | 47 ++++++++++++++--- .../contrib/extensions/common/extensions.ts | 1 + .../common/extensionManagement.ts | 3 +- .../common/extensionManagementService.ts | 50 +++++++++++++++---- .../test/browser/workbenchTestServices.ts | 3 +- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 0967e7d34c7fc..33692adf1485d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -2342,35 +2342,69 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } else { let installableInfo: IExtensionInfo | undefined; let gallery: IGalleryExtension | undefined; + + // Install by id if (isString(arg)) { extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg })); if (!extension?.isBuiltin) { installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.preferPreReleases }; } - } else if (arg.gallery) { + } + // Install by gallery + else if (arg.gallery) { extension = arg; gallery = arg.gallery; if (installOptions.version && installOptions.version !== gallery?.version) { installableInfo = { id: extension.identifier.id, version: installOptions.version }; } - } else if (arg.resourceExtension) { + } + // Install by resource + else if (arg.resourceExtension) { extension = arg; installable = arg.resourceExtension; } + if (installableInfo) { const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; gallery = (await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)).at(0); } + if (!extension && gallery) { extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery, undefined); (extension).setExtensionsControlManifest(await this.extensionManagementService.getExtensionsControlManifest()); } + if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } - // TODO: @sandy081 - Install the extension only on servers where it is not installed - // Do not install if requested to enable and extension is already installed - if (installOptions.installEverywhere || !(installOptions.enable && extension?.local)) { + + if (gallery) { + // If requested to install everywhere + // then install the extension in all the servers where it is not installed + if (installOptions.installEverywhere) { + installOptions.servers = []; + const installableServers = await this.extensionManagementService.getInstallableServers(gallery); + for (const extensionsServer of this.extensionsServers) { + if (installableServers.includes(extensionsServer.server) && !extensionsServer.local.find(e => areSameExtensions(e.identifier, gallery.identifier))) { + installOptions.servers.push(extensionsServer.server); + } + } + } + // If requested to enable and extension is already installed + // Check if the extension is disabled because of extension kind + // If so, install the extension in the server that is compatible. + else if (installOptions.enable && extension?.local) { + installOptions.servers = []; + if (extension.enablementState === EnablementState.DisabledByExtensionKind) { + const [installableServer] = await this.extensionManagementService.getInstallableServers(gallery); + if (installableServer) { + installOptions.servers.push(installableServer); + } + } + } + } + + if (!installOptions.servers || installOptions.servers.length) { if (!installable) { if (!gallery) { const id = isString(arg) ? arg : (arg).identifier.id; @@ -2734,8 +2768,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallExtensionOptions): Promise { installOptions = installOptions ?? {}; installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension); - // TODO: @sandy081 - Install the extension only on servers where it is not installed - if (!installOptions.installEverywhere && extension.local) { + if (extension.local && !installOptions.servers) { installOptions.productVersion = this.getProductVersion(); installOptions.operation = InstallOperation.Update; return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 4cd9d5ea1d4e2..e8f26f634cabc 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -112,6 +112,7 @@ export interface InstallExtensionOptions extends IWorkbenchInstallOptions { version?: string; justification?: string | { reason: string; action: string }; enable?: boolean; + installEverywhere?: boolean; } export interface IExtensionsNotification { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 2bc9bc6e7938e..1deee908d89b7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -60,7 +60,7 @@ export type DidUninstallExtensionOnServerEvent = DidUninstallExtensionEvent & { export type DidChangeProfileForServerEvent = DidChangeProfileEvent & { server: IExtensionManagementServer }; export interface IWorkbenchInstallOptions extends InstallOptions { - readonly installEverywhere?: boolean; + servers?: IExtensionManagementServer[]; } export const IWorkbenchExtensionManagementService = refineServiceDecorator(IProfileAwareExtensionManagementService); @@ -84,6 +84,7 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten canInstall(extension: IGalleryExtension | IResourceExtension): Promise; + getInstallableServers(extension: IGalleryExtension): Promise; installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallOptions): Promise; installFromGallery(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise; installFromLocation(location: URI): Promise; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index bc0a4687128c2..7bc18909d7f14 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -554,6 +554,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } } + async getInstallableServers(gallery: IGalleryExtension): Promise { + const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); + if (!manifest) { + return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); + } + return this.getInstallableExtensionManagementServers(manifest); + } + private async uninstallExtensionFromWorkspace(extension: ILocalExtension): Promise { if (!extension.isWorkspaceScoped) { throw new Error('The extension is not a workspace extension'); @@ -606,11 +614,25 @@ export class ExtensionManagementService extends Disposable implements IWorkbench const servers: IExtensionManagementServer[] = []; - // Install everywhere if asked to install everywhere or if the extension is a language pack - if (installOptions?.installEverywhere || isLanguagePackExtension(manifest)) { + if (installOptions?.servers?.length) { + const installableServers = this.getInstallableExtensionManagementServers(manifest); + servers.push(...installOptions.servers); + for (const server of servers) { + if (!installableServers.includes(server)) { + const error = new Error(localize('cannot be installed in server', "Cannot install the '{0}' extension because it is not available in the '{1}' setup.", gallery.displayName || gallery.name, server.label)); + error.name = ExtensionManagementErrorCode.Unsupported; + throw error; + } + } + } + + // Language packs should be installed on both local and remote servers + else if (isLanguagePackExtension(manifest)) { servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer)); - } else { - const server = this.getExtensionManagementServerToInstall(manifest); + } + + else { + const [server] = this.getInstallableExtensionManagementServers(manifest); if (server) { servers.push(server); } @@ -633,27 +655,33 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return servers; } - private getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null { + private getInstallableExtensionManagementServers(manifest: IExtensionManifest): IExtensionManagementServer[] { // Only local server if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; + return [this.extensionManagementServerService.localExtensionManagementServer]; } + const servers: IExtensionManagementServer[] = []; + const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest); for (const kind of extensionKind) { if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer; + servers.push(this.extensionManagementServerService.localExtensionManagementServer); } if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer; + servers.push(this.extensionManagementServerService.remoteExtensionManagementServer); } if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) { - return this.extensionManagementServerService.webExtensionManagementServer; + servers.push(this.extensionManagementServerService.webExtensionManagementServer); } } - // Local server can accept any extension. So return local server if not compatible server found. - return this.extensionManagementServerService.localExtensionManagementServer; + // Local server can accept any extension. + if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) { + servers.push(this.extensionManagementServerService.localExtensionManagementServer); + } + + return servers; } private isExtensionsSyncEnabled(): boolean { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a914f4586d4c4..259de7b24b0be 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -160,7 +160,7 @@ import { ILayoutOffsetInfo } from '../../../platform/layout/browser/layoutServic import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js'; import { UserDataProfileService } from '../../services/userDataProfile/common/userDataProfileService.js'; import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js'; -import { EnablementState, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; +import { EnablementState, IExtensionManagementServer, IResourceExtension, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../services/extensionManagement/common/extensionManagement.js'; import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo, UninstallExtensionInfo } from '../../../platform/extensionManagement/common/extensionManagement.js'; import { Codicon } from '../../../base/common/codicons.js'; import { IRemoteExtensionsScannerService } from '../../../platform/remote/common/remoteExtensionsScanner.js'; @@ -2246,6 +2246,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens installResourceExtension(): Promise { throw new Error('Method not implemented.'); } getExtensions(): Promise { throw new Error('Method not implemented.'); } resetPinnedStateForAllUserExtensions(pinned: boolean): Promise { throw new Error('Method not implemented.'); } + getInstallableServers(extension: IGalleryExtension): Promise { throw new Error('Method not implemented.'); } } export class TestUserDataProfileService implements IUserDataProfileService {