From e045e3b1b25de81232b7db32747f31bf90d02d13 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Aug 2021 20:41:45 +0200 Subject: [PATCH 1/8] introduce target platforms support --- src/vs/base/common/platform.ts | 1 + src/vs/base/common/process.ts | 12 +- .../abstractExtensionManagementService.ts | 18 ++- .../common/extensionGalleryService.ts | 142 ++++++++++++------ .../common/extensionManagement.ts | 97 +++++++++++- .../common/extensionManagementCLIService.ts | 22 +-- .../remote/common/remoteAgentEnvironment.ts | 1 + .../userDataSync/common/extensionsSync.ts | 4 +- .../browser/extensions.contribution.ts | 2 +- .../extensions/browser/extensionsActions.ts | 120 +++++---------- .../browser/extensionsWorkbenchService.ts | 42 +++--- .../extensionsActions.test.ts | 13 +- .../electron-browser/extensionsViews.test.ts | 4 +- .../extensionsWorkbenchService.test.ts | 11 +- .../common/extensionManagement.ts | 13 +- .../extensionManagementServerService.ts | 15 +- .../common/extensionManagementService.ts | 4 +- .../common/webExtensionManagementService.ts | 4 +- .../common/webExtensionsScannerService.ts | 2 +- .../extensionManagementServerService.ts | 13 +- .../remoteExtensionManagementService.ts | 22 +-- .../extensionEnablementService.test.ts | 11 +- .../extensions/browser/extensionUrlHandler.ts | 3 +- .../electron-browser/extensionService.ts | 3 +- .../common/remoteAgentEnvironmentChannel.ts | 2 + 25 files changed, 354 insertions(+), 227 deletions(-) diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 587e2fe8088..1a571e41337 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -36,6 +36,7 @@ export interface IProcessEnvironment { */ export interface INodeProcess { platform: string; + arch: string; env: IProcessEnvironment; nextTick?: (callback: (...args: any[]) => void) => void; versions?: { diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 428ba622512..07c72d4640f 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -5,7 +5,7 @@ import { globals, INodeProcess, isMacintosh, isWindows, setImmediate } from 'vs/base/common/platform'; -let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) => void; }; +let safeProcess: Omit & { nextTick: (callback: (...args: any[]) => void) => void; arch: string | undefined; }; declare const process: INodeProcess; // Native sandbox environment @@ -13,6 +13,7 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== ' const sandboxProcess: INodeProcess = globals.vscode.process; safeProcess = { get platform() { return sandboxProcess.platform; }, + get arch() { return sandboxProcess.arch; }, get env() { return sandboxProcess.env; }, cwd() { return sandboxProcess.cwd(); }, nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); } @@ -23,6 +24,7 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== ' else if (typeof process !== 'undefined') { safeProcess = { get platform() { return process.platform; }, + get arch() { return process.arch; }, get env() { return process.env; }, cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }, nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); } @@ -35,6 +37,7 @@ else { // Supported get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, + get arch() { return undefined; /* arch is undefined in web */ }, nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }, // Unsupported @@ -70,3 +73,10 @@ export const platform = safeProcess.platform; * environments. */ export const nextTick = safeProcess.nextTick; + +/** + * Provides safe access to the `arch` method in node.js, sandboxed or web + * environments. + * Note: `arch` is `undefined` in web + */ +export const arch = safeProcess.arch; diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 08eb855697b..c811dbf73ad 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { DidUninstallExtensionEvent, ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOperation, InstallOptions, - InstallVSIXOptions, INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, IReportedExtension, StatisticType, UninstallOptions + InstallVSIXOptions, INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, IReportedExtension, StatisticType, CURRENT_TARGET_PLATFORM, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -86,7 +86,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } try { - extension = await this.checkAndGetCompatibleVersion(extension); + extension = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion); } catch (error) { this.logService.error(getErrorMessage(error)); reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error); @@ -334,7 +334,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { continue; } - const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension); + const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension, true); if (!await this.canInstall(compatibleExtension)) { this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id); continue; @@ -355,12 +355,20 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier))); } - private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise { + private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise { if (await this.isMalicious(extension)) { throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS); } - const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); + let compatibleExtension: IGalleryExtension | null = null; + if (await this.galleryService.isExtensionCompatible(extension, CURRENT_TARGET_PLATFORM)) { + compatibleExtension = extension; + } + + if (!compatibleExtension && fetchCompatibleVersion) { + compatibleExtension = await this.galleryService.getCompatibleExtension(extension, CURRENT_TARGET_PLATFORM); + } + if (!compatibleExtension) { throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE); } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3226e5c7b02..2fc452dc2c3 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct, flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { getOrDefault } from 'vs/base/common/objects'; @@ -11,8 +12,8 @@ import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { adoptToGalleryExtensionId, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { arePlatformsValid, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IFileService } from 'vs/platform/files/common/files'; @@ -41,6 +42,7 @@ interface IRawGalleryExtensionVersion { readonly fallbackAssetUri: string; readonly files: IRawGalleryExtensionFile[]; readonly properties?: IRawGalleryExtensionProperty[]; + readonly targetPlatform?: TargetPlatform; } interface IRawGalleryExtensionStatistics { @@ -181,6 +183,17 @@ type GalleryServiceQueryEvent = QueryTelemetryData & { readonly count?: string; }; +const ANY_TARGET_PLATFORMS = Object.freeze([ + TargetPlatform.WIN32_X64, + TargetPlatform.WIN32_IA32, + TargetPlatform.WIN32_ARM64, + TargetPlatform.LINUX_X64, + TargetPlatform.LINUX_ARM64, + TargetPlatform.LINUX_ARMHF, + TargetPlatform.DARWIN_X64, + TargetPlatform.DARWIN_ARM64, +]); + class Query { constructor(private state = DefaultQueryState) { } @@ -310,6 +323,10 @@ function getIsPreview(flags: string): boolean { return flags.indexOf('preview') !== -1; } +function getTargetPlatforms(version: IRawGalleryExtensionVersion): TargetPlatform[] { + return version.targetPlatform ? [version.targetPlatform] : [...ANY_TARGET_PLATFORMS]; +} + function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension { const assets = { manifest: getVersionAsset(version, AssetType.Manifest), @@ -322,6 +339,16 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller coreTranslations: getCoreTranslationAssets(version) }; + const allTargetPlatforms = distinct(flatten(galleryExtension.versions.map(getTargetPlatforms))); + if (galleryExtension.tags?.includes(WEB_EXTENSION_TAG) && !allTargetPlatforms.includes(TargetPlatform.WEB)) { + allTargetPlatforms.push(TargetPlatform.WEB); + } + + const targetPlatforms = getTargetPlatforms(version); + if (allTargetPlatforms.includes(TargetPlatform.WEB) && !targetPlatforms.includes(TargetPlatform.WEB)) { + targetPlatforms.push(TargetPlatform.WEB); + } + return { identifier: { id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName), @@ -341,14 +368,16 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller tags: galleryExtension.tags || [], releaseDate: Date.parse(galleryExtension.releaseDate), lastUpdated: Date.parse(galleryExtension.lastUpdated), - webExtension: !!galleryExtension.tags?.includes(WEB_EXTENSION_TAG), + allTargetPlatforms, assets, properties: { dependencies: getExtensions(version, PropertyType.Dependency), extensionPack: getExtensions(version, PropertyType.ExtensionPack), engine: getEngine(version), localizedLanguages: getLocalizedLanguages(version), + targetPlatforms, }, + preview: getIsPreview(galleryExtension.flags), /* __GDPR__FRAGMENT__ "GalleryExtensionTelemetryData2" : { "index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -359,7 +388,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller index: ((query.pageNumber - 1) * query.pageSize) + index, querySource }, - preview: getIsPreview(galleryExtension.flags) }; } @@ -400,28 +428,42 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return !!this.extensionsGalleryUrl; } - async getExtensions(names: string[], token: CancellationToken): Promise { + async getExtensions(identifiers: ReadonlyArray, token: CancellationToken): Promise { const result: IGalleryExtension[] = []; - let { total, firstPage: pageResult, getPage } = await this.query({ names, pageSize: names.length }, token); - result.push(...pageResult); - for (let pageIndex = 1; result.length < total; pageIndex++) { - pageResult = await getPage(pageIndex, token); - if (pageResult.length) { - result.push(...pageResult); + let query = new Query() + .withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) + .withPage(1, identifiers.length) + .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') + .withFilter(FilterType.ExtensionName, ...identifiers.map(({ id }) => id.toLowerCase())); + + if (identifiers.every(identifier => !(identifier).version)) { + query = query.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.IncludeLatestVersionOnly); + } + + const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None); + for (let index = 0; index < galleryExtensions.length; index++) { + const galleryExtension = galleryExtensions[index]; + if (!galleryExtension.versions.length) { + continue; + } + const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName); + const version = (identifiers.find(identifier => areSameExtensions(identifier, { id })))?.version; + if (version) { + const versionAsset = galleryExtension.versions.find(v => v.version === version); + if (versionAsset) { + result.push(toExtension(galleryExtension, versionAsset, index, query)); + } } else { - break; + result.push(toExtension(galleryExtension, galleryExtension.versions[0], index, query)); } } + return result; } - async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise { - return this.getCompatibleExtensionByEngine(arg1, version); - } - - private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise { + async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, targetPlatform: TargetPlatform): Promise { const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1; - if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) { + if (extension && extension.properties.engine && this.isCompatible(extension.properties.engine, extension.properties.targetPlatforms, targetPlatform)) { return extension; } const { id, uuid } = extension ? extension.identifier : arg1; @@ -442,24 +484,25 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return null; } - if (version) { - const versionAsset = rawExtension.versions.filter(v => v.version === version)[0]; - if (versionAsset) { - const extension = toExtension(rawExtension, versionAsset, 0, query); - if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) { - return extension; - } - } - return null; - } - - const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions); + const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions, targetPlatform); if (rawVersion) { return toExtension(rawExtension, rawVersion, 0, query); } return null; } + async isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise { + let engine = extension.properties.engine; + if (!engine) { + const manifest = await this.getManifest(extension, CancellationToken.None); + if (!manifest) { + throw new Error('Manifest was not found'); + } + engine = manifest.engines.vscode; + } + return this.isCompatible(engine, extension.properties.targetPlatforms, targetPlatform); + } + query(token: CancellationToken): Promise>; query(options: IQueryOptions, token: CancellationToken): Promise>; async query(arg1: any, arg2?: any): Promise> { @@ -681,7 +724,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return ''; } - async getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise { + async getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise { let query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, 1) @@ -696,19 +739,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const result: IGalleryExtensionVersion[] = []; const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None); if (galleryExtensions.length) { - if (compatible) { - await Promise.all(galleryExtensions[0].versions.map(async v => { - let engine: string | undefined; - try { - engine = await this.getEngine(v); - } catch (error) { /* Ignore error and skip version */ } - if (engine && isEngineValid(engine, this.productService.version, this.productService.date)) { + await Promise.all(galleryExtensions[0].versions.map(async v => { + try { + const engine = await this.getEngine(v); + if (this.isCompatible(engine, getTargetPlatforms(v), targetPlatform)) { result.push({ version: v!.version, date: v!.lastUpdated }); } - })); - } else { - result.push(...galleryExtensions[0].versions.map(v => ({ version: v.version, date: v.lastUpdated }))); - } + } catch (error) { /* Ignore error and skip version */ } + })); } return result; } @@ -751,21 +789,21 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } - private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { - const version = this.getLastValidExtensionVersionFromProperties(extension, versions); + private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise { + const version = this.getLastValidExtensionVersionFromProperties(extension, versions, targetPlatform); if (version) { return version; } - return this.getLastValidExtensionVersionRecursively(extension, versions); + return this.getLastValidExtensionVersionRecursively(extension, versions, targetPlatform); } - private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): IRawGalleryExtensionVersion | null { + private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): IRawGalleryExtensionVersion | null { for (const version of versions) { const engine = getEngine(version); if (!engine) { return null; } - if (isEngineValid(engine, this.productService.version, this.productService.date)) { + if (this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) { return version; } } @@ -793,15 +831,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService { throw new Error('Error while reading manifest'); } - private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { + private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise { if (!versions.length) { return null; } const version = versions[0]; const engine = await this.getEngine(version); - if (!isEngineValid(engine, this.productService.version, this.productService.date)) { - return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1)); + if (!this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) { + return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1), targetPlatform); } return { @@ -810,6 +848,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }; } + private isCompatible(engine: string, targetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): boolean { + return isEngineValid(engine, this.productService.version, this.productService.date) && arePlatformsValid(targetPlatforms, targetPlatform); + } + async getExtensionsReport(): Promise { if (!this.isEnabled()) { throw new Error('No extension gallery service configured.'); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 94e74b30f18..630308f92f4 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -7,6 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { FileAccess } from 'vs/base/common/network'; import { IPager } from 'vs/base/common/paging'; +import { isWeb, OperatingSystem, Platform, platform } from 'vs/base/common/platform'; +import { arch } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ExtensionType, IExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -16,11 +18,89 @@ export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); export const WEB_EXTENSION_TAG = '__web_extension'; +export const enum TargetPlatform { + WIN32_X64 = 'win32-x64', + WIN32_IA32 = 'win32-ia32', + WIN32_ARM64 = 'win32-arm64', + + LINUX_X64 = 'linux-x64', + LINUX_ARM64 = 'linux-arm64', + LINUX_ARMHF = 'linux-armhf', + + DARWIN_X64 = 'darwin-x64', + DARWIN_ARM64 = 'darwin-arm64', + + WEB = 'web', +} + +export function getTargetPlatformFromOS(os: OperatingSystem, arch: string): TargetPlatform { + let platform: Platform; + switch (os) { + case OperatingSystem.Windows: platform = Platform.Windows; break; + case OperatingSystem.Linux: platform = Platform.Linux; break; + case OperatingSystem.Macintosh: platform = Platform.Mac; break; + } + return getTargetPlatform(platform, arch); +} + +export function getTargetPlatform(platform: Platform, arch: string | undefined): TargetPlatform { + if (isWeb) { + return TargetPlatform.WEB; + } + switch (platform) { + case Platform.Windows: + if (arch === 'x64') { + return TargetPlatform.WIN32_X64; + } + if (arch === 'ia32') { + return TargetPlatform.WIN32_IA32; + } + if (arch === 'arm64') { + return TargetPlatform.WIN32_ARM64; + } + throw new Error(`Unknown Architecture '${arch}'`); + + case Platform.Linux: + if (arch === 'x64') { + return TargetPlatform.LINUX_X64; + } + if (arch === 'arm64') { + return TargetPlatform.LINUX_ARM64; + } + if (arch === 'arm') { + return TargetPlatform.LINUX_ARMHF; + } + throw new Error(`Unknown Architecture '${arch}'`); + + case Platform.Mac: + if (arch === 'x64') { + return TargetPlatform.DARWIN_X64; + } + if (arch === 'arm64') { + return TargetPlatform.DARWIN_ARM64; + } + throw new Error(`Unknown Architecture '${arch}'`); + + case Platform.Web: return TargetPlatform.WEB; + } +} + +export function arePlatformsValid(targetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): boolean { + switch (targetPlatform) { + case TargetPlatform.WIN32_X64: return targetPlatforms.some(t => t === TargetPlatform.WIN32_X64 || t === TargetPlatform.WIN32_IA32); + case TargetPlatform.WIN32_ARM64: return targetPlatforms.some(t => t === TargetPlatform.WIN32_ARM64 || t === TargetPlatform.WIN32_IA32); + default: return targetPlatforms.includes(targetPlatform); + } +} + +export const CURRENT_TARGET_PLATFORM = getTargetPlatform(platform, arch); + export interface IGalleryExtensionProperties { dependencies?: string[]; extensionPack?: string[]; engine?: string; localizedLanguages?: string[]; + targetPlatforms: TargetPlatform[]; } export interface IGalleryExtensionAsset { @@ -88,11 +168,11 @@ export interface IGalleryExtension { tags: readonly string[]; releaseDate: number; lastUpdated: number; + preview: boolean; + allTargetPlatforms: TargetPlatform[]; assets: IGalleryExtensionAssets; properties: IGalleryExtensionProperties; telemetryData: any; - preview: boolean; - webExtension: boolean; } export interface IGalleryMetadata { @@ -161,17 +241,18 @@ export interface IExtensionGalleryService { isEnabled(): boolean; query(token: CancellationToken): Promise>; query(options: IQueryOptions, token: CancellationToken): Promise>; - getExtensions(ids: string[], token: CancellationToken): Promise; + getExtensions(identifiers: ReadonlyArray, token: CancellationToken): Promise; download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise; getReadme(extension: IGalleryExtension, token: CancellationToken): Promise; getManifest(extension: IGalleryExtension, token: CancellationToken): Promise; getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise; getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise; - getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise; getExtensionsReport(): Promise; - getCompatibleExtension(extension: IGalleryExtension): Promise; - getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise; + isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise; + getCompatibleExtension(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise; + getCompatibleExtension(id: IExtensionIdentifier, targetPlatform: TargetPlatform): Promise; + getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise; } export interface InstallExtensionEvent { @@ -202,8 +283,8 @@ export class ExtensionManagementError extends Error { } } -export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean }; -export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean }; +export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean }; +export type InstallVSIXOptions = Omit & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean }; export interface IExtensionManagementParticipant { diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts index d20789ead6b..23ee9e4f08a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLIService.ts @@ -199,23 +199,11 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer } private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise> { - const extensionIds = extensions.filter(({ version }) => version === undefined).map(({ id }) => id); - const extensionsWithIdAndVersion = extensions.filter(({ version }) => version !== undefined); - const galleryExtensions = new Map(); - await Promise.all([ - (async () => { - const result = await this.extensionGalleryService.getExtensions(extensionIds, CancellationToken.None); - result.forEach(extension => galleryExtensions.set(extension.identifier.id.toLowerCase(), extension)); - })(), - Promise.all(extensionsWithIdAndVersion.map(async ({ id, version }) => { - const extension = await this.extensionGalleryService.getCompatibleExtension({ id }, version); - if (extension) { - galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); - } - })) - ]); - + const result = await this.extensionGalleryService.getExtensions(extensions, CancellationToken.None); + for (const extension of result) { + galleryExtensions.set(extension.identifier.id.toLowerCase(), extension); + } return galleryExtensions; } @@ -241,7 +229,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer output.log(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); } - await this.extensionManagementService.installFromGallery(galleryExtension, installOptions); + await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version }); output.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, galleryExtension.version)); return manifest; } catch (error) { diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index 46f366a7571..3feda96194f 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -19,6 +19,7 @@ export interface IRemoteAgentEnvironment { workspaceStorageHome: URI; userHome: URI; os: OperatingSystem; + arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 67b568b4691..44b17235da8 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -14,7 +14,7 @@ import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; @@ -375,7 +375,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } // User Extension Sync: Install/Update, Enablement & State - const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier); + const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, CURRENT_TARGET_PLATFORM); /* Update extension state only if * extension is installed and version is same as synced version or diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index fbdd59979fd..09e3e0e58e2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -279,7 +279,7 @@ CommandsRegistry.registerCommand({ const extensionGalleryService = accessor.get(IExtensionGalleryService); try { if (typeof arg === 'string') { - const extension = await extensionGalleryService.getCompatibleExtension({ id: arg }); + const [extension] = await extensionGalleryService.getExtensions([{ id: arg }], CancellationToken.None); if (extension) { await extensionManagementService.installFromGallery(extension); } else { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 7675d13a747..95dafa29014 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { dispose } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, InstallOperation, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -42,7 +42,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -981,36 +980,31 @@ export class InstallAnotherVersionAction extends ExtensionAction { } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && !!this.extension.server && this.extension.state === ExtensionState.Installed; } - override run(): Promise { + override async run(): Promise { if (!this.enabled) { - return Promise.resolve(); + return; } - return this.quickInputService.pick(this.getVersionEntries(), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }) - .then(async pick => { - if (pick) { - if (this.extension!.version === pick.id) { - return Promise.resolve(); - } - try { - if (pick.latest) { - await this.extensionsWorkbenchService.install(this.extension!); - } else { - await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); - } - } catch (error) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); - } + const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension!.gallery!, await this.extension!.server!.getTargetPlatform()); + const versionEntries = allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 })); + const pick = await this.quickInputService.pick(versionEntries, { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }); + if (pick) { + if (this.extension!.version === pick.id) { + return; + } + try { + if (pick.latest) { + await this.extensionsWorkbenchService.install(this.extension!); + } else { + await this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); } - return null; - }); - } - - private getVersionEntries(): Promise<(IQuickPickItem & { latest: boolean, id: string })[]> { - return this.extensionGalleryService.getAllVersions(this.extension!.gallery!, true) - .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); + } catch (error) { + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); + } + } + return null; } } @@ -2011,7 +2005,7 @@ export class ExtensionStatusAction extends ExtensionAction { && !this.extensionManagementServerService.remoteExtensionManagementServer) { const productName = isWeb ? localize({ key: 'vscode web', comment: ['VS Code Web is the name of the product'] }, "VS Code Web") : this.productService.nameLong; let message; - if (this.extension.gallery.webExtension) { + if (this.extension.gallery.allTargetPlatforms.includes(TargetPlatform.WEB)) { message = new MarkdownString(localize('user disabled', "You have configured the '{0}' extension to be disabled in {1}. To enable it, please open user settings and remove it from `remote.extensionKind` setting.", this.extension.displayName || this.extension.identifier.id, productName)); } else { message = new MarkdownString(`${localize('not web tooltip', "The '{0}' extension is not available in {1}.", this.extension.displayName || this.extension.identifier.id, productName)} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-remote-codespaces#_why-is-an-extension-not-installable-in-the-browser)`); @@ -2283,12 +2277,8 @@ export class InstallSpecificVersionOfExtensionAction extends Action { constructor( id: string = InstallSpecificVersionOfExtensionAction.ID, label: string = InstallSpecificVersionOfExtensionAction.LABEL, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @INotificationService private readonly notificationService: INotificationService, - @IHostService private readonly hostService: IHostService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(id, label); @@ -2301,68 +2291,38 @@ export class InstallSpecificVersionOfExtensionAction extends Action { override async run(): Promise { const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true }); if (extensionPick && extensionPick.extension) { - const versionPick = await this.quickInputService.pick(extensionPick.versions.map(v => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === extensionPick.extension.version ? ` (${localize('current', "Current")})` : ''}` })), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }); - if (versionPick) { - if (extensionPick.extension.version !== versionPick.id) { - await this.install(extensionPick.extension, versionPick.id); - } - } + const action = this.instantiationService.createInstance(InstallAnotherVersionAction); + action.extension = extensionPick.extension; + await action.run(); + await this.instantiationService.createInstance(SearchExtensionsAction, extensionPick.extension.identifier.id).run(); } } private isEnabled(extension: IExtension): boolean { - return !!extension.gallery && !!extension.local && this.extensionEnablementService.isEnabled(extension.local); + const action = this.instantiationService.createInstance(InstallAnotherVersionAction); + action.extension = extension; + return action.enabled && !!extension.local && this.extensionEnablementService.isEnabled(extension.local); } - private async getExtensionEntries(): Promise<(IQuickPickItem & { extension: IExtension, versions: IGalleryExtensionVersion[] })[]> { + private async getExtensionEntries(): Promise { const installed = await this.extensionsWorkbenchService.queryLocal(); - const versionsPromises: Promise<{ extension: IExtension, versions: IGalleryExtensionVersion[] } | null>[] = []; + const entries: IExtensionPickItem[] = []; for (const extension of installed) { if (this.isEnabled(extension)) { - versionsPromises.push(this.extensionGalleryService.getAllVersions(extension.gallery!, true) - .then(versions => (versions.length ? { extension, versions } : null))); - } - } - - const extensions = await Promise.all(versionsPromises); - return coalesce(extensions) - .sort((e1, e2) => e1.extension.displayName.localeCompare(e2.extension.displayName)) - .map(({ extension, versions }) => { - return { + entries.push({ id: extension.identifier.id, label: extension.displayName || extension.identifier.id, description: extension.identifier.id, extension, - versions - } as (IQuickPickItem & { extension: IExtension, versions: IGalleryExtensionVersion[] }); - }); - } - - private install(extension: IExtension, version: string): Promise { - return this.instantiationService.createInstance(SearchExtensionsAction, '@installed ').run() - .then(() => { - return this.extensionsWorkbenchService.installVersion(extension, version) - .then(extension => { - const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('InstallAnotherVersionExtensionAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.identifier.id) - : localize('InstallAnotherVersionExtensionAction.success', "Installing the extension {0} is completed.", extension.identifier.id); - const actions = requireReload ? [{ - label: localize('InstallAnotherVersionExtensionAction.reloadNow', "Reload Now"), - run: () => this.hostService.reload() - }] : []; - this.notificationService.prompt( - Severity.Info, - message, - actions, - { sticky: true } - ); - }, error => this.notificationService.error(error)); - }); + }); + } + } + return entries.sort((e1, e2) => e1.extension.displayName.localeCompare(e2.extension.displayName)); } } interface IExtensionPickItem extends IQuickPickItem { - extension?: IExtension; + extension: IExtension; } export abstract class AbstractInstallExtensionsInServerAction extends Action { @@ -2489,9 +2449,10 @@ export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensi protected async installExtensions(localExtensionsToInstall: IExtension[]): Promise { const galleryExtensions: IGalleryExtension[] = []; const vsixs: URI[] = []; + const remoteTargetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer!.getTargetPlatform(); await Promises.settled(localExtensionsToInstall.map(async extension => { if (this.extensionGalleryService.isEnabled()) { - const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, extension.version); + const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, remoteTargetPlatform); if (gallery) { galleryExtensions.push(gallery); return; @@ -2537,9 +2498,10 @@ export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensi protected async installExtensions(extensions: IExtension[]): Promise { const galleryExtensions: IGalleryExtension[] = []; const vsixs: URI[] = []; + const localTargetPlatform = await this.extensionManagementServerService.localExtensionManagementServer!.getTargetPlatform(); await Promises.settled(extensions.map(async extension => { if (this.extensionGalleryService.isEnabled()) { - const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, extension.version); + const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, localTargetPlatform); if (gallery) { galleryExtensions.push(gallery); return; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 854635b0cf6..d1d7ec36093 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -14,7 +14,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult + InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -399,7 +399,7 @@ class Extensions extends Disposable { } // Loading the compatible version only there is an engine property // Otherwise falling back to old way so that we will not make many roundtrips - const compatible = gallery.properties.engine ? await this.galleryService.getCompatibleExtension(gallery) : gallery; + const compatible = gallery.properties.engine ? await this.galleryService.getCompatibleExtension(gallery, await this.server.getTargetPlatform()) : gallery; if (!compatible) { return false; } @@ -415,7 +415,7 @@ class Extensions extends Disposable { } private async syncInstalledExtensionWithGallery(extension: Extension): Promise { - const compatible = await this.galleryService.getCompatibleExtension(extension.identifier); + const compatible = await this.galleryService.getCompatibleExtension(extension.identifier, await this.server.getTargetPlatform()); if (compatible) { extension.gallery = compatible; this._onChange.fire({ extension }); @@ -991,7 +991,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (this.extensionManagementServerService.webExtensionManagementServer) { const configuredExtensionKind = this.extensionManifestPropertiesService.getUserConfiguredExtensionKind(extension.gallery.identifier); - return configuredExtensionKind ? configuredExtensionKind.includes('web') : extension.gallery.webExtension; + return configuredExtensionKind ? configuredExtensionKind.includes('web') : extension.gallery.allTargetPlatforms.includes(TargetPlatform.WEB); } return false; @@ -1034,29 +1034,27 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }, () => this.extensionManagementService.uninstall(toUninstall).then(() => undefined)); } - installVersion(extension: IExtension, version: string): Promise { + async installVersion(extension: IExtension, version: string): Promise { if (!(extension instanceof Extension)) { - return Promise.resolve(extension); + return extension; } if (!extension.gallery) { - return Promise.reject(new Error('Missing gallery')); + throw new Error('Missing gallery'); } - return this.galleryService.getCompatibleExtension(extension.gallery.identifier, version) - .then(gallery => { - if (!gallery) { - return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.gallery!.identifier.id, version))); - } - return this.installWithProgress(async () => { - const installed = await this.installFromGallery(extension, gallery); - if (extension.latestVersion !== version) { - this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(gallery.identifier, version)); - } - return installed; - } - , gallery.displayName); - }); + const [gallery] = await this.galleryService.getExtensions([{ id: extension.gallery.identifier.id, version }], CancellationToken.None); + if (!gallery) { + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because the requested version '{1}' is not found.", extension.gallery!.identifier.id, version)); + } + + return this.installWithProgress(async () => { + const installed = await this.installFromGallery(extension, gallery, { installGivenVersion: true }); + if (extension.latestVersion !== version) { + this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(gallery.identifier, version)); + } + return installed; + }, gallery.displayName); } reinstall(extension: IExtension): Promise { @@ -1135,7 +1133,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this._onChange.fire(extension); try { if (extension.state === ExtensionState.Installed && extension.local) { - await this.extensionManagementService.updateFromGallery(gallery, extension.local); + await this.extensionManagementService.updateFromGallery(gallery, extension.local, installOptions); } else { await this.extensionManagementService.installFromGallery(gallery, installOptions); } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 26ab102d984..df97626b793 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -10,7 +10,7 @@ import * as ExtensionsActions from 'vs/workbench/contrib/extensions/browser/exte import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, - DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata, InstallExtensionResult + DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata, InstallExtensionResult, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -110,7 +110,7 @@ async function setupTest() { instantiationService.stub(IRemoteAgentService, RemoteAgentService); - const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; + const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local', getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; instantiationService.stub(IExtensionManagementServerService, >{ get localExtensionManagementServer(): IExtensionManagementServer { return localExtensionManagementServer; @@ -2348,7 +2348,8 @@ function aSingleRemoteExtensionManagementServerService(instantiationService: Tes const remoteExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-remote', label: 'remote', - extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService(), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; return { _serviceBrand: undefined, @@ -2368,12 +2369,14 @@ function aMultiExtensionManagementServerService(instantiationService: TestInstan const localExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-local', label: 'local', - extensionManagementService: localExtensionManagementService || createExtensionManagementService() + extensionManagementService: localExtensionManagementService || createExtensionManagementService(), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; const remoteExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-remote', label: 'remote', - extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService(), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; return { _serviceBrand: undefined, diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index d56c339f746..3ffbec605bd 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -11,7 +11,7 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, SortBy, InstallExtensionResult + DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, SortBy, InstallExtensionResult, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -100,7 +100,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IMenuService, new TestMenuService()); - const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; + const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local', getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; instantiationService.stub(IExtensionManagementServerService, >{ get localExtensionManagementServer(): IExtensionManagementServer { return localExtensionManagementServer; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index fbbf5a174b0..3af3f36ccfd 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -11,7 +11,7 @@ import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurat import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, - DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata, InstallExtensionResult + DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata, InstallExtensionResult, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -104,7 +104,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService({ id: 'local', label: 'local', - extensionManagementService: instantiationService.get(IExtensionManagementService) + extensionManagementService: instantiationService.get(IExtensionManagementService), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }, null, null)); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); @@ -1471,12 +1472,14 @@ suite('ExtensionsWorkbenchServiceTest', () => { const localExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-local', label: 'local', - extensionManagementService: localExtensionManagementService || createExtensionManagementService() + extensionManagementService: localExtensionManagementService || createExtensionManagementService(), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; const remoteExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-remote', label: 'remote', - extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService(), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; return { _serviceBrand: undefined, diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 5374f4843d4..740c96557e2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -6,14 +6,15 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtension, ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; export interface IExtensionManagementServer { - id: string; - label: string; - extensionManagementService: IExtensionManagementService; + readonly id: string; + readonly label: string; + readonly extensionManagementService: IExtensionManagementService; + getTargetPlatform(): Promise; } export const IExtensionManagementServerService = createDecorator('extensionManagementServerService'); @@ -40,8 +41,8 @@ export interface IWorkbenchExtensionManagementService extends IExtensionManageme installWebExtension(location: URI): Promise; installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise; - updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension): Promise; - getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null + updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise; + getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null; } export const enum EnablementState { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index d824f860b7d..68c57a3ce75 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -16,7 +16,7 @@ import { WebExtensionManagementService } from 'vs/workbench/services/extensionMa import { IExtension } from 'vs/platform/extensions/common/extensions'; import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getTargetPlatformFromOS, IExtensionGalleryService, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IProductService } from 'vs/platform/product/common/productService'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; @@ -40,10 +40,18 @@ export class ExtensionManagementServerService implements IExtensionManagementSer const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel('extensions'), galleryService, configurationService, productService, extensionManifestPropertiesService); + const remoteEnvironemntPromise = remoteAgentService.getEnvironment(); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }, + async getTargetPlatform() { + const remoteEnvironment = await remoteEnvironemntPromise; + if (remoteEnvironment) { + return getTargetPlatformFromOS(remoteEnvironment.os, remoteEnvironment.arch); + } + throw new Error('Cannot get remote environment'); + } }; } if (isWeb) { @@ -51,7 +59,8 @@ export class ExtensionManagementServerService implements IExtensionManagementSer this.webExtensionManagementServer = { id: 'web', extensionManagementService, - label: localize('browser', "Browser") + label: localize('browser', "Browser"), + getTargetPlatform() { return Promise.resolve(TargetPlatform.WEB); } }; } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 82b0f085c38..31dbb33fe9c 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -230,7 +230,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return false; } - async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension): Promise { + async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise { const server = this.getServer(extension); if (!server) { return Promise.reject(`Invalid location ${extension.location.toString()}`); @@ -245,7 +245,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench servers.push(server); } - return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); + return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local); } async installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index de1706998fd..041231ead2c 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -44,7 +44,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe } async canInstall(gallery: IGalleryExtension): Promise { - const compatibleExtension = await this.galleryService.getCompatibleExtension(gallery); + const compatibleExtension = await this.galleryService.getCompatibleExtension(gallery, TargetPlatform.WEB); if (!compatibleExtension) { return false; } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 3d666557bfd..c1dbd2693c4 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -155,7 +155,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } if (extensionIds.length) { - const galleryExtensions = await this.galleryService.getExtensions(extensionIds, CancellationToken.None); + const galleryExtensions = await this.galleryService.getExtensions(extensionIds.map(id => ({ id })), CancellationToken.None); const missingExtensions = extensionIds.filter(id => !galleryExtensions.find(({ identifier }) => areSameExtensions(identifier, { id }))); if (missingExtensions.length) { this.logService.info('Cannot find static extensions from gallery', missingExtensions); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts index 3b6c4f7c127..fcb0584741f 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts @@ -15,6 +15,7 @@ import { NativeRemoteExtensionManagementService } from 'vs/workbench/services/ex import { ILabelService } from 'vs/platform/label/common/label'; import { IExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getTargetPlatformFromOS, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; export class ExtensionManagementServerService implements IExtensionManagementServerService { @@ -33,14 +34,22 @@ export class ExtensionManagementServerService implements IExtensionManagementSer ) { const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions')); - this._localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") }; + this._localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local"), getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { const extensionManagementService = instantiationService.createInstance(NativeRemoteExtensionManagementService, remoteAgentConnection.getChannel('extensions'), this.localExtensionManagementServer); + const remoteEnvironemntPromise = remoteAgentService.getEnvironment(); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); }, + async getTargetPlatform() { + const remoteEnvironment = await remoteEnvironemntPromise; + if (remoteEnvironment) { + return getTargetPlatformFromOS(remoteEnvironment.os, remoteEnvironment.arch); + } + throw new Error('Cannot get remote environment'); + } }; } } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index d177c9c3e22..46fe6943d1c 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, getTargetPlatformFromOS } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -22,23 +22,22 @@ import { IExtensionManagementServer } from 'vs/workbench/services/extensionManag import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Promises } from 'vs/base/common/async'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class NativeRemoteExtensionManagementService extends WebRemoteExtensionManagementService implements IExtensionManagementService { - private readonly localExtensionManagementService: IExtensionManagementService; - constructor( channel: IChannel, - localExtensionManagementServer: IExtensionManagementServer, + private readonly localExtensionManagementServer: IExtensionManagementServer, @ILogService private readonly logService: ILogService, @IExtensionGalleryService galleryService: IExtensionGalleryService, @IConfigurationService configurationService: IConfigurationService, @IProductService productService: IProductService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, ) { super(channel, galleryService, configurationService, productService, extensionManifestPropertiesService); - this.localExtensionManagementService = localExtensionManagementServer.extensionManagementService; } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { @@ -77,7 +76,11 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa private async downloadCompatibleAndInstall(extension: IGalleryExtension): Promise { const installed = await this.getInstalled(ExtensionType.User); - const compatible = await this.galleryService.getCompatibleExtension(extension); + const remoteEnvironment = await this.remoteAgentService.getEnvironment(); + if (!remoteEnvironment) { + return Promise.reject(new Error('Cannot get the remote environment')); + } + const compatible = await this.galleryService.getCompatibleExtension(extension, getTargetPlatformFromOS(remoteEnvironment.os, remoteEnvironment.arch)); if (!compatible) { return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version))); } @@ -97,9 +100,9 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise { const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None); - const installed = await this.localExtensionManagementService.getInstalled(); + const installed = await this.localExtensionManagementServer.extensionManagementService.getInstalled(); const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier))); - await Promises.settled(toInstall.map(d => this.localExtensionManagementService.installFromGallery(d))); + await Promises.settled(toInstall.map(d => this.localExtensionManagementServer.extensionManagementService.installFromGallery(d))); } private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise { @@ -121,7 +124,8 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa return Promise.resolve(); } - const extensions = coalesce(await Promises.settled(toGet.map(id => this.galleryService.getCompatibleExtension({ id })))); + const targetPlatform = await this.localExtensionManagementServer.getTargetPlatform(); + const extensions = coalesce(await Promises.settled(toGet.map(id => this.galleryService.getCompatibleExtension({ id }, targetPlatform)))); const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token))); const extensionsManifests: IExtensionManifest[] = []; for (let idx = 0; idx < extensions.length; idx++) { diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 4e265c3a881..28a9d9d71fc 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, CURRENT_TARGET_PLATFORM } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -64,7 +64,8 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { onDidInstallExtensions: new Emitter().event, onUninstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event, - } + }, + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }, null, null)); const workbenchExtensionManagementService = instantiationService.get(IWorkbenchExtensionManagementService) || instantiationService.stub(IWorkbenchExtensionManagementService, instantiationService.createInstance(ExtensionManagementService)); const workspaceTrustManagementService = instantiationService.get(IWorkspaceTrustManagementService) || instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); @@ -128,7 +129,8 @@ suite('ExtensionEnablementService Test', () => { onDidInstallExtensions: didInstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve(installed) - } + }, + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }, null, null)); instantiationService.stub(IWorkbenchExtensionManagementService, instantiationService.createInstance(ExtensionManagementService)); testObject = new TestExtensionEnablementService(instantiationService); @@ -920,7 +922,8 @@ function anExtensionManagementServer(authority: string, instantiationService: Te return { id: authority, label: authority, - extensionManagementService: instantiationService.get(IExtensionManagementService) + extensionManagementService: instantiationService.get(IExtensionManagementService), + getTargetPlatform() { return Promise.resolve(CURRENT_TARGET_PLATFORM); } }; } diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 018c5e71474..324c97bb124 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -27,6 +27,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; +import { CancellationToken } from 'vs/base/common/cancellation'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -258,7 +259,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { let galleryExtension: IGalleryExtension | undefined; try { - galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier) ?? undefined; + galleryExtension = (await this.galleryService.getExtensions([extensionIdentifier], CancellationToken.None))[0] ?? undefined; } catch (err) { return; } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index f746923b8d6..9a91ad3f41b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -43,6 +43,7 @@ import { updateProxyConfigurationsScope } from 'vs/platform/request/common/reque import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -513,7 +514,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten label: nls.localize('install', 'Install and Reload'), run: async () => { sendTelemetry('install'); - const galleryExtension = await this._extensionGalleryService.getCompatibleExtension({ id: resolverExtensionId }); + const [galleryExtension] = await this._extensionGalleryService.getExtensions([{ id: resolverExtensionId }], CancellationToken.None); if (galleryExtension) { await this._extensionManagementService.installFromGallery(galleryExtension); await this._hostService.reload(); diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 2caad0f4ece..8e1e902d7b0 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -42,6 +42,7 @@ export interface IRemoteAgentEnvironmentDTO { workspaceStorageHome: UriComponents; userHome: UriComponents; os: platform.OperatingSystem; + arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; } @@ -67,6 +68,7 @@ export class RemoteExtensionEnvironmentChannelClient { workspaceStorageHome: URI.revive(data.workspaceStorageHome), userHome: URI.revive(data.userHome), os: data.os, + arch: data.arch, marks: data.marks, useHostProxy: data.useHostProxy }; From 710c4b3a99f0ef02435c3a81597e738b22b6f5dd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Aug 2021 20:43:51 +0200 Subject: [PATCH 2/8] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1374bb0af67..ed39091ca9d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.60.0", - "distro": "0d8a917f4624b966e7031ad64f2f7504926ae67d", + "distro": "75777cadcd9d0938683fddf3bac145119b03947d", "author": { "name": "Microsoft Corporation" }, From 0651aee3509da6dc58d657a201c03a6604d461e9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Aug 2021 10:44:04 +0200 Subject: [PATCH 3/8] use latest version that is compatible with current target platform --- .../common/extensionGalleryService.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 2fc452dc2c3..19a751d953f 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -12,7 +12,7 @@ import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { arePlatformsValid, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { arePlatformsValid, CURRENT_TARGET_PLATFORM, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -391,6 +391,11 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller }; } +function getLatestVersion(versions: IRawGalleryExtensionVersion[]): IRawGalleryExtensionVersion { + const latestVersion = versions[0]; + return versions.find(v => v.version === latestVersion.version && arePlatformsValid(getTargetPlatforms(v), CURRENT_TARGET_PLATFORM)) || latestVersion; +} + interface IRawExtensionsReport { malicious: string[]; slow: string[]; @@ -454,7 +459,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { result.push(toExtension(galleryExtension, versionAsset, index, query)); } } else { - result.push(toExtension(galleryExtension, galleryExtension.versions[0], index, query)); + result.push(toExtension(galleryExtension, getLatestVersion(galleryExtension.versions), index, query)); } } @@ -565,14 +570,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const { galleryExtensions, total } = await this.queryGallery(query, token); - const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source)); + const extensions = galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, query, options.source)); const getPage = async (pageIndex: number, ct: CancellationToken) => { if (ct.isCancellationRequested) { throw canceled(); } const nextPageQuery = query.withPage(pageIndex + 1); const { galleryExtensions } = await this.queryGallery(nextPageQuery, ct); - return galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source)); + return galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, nextPageQuery, options.source)); }; return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager; From 2b695ada03a01755aded817e811731f4cd663787 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Aug 2021 12:05:26 +0200 Subject: [PATCH 4/8] use targetPlatform query param --- .../extensionManagement/common/extensionGalleryService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 19a751d953f..d2324b3f8bf 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -283,8 +283,8 @@ function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExten function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset { return { - uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true`, - fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}` + uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`, + fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}` }; } @@ -683,8 +683,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : ''; const downloadAsset = operationParam ? { - uri: `${extension.assets.download.uri}&${operationParam}=true`, - fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true` + uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`, + fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true` } : extension.assets.download; const context = await this.getAsset(downloadAsset); From b6766367efa27c2d2976197b3af1aae87b93453b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Aug 2021 12:17:25 +0200 Subject: [PATCH 5/8] support universal target platform --- .../common/extensionGalleryService.ts | 7 ++++--- .../common/extensionManagement.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index d2324b3f8bf..3f22e968f38 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -9,10 +9,11 @@ import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/commo import { getOrDefault } from 'vs/base/common/objects'; import { IPager } from 'vs/base/common/paging'; import { isWeb } from 'vs/base/common/platform'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { arePlatformsValid, CURRENT_TARGET_PLATFORM, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { arePlatformsValid, CURRENT_TARGET_PLATFORM, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -42,7 +43,7 @@ interface IRawGalleryExtensionVersion { readonly fallbackAssetUri: string; readonly files: IRawGalleryExtensionFile[]; readonly properties?: IRawGalleryExtensionProperty[]; - readonly targetPlatform?: TargetPlatform; + readonly targetPlatform?: string; } interface IRawGalleryExtensionStatistics { @@ -324,7 +325,7 @@ function getIsPreview(flags: string): boolean { } function getTargetPlatforms(version: IRawGalleryExtensionVersion): TargetPlatform[] { - return version.targetPlatform ? [version.targetPlatform] : [...ANY_TARGET_PLATFORMS]; + return version.targetPlatform && !equalsIgnoreCase(version.targetPlatform, 'universal') ? [toTargetPlatform(version.targetPlatform)] : [...ANY_TARGET_PLATFORMS]; } function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 630308f92f4..260bf4f11ce 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -31,6 +31,22 @@ export const enum TargetPlatform { DARWIN_ARM64 = 'darwin-arm64', WEB = 'web', + UNKNOWN = 'unknown', +} + +export function toTargetPlatform(targetPlatform: string): TargetPlatform { + switch (targetPlatform) { + case TargetPlatform.WIN32_X64: return TargetPlatform.WIN32_X64; + case TargetPlatform.WIN32_IA32: return TargetPlatform.WIN32_IA32; + case TargetPlatform.WIN32_ARM64: return TargetPlatform.WIN32_ARM64; + case TargetPlatform.LINUX_X64: return TargetPlatform.LINUX_X64; + case TargetPlatform.LINUX_ARM64: return TargetPlatform.LINUX_ARM64; + case TargetPlatform.LINUX_ARMHF: return TargetPlatform.LINUX_ARMHF; + case TargetPlatform.DARWIN_X64: return TargetPlatform.DARWIN_X64; + case TargetPlatform.DARWIN_ARM64: return TargetPlatform.DARWIN_ARM64; + case TargetPlatform.WEB: return TargetPlatform.WEB; + } + return TargetPlatform.UNKNOWN; } export function getTargetPlatformFromOS(os: OperatingSystem, arch: string): TargetPlatform { From 959b5ea26908071a5bdada0ff4d5389ece856a44 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Aug 2021 01:00:51 +0200 Subject: [PATCH 6/8] support universal target platform and web extensions --- .../common/extensionGalleryService.ts | 259 ++++++++++-------- .../common/extensionManagement.ts | 30 +- 2 files changed, 160 insertions(+), 129 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3f22e968f38..c9df3c185a9 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -3,17 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct, flatten } from 'vs/base/common/arrays'; +import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { getOrDefault } from 'vs/base/common/objects'; import { IPager } from 'vs/base/common/paging'; import { isWeb } from 'vs/base/common/platform'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { arePlatformsValid, CURRENT_TARGET_PLATFORM, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { CURRENT_TARGET_PLATFORM, DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -184,17 +183,6 @@ type GalleryServiceQueryEvent = QueryTelemetryData & { readonly count?: string; }; -const ANY_TARGET_PLATFORMS = Object.freeze([ - TargetPlatform.WIN32_X64, - TargetPlatform.WIN32_IA32, - TargetPlatform.WIN32_ARM64, - TargetPlatform.LINUX_X64, - TargetPlatform.LINUX_ARM64, - TargetPlatform.LINUX_ARMHF, - TargetPlatform.DARWIN_X64, - TargetPlatform.DARWIN_ARM64, -]); - class Query { constructor(private state = DefaultQueryState) { } @@ -324,11 +312,75 @@ function getIsPreview(flags: string): boolean { return flags.indexOf('preview') !== -1; } -function getTargetPlatforms(version: IRawGalleryExtensionVersion): TargetPlatform[] { - return version.targetPlatform && !equalsIgnoreCase(version.targetPlatform, 'universal') ? [toTargetPlatform(version.targetPlatform)] : [...ANY_TARGET_PLATFORMS]; +function getTargetPlatform(version: IRawGalleryExtensionVersion): TargetPlatform { + return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNIVERSAL; } -function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension { +function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] { + const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatform)); + + // Is a web extension only if it has WEB_EXTENSION_TAG + const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG); + + // Include Web Target Platform only if it is a web extension + const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB); + if (isWebExtension) { + if (webTargetPlatformIndex === -1) { + // Web extension but does not has web target platform -> add it + allTargetPlatforms.push(TargetPlatform.WEB); + } + } else { + if (webTargetPlatformIndex !== -1) { + // Not a web extension but has web target platform -> remove it + allTargetPlatforms.splice(webTargetPlatformIndex, 1); + } + } + + return allTargetPlatforms; +} + +function isNotWebExtensionInWebTargetPlatform(allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean { + // Not a web extension in web target platform + return productTargetPlatform === TargetPlatform.WEB && !allTargetPlatforms.includes(TargetPlatform.WEB); +} + +function isTargetPlatformCompatible(extensionTargetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean { + // Not compatible when extension is not a web extension in web target platform + if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, productTargetPlatform)) { + return false; + } + + // Compatible when extension target platform is universal + if (extensionTargetPlatform === TargetPlatform.UNIVERSAL) { + return true; + } + + // Not compatible when extension target platform is unknown + if (extensionTargetPlatform === TargetPlatform.UNKNOWN) { + return false; + } + + // Compatible when extension and product target platforms matches + if (extensionTargetPlatform === productTargetPlatform) { + return true; + } + + // Fallback + switch (productTargetPlatform) { + case TargetPlatform.WIN32_X64: return extensionTargetPlatform === TargetPlatform.WIN32_IA32; + case TargetPlatform.WIN32_ARM64: return extensionTargetPlatform === TargetPlatform.WIN32_IA32; + default: return false; + } +} + +function toExtensionWithLatestVersion(galleryExtension: IRawGalleryExtension, index: number, query: Query, querySource?: string): IGalleryExtension { + const allTargetPlatforms = getAllTargetPlatforms(galleryExtension); + let latestVersion = galleryExtension.versions[0]; + latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatform(version), allTargetPlatforms, CURRENT_TARGET_PLATFORM)) || latestVersion; + return toExtension(galleryExtension, latestVersion, allTargetPlatforms, index, query, querySource); +} + +function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], index: number, query: Query, querySource?: string): IGalleryExtension { const assets = { manifest: getVersionAsset(version, AssetType.Manifest), readme: getVersionAsset(version, AssetType.Details), @@ -340,16 +392,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller coreTranslations: getCoreTranslationAssets(version) }; - const allTargetPlatforms = distinct(flatten(galleryExtension.versions.map(getTargetPlatforms))); - if (galleryExtension.tags?.includes(WEB_EXTENSION_TAG) && !allTargetPlatforms.includes(TargetPlatform.WEB)) { - allTargetPlatforms.push(TargetPlatform.WEB); - } - - const targetPlatforms = getTargetPlatforms(version); - if (allTargetPlatforms.includes(TargetPlatform.WEB) && !targetPlatforms.includes(TargetPlatform.WEB)) { - targetPlatforms.push(TargetPlatform.WEB); - } - return { identifier: { id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName), @@ -376,7 +418,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller extensionPack: getExtensions(version, PropertyType.ExtensionPack), engine: getEngine(version), localizedLanguages: getLocalizedLanguages(version), - targetPlatforms, + targetPlatform: getTargetPlatform(version), }, preview: getIsPreview(galleryExtension.flags), /* __GDPR__FRAGMENT__ @@ -392,11 +434,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller }; } -function getLatestVersion(versions: IRawGalleryExtensionVersion[]): IRawGalleryExtensionVersion { - const latestVersion = versions[0]; - return versions.find(v => v.version === latestVersion.version && arePlatformsValid(getTargetPlatforms(v), CURRENT_TARGET_PLATFORM)) || latestVersion; -} - interface IRawExtensionsReport { malicious: string[]; slow: string[]; @@ -457,10 +494,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (version) { const versionAsset = galleryExtension.versions.find(v => v.version === version); if (versionAsset) { - result.push(toExtension(galleryExtension, versionAsset, index, query)); + result.push(toExtension(galleryExtension, versionAsset, getAllTargetPlatforms(galleryExtension), index, query)); } } else { - result.push(toExtension(galleryExtension, getLatestVersion(galleryExtension.versions), index, query)); + result.push(toExtensionWithLatestVersion(galleryExtension, index, query)); } } @@ -469,8 +506,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService { async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, targetPlatform: TargetPlatform): Promise { const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1; - if (extension && extension.properties.engine && this.isCompatible(extension.properties.engine, extension.properties.targetPlatforms, targetPlatform)) { - return extension; + if (extension) { + if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) { + return null; + } + if (await this.isExtensionCompatible(extension, targetPlatform)) { + return extension; + } } const { id, uuid } = extension ? extension.identifier : arg1; let query = new Query() @@ -490,14 +532,33 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return null; } - const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions, targetPlatform); - if (rawVersion) { - return toExtension(rawExtension, rawVersion, 0, query); + const allTargetPlatforms = getAllTargetPlatforms(rawExtension); + if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) { + return null; } + + for (let rawVersion of rawExtension.versions) { + // set engine property if does not exist + if (!getEngine(rawVersion)) { + const engine = await this.getEngine(rawVersion); + rawVersion = { + ...rawVersion, + properties: [...(rawVersion.properties || []), { key: PropertyType.Engine, value: engine }] + }; + } + if (await this.isRawExtensionVersionCompatible(rawVersion, allTargetPlatforms, targetPlatform)) { + return toExtension(rawExtension, rawVersion, allTargetPlatforms, 0, query); + } + } + return null; } async isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise { + if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) { + return false; + } + let engine = extension.properties.engine; if (!engine) { const manifest = await this.getManifest(extension, CancellationToken.None); @@ -506,7 +567,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } engine = manifest.engines.vscode; } - return this.isCompatible(engine, extension.properties.targetPlatforms, targetPlatform); + return isEngineValid(engine, this.productService.version, this.productService.date); + } + + private async isRawExtensionVersionCompatible(rawExtensionVersion: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise { + if (!isTargetPlatformCompatible(getTargetPlatform(rawExtensionVersion), allTargetPlatforms, targetPlatform)) { + return false; + } + + const engine = await this.getEngine(rawExtensionVersion); + return isEngineValid(engine, this.productService.version, this.productService.date); } query(token: CancellationToken): Promise>; @@ -571,14 +641,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const { galleryExtensions, total } = await this.queryGallery(query, token); - const extensions = galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, query, options.source)); + const extensions = galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, query, options.source)); const getPage = async (pageIndex: number, ct: CancellationToken) => { if (ct.isCancellationRequested) { throw canceled(); } const nextPageQuery = query.withPage(pageIndex + 1); const { galleryExtensions } = await this.queryGallery(nextPageQuery, ct); - return galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, nextPageQuery, options.source)); + return galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, nextPageQuery, options.source)); }; return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager; @@ -711,6 +781,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return null; } + private async getManifestFromRawExtensionVersion(rawExtensionVersion: IRawGalleryExtensionVersion, token: CancellationToken): Promise { + const manifestAsset = getVersionAsset(rawExtensionVersion, AssetType.Manifest); + if (!manifestAsset) { + throw new Error('Manifest was not found'); + } + const headers = { 'Accept-Encoding': 'gzip' }; + const context = await this.getAsset(manifestAsset, { headers }); + return await asJson(context); + } + async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise { const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0]; if (asset) { @@ -742,17 +822,23 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withFilter(FilterType.ExtensionName, extension.identifier.id); } - const result: IGalleryExtensionVersion[] = []; const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None); - if (galleryExtensions.length) { - await Promise.all(galleryExtensions[0].versions.map(async v => { - try { - const engine = await this.getEngine(v); - if (this.isCompatible(engine, getTargetPlatforms(v), targetPlatform)) { - result.push({ version: v!.version, date: v!.lastUpdated }); - } - } catch (error) { /* Ignore error and skip version */ } - })); + if (!galleryExtensions.length) { + return []; + } + + const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]); + if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) { + return []; + } + + const result: IGalleryExtensionVersion[] = []; + for (const version of galleryExtensions[0].versions) { + try { + if (await this.isRawExtensionVersionCompatible(version, allTargetPlatforms, targetPlatform)) { + result.push({ version: version!.version, date: version!.lastUpdated }); + } + } catch (error) { /* Ignore error and skip version */ } } return result; } @@ -795,67 +881,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } - private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise { - const version = this.getLastValidExtensionVersionFromProperties(extension, versions, targetPlatform); - if (version) { - return version; - } - return this.getLastValidExtensionVersionRecursively(extension, versions, targetPlatform); - } - - private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): IRawGalleryExtensionVersion | null { - for (const version of versions) { - const engine = getEngine(version); - if (!engine) { - return null; - } - if (this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) { - return version; + private async getEngine(rawExtensionVersion: IRawGalleryExtensionVersion): Promise { + let engine = getEngine(rawExtensionVersion); + if (!engine) { + const manifest = await this.getManifestFromRawExtensionVersion(rawExtensionVersion, CancellationToken.None); + if (!manifest) { + throw new Error('Manifest was not found'); } + engine = manifest.engines.vscode; } - return null; - } - - private async getEngine(version: IRawGalleryExtensionVersion): Promise { - const engine = getEngine(version); - if (engine) { - return engine; - } - - const manifestAsset = getVersionAsset(version, AssetType.Manifest); - if (!manifestAsset) { - throw new Error('Manifest was not found'); - } - - const headers = { 'Accept-Encoding': 'gzip' }; - const context = await this.getAsset(manifestAsset, { headers }); - const manifest = await asJson(context); - if (manifest) { - return manifest.engines.vscode; - } - - throw new Error('Error while reading manifest'); - } - - private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise { - if (!versions.length) { - return null; - } - - const version = versions[0]; - const engine = await this.getEngine(version); - if (!this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) { - return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1), targetPlatform); - } - - return { - ...version, - properties: [...(version.properties || []), { key: PropertyType.Engine, value: engine }] - }; - } - - private isCompatible(engine: string, targetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): boolean { - return isEngineValid(engine, this.productService.version, this.productService.date) && arePlatformsValid(targetPlatforms, targetPlatform); + return engine; } async getExtensionsReport(): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 260bf4f11ce..f010f878f10 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -31,6 +31,8 @@ export const enum TargetPlatform { DARWIN_ARM64 = 'darwin-arm64', WEB = 'web', + + UNIVERSAL = 'universal', UNKNOWN = 'unknown', } @@ -39,14 +41,19 @@ export function toTargetPlatform(targetPlatform: string): TargetPlatform { case TargetPlatform.WIN32_X64: return TargetPlatform.WIN32_X64; case TargetPlatform.WIN32_IA32: return TargetPlatform.WIN32_IA32; case TargetPlatform.WIN32_ARM64: return TargetPlatform.WIN32_ARM64; + case TargetPlatform.LINUX_X64: return TargetPlatform.LINUX_X64; case TargetPlatform.LINUX_ARM64: return TargetPlatform.LINUX_ARM64; case TargetPlatform.LINUX_ARMHF: return TargetPlatform.LINUX_ARMHF; + case TargetPlatform.DARWIN_X64: return TargetPlatform.DARWIN_X64; case TargetPlatform.DARWIN_ARM64: return TargetPlatform.DARWIN_ARM64; + case TargetPlatform.WEB: return TargetPlatform.WEB; + + case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL; + default: return TargetPlatform.UNKNOWN; } - return TargetPlatform.UNKNOWN; } export function getTargetPlatformFromOS(os: OperatingSystem, arch: string): TargetPlatform { @@ -60,9 +67,6 @@ export function getTargetPlatformFromOS(os: OperatingSystem, arch: string): Targ } export function getTargetPlatform(platform: Platform, arch: string | undefined): TargetPlatform { - if (isWeb) { - return TargetPlatform.WEB; - } switch (platform) { case Platform.Windows: if (arch === 'x64') { @@ -74,7 +78,7 @@ export function getTargetPlatform(platform: Platform, arch: string | undefined): if (arch === 'arm64') { return TargetPlatform.WIN32_ARM64; } - throw new Error(`Unknown Architecture '${arch}'`); + return TargetPlatform.UNKNOWN; case Platform.Linux: if (arch === 'x64') { @@ -86,7 +90,7 @@ export function getTargetPlatform(platform: Platform, arch: string | undefined): if (arch === 'arm') { return TargetPlatform.LINUX_ARMHF; } - throw new Error(`Unknown Architecture '${arch}'`); + return TargetPlatform.UNKNOWN; case Platform.Mac: if (arch === 'x64') { @@ -95,28 +99,20 @@ export function getTargetPlatform(platform: Platform, arch: string | undefined): if (arch === 'arm64') { return TargetPlatform.DARWIN_ARM64; } - throw new Error(`Unknown Architecture '${arch}'`); + return TargetPlatform.UNKNOWN; case Platform.Web: return TargetPlatform.WEB; } } -export function arePlatformsValid(targetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): boolean { - switch (targetPlatform) { - case TargetPlatform.WIN32_X64: return targetPlatforms.some(t => t === TargetPlatform.WIN32_X64 || t === TargetPlatform.WIN32_IA32); - case TargetPlatform.WIN32_ARM64: return targetPlatforms.some(t => t === TargetPlatform.WIN32_ARM64 || t === TargetPlatform.WIN32_IA32); - default: return targetPlatforms.includes(targetPlatform); - } -} - -export const CURRENT_TARGET_PLATFORM = getTargetPlatform(platform, arch); +export const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch); export interface IGalleryExtensionProperties { dependencies?: string[]; extensionPack?: string[]; engine?: string; localizedLanguages?: string[]; - targetPlatforms: TargetPlatform[]; + targetPlatform: TargetPlatform; } export interface IGalleryExtensionAsset { From 41fd62dd64aa907830a42bd6eccdf1202c20add4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Aug 2021 01:04:20 +0200 Subject: [PATCH 7/8] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d4e72bec15..93bc2c42de4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.60.0", - "distro": "75777cadcd9d0938683fddf3bac145119b03947d", + "distro": "e9e8f72b2e36f256d7de545895d6f5a228739804", "author": { "name": "Microsoft Corporation" }, From 19c8f1090596d2d2894e84ad3e81f2ed03513b5b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Aug 2021 14:26:25 +0200 Subject: [PATCH 8/8] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93bc2c42de4..71572e139c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.60.0", - "distro": "e9e8f72b2e36f256d7de545895d6f5a228739804", + "distro": "c81cac1012f4ca9187c2e838d5272354d251a617", "author": { "name": "Microsoft Corporation" },