support universal target platform and web extensions

This commit is contained in:
Sandeep Somavarapu 2021-08-20 01:00:51 +02:00
parent b6766367ef
commit 959b5ea269
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
2 changed files with 160 additions and 129 deletions

View file

@ -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 = <IGalleryExtensionAssets>{
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<IGalleryExtension | null> {
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 : <IExtensionIdentifier>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<boolean> {
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<boolean> {
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<IPager<IGalleryExtension>>;
@ -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<IGalleryExtension>;
@ -711,6 +781,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return null;
}
private async getManifestFromRawExtensionVersion(rawExtensionVersion: IRawGalleryExtensionVersion, token: CancellationToken): Promise<IExtensionManifest | null> {
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<IExtensionManifest>(context);
}
async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
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<IRawGalleryExtensionVersion | null> {
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<string> {
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<string> {
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<IExtensionManifest>(context);
if (manifest) {
return manifest.engines.vscode;
}
throw new Error('Error while reading manifest');
}
private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise<IRawGalleryExtensionVersion | null> {
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<IReportedExtension[]> {

View file

@ -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 {