Skip to content

Commit f3aa9a2

Browse files
authored
1 parent 04f7885 commit f3aa9a2

File tree

12 files changed

+213
-94
lines changed

12 files changed

+213
-94
lines changed

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

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@ import * as nls from 'vs/nls';
1616
import {
1717
ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation,
1818
IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode,
19-
InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService
19+
InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo
2020
} from 'vs/platform/extensionManagement/common/extensionManagement';
21-
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
21+
import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
2222
import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
2323
import { ILogService } from 'vs/platform/log/common/log';
2424
import { IProductService } from 'vs/platform/product/common/productService';
2525
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2626
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
2727

2828
export type ExtensionVerificationStatus = boolean | string;
29+
export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions & InstallVSIXOptions };
2930

3031
export type InstallExtensionTaskOptions = InstallOptions & InstallVSIXOptions & { readonly profileLocation: URI };
3132
export interface IInstallExtensionTask {
@@ -93,19 +94,54 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
9394

9495
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
9596
try {
96-
if (!this.galleryService.isEnabled()) {
97-
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
97+
const results = await this.installGalleryExtensions([{ extension, options }]);
98+
const result = results.find(({ identifier }) => areSameExtensions(identifier, extension.identifier));
99+
if (result?.local) {
100+
return result?.local;
98101
}
99-
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options.installGivenVersion, !!options.installPreReleaseVersion);
100-
return await this.installExtension(compatible.manifest, compatible.extension, options);
102+
if (result?.error) {
103+
throw result.error;
104+
}
105+
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extension.identifier.id}`));
101106
} catch (error) {
102-
reportTelemetry(this.telemetryService, 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(extension), error });
103-
this.logService.error(`Failed to install extension.`, extension.identifier.id);
104-
this.logService.error(error);
105107
throw toExtensionManagementError(error);
106108
}
107109
}
108110

111+
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
112+
if (!this.galleryService.isEnabled()) {
113+
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
114+
}
115+
116+
const results: InstallExtensionResult[] = [];
117+
const installableExtensions: InstallableExtension[] = [];
118+
119+
await Promise.allSettled(extensions.map(async ({ extension, options }) => {
120+
try {
121+
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion);
122+
installableExtensions.push({ ...compatible, options });
123+
} catch (error) {
124+
results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error });
125+
}
126+
}));
127+
128+
if (installableExtensions.length) {
129+
results.push(...await this.installExtensions(installableExtensions));
130+
}
131+
132+
for (const result of results) {
133+
if (result.error) {
134+
this.logService.error(`Failed to install extension.`, result.identifier.id);
135+
this.logService.error(result.error);
136+
if (result.source && !URI.isUri(result.source)) {
137+
reportTelemetry(this.telemetryService, 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(result.source), error: result.error });
138+
}
139+
}
140+
}
141+
142+
return results;
143+
}
144+
109145
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
110146
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
111147
return this.uninstallExtension(extension, options);
@@ -126,7 +162,21 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
126162
this.participants.push(participant);
127163
}
128164

129-
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
165+
protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> {
166+
const results: InstallExtensionResult[] = [];
167+
await Promise.allSettled(extensions.map(async e => {
168+
try {
169+
const result = await this.installExtension(e);
170+
results.push(...result);
171+
} catch (error) {
172+
results.push({ identifier: { id: getGalleryExtensionId(e.manifest.publisher, e.manifest.name) }, operation: InstallOperation.Install, source: e.extension, error });
173+
}
174+
}));
175+
this._onDidInstallExtensions.fire(results);
176+
return results;
177+
}
178+
179+
private async installExtension({ manifest, extension, options }: InstallableExtension): Promise<InstallExtensionResult[]> {
130180

131181
const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
132182
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
@@ -142,7 +192,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
142192
const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension));
143193
if (installingExtension) {
144194
this.logService.info('Extensions is already requested to install', extension.identifier.id);
145-
return installingExtension.task.waitUntilTaskIsFinished();
195+
await installingExtension.task.waitUntilTaskIsFinished();
196+
return [];
146197
}
147198
}
148199

@@ -272,8 +323,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
272323
}
273324

274325
installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
275-
this._onDidInstallExtensions.fire(installResults);
276-
return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
326+
return installResults;
277327

278328
} catch (error) {
279329

@@ -299,8 +349,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
299349
}
300350
}
301351

302-
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation })));
303-
throw error;
352+
return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error }));
304353
} finally {
305354
// Finally, remove all the tasks from the cache
306355
for (const { task } of allInstallExtensionTasks) {
@@ -678,7 +727,7 @@ export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | stri
678727
}, new Error(''));
679728
}
680729

681-
function toExtensionManagementError(error: Error): ExtensionManagementError {
730+
export function toExtensionManagementError(error: Error): ExtensionManagementError {
682731
if (error instanceof ExtensionManagementError) {
683732
return error;
684733
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ export interface InstallExtensionResult {
377377
readonly operation: InstallOperation;
378378
readonly source?: URI | IGalleryExtension;
379379
readonly local?: ILocalExtension;
380+
readonly error?: Error;
380381
readonly context?: IStringDictionary<any>;
381382
readonly profileLocation?: URI;
382383
readonly applicationScoped?: boolean;
@@ -450,6 +451,8 @@ export interface IExtensionManagementParticipant {
450451
postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise<void>;
451452
}
452453

454+
export type InstallExtensionInfo = { readonly extension: IGalleryExtension; readonly options: InstallOptions };
455+
453456
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
454457
export interface IExtensionManagementService {
455458
readonly _serviceBrand: undefined;
@@ -466,6 +469,7 @@ export interface IExtensionManagementService {
466469
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
467470
canInstall(extension: IGalleryExtension): Promise<boolean>;
468471
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
472+
installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]>;
469473
installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;
470474
installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]>;
471475
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
99
import { URI, UriComponents } from 'vs/base/common/uri';
1010
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
1111
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
12-
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
12+
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
1313
import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
1414

1515
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;
@@ -128,6 +128,10 @@ export class ExtensionManagementChannel implements IServerChannel {
128128
case 'installFromGallery': {
129129
return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer));
130130
}
131+
case 'installGalleryExtensions': {
132+
const arg: InstallExtensionInfo[] = args[0];
133+
return this.service.installGalleryExtensions(arg.map(({ extension, options }) => ({ extension, options: transformIncomingOptions(options, uriTransformer) ?? {} })));
134+
}
131135
case 'uninstall': {
132136
return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer));
133137
}
@@ -250,6 +254,11 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
250254
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
251255
}
252256

257+
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
258+
const results = await this.channel.call<InstallExtensionResult[]>('installGalleryExtensions', [extensions]);
259+
return results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }));
260+
}
261+
253262
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
254263
return Promise.resolve(this.channel.call<void>('uninstall', [extension!, options]));
255264
}

src/vs/platform/extensionManagement/node/extensionManagementService.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { extract, ExtractError, IFile, zip } from 'vs/base/node/zip';
2525
import * as nls from 'vs/nls';
2626
import { IDownloadService } from 'vs/platform/download/common/download';
2727
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
28-
import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
28+
import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
2929
import {
3030
ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation,
3131
Metadata, InstallVSIXOptions
@@ -148,11 +148,19 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
148148

149149
try {
150150
const manifest = await getManifest(path.resolve(location.fsPath));
151+
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
151152
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) {
152-
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), this.productService.version));
153+
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version));
153154
}
154155

155-
return await this.installExtension(manifest, location, options);
156+
const result = await this.installExtensions([{ manifest, extension: location, options }]);
157+
if (result[0]?.local) {
158+
return result[0]?.local;
159+
}
160+
if (result[0]?.error) {
161+
throw result[0].error;
162+
}
163+
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extensionId}`));
156164
} finally {
157165
await cleanup();
158166
}

0 commit comments

Comments
 (0)