Skip to content

Commit 959b5ea

Browse files
committed
support universal target platform and web extensions
1 parent b676636 commit 959b5ea

File tree

2 files changed

+160
-129
lines changed

2 files changed

+160
-129
lines changed

src/vs/platform/extensionManagement/common/extensionGalleryService.ts

+147-112
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { distinct, flatten } from 'vs/base/common/arrays';
6+
import { distinct } from 'vs/base/common/arrays';
77
import { CancellationToken } from 'vs/base/common/cancellation';
88
import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
99
import { getOrDefault } from 'vs/base/common/objects';
1010
import { IPager } from 'vs/base/common/paging';
1111
import { isWeb } from 'vs/base/common/platform';
12-
import { equalsIgnoreCase } from 'vs/base/common/strings';
1312
import { URI } from 'vs/base/common/uri';
1413
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
1514
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
16-
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';
15+
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';
1716
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
1817
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
1918
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
@@ -184,17 +183,6 @@ type GalleryServiceQueryEvent = QueryTelemetryData & {
184183
readonly count?: string;
185184
};
186185

187-
const ANY_TARGET_PLATFORMS = Object.freeze([
188-
TargetPlatform.WIN32_X64,
189-
TargetPlatform.WIN32_IA32,
190-
TargetPlatform.WIN32_ARM64,
191-
TargetPlatform.LINUX_X64,
192-
TargetPlatform.LINUX_ARM64,
193-
TargetPlatform.LINUX_ARMHF,
194-
TargetPlatform.DARWIN_X64,
195-
TargetPlatform.DARWIN_ARM64,
196-
]);
197-
198186
class Query {
199187

200188
constructor(private state = DefaultQueryState) { }
@@ -324,11 +312,75 @@ function getIsPreview(flags: string): boolean {
324312
return flags.indexOf('preview') !== -1;
325313
}
326314

327-
function getTargetPlatforms(version: IRawGalleryExtensionVersion): TargetPlatform[] {
328-
return version.targetPlatform && !equalsIgnoreCase(version.targetPlatform, 'universal') ? [toTargetPlatform(version.targetPlatform)] : [...ANY_TARGET_PLATFORMS];
315+
function getTargetPlatform(version: IRawGalleryExtensionVersion): TargetPlatform {
316+
return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNIVERSAL;
317+
}
318+
319+
function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] {
320+
const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatform));
321+
322+
// Is a web extension only if it has WEB_EXTENSION_TAG
323+
const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG);
324+
325+
// Include Web Target Platform only if it is a web extension
326+
const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB);
327+
if (isWebExtension) {
328+
if (webTargetPlatformIndex === -1) {
329+
// Web extension but does not has web target platform -> add it
330+
allTargetPlatforms.push(TargetPlatform.WEB);
331+
}
332+
} else {
333+
if (webTargetPlatformIndex !== -1) {
334+
// Not a web extension but has web target platform -> remove it
335+
allTargetPlatforms.splice(webTargetPlatformIndex, 1);
336+
}
337+
}
338+
339+
return allTargetPlatforms;
340+
}
341+
342+
function isNotWebExtensionInWebTargetPlatform(allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
343+
// Not a web extension in web target platform
344+
return productTargetPlatform === TargetPlatform.WEB && !allTargetPlatforms.includes(TargetPlatform.WEB);
345+
}
346+
347+
function isTargetPlatformCompatible(extensionTargetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
348+
// Not compatible when extension is not a web extension in web target platform
349+
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, productTargetPlatform)) {
350+
return false;
351+
}
352+
353+
// Compatible when extension target platform is universal
354+
if (extensionTargetPlatform === TargetPlatform.UNIVERSAL) {
355+
return true;
356+
}
357+
358+
// Not compatible when extension target platform is unknown
359+
if (extensionTargetPlatform === TargetPlatform.UNKNOWN) {
360+
return false;
361+
}
362+
363+
// Compatible when extension and product target platforms matches
364+
if (extensionTargetPlatform === productTargetPlatform) {
365+
return true;
366+
}
367+
368+
// Fallback
369+
switch (productTargetPlatform) {
370+
case TargetPlatform.WIN32_X64: return extensionTargetPlatform === TargetPlatform.WIN32_IA32;
371+
case TargetPlatform.WIN32_ARM64: return extensionTargetPlatform === TargetPlatform.WIN32_IA32;
372+
default: return false;
373+
}
374+
}
375+
376+
function toExtensionWithLatestVersion(galleryExtension: IRawGalleryExtension, index: number, query: Query, querySource?: string): IGalleryExtension {
377+
const allTargetPlatforms = getAllTargetPlatforms(galleryExtension);
378+
let latestVersion = galleryExtension.versions[0];
379+
latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatform(version), allTargetPlatforms, CURRENT_TARGET_PLATFORM)) || latestVersion;
380+
return toExtension(galleryExtension, latestVersion, allTargetPlatforms, index, query, querySource);
329381
}
330382

