Update extensions compatible with newly available VS Code version (#206924)

#125417 update extensions compatible with newly available VS Code version
This commit is contained in:
Sandeep Somavarapu 2024-03-06 01:18:59 +01:00 committed by GitHub
parent 8d5de70f54
commit 1be73b4a51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 280 additions and 164 deletions

View file

@ -16,7 +16,8 @@ import * as nls from 'vs/nls';
import {
ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation,
IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode,
InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError
InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError,
IProductVersion
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
@ -29,7 +30,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use
export type ExtensionVerificationStatus = boolean | string;
export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions };
export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI };
export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI; readonly productVersion: IProductVersion };
export interface IInstallExtensionTask {
readonly identifier: IExtensionIdentifier;
readonly source: IGalleryExtension | URI;
@ -124,7 +125,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
await Promise.allSettled(extensions.map(async ({ extension, options }) => {
try {
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion);
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, options.productVersion ?? { version: this.productService.version, date: this.productService.date });
installableExtensions.push({ ...compatible, options });
} catch (error) {
results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error });
@ -230,7 +231,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
...options,
installOnlyNewlyAddedFromExtensionPack: options.installOnlyNewlyAddedFromExtensionPack ?? !URI.isUri(extension) /* always true for gallery extensions */,
isApplicationScoped,
profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation()
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;
@ -248,8 +250,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id);
} else {
try {
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation);
const installed = await this.getInstalled(undefined, task.options.profileLocation);
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, 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, donotIncludePackAndDependencies: true, 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()}`)) {
@ -405,12 +407,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return results;
}
private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> {
private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined, productVersion: IProductVersion): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> {
if (!this.galleryService.isEnabled()) {
return [];
}
const installed = await this.getInstalled(undefined, profile);
const installed = await this.getInstalled(undefined, profile, productVersion);
const knownIdentifiers: IExtensionIdentifier[] = [];
const allDependenciesAndPacks: { gallery: IGalleryExtension; manifest: IExtensionManifest }[] = [];
@ -442,7 +444,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier));
let compatible;
try {
compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease);
compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, installPreRelease, productVersion);
} catch (error) {
if (!isDependency) {
this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error));
@ -462,7 +464,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return allDependenciesAndPacks;
}
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> {
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, productVersion: IProductVersion): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> {
let compatibleExtension: IGalleryExtension | null;
const extensionsControlManifest = await this.getExtensionsControlManifest();
@ -473,7 +475,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const deprecationInfo = extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()];
if (deprecationInfo?.extension?.autoMigrate) {
this.logService.info(`The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`);
compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true }, CancellationToken.None))[0];
compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true, productVersion }, CancellationToken.None))[0];
if (!compatibleExtension) {
throw new ExtensionManagementError(nls.localize('notFoundDeprecatedReplacementExtension', "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", extension.identifier.id, deprecationInfo.extension.id), ExtensionManagementErrorCode.Deprecated);
}
@ -485,7 +487,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform);
}
compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease);
compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease, productVersion);
if (!compatibleExtension) {
/** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */
if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) {
@ -508,23 +510,23 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return { extension: compatibleExtension, manifest };
}
protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> {
protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise<IGalleryExtension | null> {
const targetPlatform = await this.getTargetPlatform();
let compatibleExtension: IGalleryExtension | null = null;
if (!sameVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null;
}
if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform, productVersion)) {
compatibleExtension = extension;
}
if (!compatibleExtension) {
if (sameVersion) {
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null;
} else {
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform);
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform, productVersion);
}
}
@ -718,7 +720,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
abstract install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;
abstract installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;
abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]>;
abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
abstract getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]>;
abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void>;
abstract download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI>;
abstract reinstallFromGallery(extension: ILocalExtension): Promise<ILocalExtension>;

View file

@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
@ -295,6 +295,7 @@ type GalleryServiceAdditionalQueryEvent = {
};
interface IExtensionCriteria {
readonly productVersion: IProductVersion;
readonly targetPlatform: TargetPlatform;
readonly compatible: boolean;
readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[];
@ -662,14 +663,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
query = query.withSource(options.source);
}
const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, includePreRelease: includePreReleases, versions, compatible: !!options.compatible }, token);
const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, includePreRelease: includePreReleases, versions, compatible: !!options.compatible, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token);
if (options.source) {
extensions.forEach((e, index) => setTelemetry(e, index, options.source));
}
return extensions;
}
async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null> {
async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {
if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {
return null;
}
@ -680,11 +681,11 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
.withFlags(Flags.IncludeVersions)
.withPage(1, 1)
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform, compatible: true, includePreRelease }, CancellationToken.None);
const { extensions } = await this.queryGalleryExtensions(query, { targetPlatform, compatible: true, includePreRelease, productVersion }, CancellationToken.None);
return extensions[0] || null;
}
async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean> {
async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {
if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) {
return false;
}
@ -702,10 +703,10 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
}
engine = manifest.engines.vscode;
}
return isEngineValid(engine, this.productService.version, this.productService.date);
return isEngineValid(engine, productVersion.version, productVersion.date);
}
private async isValidVersion(rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise<boolean> {
private async isValidVersion(rawGalleryExtensionVersion: IRawGalleryExtensionVersion, versionType: 'release' | 'prerelease' | 'any', compatible: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {
if (!isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion), allTargetPlatforms, targetPlatform)) {
return false;
}
@ -717,7 +718,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
if (compatible) {
try {
const engine = await this.getEngine(rawGalleryExtensionVersion);
if (!isEngineValid(engine, this.productService.version, this.productService.date)) {
if (!isEngineValid(engine, productVersion.version, productVersion.date)) {
return false;
}
} catch (error) {
@ -784,7 +785,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
}
const runQuery = async (query: Query, token: CancellationToken) => {
const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease }, token);
const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, token);
extensions.forEach((e, index) => setTelemetry(e, ((query.pageNumber - 1) * query.pageSize) + index, options.source));
return { extensions, total };
};
@ -913,7 +914,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
continue;
}
// Allow any version if includePreRelease flag is set otherwise only release versions are allowed
if (await this.isValidVersion(rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, allTargetPlatforms, criteria.targetPlatform)) {
if (await this.isValidVersion(rawGalleryExtensionVersion, includePreRelease ? 'any' : 'release', criteria.compatible, allTargetPlatforms, criteria.targetPlatform, criteria.productVersion)) {
return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, queryContext);
}
if (version && rawGalleryExtensionVersion.version === version) {

View file

@ -20,6 +20,11 @@ export const EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT = 'skipWalkthrough';
export const EXTENSION_INSTALL_SYNC_CONTEXT = 'extensionsSync';
export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = 'dependecyOrPackExtensionInstall';
export interface IProductVersion {
readonly version: string;
readonly date?: string;
}
export function TargetPlatformToString(targetPlatform: TargetPlatform) {
switch (targetPlatform) {
case TargetPlatform.WIN32_X64: return 'Windows 64 bit';
@ -281,6 +286,7 @@ export interface IQueryOptions {
sortOrder?: SortOrder;
source?: string;
includePreRelease?: boolean;
productVersion?: IProductVersion;
}
export const enum StatisticType {
@ -330,6 +336,7 @@ export interface IExtensionInfo extends IExtensionIdentifier {
export interface IExtensionQueryOptions {
targetPlatform?: TargetPlatform;
productVersion?: IProductVersion;
compatible?: boolean;
queryAllVersions?: boolean;
source?: string;
@ -347,8 +354,8 @@ export interface IExtensionGalleryService {
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, token: CancellationToken): Promise<IGalleryExtension[]>;
getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, token: CancellationToken): Promise<IGalleryExtension[]>;
isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean>;
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise<boolean>;
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion?: IProductVersion): Promise<IGalleryExtension | null>;
getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void>;
@ -454,6 +461,7 @@ export type InstallOptions = {
operation?: InstallOperation;
profileLocation?: URI;
installOnlyNewlyAddedFromExtensionPack?: boolean;
productVersion?: IProductVersion;
/**
* Context passed through to InstallExtensionResult
*/
@ -490,7 +498,7 @@ export interface IExtensionManagementService {
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
toggleAppliationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension>;
reinstallFromGallery(extension: ILocalExtension): Promise<ILocalExtension>;
getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]>;
getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;
copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void>;
updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, profileLocation?: URI): Promise<ILocalExtension>;

View file

@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;
@ -139,7 +139,7 @@ export class ExtensionManagementChannel implements IServerChannel {
return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer));
}
case 'getInstalled': {
const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer));
const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer), args[2]);
return extensions.map(e => transformOutgoingExtension(e, uriTransformer));
}
case 'toggleAppliationScope': {
@ -271,8 +271,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
return Promise.resolve(this.channel.call<ILocalExtension>('reinstallFromGallery', [extension])).then(local => transformIncomingExtension(local, null));
}
getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise<ILocalExtension[]> {
return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type, extensionsProfileResource]))
getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {
return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type, extensionsProfileResource, productVersion]))
.then(extensions => extensions.map(extension => transformIncomingExtension(extension, null)));
}

View file

@ -83,7 +83,7 @@ export interface IExtensionsProfileScannerService {
readonly onDidRemoveExtensions: Event<DidRemoveProfileExtensionsEvent>;
scanProfileExtensions(profileLocation: URI, options?: IProfileExtensionsScanOptions): Promise<IScannedProfileExtension[]>;
addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise<IScannedProfileExtension[]>;
addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI, keepExistingVersions?: boolean): Promise<IScannedProfileExtension[]>;
updateMetadata(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise<IScannedProfileExtension[]>;
removeExtensionFromProfile(extension: IExtension, profileLocation: URI): Promise<void>;
}
@ -120,18 +120,22 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable
return this.withProfileExtensions(profileLocation, undefined, options);
}
async addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise<IScannedProfileExtension[]> {
async addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI, keepExistingVersions?: boolean): Promise<IScannedProfileExtension[]> {
const extensionsToRemove: IScannedProfileExtension[] = [];
const extensionsToAdd: IScannedProfileExtension[] = [];
try {
await this.withProfileExtensions(profileLocation, existingExtensions => {
const result: IScannedProfileExtension[] = [];
for (const existing of existingExtensions) {
if (extensions.some(([e]) => areSameExtensions(e.identifier, existing.identifier) && e.manifest.version !== existing.version)) {
// Remove the existing extension with different version
extensionsToRemove.push(existing);
} else {
result.push(existing);
if (keepExistingVersions) {
result.push(...existingExtensions);
} else {
for (const existing of existingExtensions) {
if (extensions.some(([e]) => areSameExtensions(e.identifier, existing.identifier) && e.manifest.version !== existing.version)) {
// Remove the existing extension with different version
extensionsToRemove.push(existing);
} else {
result.push(existing);
}
}
}
for (const [extension, metadata] of extensions) {

View file

@ -22,7 +22,7 @@ import { isEmptyObject } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IProductVersion, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions';
import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator';
@ -108,6 +108,7 @@ export type ScanOptions = {
readonly checkControlFile?: boolean;
readonly language?: string;
readonly useCache?: boolean;
readonly productVersion?: IProductVersion;
};
export const IExtensionsScannerService = createDecorator<IExtensionsScannerService>('IExtensionsScannerService');
@ -195,7 +196,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
const location = scanOptions.profileLocation ?? this.userExtensionsLocation;
this.logService.trace('Started scanning user extensions', location);
const profileScanOptions: IProfileExtensionsScanOptions | undefined = this.uriIdentityService.extUri.isEqual(scanOptions.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource) ? { bailOutWhenFileNotFound: true } : undefined;
const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions);
const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language, true, profileScanOptions, scanOptions.productVersion ?? this.getProductVersion());
const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner;
let extensions: IRelaxedScannedExtension[];
try {
@ -217,7 +218,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) {
const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file)
.map(async extensionDevelopmentLocationURI => {
const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined);
const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, false, ExtensionType.User, true, scanOptions.language, false /* do not validate */, undefined, scanOptions.productVersion ?? this.getProductVersion());
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input);
return extensions.map(extension => {
// Override the extension type from the existing extensions
@ -233,7 +234,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
}
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension | null> {
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined);
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion());
const extension = await this.extensionsScanner.scanExtension(extensionsScannerInput);
if (!extension) {
return null;
@ -245,7 +246,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
}
async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined);
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language, true, undefined, scanOptions.productVersion ?? this.getProductVersion());
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput);
return this.applyScanOptions(extensions, extensionType, scanOptions, true);
}
@ -392,7 +393,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
private async scanDefaultSystemExtensions(useCache: boolean, language: string | undefined): Promise<IRelaxedScannedExtension[]> {
this.logService.trace('Started scanning system extensions');
const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined);
const extensionsScannerInput = await this.createExtensionScannerInput(this.systemExtensionsLocation, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion());
const extensionsScanner = useCache && !extensionsScannerInput.devMode ? this.systemExtensionsCachedScanner : this.extensionsScanner;
const result = await extensionsScanner.scanExtensions(extensionsScannerInput);
this.logService.trace('Scanned system extensions:', result.length);
@ -422,7 +423,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
break;
}
}
const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined)))));
const result = await Promise.all(devSystemExtensionsLocations.map(async location => this.extensionsScanner.scanExtension((await this.createExtensionScannerInput(location, false, ExtensionType.System, true, language, true, undefined, this.getProductVersion())))));
this.logService.trace('Scanned dev system extensions:', result.length);
return coalesce(result);
}
@ -436,7 +437,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
}
}
private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined): Promise<ExtensionScannerInput> {
private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean, profileScanOptions: IProfileExtensionsScanOptions | undefined, productVersion: IProductVersion): Promise<ExtensionScannerInput> {
const translations = await this.getTranslations(language ?? platform.language);
const mtime = await this.getMtime(location);
const applicationExtensionsLocation = profile && !this.uriIdentityService.extUri.isEqual(location, this.userDataProfilesService.defaultProfile.extensionsResource) ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined;
@ -451,8 +452,8 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
type,
excludeObsolete,
validate,
this.productService.version,
this.productService.date,
productVersion.version,
productVersion.date,
this.productService.commit,
!this.environmentService.isBuilt,
language,
@ -472,6 +473,13 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
return undefined;
}
private getProductVersion(): IProductVersion {
return {
version: this.productService.version,
date: this.productService.date,
};
}
}
export class ExtensionScannerInput {

View file

@ -28,7 +28,8 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import {
ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation,
Metadata, InstallOptions
Metadata, InstallOptions,
IProductVersion
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
@ -128,8 +129,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
}
}
getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise<ILocalExtension[]> {
return this.extensionsScanner.scanExtensions(type ?? null, profileLocation);
getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<ILocalExtension[]> {
return this.extensionsScanner.scanExtensions(type ?? null, profileLocation, productVersion);
}
scanAllUserInstalledExtensions(): Promise<ILocalExtension[]> {
@ -179,7 +180,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]> {
this.logService.trace('ExtensionManagementService#installExtensionsFromProfile', extensions, fromProfileLocation.toString(), toProfileLocation.toString());
const extensionsToInstall = (await this.extensionsScanner.scanExtensions(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier)));
const extensionsToInstall = (await this.getInstalled(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier)));
if (extensionsToInstall.length) {
const metadata = await Promise.all(extensionsToInstall.map(e => this.extensionsScanner.scanMetadata(e, fromProfileLocation)));
await this.addExtensionsToProfile(extensionsToInstall.map((e, index) => [e, metadata[index]]), toProfileLocation);
@ -236,7 +237,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
}
copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void> {
return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation);
return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date });
}
markAsUninstalled(...extensions: IExtension[]): Promise<void> {
@ -333,7 +334,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
}
}
if (added) {
const extensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, added.profileLocation);
const extensions = await this.getInstalled(ExtensionType.User, added.profileLocation);
const addedExtensions = extensions.filter(e => added.extensions.some(identifier => areSameExtensions(identifier, e.identifier)));
this._onDidInstallExtensions.fire(addedExtensions.map(local => {
this.logService.info('Extensions added from another source', local.identifier.id, added.profileLocation.toString());
@ -449,8 +450,8 @@ export class ExtensionsScanner extends Disposable {
await this.removeUninstalledExtensions();
}
async scanExtensions(type: ExtensionType | null, profileLocation: URI): Promise<ILocalExtension[]> {
const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation };
async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise<ILocalExtension[]> {
const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion };
let scannedExtensions: IScannedExtension[] = [];
if (type === null || type === ExtensionType.System) {
scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false));
@ -613,8 +614,8 @@ export class ExtensionsScanner extends Disposable {
return this.scanLocalExtension(extension.location, extension.type, toProfileLocation);
}
async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void> {
const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation);
async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, productVersion: IProductVersion): Promise<void> {
const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation, productVersion);
const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions
.filter(e => !e.isApplicationScoped) /* remove application scoped extensions */
.map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)])));
@ -819,7 +820,7 @@ abstract class InstallExtensionTask extends AbstractExtensionTask<ILocalExtensio
if (this.uriIdentityService.extUri.isEqual(this.userDataProfilesService.defaultProfile.extensionsResource, this._profileLocation)) {
await this.extensionsScannerService.initializeDefaultProfileExtensions();
}
await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this._profileLocation);
await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this._profileLocation, !local.isValid);
return local;
}
@ -879,7 +880,7 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask {
protected async install(token: CancellationToken): Promise<[ILocalExtension, Metadata]> {
let installed;
try {
installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation);
installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion);
} catch (error) {
throw new ExtensionManagementError(error, ExtensionManagementErrorCode.Scanning);
}
@ -969,7 +970,7 @@ class InstallVSIXTask extends InstallExtensionTask {
protected async install(token: CancellationToken): Promise<[ILocalExtension, Metadata]> {
const extensionKey = new ExtensionKey(this.identifier, this.manifest.version);
const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation);
const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion);
const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier));
const metadata: Metadata = {
isApplicationScoped: this.options.isApplicationScoped || existing?.isApplicationScoped,

View file

@ -102,7 +102,7 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask {
engines: { vscode: '*' },
},
extension,
{ profileLocation: userDataProfilesService.defaultProfile.extensionsResource },
{ profileLocation: userDataProfilesService.defaultProfile.extensionsResource, productVersion: { version: '' } },
extensionDownloader,
new TestExtensionsScanner(),
uriIdentityService,

View file

@ -9,6 +9,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export interface IUpdate {
version: string;
productVersion: string;
timestamp?: number;
url?: string;
sha256hash?: string;
}

View file

@ -24,8 +24,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
@memoize private get onRawError(): Event<string> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); }
@memoize private get onRawUpdateNotAvailable(): Event<void> { return Event.fromNodeEventEmitter<void>(electron.autoUpdater, 'update-not-available'); }
@memoize private get onRawUpdateAvailable(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version) => ({ url, version, productVersion: version })); }
@memoize private get onRawUpdateDownloaded(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); }
@memoize private get onRawUpdateAvailable(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version, timestamp) => ({ url, version, productVersion: version, timestamp })); }
@memoize private get onRawUpdateDownloaded(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, version, timestamp) => ({ version, productVersion: version, timestamp })); }
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,

View file

@ -12,7 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import * as json from 'vs/base/common/json';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { disposeIfDisposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@ -73,6 +73,7 @@ import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConst
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures';
import { Registry } from 'vs/platform/registry/common/platform';
import { IUpdateService } from 'vs/platform/update/common/update';
export class PromptExtensionInstallFailureAction extends Action {
@ -1562,7 +1563,9 @@ export class ReloadAction extends ExtensionAction {
constructor(
@IHostService private readonly hostService: IHostService,
@IUpdateService private readonly updateService: IUpdateService,
@IExtensionService private readonly extensionService: IExtensionService,
@IProductService private readonly productService: IProductService,
) {
super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false);
this._register(this.extensionService.onDidChangeExtensions(() => this.update()));
@ -1572,27 +1575,53 @@ export class ReloadAction extends ExtensionAction {
update(): void {
this.enabled = false;
this.tooltip = '';
this.class = ReloadAction.DisabledClass;
if (!this.extension) {
return;
}
const state = this.extension.state;
if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) {
return;
}
if (this.extension.local && this.extension.local.manifest && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.localizations && this.extension.local.manifest.contributes.localizations.length > 0) {
return;
}
const reloadTooltip = this.extension.reloadRequiredStatus;
this.enabled = reloadTooltip !== undefined;
this.label = reloadTooltip !== undefined ? localize('reload required', 'Reload Required') : '';
this.tooltip = reloadTooltip !== undefined ? reloadTooltip : '';
const runtimeState = this.extension.runtimeState;
if (!runtimeState) {
return;
}
this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass;
this.enabled = true;
this.class = ReloadAction.EnabledClass;
this.tooltip = runtimeState.reason;
this.label = runtimeState.action === ExtensionRuntimeActionType.Reload ? localize('reload required', 'Reload {0}', this.productService.nameShort)
: runtimeState.action === ExtensionRuntimeActionType.QuitAndInstall ? localize('restart product', 'Restart {0}', this.productService.nameShort)
: runtimeState.action === ExtensionRuntimeActionType.ApplyUpdate || runtimeState.action === ExtensionRuntimeActionType.DownloadUpdate ? localize('update product', 'Update {0}', this.productService.nameShort) : '';
}
override run(): Promise<any> {
return Promise.resolve(this.hostService.reload());
override async run(): Promise<any> {
const runtimeState = this.extension?.runtimeState;
if (runtimeState?.action === ExtensionRuntimeActionType.Reload) {
return this.hostService.reload();
}
else if (runtimeState?.action === ExtensionRuntimeActionType.DownloadUpdate) {
return this.updateService.downloadUpdate();
}
else if (runtimeState?.action === ExtensionRuntimeActionType.ApplyUpdate) {
return this.updateService.applyUpdate();
}
else if (runtimeState?.action === ExtensionRuntimeActionType.QuitAndInstall) {
return this.updateService.quitAndInstall();
}
}
}

View file

@ -853,19 +853,19 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution
private onServiceChange(): void {
this.badgeHandle.clear();
const extensionsReloadRequired = this.extensionsWorkbenchService.installed.filter(e => e.reloadRequiredStatus !== undefined);
const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !extensionsReloadRequired.includes(e) ? 1 : 0), 0);
const newBadgeNumber = outdated + extensionsReloadRequired.length;
const actionRequired = this.extensionsWorkbenchService.installed.filter(e => e.runtimeState !== undefined);
const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) && !actionRequired.includes(e) ? 1 : 0), 0);
const newBadgeNumber = outdated + actionRequired.length;
if (newBadgeNumber > 0) {
let msg = '';
if (outdated) {
msg += outdated === 1 ? localize('extensionToUpdate', '{0} requires update', outdated) : localize('extensionsToUpdate', '{0} require update', outdated);
}
if (outdated > 0 && extensionsReloadRequired.length > 0) {
if (outdated > 0 && actionRequired.length > 0) {
msg += ', ';
}
if (extensionsReloadRequired.length) {
msg += extensionsReloadRequired.length === 1 ? localize('extensionToReload', '{0} requires reload', extensionsReloadRequired.length) : localize('extensionsToReload', '{0} require reload', extensionsReloadRequired.length);
if (actionRequired.length) {
msg += actionRequired.length === 1 ? localize('extensionToReload', '{0} requires restart', actionRequired.length) : localize('extensionsToReload', '{0} require restart', actionRequired.length);
}
const badge = new NumberBadge(newBadgeNumber, () => msg);
this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge });

View file

@ -522,7 +522,7 @@ export class ExtensionsListView extends ViewPane {
result = local.filter(e => !e.isBuiltin && matchingText(e));
result = this.sortExtensions(result, options);
} else {
result = local.filter(e => (!e.isBuiltin || e.outdated || e.reloadRequiredStatus !== undefined) && matchingText(e));
result = local.filter(e => (!e.isBuiltin || e.outdated || e.runtimeState !== undefined) && matchingText(e));
const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(e.identifier.value, e); return result; }, new ExtensionIdentifierMap<IExtensionDescription>());
const defaultSort = (e1: IExtension, e2: IExtension) => {
@ -551,21 +551,21 @@ export class ExtensionsListView extends ViewPane {
};
const outdated: IExtension[] = [];
const reloadRequired: IExtension[] = [];
const actionRequired: IExtension[] = [];
const noActionRequired: IExtension[] = [];
result.forEach(e => {
if (e.outdated) {
outdated.push(e);
}
else if (e.reloadRequiredStatus) {
reloadRequired.push(e);
else if (e.runtimeState) {
actionRequired.push(e);
}
else {
noActionRequired.push(e);
}
});
result = [...outdated.sort(defaultSort), ...reloadRequired.sort(defaultSort), ...noActionRequired.sort(defaultSort)];
result = [...outdated.sort(defaultSort), ...actionRequired.sort(defaultSort), ...noActionRequired.sort(defaultSort)];
}
return result;
}

View file

@ -616,10 +616,10 @@ export class ExtensionHoverWidget extends ExtensionWidget {
const preReleaseMessage = ExtensionHoverWidget.getPreReleaseMessage(this.extension);
const extensionRuntimeStatus = this.extensionsWorkbenchService.getExtensionStatus(this.extension);
const extensionStatus = this.extensionStatusAction.status;
const reloadRequiredMessage = this.extension.reloadRequiredStatus;
const runtimeState = this.extension.runtimeState;
const recommendationMessage = this.getRecommendationMessage(this.extension);
if (extensionRuntimeStatus || extensionStatus || reloadRequiredMessage || recommendationMessage || preReleaseMessage) {
if (extensionRuntimeStatus || extensionStatus || runtimeState || recommendationMessage || preReleaseMessage) {
markdown.appendMarkdown(`---`);
markdown.appendText(`\n`);
@ -656,9 +656,9 @@ export class ExtensionHoverWidget extends ExtensionWidget {
markdown.appendText(`\n`);
}
if (reloadRequiredMessage) {
if (runtimeState) {
markdown.appendMarkdown(`$(${infoIcon.id})&nbsp;`);
markdown.appendMarkdown(`${reloadRequiredMessage}`);
markdown.appendMarkdown(`${runtimeState.reason}`);
markdown.appendText(`\n`);
}

View file

@ -16,7 +16,7 @@ import {
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, WEB_EXTENSION_TAG, InstallExtensionResult,
IExtensionsControlManifest, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo, EXTENSION_IDENTIFIER_REGEX,
InstallOptions
InstallOptions, IProductVersion
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@ -24,7 +24,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { URI } from 'vs/base/common/uri';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, AutoUpdateConfigurationValue, InstallExtensionOptions, ExtensionRuntimeState, ExtensionRuntimeActionType } from 'vs/workbench/contrib/extensions/common/extensions';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url';
import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
@ -54,6 +54,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c
import { mainWindow } from 'vs/base/browser/window';
import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@ -76,7 +77,7 @@ export class Extension implements IExtension {
constructor(
private stateProvider: IExtensionStateProvider<ExtensionState>,
private runtimeStateProvider: IExtensionStateProvider<string | undefined>,
private runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
public readonly server: IExtensionManagementServer | undefined,
public local: ILocalExtension | undefined,
public gallery: IGalleryExtension | undefined,
@ -274,7 +275,7 @@ export class Extension implements IExtension {
&& semver.eq(this.latestVersion, this.version);
}
get reloadRequiredStatus(): string | undefined {
get runtimeState(): ExtensionRuntimeState | undefined {
return this.runtimeStateProvider(this);
}
@ -463,7 +464,7 @@ class Extensions extends Disposable {
constructor(
readonly server: IExtensionManagementServer,
private readonly stateProvider: IExtensionStateProvider<ExtensionState>,
private readonly runtimeStateProvider: IExtensionStateProvider<string | undefined>,
private readonly runtimeStateProvider: IExtensionStateProvider<ExtensionRuntimeState | undefined>,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ -496,15 +497,14 @@ class Extensions extends Disposable {
return this._local;
}
async queryInstalled(): Promise<IExtension[]> {
await this.fetchInstalledExtensions();
async queryInstalled(productVersion: IProductVersion): Promise<IExtension[]> {
await this.fetchInstalledExtensions(productVersion);
this._onChange.fire(undefined);
return this.local;
}
async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[]): Promise<boolean> {
let hasChanged: boolean = false;
const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions);
async syncInstalledExtensionsWithGallery(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<void> {
const extensions = await this.mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions, productVersion);
for (const [extension, gallery] of extensions) {
// update metadata of the extension if it does not exist
if (extension.local && !extension.local.identifier.uuid) {
@ -513,20 +513,18 @@ class Extensions extends Disposable {
if (!extension.gallery || extension.gallery.version !== gallery.version || extension.gallery.properties.targetPlatform !== gallery.properties.targetPlatform) {
extension.gallery = gallery;
this._onChange.fire({ extension });
hasChanged = true;
}
}
return hasChanged;
}
private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[]): Promise<[Extension, IGalleryExtension][]> {
private async mapInstalledExtensionWithCompatibleGalleryExtension(galleryExtensions: IGalleryExtension[], productVersion: IProductVersion): Promise<[Extension, IGalleryExtension][]> {
const mappedExtensions = this.mapInstalledExtensionWithGalleryExtension(galleryExtensions);
const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
const compatibleGalleryExtensions: IGalleryExtension[] = [];
const compatibleGalleryExtensionsToFetch: IExtensionInfo[] = [];
await Promise.allSettled(mappedExtensions.map(async ([extension, gallery]) => {
if (extension.local) {
if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform)) {
if (await this.galleryService.isExtensionCompatible(gallery, extension.local.preRelease, targetPlatform, productVersion)) {
compatibleGalleryExtensions.push(gallery);
} else {
compatibleGalleryExtensionsToFetch.push({ ...extension.local.identifier, preRelease: extension.local.preRelease });
@ -534,7 +532,7 @@ class Extensions extends Disposable {
}
}));
if (compatibleGalleryExtensionsToFetch.length) {
const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true }, CancellationToken.None);
const result = await this.galleryService.getExtensions(compatibleGalleryExtensionsToFetch, { targetPlatform, compatible: true, queryAllVersions: true, productVersion }, CancellationToken.None);
compatibleGalleryExtensions.push(...result);
}
return this.mapInstalledExtensionWithGalleryExtension(compatibleGalleryExtensions);
@ -591,9 +589,9 @@ class Extensions extends Disposable {
}
}
private async fetchInstalledExtensions(): Promise<void> {
private async fetchInstalledExtensions(productVersion?: IProductVersion): Promise<void> {
const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest();
const all = await this.server.extensionManagementService.getInstalled();
const all = await this.server.extensionManagementService.getInstalled(undefined, undefined, productVersion);
// dedup user and system extensions by giving priority to user extensions.
const installed = groupByExtension(all, r => r.identifier).reduce((result, extensions) => {
@ -793,19 +791,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
this.hasOutdatedExtensionsContextKey = HasOutdatedExtensionsContext.bindTo(contextKeyService);
if (extensionManagementServerService.localExtensionManagementServer) {
this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext)));
this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext)));
this._register(this.localExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
this._register(this.localExtensions.onReset(e => this.reset()));
this.extensionsServers.push(this.localExtensions);
}
if (extensionManagementServerService.remoteExtensionManagementServer) {
this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext)));
this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext)));
this._register(this.remoteExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
this._register(this.remoteExtensions.onReset(e => this.reset()));
this.extensionsServers.push(this.remoteExtensions);
}
if (extensionManagementServerService.webExtensionManagementServer) {
this.webExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.webExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext)));
this.webExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.webExtensionManagementServer, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext)));
this._register(this.webExtensions.onChange(e => this.onDidChangeExtensions(e?.extension)));
this._register(this.webExtensions.onReset(e => this.reset()));
this.extensionsServers.push(this.webExtensions);
@ -861,8 +859,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}));
this._register(Event.debounce(this.onChange, () => undefined, 100)(() => this.hasOutdatedExtensionsContextKey.set(this.outdated.length > 0)));
this._register(this.updateService.onStateChange(e => {
if ((e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) && this.isAutoUpdateEnabled()) {
this.checkForUpdates();
if (!this.isAutoUpdateEnabled()) {
return;
}
if ((e.type === StateType.CheckingForUpdates && e.explicit) || e.type === StateType.AvailableForDownload || e.type === StateType.Downloading) {
this.eventuallyCheckForUpdates(true);
}
}));
@ -964,19 +965,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
async queryLocal(server?: IExtensionManagementServer): Promise<IExtension[]> {
if (server) {
if (this.localExtensions && this.extensionManagementServerService.localExtensionManagementServer === server) {
return this.localExtensions.queryInstalled();
return this.localExtensions.queryInstalled(this.getProductVersion());
}
if (this.remoteExtensions && this.extensionManagementServerService.remoteExtensionManagementServer === server) {
return this.remoteExtensions.queryInstalled();
return this.remoteExtensions.queryInstalled(this.getProductVersion());
}
if (this.webExtensions && this.extensionManagementServerService.webExtensionManagementServer === server) {
return this.webExtensions.queryInstalled();
return this.webExtensions.queryInstalled(this.getProductVersion());
}
}
if (this.localExtensions) {
try {
await this.localExtensions.queryInstalled();
await this.localExtensions.queryInstalled(this.getProductVersion());
}
catch (error) {
this.logService.error(error);
@ -984,7 +985,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
if (this.remoteExtensions) {
try {
await this.remoteExtensions.queryInstalled();
await this.remoteExtensions.queryInstalled(this.getProductVersion());
}
catch (error) {
this.logService.error(error);
@ -992,7 +993,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
if (this.webExtensions) {
try {
await this.webExtensions.queryInstalled();
await this.webExtensions.queryInstalled(this.getProductVersion());
}
catch (error) {
this.logService.error(error);
@ -1068,7 +1069,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {
let extension = this.getInstalledExtensionMatchingGallery(gallery);
if (!extension) {
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery);
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery);
Extensions.updateExtensionFromControlManifest(<Extension>extension, extensionsControlManifest);
}
return extension;
@ -1110,7 +1111,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return undefined;
}
private getReloadStatus(extension: IExtension): string | undefined {
private getRuntimeState(extension: IExtension): ExtensionRuntimeState | undefined {
const isUninstalled = extension.state === ExtensionState.Uninstalled;
const runningExtension = this.extensionService.extensions.find(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier));
@ -1118,7 +1119,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension);
const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)));
if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) {
return nls.localize('postUninstallTooltip', "Please reload Visual Studio Code to complete the uninstallation of this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUninstallTooltip', "Please reload {0} to complete the uninstallation of this extension.", this.productService.nameLong) };
}
return undefined;
}
@ -1138,7 +1139,25 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (isSameExtensionRunning) {
// Different version or target platform of same extension is running. Requires reload to run the current version
if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) {
return nls.localize('postUpdateTooltip', "Please reload Visual Studio Code to enable the updated extension.");
const productCurrentVersion = this.getProductCurrentVersion();
const productUpdateVersion = this.getProductUpdateVersion();
if (productUpdateVersion
&& !isEngineValid(extension.local.manifest.engines.vscode, productCurrentVersion.version, productCurrentVersion.date)
&& isEngineValid(extension.local.manifest.engines.vscode, productUpdateVersion.version, productUpdateVersion.date)
) {
const state = this.updateService.state;
if (state.type === StateType.AvailableForDownload) {
return { action: ExtensionRuntimeActionType.DownloadUpdate, reason: nls.localize('postUpdateDownloadTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };
}
if (state.type === StateType.Downloaded) {
return { action: ExtensionRuntimeActionType.ApplyUpdate, reason: nls.localize('postUpdateUpdateTooltip', "Please update {0} to enable the updated extension.", this.productService.nameLong) };
}
if (state.type === StateType.Ready) {
return { action: ExtensionRuntimeActionType.QuitAndInstall, reason: nls.localize('postUpdateRestartTooltip', "Please restart {0} to enable the updated extension.", this.productService.nameLong) };
}
return undefined;
}
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postUpdateTooltip', "Please reload {0} to enable the updated extension.", this.productService.nameLong) };
}
if (this.extensionsServers.length > 1) {
@ -1146,12 +1165,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (extensionInOtherServer) {
// This extension prefers to run on UI/Local side but is running in remote
if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.localExtensionManagementServer) {
return nls.localize('enable locally', "Please reload Visual Studio Code to enable this extension locally.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable locally', "Please reload {0} to enable this extension locally.", this.productService.nameLong) };
}
// This extension prefers to run on Workspace/Remote side but is running in local
if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest) && extensionInOtherServer.server === this.extensionManagementServerService.remoteExtensionManagementServer) {
return nls.localize('enable remote', "Please reload Visual Studio Code to enable this extension in {0}.", this.extensionManagementServerService.remoteExtensionManagementServer?.label);
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('enable remote', "Please reload {0} to enable this extension in {1}.", this.productService.nameLong, this.extensionManagementServerService.remoteExtensionManagementServer?.label) };
}
}
}
@ -1161,20 +1180,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) {
// This extension prefers to run on UI/Local side but is running in remote
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(extension.local.manifest)) {
return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) };
}
}
if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) {
// This extension prefers to run on Workspace/Remote side but is running in local
if (this.extensionManifestPropertiesService.prefersExecuteOnWorkspace(extension.local.manifest)) {
return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) };
}
}
}
return undefined;
} else {
if (isSameExtensionRunning) {
return nls.localize('postDisableTooltip', "Please reload Visual Studio Code to disable this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postDisableTooltip', "Please reload {0} to disable this extension.", this.productService.nameLong) };
}
}
return undefined;
@ -1183,7 +1202,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
// Extension is not running
else {
if (isEnabled && !this.extensionService.canAddExtension(toExtensionDescription(extension.local))) {
return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) };
}
const otherServer = extension.server ? extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null;
@ -1191,7 +1210,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
const extensionInOtherServer = this.local.filter(e => areSameExtensions(e.identifier, extension.identifier) && e.server === otherServer)[0];
// Same extension in other server exists and
if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) {
return nls.localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension.");
return { action: ExtensionRuntimeActionType.Reload, reason: nls.localize('postEnableTooltip', "Please reload {0} to enable this extension.", this.productService.nameLong) };
}
}
}
@ -1376,7 +1395,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
this.telemetryService.publicLog2<GalleryServiceUpdatesCheckEvent, GalleryServiceUpdatesCheckClassification>('galleryService:checkingForUpdates', {
count: infos.length,
});
const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true }, CancellationToken.None);
const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None);
if (galleryExtensions.length) {
await this.syncInstalledExtensionsWithGallery(galleryExtensions);
}
@ -1414,8 +1433,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (!extensions.length) {
return;
}
const result = await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery)));
if (this.isAutoUpdateEnabled() && result.some(r => r.status === 'fulfilled' && r.value)) {
await Promise.allSettled(extensions.map(extensions => extensions.syncInstalledExtensionsWithGallery(gallery, this.getProductVersion())));
if (this.isAutoUpdateEnabled()) {
this.eventuallyAutoUpdateExtensions();
}
}
@ -1434,12 +1453,20 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
private eventuallyCheckForUpdates(immediate = false): void {
this.updatesCheckDelayer.cancel();
this.updatesCheckDelayer.trigger(async () => {
if (this.isAutoUpdateEnabled() || this.isAutoCheckUpdatesEnabled()) {
await this.checkForUpdates();
}
this.eventuallyCheckForUpdates();
}, immediate ? 0 : ExtensionsWorkbenchService.UpdatesCheckInterval).then(undefined, err => null);
}, immediate ? 0 : this.getUpdatesCheckInterval()).then(undefined, err => null);
}
private getUpdatesCheckInterval(): number {
if (this.productService.quality === 'insider' && this.getProductUpdateVersion()) {
return 1000 * 60 * 60 * 1; // 1 hour
}
return ExtensionsWorkbenchService.UpdatesCheckInterval;
}
private eventuallyAutoUpdateExtensions(): void {
@ -1474,8 +1501,32 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
const toUpdate = this.outdated.filter(e => !e.local?.pinned && this.shouldAutoUpdateExtension(e));
if (!toUpdate.length) {
return;
}
await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true } : undefined)));
const productVersion = this.getProductVersion();
await Promises.settled(toUpdate.map(e => this.install(e, e.local?.preRelease ? { installPreReleaseVersion: true, productVersion } : { productVersion })));
}
private getProductVersion(): IProductVersion {
return this.getProductUpdateVersion() ?? this.getProductCurrentVersion();
}
private getProductCurrentVersion(): IProductVersion {
return { version: this.productService.version, date: this.productService.date };
}
private getProductUpdateVersion(): IProductVersion | undefined {
switch (this.updateService.state.type) {
case StateType.AvailableForDownload:
case StateType.Downloading:
case StateType.Downloaded:
case StateType.Updating:
case StateType.Ready:
return { version: this.updateService.state.update.version, date: this.updateService.state.update.timestamp ? new Date(this.updateService.state.update.timestamp).toISOString() : undefined };
}
return undefined;
}
private async updateExtensionsPinnedState(): Promise<void> {
@ -1682,7 +1733,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None));
}
if (!extension && gallery) {
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery);
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getRuntimeState(ext), undefined, undefined, gallery);
Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest());
}
if (extension?.isMalicious) {
@ -1953,6 +2004,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
installOptions = installOptions ?? {};
installOptions.pinned = extension.local?.pinned || !this.shouldAutoUpdateExtension(extension);
if (extension.local) {
installOptions.productVersion = this.getProductVersion();
return this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions);
} else {
return this.extensionManagementService.installFromGallery(gallery, installOptions);

View file

@ -39,6 +39,15 @@ export const enum ExtensionState {
Uninstalled
}
export const enum ExtensionRuntimeActionType {
Reload = 'reload',
DownloadUpdate = 'downloadUpdate',
ApplyUpdate = 'applyUpdate',
QuitAndInstall = 'quitAndInstall',
}
export type ExtensionRuntimeState = { action: ExtensionRuntimeActionType; reason: string };
export interface IExtension {
readonly type: ExtensionType;
readonly isBuiltin: boolean;
@ -69,7 +78,7 @@ export interface IExtension {
readonly ratingCount?: number;
readonly outdated: boolean;
readonly outdatedTargetPlatform: boolean;
readonly reloadRequiredStatus?: string;
readonly runtimeState: ExtensionRuntimeState | undefined;
readonly enablementState: EnablementState;
readonly tags: readonly string[];
readonly categories: readonly string[];

View file

@ -63,7 +63,7 @@ import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { timeout } from 'vs/base/common/async';
import { IUpdateService } from 'vs/platform/update/common/update';
import { IUpdateService, State } from 'vs/platform/update/common/update';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
@ -275,7 +275,7 @@ suite('ExtensionRecommendationsService Test', () => {
},
});
instantiationService.stub(IUpdateService, { onStateChange: Event.None });
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService)));

View file

@ -56,7 +56,7 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi
import { platform } from 'vs/base/common/platform';
import { arch } from 'vs/base/common/process';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { IUpdateService } from 'vs/platform/update/common/update';
import { IUpdateService, State } from 'vs/platform/update/common/update';
let instantiationService: TestInstantiationService;
let installEvent: Emitter<InstallExtensionEvent>,
@ -137,7 +137,7 @@ function setupTest(disposables: Pick<DisposableStore, 'add'>) {
instantiationService.stub(IUserDataSyncEnablementService, disposables.add(instantiationService.createInstance(UserDataSyncEnablementService)));
instantiationService.stub(IUpdateService, { onStateChange: Event.None });
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService()));
}
@ -1010,7 +1010,7 @@ suite('ReloadAction', () => {
didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]);
await promise;
assert.ok(testObject.enabled);
assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.');
assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`);
});
test('Test ReloadAction when extension is newly installed and reload is not required', async () => {
@ -1078,7 +1078,7 @@ suite('ReloadAction', () => {
uninstallEvent.fire({ identifier: local.identifier });
didUninstallEvent.fire({ identifier: local.identifier });
assert.ok(testObject.enabled);
assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to complete the uninstallation of this extension.');
assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to complete the uninstallation of this extension.`);
});
test('Test ReloadAction when extension is uninstalled and can be removed', async () => {
@ -1146,7 +1146,7 @@ suite('ReloadAction', () => {
return new Promise<void>(c => {
disposables.add(testObject.onDidChange(() => {
if (testObject.enabled && testObject.tooltip === 'Please reload Visual Studio Code to enable the updated extension.') {
if (testObject.enabled && testObject.tooltip === `Please reload ${instantiationService.get(IProductService).nameLong} to enable the updated extension.`) {
c();
}
}));
@ -1200,7 +1200,7 @@ suite('ReloadAction', () => {
await testObject.update();
assert.ok(testObject.enabled);
assert.strictEqual('Please reload Visual Studio Code to disable this extension.', testObject.tooltip);
assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to disable this extension.`, testObject.tooltip);
});
test('Test ReloadAction when extension enablement is toggled when running', async () => {
@ -1243,7 +1243,7 @@ suite('ReloadAction', () => {
await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally);
await testObject.update();
assert.ok(testObject.enabled);
assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip);
assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip);
});
test('Test ReloadAction when extension enablement is toggled when not running', async () => {
@ -1290,7 +1290,7 @@ suite('ReloadAction', () => {
await workbenchService.setEnablement(extensions[0], EnablementState.EnabledGlobally);
await testObject.update();
assert.ok(testObject.enabled);
assert.strictEqual('Please reload Visual Studio Code to enable this extension.', testObject.tooltip);
assert.strictEqual(`Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`, testObject.tooltip);
});
test('Test ReloadAction when a localization extension is newly installed', async () => {
@ -1441,7 +1441,7 @@ suite('ReloadAction', () => {
await promise;
assert.ok(testObject.enabled);
assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.');
assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`);
});
test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => {
@ -1480,7 +1480,7 @@ suite('ReloadAction', () => {
await promise;
assert.ok(testObject.enabled);
assert.strictEqual(testObject.tooltip, 'Please reload Visual Studio Code to enable this extension.');
assert.strictEqual(testObject.tooltip, `Please reload ${instantiationService.get(IProductService).nameLong} to enable this extension.`);
});
test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => {

View file

@ -48,7 +48,7 @@ import { arch } from 'vs/base/common/process';
import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { IUpdateService } from 'vs/platform/update/common/update';
import { IUpdateService, State } from 'vs/platform/update/common/update';
suite('ExtensionsViews Tests', () => {
@ -188,7 +188,7 @@ suite('ExtensionsViews Tests', () => {
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally);
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally);
instantiationService.stub(IUpdateService, { onStateChange: Event.None });
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }));
});

View file

@ -51,7 +51,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { toDisposable } from 'vs/base/common/lifecycle';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Mutable } from 'vs/base/common/types';
import { IUpdateService } from 'vs/platform/update/common/update';
import { IUpdateService, State } from 'vs/platform/update/common/update';
suite('ExtensionsWorkbenchServiceTest', () => {
@ -132,7 +132,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', []);
instantiationService.stubPromise(INotificationService, 'prompt', 0);
(<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).reset();
instantiationService.stub(IUpdateService, { onStateChange: Event.None });
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
});
test('test gallery extension', async () => {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, InstallOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallExtensionInfo, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier, ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
@ -89,8 +89,8 @@ export abstract class ProfileAwareExtensionManagementChannelClient extends BaseE
return super.uninstall(extension, options);
}
override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise<ILocalExtension[]> {
return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource));
override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {
return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource), productVersion);
}
override async updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, extensionsProfileResource?: URI): Promise<ILocalExtension> {

View file

@ -5,7 +5,8 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo,
IProductVersion
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions';
@ -80,8 +81,8 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer<DidChangeProfileForServerEvent>, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer<DidChangeProfileForServerEvent>()))).event;
}
async getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]> {
const result = await Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type, profileLocation)));
async getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {
const result = await Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type, profileLocation, productVersion)));
return flatten(result);
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { ILocalExtension, IGalleryExtension, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions, IProductVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@ -170,8 +170,8 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
await this.webExtensionsScannerService.copyExtensions(fromProfileLocation, toProfileLocation, e => !e.metadata?.isApplicationScoped);
}
protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> {
const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease);
protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise<IGalleryExtension | null> {
const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease, productVersion);
if (compatibleExtension) {
return compatibleExtension;
}

View file

@ -87,7 +87,7 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag
this.logService.info(`Downloading the '${extension.identifier.id}' extension locally and install`);
const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);
installOptions = { ...installOptions, donotIncludePackAndDependencies: true };
const installed = await this.getInstalled(ExtensionType.User);
const installed = await this.getInstalled(ExtensionType.User, undefined, installOptions.productVersion);
const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(compatible, CancellationToken.None);
if (workspaceExtensions.length) {
this.logService.info(`Downloading the workspace dependencies and packed extensions of '${compatible.identifier.id}' locally and install`);