Skip to content

Commit

Permalink
#140088 - extension storage migration support while migrating to pre-…
Browse files Browse the repository at this point in the history
…releases
  • Loading branch information
sandy081 committed Jan 12, 2022
1 parent 3774c64 commit 08b9273
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
*--------------------------------------------------------------------------------------------*/

import { Disposable } from 'vs/base/common/lifecycle';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ILogService } from 'vs/platform/log/common/log';

export class ExtensionsCleaner extends Disposable {

constructor(
@IExtensionManagementService extensionManagementService: ExtensionManagementService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
@ILogService logService: ILogService,
) {
super();
extensionManagementService.removeDeprecatedExtensions();
extensionManagementService.migrateUnsupportedExtensions();
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,44 +99,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
}

async migrateUnsupportedExtensions(): Promise<void> {
try {
const extensionsControlManifest = await this.getExtensionsControlManifest();
if (!extensionsControlManifest.unsupportedPreReleaseExtensions) {
return;
}
const installed = await this.getInstalled(ExtensionType.User);
for (const [unsupportedPreReleaseExtensionId, preReleaseExtension] of Object.entries(extensionsControlManifest.unsupportedPreReleaseExtensions)) {
const local = installed.find(i => areSameExtensions(i.identifier, { id: unsupportedPreReleaseExtensionId }));
if (!local) {
continue;
}
const gallery = await this.galleryService.getCompatibleExtension({ id: preReleaseExtension.id }, true, await this.getTargetPlatform());
if (!gallery) {
this.logService.info(`Skipping migrating '${local.identifier.id}' extension because, the comaptible target '${preReleaseExtension.id}' extension is not found`);
continue;
}
const manifest = await this.galleryService.getManifest(gallery, CancellationToken.None);
if (!manifest) {
this.logService.info(`Skipping migrating '${local.identifier.id}' extension because, the manifest for '${preReleaseExtension.id}' extension is not found`);
continue;
}
try {
this.logService.info(`Migrating '${local.identifier.id}' extension to '${preReleaseExtension.id}' extension...`);
await Promise.allSettled([
this.uninstall(local),
this.installExtension(manifest, gallery, { installPreReleaseVersion: true, isMachineScoped: local.isMachineScoped, operation: InstallOperation.Migrate })
]);
this.logService.info(`Migrated '${local.identifier.id}' extension to '${preReleaseExtension.id}' extension.`);
} catch (error) {
this.logService.error(error);
}
}
} catch (error) {
this.logService.error(error);
}
}

async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.unininstallExtension(extension, options);
Expand Down Expand Up @@ -172,7 +134,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
this.participants.push(participant);
}

protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions & { operation?: InstallOperation }): Promise<ILocalExtension> {
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,11 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
};
}

type PreReleaseMigrationInfo = { id: string, displayName: string, migrateStorage?: boolean, engine?: string };
interface IRawExtensionsControlManifest {
malicious: string[];
unsupported: IStringDictionary<boolean | { preReleaseExtension: { id: string, displayName: string; }; }>;
unsupported?: IStringDictionary<boolean | { preReleaseExtension: { id: string, displayName: string }; }>;
migrateToPreRelease?: IStringDictionary<PreReleaseMigrationInfo>;
}

abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
Expand Down Expand Up @@ -1011,7 +1013,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi

const result = await asJson<IRawExtensionsControlManifest>(context);
const malicious: IExtensionIdentifier[] = [];
const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string, displayName: string; }> = {};
const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string, displayName: string, migrateStorage?: boolean }> = {};

if (result) {
for (const id of result.malicious) {
Expand All @@ -1025,6 +1027,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
}
}
}
if (result.migrateToPreRelease) {
for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) {
if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) {
unsupportedPreReleaseExtensions[unsupportedPreReleaseExtensionId.toLowerCase()] = preReleaseExtensionInfo;
}
}
}
}

return { malicious, unsupportedPreReleaseExtensions };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export const enum StatisticType {

export interface IExtensionsControlManifest {
malicious: IExtensionIdentifier[];
unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string }>;
unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string, migrateStorage?: boolean }>;
}

export const enum InstallOperation {
Expand Down Expand Up @@ -397,7 +397,7 @@ export class ExtensionManagementError extends Error {
}
}

export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean };
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean, operation?: InstallOperation };
export type InstallVSIXOptions = Omit<InstallOptions, 'installGivenVersion'> & { installOnlyNewlyAddedFromExtensionPack?: boolean };
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';