331-
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
383+
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], index: number, query: Query, querySource?: string): IGalleryExtension {
332384
const assets = <IGalleryExtensionAssets>{
333385
manifest: getVersionAsset(version, AssetType.Manifest),
334386
readme: getVersionAsset(version, AssetType.Details),
@@ -340,16 +392,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
340392
coreTranslations: getCoreTranslationAssets(version)
341393
};
342394

343-
const allTargetPlatforms = distinct(flatten(galleryExtension.versions.map(getTargetPlatforms)));
344-
if (galleryExtension.tags?.includes(WEB_EXTENSION_TAG) && !allTargetPlatforms.includes(TargetPlatform.WEB)) {
345-
allTargetPlatforms.push(TargetPlatform.WEB);
346-
}
347-
348-
const targetPlatforms = getTargetPlatforms(version);
349-
if (allTargetPlatforms.includes(TargetPlatform.WEB) && !targetPlatforms.includes(TargetPlatform.WEB)) {
350-
targetPlatforms.push(TargetPlatform.WEB);
351-
}
352-
353395
return {
354396
identifier: {
355397
id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName),
@@ -376,7 +418,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
376418
extensionPack: getExtensions(version, PropertyType.ExtensionPack),
377419
engine: getEngine(version),
378420
localizedLanguages: getLocalizedLanguages(version),
379-
targetPlatforms,
421+
targetPlatform: getTargetPlatform(version),
380422
},
381423
preview: getIsPreview(galleryExtension.flags),
382424
/* __GDPR__FRAGMENT__
@@ -392,11 +434,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
392434
};
393435
}
394436

395-
function getLatestVersion(versions: IRawGalleryExtensionVersion[]): IRawGalleryExtensionVersion {
396-
const latestVersion = versions[0];
397-
return versions.find(v => v.version === latestVersion.version && arePlatformsValid(getTargetPlatforms(v), CURRENT_TARGET_PLATFORM)) || latestVersion;
398-
}
399-
400437
interface IRawExtensionsReport {
401438
malicious: string[];
402439
slow: string[];
@@ -457,10 +494,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
457494
if (version) {
458495
const versionAsset = galleryExtension.versions.find(v => v.version === version);
459496
if (versionAsset) {
460-
result.push(toExtension(galleryExtension, versionAsset, index, query));
497+
result.push(toExtension(galleryExtension, versionAsset, getAllTargetPlatforms(galleryExtension), index, query));
461498
}
462499
} else {
463-
result.push(toExtension(galleryExtension, getLatestVersion(galleryExtension.versions), index, query));
500+
result.push(toExtensionWithLatestVersion(galleryExtension, index, query));
464501
}
465502
}
466503

@@ -469,8 +506,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
469506

470507
async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null> {
471508
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
472-
if (extension && extension.properties.engine && this.isCompatible(extension.properties.engine, extension.properties.targetPlatforms, targetPlatform)) {
473-
return extension;
509+
if (extension) {
510+
if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {
511+
return null;
512+
}
513+
if (await this.isExtensionCompatible(extension, targetPlatform)) {
514+
return extension;
515+
}
474516
}
475517
const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1;
476518
let query = new Query()
@@ -490,14 +532,33 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
490532
return null;
491533
}
492534

493-
const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions, targetPlatform);
494-
if (rawVersion) {
495-
return toExtension(rawExtension, rawVersion, 0, query);
535+
const allTargetPlatforms = getAllTargetPlatforms(rawExtension);
536+
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) {
537+
return null;
496538
}
539+
540+
for (let rawVersion of rawExtension.versions) {
541+
// set engine property if does not exist
542+
if (!getEngine(rawVersion)) {
543+
const engine = await this.getEngine(rawVersion);
544+
rawVersion = {
545+
...rawVersion,
546+
properties: [...(rawVersion.properties || []), { key: PropertyType.Engine, value: engine }]
547+
};
548+
}
549+
if (await this.isRawExtensionVersionCompatible(rawVersion, allTargetPlatforms, targetPlatform)) {
550+
return toExtension(rawExtension, rawVersion, allTargetPlatforms, 0, query);
551+
}
552+
}
553+
497554
return null;
498555
}
499556

500557
async isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<boolean> {
558+
if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) {
559+
return false;
560+
}
561+
501562
let engine = extension.properties.engine;
502563
if (!engine) {
503564
const manifest = await this.getManifest(extension, CancellationToken.None);
@@ -506,7 +567,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
506567
}
507568
engine = manifest.engines.vscode;
508569
}
509-
return this.isCompatible(engine, extension.properties.targetPlatforms, targetPlatform);
570+
return isEngineValid(engine, this.productService.version, this.productService.date);
571+
}
572+
573+
private async isRawExtensionVersionCompatible(rawExtensionVersion: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise<boolean> {
574+
if (!isTargetPlatformCompatible(getTargetPlatform(rawExtensionVersion), allTargetPlatforms, targetPlatform)) {
575+
return false;
576+
}
577+
578+
const engine = await this.getEngine(rawExtensionVersion);
579+
return isEngineValid(engine, this.productService.version, this.productService.date);
510580
}
511581

512582
query(token: CancellationToken): Promise<IPager<IGalleryExtension>>;
@@ -571,14 +641,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
571641
}
572642

573643
const { galleryExtensions, total } = await this.queryGallery(query, token);
574-
const extensions = galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, query, options.source));
644+
const extensions = galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, query, options.source));
575645
const getPage = async (pageIndex: number, ct: CancellationToken) => {
576646
if (ct.isCancellationRequested) {
577647
throw canceled();
578648
}
579649
const nextPageQuery = query.withPage(pageIndex + 1);
580650
const { galleryExtensions } = await this.queryGallery(nextPageQuery, ct);
581-
return galleryExtensions.map((e, index) => toExtension(e, getLatestVersion(e.versions), index, nextPageQuery, options.source));
651+
return galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, nextPageQuery, options.source));
582652
};
583653

584654
return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager<IGalleryExtension>;
@@ -711,6 +781,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
711781
return null;
712782
}
713783

784+
private async getManifestFromRawExtensionVersion(rawExtensionVersion: IRawGalleryExtensionVersion, token: CancellationToken): Promise<IExtensionManifest | null> {
785+
const manifestAsset = getVersionAsset(rawExtensionVersion, AssetType.Manifest);
786+
if (!manifestAsset) {
787+
throw new Error('Manifest was not found');
788+
}
789+
const headers = { 'Accept-Encoding': 'gzip' };
790+
const context = await this.getAsset(manifestAsset, { headers });
791+
return await asJson<IExtensionManifest>(context);
792+
}
793+
714794
async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
715795
const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];
716796
if (asset) {
@@ -742,17 +822,23 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
742822
query = query.withFilter(FilterType.ExtensionName, extension.identifier.id);
743823
}
744824

745-
const result: IGalleryExtensionVersion[] = [];
746825
const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None);
747-
if (galleryExtensions.length) {
748-
await Promise.all(galleryExtensions[0].versions.map(async v => {
749-
try {
750-
const engine = await this.getEngine(v);
751-
if (this.isCompatible(engine, getTargetPlatforms(v), targetPlatform)) {
752-
result.push({ version: v!.version, date: v!.lastUpdated });
753-
}
754-
} catch (error) { /* Ignore error and skip version */ }
755-
}));
826+
if (!galleryExtensions.length) {
827+
return [];
828+
}
829+
830+
const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);
831+
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) {
832+
return [];
833+
}
834+
835+
const result: IGalleryExtensionVersion[] = [];
836+
for (const version of galleryExtensions[0].versions) {
837+
try {
838+
if (await this.isRawExtensionVersionCompatible(version, allTargetPlatforms, targetPlatform)) {
839+
result.push({ version: version!.version, date: version!.lastUpdated });
840+
}
841+
} catch (error) { /* Ignore error and skip version */ }
756842
}
757843
return result;
758844
}
@@ -795,67 +881,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
795881
}
796882
}
797883

