Skip to content

Commit

Permalink
Proper fix for #236537 (#236596)
Browse files Browse the repository at this point in the history
- Support installEveryWhere option by installing the extension only on servers on which it is not installed
  • Loading branch information
sandy081 authored Dec 19, 2024
1 parent 8467007 commit 986871f
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>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 : (<IExtension>arg).identifier.id;
Expand Down Expand Up @@ -2734,8 +2768,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
private installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallExtensionOptions): Promise<ILocalExtension> {
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);
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/extensions/common/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export interface InstallExtensionOptions extends IWorkbenchInstallOptions {
version?: string;
justification?: string | { reason: string; action: string };
enable?: boolean;
installEverywhere?: boolean;
}

export interface IExtensionsNotification {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, IWorkbenchExtensionManagementService>(IProfileAwareExtensionManagementService);
Expand All @@ -84,6 +84,7 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten

canInstall(extension: IGalleryExtension | IResourceExtension): Promise<true | IMarkdownString>;

getInstallableServers(extension: IGalleryExtension): Promise<IExtensionManagementServer[]>;
installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallOptions): Promise<ILocalExtension>;
installFromGallery(gallery: IGalleryExtension, installOptions?: IWorkbenchInstallOptions): Promise<ILocalExtension>;
installFromLocation(location: URI): Promise<ILocalExtension>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
}
}

async getInstallableServers(gallery: IGalleryExtension): Promise<IExtensionManagementServer[]> {
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<void> {
if (!extension.isWorkspaceScoped) {
throw new Error('The extension is not a workspace extension');
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/test/browser/workbenchTestServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -2246,6 +2246,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens
installResourceExtension(): Promise<ILocalExtension> { throw new Error('Method not implemented.'); }
getExtensions(): Promise<IResourceExtension[]> { throw new Error('Method not implemented.'); }
resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void> { throw new Error('Method not implemented.'); }
getInstallableServers(extension: IGalleryExtension): Promise<IExtensionManagementServer[]> { throw new Error('Method not implemented.'); }
}

export class TestUserDataProfileService implements IUserDataProfileService {
Expand Down

0 comments on commit 986871f

Please sign in to comment.