/**
* Migrates the installed unsupported nightly extension to a supported pre-release extension. It includes following:
* - Uninstall the Unsupported extension
* - Install (with optional storage migration) the Pre-release extension only if
* - the extension is not installed
* - or it is a release version and the unsupported extension is enabled.
*/
export async function migrateUnsupportedExtensions(extensionManagementService: IExtensionManagementService, galleryService: IExtensionGalleryService, extensionStorageService: IExtensionStorageService, extensionEnablementService: IGlobalExtensionEnablementService, logService: ILogService): Promise<void> {
try {
const extensionsControlManifest = await extensionManagementService.getExtensionsControlManifest();
if (!extensionsControlManifest.unsupportedPreReleaseExtensions) {
return;
}
const installed = await extensionManagementService.getInstalled(ExtensionType.User);
for (const [unsupportedExtensionId, { id: preReleaseExtensionId, migrateStorage }] of Object.entries(extensionsControlManifest.unsupportedPreReleaseExtensions)) {
const unsupportedExtension = installed.find(i => areSameExtensions(i.identifier, { id: unsupportedExtensionId }));
// Unsupported Extension is not installed
if (!unsupportedExtension) {
continue;
}

const gallery = await galleryService.getCompatibleExtension({ id: preReleaseExtensionId }, true, await extensionManagementService.getTargetPlatform());
if (!gallery) {
logService.info(`Skipping migrating '${unsupportedExtension.identifier.id}' extension because, the comaptible target '${preReleaseExtensionId}' extension is not found`);
continue;
}

try {
logService.info(`Migrating '${unsupportedExtension.identifier.id}' extension to '${preReleaseExtensionId}' extension...`);

const isUnsupportedExtensionEnabled = !extensionEnablementService.getDisabledExtensions().some(e => areSameExtensions(e, unsupportedExtension.identifier));
await extensionManagementService.uninstall(unsupportedExtension);
logService.info(`Uninstalled the unsupported extension '${unsupportedExtension.identifier.id}'`);

let preReleaseExtension = installed.find(i => areSameExtensions(i.identifier, { id: preReleaseExtensionId }));
if (!preReleaseExtension || (!preReleaseExtension.isPreReleaseVersion && isUnsupportedExtensionEnabled)) {
preReleaseExtension = await extensionManagementService.installFromGallery(gallery, { installPreReleaseVersion: true, isMachineScoped: unsupportedExtension.isMachineScoped, operation: InstallOperation.Migrate });
logService.info(`Installed the pre-release extension '${preReleaseExtension.identifier.id}'`);
if (!isUnsupportedExtensionEnabled) {
await extensionEnablementService.disableExtension(preReleaseExtension.identifier);
logService.info(`Disabled the pre-release extension '${preReleaseExtension.identifier.id}' because the unsupported extension '${unsupportedExtension.identifier.id}' is disabled`);
}
if (migrateStorage) {
extensionStorageService.addToMigrationList(getExtensionId(unsupportedExtension.manifest.publisher, unsupportedExtension.manifest.name), getExtensionId(preReleaseExtension.manifest.publisher, preReleaseExtension.manifest.name));
logService.info(`Added pre-release extension to the storage migration list`);
}
}
logService.info(`Migrated '${unsupportedExtension.identifier.id}' extension to '${preReleaseExtensionId}' extension.`);
} catch (error) {
logService.error(error);
}
}
} catch (error) {
logService.error(error);
}
}
3 changes: 0 additions & 3 deletions src/vs/server/remoteExtensionHostAgentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,6 @@ export class RemoteExtensionHostAgentServer extends Disposable {
// clean up deprecated extensions
(extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions();

// migrate unsupported extensions
(extensionManagementService as ExtensionManagementService).migrateUnsupportedExtensions();

this._register(new ErrorTelemetry(accessor.get(ITelemetryService)));

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensio
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Event } from 'vs/base/common/event';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/extensions/browser/unsupportedExtensionsMigrationContribution';

// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
Expand Down Expand Up @@ -1533,6 +1534,7 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, Lif
workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementWorkspaceTrustTransitionParticipant, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsCompletionItemsProvider, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(UnsupportedExtensionsMigrationContrib, LifecyclePhase.Eventually);

// Running Extensions
registerAction2(ShowRuntimeExtensionsAction);
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';

export class UnsupportedExtensionsMigrationContrib implements IWorkbenchContribution {

constructor(
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
@ILogService logService: ILogService,
) {
// Unsupported extensions are not migrated for local extension management server, because it is done in shared process
if (extensionManagementServerService.remoteExtensionManagementServer) {
migrateUnsupportedExtensions(extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
}
if (extensionManagementServerService.webExtensionManagementServer) {
migrateUnsupportedExtensions(extensionManagementServerService.webExtensionManagementServer.extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
extensionManagementService,
label: localize('browser', "Browser"),
};
extensionManagementService.migrateUnsupportedExtensions();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';

// TODO: @sandy081 - Remove it after 6 months
export function getMigrateFromLowerCaseStorageKey(extensionId: string) {
return `extension.storage.migrateFromLowerCaseKey.${extensionId.toLowerCase()}`;
}

/**
* An extension storage has following
* - State: Stored using storage service with extension id as key and state as value.
Expand Down

0 comments on commit 08b9273

Please sign in to comment.