798-
private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise<IRawGalleryExtensionVersion | null> {
799-
const version = this.getLastValidExtensionVersionFromProperties(extension, versions, targetPlatform);
800-
if (version) {
801-
return version;
802-
}
803-
return this.getLastValidExtensionVersionRecursively(extension, versions, targetPlatform);
804-
}
805-
806-
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): IRawGalleryExtensionVersion | null {
807-
for (const version of versions) {
808-
const engine = getEngine(version);
809-
if (!engine) {
810-
return null;
811-
}
812-
if (this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) {
813-
return version;
884+
private async getEngine(rawExtensionVersion: IRawGalleryExtensionVersion): Promise<string> {
885+
let engine = getEngine(rawExtensionVersion);
886+
if (!engine) {
887+
const manifest = await this.getManifestFromRawExtensionVersion(rawExtensionVersion, CancellationToken.None);
888+
if (!manifest) {
889+
throw new Error('Manifest was not found');
814890
}
891+
engine = manifest.engines.vscode;
815892
}
816-
return null;
817-
}
818-
819-
private async getEngine(version: IRawGalleryExtensionVersion): Promise<string> {
820-
const engine = getEngine(version);
821-
if (engine) {
822-
return engine;
823-
}
824-
825-
const manifestAsset = getVersionAsset(version, AssetType.Manifest);
826-
if (!manifestAsset) {
827-
throw new Error('Manifest was not found');
828-
}
829-
830-
const headers = { 'Accept-Encoding': 'gzip' };
831-
const context = await this.getAsset(manifestAsset, { headers });
832-
const manifest = await asJson<IExtensionManifest>(context);
833-
if (manifest) {
834-
return manifest.engines.vscode;
835-
}
836-
837-
throw new Error('Error while reading manifest');
838-
}
839-
840-
private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform): Promise<IRawGalleryExtensionVersion | null> {
841-
if (!versions.length) {
842-
return null;
843-
}
844-
845-
const version = versions[0];
846-
const engine = await this.getEngine(version);
847-
if (!this.isCompatible(engine, getTargetPlatforms(version), targetPlatform)) {
848-
return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1), targetPlatform);
849-
}
850-
851-
return {
852-
...version,
853-
properties: [...(version.properties || []), { key: PropertyType.Engine, value: engine }]
854-
};
855-
}
856-
857-
private isCompatible(engine: string, targetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): boolean {
858-
return isEngineValid(engine, this.productService.version, this.productService.date) && arePlatformsValid(targetPlatforms, targetPlatform);
893+
return engine;
859894
}
860895

861896
async getExtensionsReport(): Promise<IReportedExtension[]> {

0 commit comments

Comments
 (0)