diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 77d17393fe5b387816c04d30707f0d1182e99a45..8b024db44b21597ce92084779983211797a6345f 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -158,7 +158,6 @@ export interface IExtensionGalleryService { getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise; getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise; getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise; - loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise; getExtensionsReport(): Promise; getCompatibleExtension(extension: IGalleryExtension): Promise; getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 7c6d9cdf36c1aee209ea34ccdcda30740cf429b6..ec406ac1442a417b44c0dbbedf0818ad0377e6ac 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -5,7 +5,6 @@ import { tmpdir } from 'os'; import * as path from 'vs/base/common/path'; -import { distinct } from 'vs/base/common/arrays'; import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors'; import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -595,10 +594,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return Promise.resolve(''); } - loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise { - return this.getDependenciesRecursively(extensions.map(e => e.id), [], token); - } - getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise { let query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated) @@ -627,59 +622,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - - private loadDependencies(extensionNames: string[], token: CancellationToken): Promise { - if (!extensionNames || extensionNames.length === 0) { - return Promise.resolve([]); - } - - let query = new Query() - .withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties) - .withPage(1, extensionNames.length) - .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') - .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)) - .withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX) - .withFilter(FilterType.ExtensionName, ...extensionNames); - - return this.queryGallery(query, token).then(result => { - const dependencies: IGalleryExtension[] = []; - const ids: string[] = []; - - for (let index = 0; index < result.galleryExtensions.length; index++) { - const rawExtension = result.galleryExtensions[index]; - if (ids.indexOf(rawExtension.extensionId) === -1) { - dependencies.push(toExtension(rawExtension, rawExtension.versions[0], index, query, 'dependencies')); - ids.push(rawExtension.extensionId); - } - } - return dependencies; - }); - } - - private getDependenciesRecursively(toGet: string[], result: IGalleryExtension[], token: CancellationToken): Promise { - if (!toGet || !toGet.length) { - return Promise.resolve(result); - } - toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet; - if (!toGet.length) { - return Promise.resolve(result); - } - - return this.loadDependencies(toGet, token) - .then(loadedDependencies => { - const dependenciesSet = new Set(); - for (const dep of loadedDependencies) { - if (dep.properties.dependencies) { - dep.properties.dependencies.forEach(d => dependenciesSet.add(d)); - } - } - result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid); - const dependencies: string[] = []; - dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d)); - return this.getDependenciesRecursively(dependencies, result, token); - }); - } - private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise { return this.commonHeadersPromise.then(commonHeaders => { const baseOptions = { type: 'GET' }; @@ -798,15 +740,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean { - for (const extension of extensions) { - if (`${extension.publisher}.${extension.name}` === name) { - return true; - } - } - return false; - } - getExtensionsReport(): Promise { if (!this.isEnabled()) { return Promise.reject(new Error('No extension gallery service configured.')); diff --git a/src/vs/platform/extensions/node/extensionsUtil.ts b/src/vs/platform/extensions/node/extensionsUtil.ts index a84379d312eae281afa94d1bf0669519cf232d08..7d5576d8331f57506a7f8202067b9e68c10d5316 100644 --- a/src/vs/platform/extensions/node/extensionsUtil.ts +++ b/src/vs/platform/extensions/node/extensionsUtil.ts @@ -16,18 +16,24 @@ export function isUIExtension(manifest: IExtensionManifest, uiContributions: str case 'ui': return true; case 'workspace': return false; default: { + // Tagged as UI extension in product if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { return true; } + // Not an UI extension if it has main if (manifest.main) { return false; } + // Not an UI extension if it has dependencies or an extension pack + if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { + return false; + } if (manifest.contributes) { + // Not an UI extension if it has no ui contributions if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) { return false; } } - // Default is UI Extension return true; } } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index dd6c2fed8e911b7f8b3575f8754d3d9e90e34622..840adf3a8300e76cc02a78aa04acd6c51023dc53 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -333,11 +333,6 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService { return Promise.resolve(undefined); } - loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - getExtensionsReport(): Promise { // @ts-ignore return Promise.resolve(undefined); diff --git a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts index c430a811ac53a4453c33598f8eb8b9f43418a3b8..02d2f0e01161bd6927b8dbeb381473ab4d45df40 100644 --- a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts @@ -18,6 +18,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { localize } from 'vs/nls'; import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { values } from 'vs/base/common/map'; export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -145,7 +147,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte // Install only on remote server const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix); // Install UI Dependencies on local server - await this.installUIDependencies(manifest); + await this.installUIDependenciesAndPackedExtensions(manifest); return promise; } return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); @@ -165,8 +167,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte } // Install only on remote server const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery); - // Install UI Dependencies on local server - await this.installUIDependencies(manifest); + // Install UI dependencies and packed extensions on local server + await this.installUIDependenciesAndPackedExtensions(manifest); return promise; } else { return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); @@ -175,18 +177,11 @@ export class MultiExtensionManagementService extends Disposable implements IExte return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } - private async installUIDependencies(manifest: IExtensionManifest): Promise { - if (manifest.extensionDependencies && manifest.extensionDependencies.length) { - const dependencies = await this.extensionGalleryService.loadAllDependencies(manifest.extensionDependencies.map(id => ({ id })), CancellationToken.None); - if (dependencies.length) { - await Promise.all(dependencies.map(async d => { - const manifest = await this.extensionGalleryService.getManifest(d, CancellationToken.None); - if (manifest && isUIExtension(manifest, this.configurationService)) { - await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d); - } - })); - } - } + private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise { + const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None); + const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled(); + const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier))); + await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d))); } getExtensionsReport(): Promise { @@ -196,6 +191,49 @@ export class MultiExtensionManagementService extends Disposable implements IExte private getServer(extension: ILocalExtension): IExtensionManagementServer | null { return this.extensionManagementServerService.getExtensionManagementServer(extension.location); } + + private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise { + const result = new Map(); + const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])]; + await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token); + return values(result); + } + + private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map, token: CancellationToken): Promise { + if (toGet.length === 0) { + return Promise.resolve(); + } + + const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage; + const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token))); + const uiExtensionsManifests: IExtensionManifest[] = []; + for (let idx = 0; idx < extensions.length; idx++) { + const extension = extensions[idx]; + const manifest = manifests[idx]; + if (manifest && isUIExtension(manifest, this.configurationService)) { + result.set(extension.identifier.id.toLowerCase(), extension); + uiExtensionsManifests.push(manifest); + } + } + toGet = []; + for (const uiExtensionManifest of uiExtensionsManifests) { + if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) { + for (const id of uiExtensionManifest.extensionDependencies) { + if (!result.has(id.toLowerCase())) { + toGet.push(id); + } + } + } + if (isNonEmptyArray(uiExtensionManifest.extensionPack)) { + for (const id of uiExtensionManifest.extensionPack) { + if (!result.has(id.toLowerCase())) { + toGet.push(id); + } + } + } + } + return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token); + } } registerSingleton(IExtensionManagementService, MultiExtensionManagementService); \ No newline at end of file