Skip to content

Commit 944c6ca

Browse files
authored
Support removing and adding the same extension but at different versions (microsoft#153761)
Fixes microsoft#153646: Support removing and adding the same extension but at different versions
1 parent f1abeea commit 944c6ca

File tree

3 files changed

+99
-36
lines changed

3 files changed

+99
-36
lines changed

src/vs/workbench/services/extensions/common/abstractExtensionService.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -546,23 +546,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
546546
}
547547

548548
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise<void> {
549-
const toAdd: IExtensionDescription[] = [];
550-
for (let i = 0, len = _toAdd.length; i < len; i++) {
551-
const extension = _toAdd[i];
552-
553-
const extensionDescription = await this._scanSingleExtension(extension);
554-
if (!extensionDescription) {
555-
// could not scan extension...
556-
continue;
557-
}
558-
559-
if (!this.canAddExtension(extensionDescription)) {
560-
continue;
561-
}
562-
563-
toAdd.push(extensionDescription);
564-
}
565-
566549
let toRemove: IExtensionDescription[] = [];
567550
for (let i = 0, len = _toRemove.length; i < len; i++) {
568551
const extensionOrId = _toRemove[i];
@@ -587,6 +570,23 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
587570
toRemove.push(extensionDescription);
588571
}
589572

573+
const toAdd: IExtensionDescription[] = [];
574+
for (let i = 0, len = _toAdd.length; i < len; i++) {
575+
const extension = _toAdd[i];
576+
577+
const extensionDescription = await this._scanSingleExtension(extension);
578+
if (!extensionDescription) {
579+
// could not scan extension...
580+
continue;
581+
}
582+
583+
if (!this._canAddExtension(extensionDescription, toRemove)) {
584+
continue;
585+
}
586+
587+
toAdd.push(extensionDescription);
588+
}
589+
590590
if (toAdd.length === 0 && toRemove.length === 0) {
591591
return;
592592
}
@@ -643,15 +643,19 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
643643
}
644644

645645
public canAddExtension(extension: IExtensionDescription): boolean {
646-
const existing = this._registry.getExtensionDescription(extension.identifier);
647-
if (existing) {
648-
// this extension is already running (most likely at a different version)
649-
return false;
650-
}
646+
return this._canAddExtension(extension, []);
647+
}
651648

652-
// Check if extension is renamed
653-
if (extension.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.uuid)) {
654-
return false;
649+
private _canAddExtension(extension: IExtensionDescription, extensionsBeingRemoved: IExtensionDescription[]): boolean {
650+
// (Also check for renamed extensions)
651+
const existing = this._registry.getExtensionDescriptionByIdOrUUID(extension.identifier, extension.id);
652+
if (existing) {
653+
// This extension is already known (most likely at a different version)
654+
// so it cannot be added again unless it is removed first
655+
const isBeingRemoved = extensionsBeingRemoved.some((extensionDescription) => ExtensionIdentifier.equals(extension.identifier, extensionDescription.identifier));
656+
if (!isBeingRemoved) {
657+
return false;
658+
}
655659
}
656660

657661
const extensionKind = this._getExtensionKind(extension);
@@ -667,7 +671,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
667671
public canRemoveExtension(extension: IExtensionDescription): boolean {
668672
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
669673
if (!extensionDescription) {
670-
// ignore removing an extension which is not running
674+
// Can't remove an extension that is unknown!
671675
return false;
672676
}
673677

src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,16 @@ export class ExtensionDescriptionRegistry {
6868
}
6969

7070
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult {
71-
if (toAdd.length > 0) {
72-
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
73-
}
71+
// It is possible that an extension is removed, only to be added again at a different version
72+
// so we will first handle removals
73+
this._extensionDescriptions = removeExtensions(this._extensionDescriptions, toRemove);
74+
75+
// Then, handle the extensions to add
76+
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
7477

7578
// Immediately remove looping extensions!
7679
const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions);
77-
toRemove = toRemove.concat(looping.map(ext => ext.identifier));
78-
79-
if (toRemove.length > 0) {
80-
const toRemoveSet = new Set<string>();
81-
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
82-
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
83-
}
80+
this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier));
8481

8582
this._initialize();
8683
this._onDidChange.fire(undefined);
@@ -194,6 +191,22 @@ export class ExtensionDescriptionRegistry {
194191
const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId));
195192
return extension ? extension : undefined;
196193
}
194+
195+
public getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined {
196+
for (const extensionDescription of this._extensionsArr) {
197+
if (extensionDescription.uuid === uuid) {
198+
return extensionDescription;
199+
}
200+
}
201+
return undefined;
202+
}
203+
204+
public getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined {
205+
return (
206+
this.getExtensionDescription(extensionId)
207+
?? (uuid ? this.getExtensionDescriptionByUUID(uuid) : undefined)
208+
);
209+
}
197210
}
198211

199212
const enum SortBucket {
@@ -226,3 +239,9 @@ function extensionCmp(a: IExtensionDescription, b: IExtensionDescription): numbe
226239
}
227240
return 0;
228241
}
242+
243+
function removeExtensions(arr: IExtensionDescription[], toRemove: ExtensionIdentifier[]): IExtensionDescription[] {
244+
const toRemoveSet = new Set<string>();
245+
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
246+
return arr.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
247+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import { URI } from 'vs/base/common/uri';
8+
import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions';
9+
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
10+
11+
suite('ExtensionDescriptionRegistry', () => {
12+
test('allow removing and adding the same extension at a different version', () => {
13+
const idA = new ExtensionIdentifier('a');
14+
const extensionA1 = desc(idA, '1.0.0');
15+
const extensionA2 = desc(idA, '2.0.0');
16+
17+
const registry = new ExtensionDescriptionRegistry([extensionA1]);
18+
registry.deltaExtensions([extensionA2], [idA]);
19+
20+
assert.deepStrictEqual(registry.getAllExtensionDescriptions(), [extensionA2]);
21+
});
22+
23+
function desc(id: ExtensionIdentifier, version: string, activationEvents: string[] = ['*']): IExtensionDescription {
24+
return {
25+
name: id.value,
26+
publisher: 'test',
27+
version: '0.0.0',
28+
engines: { vscode: '^1.0.0' },
29+
identifier: id,
30+
extensionLocation: URI.parse(`nothing://nowhere`),
31+
isBuiltin: false,
32+
isUnderDevelopment: false,
33+
isUserBuiltin: false,
34+
activationEvents,
35+
main: 'index.js',
36+
targetPlatform: TargetPlatform.UNDEFINED,
37+
extensionDependencies: []
38+
};
39+
}
40+
});

0 commit comments

Comments
 (0)