mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
committed by
GitHub
parent
6e1ea79174
commit
e2d75fb98b
@@ -173,7 +173,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
this.scanUserExtensions(userScanOptions),
|
||||
]);
|
||||
const development = await this.scanExtensionsUnderDevelopment(systemScanOptions, [...system, ...user]);
|
||||
return this.dedupExtensions([...system, ...user, ...development], await this.getTargetPlatform(), true);
|
||||
return this.dedupExtensions(system, user, development, await this.getTargetPlatform(), true);
|
||||
}
|
||||
|
||||
async scanSystemExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
@@ -181,7 +181,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
promises.push(this.scanDefaultSystemExtensions(!!scanOptions.useCache, scanOptions.language));
|
||||
promises.push(this.scanDevSystemExtensions(scanOptions.language, !!scanOptions.checkControlFile));
|
||||
const [defaultSystemExtensions, devSystemExtensions] = await Promise.all(promises);
|
||||
return this.applyScanOptions([...defaultSystemExtensions, ...devSystemExtensions], scanOptions, false);
|
||||
return this.applyScanOptions([...defaultSystemExtensions, ...devSystemExtensions], ExtensionType.System, scanOptions, false);
|
||||
}
|
||||
|
||||
async scanUserExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
@@ -189,7 +189,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(scanOptions.profileLocation ?? this.userExtensionsLocation, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language);
|
||||
const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner;
|
||||
let extensions = await extensionsScanner.scanExtensions(extensionsScannerInput);
|
||||
extensions = await this.applyScanOptions(extensions, scanOptions, true);
|
||||
extensions = await this.applyScanOptions(extensions, ExtensionType.User, scanOptions, true);
|
||||
this.logService.trace('Scanned user extensions:', extensions.length);
|
||||
return extensions;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
});
|
||||
})))
|
||||
.flat();
|
||||
return this.applyScanOptions(extensions, scanOptions, true);
|
||||
return this.applyScanOptions(extensions, 'development', scanOptions, true);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
async scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise<IScannedExtension[]> {
|
||||
const extensionsScannerInput = await this.createExtensionScannerInput(extensionLocation, false, extensionType, true, scanOptions.language);
|
||||
const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(extensionsScannerInput);
|
||||
return this.applyScanOptions(extensions, scanOptions, true);
|
||||
return this.applyScanOptions(extensions, extensionType, scanOptions, true);
|
||||
}
|
||||
|
||||
async scanMetadata(extensionLocation: URI): Promise<Metadata | undefined> {
|
||||
@@ -252,9 +252,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
await this.fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t')));
|
||||
}
|
||||
|
||||
private async applyScanOptions(extensions: IRelaxedScannedExtension[], scanOptions: ScanOptions, pickLatest: boolean): Promise<IRelaxedScannedExtension[]> {
|
||||
private async applyScanOptions(extensions: IRelaxedScannedExtension[], type: ExtensionType | 'development', scanOptions: ScanOptions, pickLatest: boolean): Promise<IRelaxedScannedExtension[]> {
|
||||
if (!scanOptions.includeAllVersions) {
|
||||
extensions = this.dedupExtensions(extensions, await this.getTargetPlatform(), pickLatest);
|
||||
extensions = this.dedupExtensions(type === ExtensionType.System ? extensions : undefined, type === ExtensionType.User ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), pickLatest);
|
||||
}
|
||||
if (!scanOptions.includeInvalid) {
|
||||
extensions = extensions.filter(extension => extension.isValid);
|
||||
@@ -272,33 +272,61 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
});
|
||||
}
|
||||
|
||||
private dedupExtensions(extensions: IRelaxedScannedExtension[], targetPlatform: TargetPlatform, pickLatest: boolean): IRelaxedScannedExtension[] {
|
||||
const result = new Map<string, IRelaxedScannedExtension>();
|
||||
for (const extension of extensions) {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id);
|
||||
const existing = result.get(extensionKey);
|
||||
if (existing) {
|
||||
if (existing.isValid && !extension.isValid) {
|
||||
continue;
|
||||
private dedupExtensions(system: IScannedExtension[] | undefined, user: IScannedExtension[] | undefined, development: IScannedExtension[] | undefined, targetPlatform: TargetPlatform, pickLatest: boolean): IScannedExtension[] {
|
||||
const pick = (existing: IScannedExtension, extension: IScannedExtension): boolean => {
|
||||
if (existing.isValid && !extension.isValid) {
|
||||
return false;
|
||||
}
|
||||
if (existing.isValid === extension.isValid) {
|
||||
if (pickLatest && semver.gt(existing.manifest.version, extension.manifest.version)) {
|
||||
this.logService.debug(`Skipping extension ${extension.location.path} with lower version ${extension.manifest.version} in favour of ${existing.location.path} with version ${existing.manifest.version}`);
|
||||
return false;
|
||||
}
|
||||
if (existing.isValid === extension.isValid) {
|
||||
if (pickLatest && semver.gt(existing.manifest.version, extension.manifest.version)) {
|
||||
this.logService.debug(`Skipping extension ${extension.location.path} with lower version ${extension.manifest.version}.`);
|
||||
continue;
|
||||
if (semver.eq(existing.manifest.version, extension.manifest.version)) {
|
||||
if (existing.type === ExtensionType.System) {
|
||||
this.logService.debug(`Skipping extension ${extension.location.path} in favour of system extension ${existing.location.path} with same version`);
|
||||
return false;
|
||||
}
|
||||
if (semver.eq(existing.manifest.version, extension.manifest.version) && existing.targetPlatform === targetPlatform) {
|
||||
if (existing.targetPlatform === targetPlatform) {
|
||||
this.logService.debug(`Skipping extension ${extension.location.path} from different target platform ${extension.targetPlatform}`);
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (existing.type === ExtensionType.System) {
|
||||
this.logService.debug(`Overwriting system extension ${existing.location.path} with ${extension.location.path}.`);
|
||||
} else {
|
||||
this.logService.warn(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`);
|
||||
}
|
||||
}
|
||||
if (existing.type === ExtensionType.System) {
|
||||
this.logService.debug(`Overwriting system extension ${existing.location.path} with ${extension.location.path}.`);
|
||||
} else {
|
||||
this.logService.warn(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const result = new Map<string, IScannedExtension>();
|
||||
system?.forEach((extension) => {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id);
|
||||
const existing = result.get(extensionKey);
|
||||
if (!existing || pick(existing, extension)) {
|
||||
result.set(extensionKey, extension);
|
||||
}
|
||||
});
|
||||
user?.forEach((extension) => {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id);
|
||||
const existing = result.get(extensionKey);
|
||||
if (!existing && system && extension.type === ExtensionType.System) {
|
||||
this.logService.debug(`Skipping obsolete system extension ${extension.location.path}.`);
|
||||
return;
|
||||
}
|
||||
if (!existing || pick(existing, extension)) {
|
||||
result.set(extensionKey, extension);
|
||||
}
|
||||
});
|
||||
development?.forEach(extension => {
|
||||
const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id);
|
||||
const existing = result.get(extensionKey);
|
||||
if (!existing || pick(existing, extension)) {
|
||||
result.set(extensionKey, extension);
|
||||
}
|
||||
result.set(extensionKey, extension);
|
||||
}
|
||||
});
|
||||
return [...result.values()];
|
||||
}
|
||||
|
||||
|
||||
@@ -432,23 +432,33 @@ class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
private async removeOutdatedExtensions(): Promise<void> {
|
||||
const systemExtensions = await this.extensionsScannerService.scanSystemExtensions({}); // System extensions
|
||||
const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions
|
||||
const toRemove: IScannedExtension[] = [];
|
||||
|
||||
// Outdated extensions
|
||||
const targetPlatform = await this.extensionsScannerService.getTargetPlatform();
|
||||
const byExtension = groupByExtension(extensions, e => e.identifier);
|
||||
toRemove.push(...byExtension.map(p => p.sort((a, b) => {
|
||||
const vcompare = semver.rcompare(a.manifest.version, b.manifest.version);
|
||||
if (vcompare !== 0) {
|
||||
return vcompare;
|
||||
for (const extensions of byExtension) {
|
||||
if (extensions.length > 1) {
|
||||
toRemove.push(...extensions.sort((a, b) => {
|
||||
const vcompare = semver.rcompare(a.manifest.version, b.manifest.version);
|
||||
if (vcompare !== 0) {
|
||||
return vcompare;
|
||||
}
|
||||
if (a.targetPlatform === targetPlatform) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}).slice(1));
|
||||
}
|
||||
if (a.targetPlatform === targetPlatform) {
|
||||
return -1;
|
||||
if (extensions[0].type === ExtensionType.System) {
|
||||
const systemExtension = systemExtensions.find(e => areSameExtensions(e.identifier, extensions[0].identifier));
|
||||
if (!systemExtension || semver.gte(systemExtension.manifest.version, extensions[0].manifest.version)) {
|
||||
toRemove.push(extensions[0]);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}).slice(1)).flat());
|
||||
|
||||
}
|
||||
await Promises.settled(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ export function dedupExtensions(system: IExtensionDescription[], user: IExtensio
|
||||
const extension = result.get(extensionKey);
|
||||
if (extension) {
|
||||
if (extension.isBuiltin) {
|
||||
if (semver.gt(extension.version, userExtension.version)) {
|
||||
logService.warn(`Skipping extension ${userExtension.extensionLocation.path} with lower version ${userExtension.version}.`);
|
||||
if (semver.gte(extension.version, userExtension.version)) {
|
||||
logService.warn(`Skipping extension ${userExtension.extensionLocation.path} in favour of the builtin extension ${extension.extensionLocation.path}.`);
|
||||
return;
|
||||
}
|
||||
// Overwriting a builtin extension inherits the `isBuiltin` property and it doesn't show a warning
|
||||
@@ -33,6 +33,9 @@ export function dedupExtensions(system: IExtensionDescription[], user: IExtensio
|
||||
} else {
|
||||
logService.warn(localize('overwritingExtension', "Overwriting extension {0} with {1}.", extension.extensionLocation.fsPath, userExtension.extensionLocation.fsPath));
|
||||
}
|
||||
} else if (userExtension.isBuiltin) {
|
||||
logService.warn(`Skipping obsolete builtin extension ${userExtension.extensionLocation.path}`);
|
||||
return;
|
||||
}
|
||||
result.set(extensionKey, userExtension);
|
||||
});
|
||||
|
||||
@@ -43,15 +43,14 @@ export class CachedExtensionScanner {
|
||||
|
||||
public async startScanningExtensions(): Promise<void> {
|
||||
try {
|
||||
const { system, user, development } = await this._scanInstalledExtensions();
|
||||
const r = dedupExtensions(system, user, development, this._logService);
|
||||
this._scannedExtensionsResolve(r);
|
||||
const extensions = await this._scanInstalledExtensions();
|
||||
this._scannedExtensionsResolve(extensions);
|
||||
} catch (err) {
|
||||
this._scannedExtensionsReject(err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _scanInstalledExtensions(): Promise<{ system: IExtensionDescription[]; user: IExtensionDescription[]; development: IExtensionDescription[] }> {
|
||||
private async _scanInstalledExtensions(): Promise<IExtensionDescription[]> {
|
||||
try {
|
||||
const language = platform.language;
|
||||
const [scannedSystemExtensions, scannedUserExtensions] = await Promise.all([
|
||||
@@ -61,6 +60,7 @@ export class CachedExtensionScanner {
|
||||
const system = scannedSystemExtensions.map(e => toExtensionDescription(e, false));
|
||||
const user = scannedUserExtensions.map(e => toExtensionDescription(e, false));
|
||||
const development = scannedDevelopedExtensions.map(e => toExtensionDescription(e, true));
|
||||
const r = dedupExtensions(system, user, development, this._logService);
|
||||
const disposable = this._extensionsScannerService.onDidChangeCache(() => {
|
||||
disposable.dispose();
|
||||
this._notificationService.prompt(
|
||||
@@ -73,11 +73,11 @@ export class CachedExtensionScanner {
|
||||
);
|
||||
});
|
||||
timeout(5000).then(() => disposable.dispose());
|
||||
return { system, user, development };
|
||||
return r;
|
||||
} catch (err) {
|
||||
this._logService.error(`Error scanning installed extensions:`);
|
||||
this._logService.error(err);
|
||||
return { system: [], user: [], development: [] };
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user