-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathremoteExtensionsScanner.ts
More file actions
359 lines (313 loc) · 15.8 KB
/
Copy pathremoteExtensionsScanner.ts
File metadata and controls
359 lines (313 loc) · 15.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isAbsolute, join, resolve } from '../../base/common/path.js';
import * as platform from '../../base/common/platform.js';
import { cwd } from '../../base/common/process.js';
import { URI } from '../../base/common/uri.js';
import * as performance from '../../base/common/performance.js';
import { Event } from '../../base/common/event.js';
import { IURITransformer, transformOutgoingURIs } from '../../base/common/uriIpc.js';
import { IServerChannel } from '../../base/parts/ipc/common/ipc.js';
import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, IContextKeyExprMapper } from '../../platform/contextkey/common/contextkey.js';
import { IExtensionGalleryService, IExtensionManagementService, InstallExtensionSummary, InstallOptions } from '../../platform/extensionManagement/common/extensionManagement.js';
import { ExtensionManagementCLI } from '../../platform/extensionManagement/common/extensionManagementCLI.js';
import { IExtensionsScannerService, toExtensionDescription } from '../../platform/extensionManagement/common/extensionsScannerService.js';
import { ExtensionType, IExtensionDescription } from '../../platform/extensions/common/extensions.js';
import { ILogService } from '../../platform/log/common/log.js';
import { IUserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfile.js';
import { IServerEnvironmentService } from './serverEnvironmentService.js';
import { dedupExtensions } from '../../workbench/services/extensions/common/extensionsUtil.js';
import { Schemas } from '../../base/common/network.js';
import { IRemoteExtensionsScannerService } from '../../platform/remote/common/remoteExtensionsScanner.js';
import { ILanguagePackService } from '../../platform/languagePacks/common/languagePacks.js';
import { areSameExtensions } from '../../platform/extensionManagement/common/extensionManagementUtil.js';
export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService {
readonly _serviceBrand: undefined;
private readonly _whenBuiltinExtensionsReady = Promise.resolve<InstallExtensionSummary>({ failed: [] });
private readonly _whenExtensionsReady = Promise.resolve<InstallExtensionSummary>({ failed: [] });
constructor(
private readonly _extensionManagementCLI: ExtensionManagementCLI,
environmentService: IServerEnvironmentService,
private readonly _userDataProfilesService: IUserDataProfilesService,
private readonly _extensionsScannerService: IExtensionsScannerService,
private readonly _logService: ILogService,
private readonly _extensionGalleryService: IExtensionGalleryService,
private readonly _languagePackService: ILanguagePackService,
private readonly _extensionManagementService: IExtensionManagementService,
) {
const builtinExtensionsToInstall = environmentService.args['install-builtin-extension'];
if (builtinExtensionsToInstall) {
_logService.trace('Installing builtin extensions passed via args...');
const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] };
performance.mark('code/server/willInstallBuiltinExtensions');
this._whenExtensionsReady = this._whenBuiltinExtensionsReady = _extensionManagementCLI.installExtensions([], this._asExtensionIdOrVSIX(builtinExtensionsToInstall), installOptions, !!environmentService.args['force'])
.then(() => {
performance.mark('code/server/didInstallBuiltinExtensions');
_logService.trace('Finished installing builtin extensions');
return { failed: [] };
}, error => {
_logService.error(error);
return { failed: [] };
});
}
const extensionsToInstall = environmentService.args['install-extension'];
if (extensionsToInstall) {
_logService.trace('Installing extensions passed via args...');
const installOptions: InstallOptions = {
isMachineScoped: !!environmentService.args['do-not-sync'],
installPreReleaseVersion: !!environmentService.args['pre-release'],
isApplicationScoped: true // extensions installed during server startup are available to all profiles
};
this._whenExtensionsReady = this._whenBuiltinExtensionsReady
.then(() => _extensionManagementCLI.installExtensions(this._asExtensionIdOrVSIX(extensionsToInstall), [], installOptions, !!environmentService.args['force']))
.then(async () => {
_logService.trace('Finished installing extensions');
return { failed: [] };
}, async error => {
_logService.error(error);
const failed: {
id: string;
installOptions: InstallOptions;
}[] = [];
const alreadyInstalled = await this._extensionManagementService.getInstalled(ExtensionType.User);
for (const id of this._asExtensionIdOrVSIX(extensionsToInstall)) {
if (typeof id === 'string') {
if (!alreadyInstalled.some(e => areSameExtensions(e.identifier, { id }))) {
failed.push({ id, installOptions });
}
}
}
if (!failed.length) {
_logService.trace(`No extensions to report as failed`);
return { failed: [] };
}
_logService.info(`Relaying the following extensions to install later: ${failed.map(f => f.id).join(', ')}`);
return { failed };
});
}
}
private _asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input);
}
whenExtensionsReady(): Promise<InstallExtensionSummary> {
return this._whenExtensionsReady;
}
async scanExtensions(
language?: string,
profileLocation?: URI,
workspaceExtensionLocations?: URI[],
extensionDevelopmentLocations?: URI[],
languagePackId?: string
): Promise<IExtensionDescription[]> {
performance.mark('code/server/willScanExtensions');
this._logService.trace(`Scanning extensions using UI language: ${language}`);
await this._whenBuiltinExtensionsReady;
const extensionDevelopmentPaths = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined;
profileLocation = profileLocation ?? this._userDataProfilesService.defaultProfile.extensionsResource;
const extensions = await this._scanExtensions(profileLocation, language ?? platform.language, workspaceExtensionLocations, extensionDevelopmentPaths, languagePackId);
this._logService.trace('Scanned Extensions', extensions);
this._massageWhenConditions(extensions);
performance.mark('code/server/didScanExtensions');
return extensions;
}
private async _scanExtensions(profileLocation: URI, language: string, workspaceInstalledExtensionLocations: URI[] | undefined, extensionDevelopmentPath: string[] | undefined, languagePackId: string | undefined): Promise<IExtensionDescription[]> {
await this._ensureLanguagePackIsInstalled(language, languagePackId);
const [builtinExtensions, installedExtensions, workspaceInstalledExtensions, developedExtensions] = await Promise.all([
this._scanBuiltinExtensions(language),
this._scanInstalledExtensions(profileLocation, language),
this._scanWorkspaceInstalledExtensions(language, workspaceInstalledExtensionLocations),
this._scanDevelopedExtensions(language, extensionDevelopmentPath)
]);
return dedupExtensions(builtinExtensions, installedExtensions, workspaceInstalledExtensions, developedExtensions, this._logService);
}
private async _scanDevelopedExtensions(language: string, extensionDevelopmentPaths?: string[]): Promise<IExtensionDescription[]> {
if (extensionDevelopmentPaths) {
return (await Promise.all(extensionDevelopmentPaths.map(extensionDevelopmentPath => this._extensionsScannerService.scanOneOrMultipleExtensions(URI.file(resolve(extensionDevelopmentPath)), ExtensionType.User, { language }))))
.flat()
.map(e => toExtensionDescription(e, true));
}
return [];
}
private async _scanWorkspaceInstalledExtensions(language: string, workspaceInstalledExtensions?: URI[]): Promise<IExtensionDescription[]> {
const result: IExtensionDescription[] = [];
if (workspaceInstalledExtensions?.length) {
const scannedExtensions = await Promise.all(workspaceInstalledExtensions.map(location => this._extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { language })));
for (const scannedExtension of scannedExtensions) {
if (scannedExtension) {
result.push(toExtensionDescription(scannedExtension, false));
}
}
}
return result;
}
private async _scanBuiltinExtensions(language: string): Promise<IExtensionDescription[]> {
const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language });
return scannedExtensions.map(e => toExtensionDescription(e, false));
}
private async _scanInstalledExtensions(profileLocation: URI, language: string): Promise<IExtensionDescription[]> {
const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ profileLocation, language, useCache: true });
return scannedExtensions.map(e => toExtensionDescription(e, false));
}
private async _ensureLanguagePackIsInstalled(language: string, languagePackId: string | undefined): Promise<void> {
if (
// No need to install language packs for the default language
language === platform.LANGUAGE_DEFAULT ||
// The extension gallery service needs to be available
!this._extensionGalleryService.isEnabled()
) {
return;
}
try {
const installed = await this._languagePackService.getInstalledLanguages();
if (installed.find(p => p.id === language)) {
this._logService.trace(`Language Pack ${language} is already installed. Skipping language pack installation.`);
return;
}
} catch (err) {
// We tried to see what is installed but failed. We can try installing anyway.
this._logService.error(err);
}
if (!languagePackId) {
this._logService.trace(`No language pack id provided for language ${language}. Skipping language pack installation.`);
return;
}
this._logService.trace(`Language Pack ${languagePackId} for language ${language} is not installed. It will be installed now.`);
try {
await this._extensionManagementCLI.installExtensions([languagePackId], [], { isMachineScoped: true }, true);
} catch (err) {
// We tried to install the language pack but failed. We can continue without it thus using the default language.
this._logService.error(err);
}
}
private _massageWhenConditions(extensions: IExtensionDescription[]): void {
// Massage "when" conditions which mention `resourceScheme`
interface WhenUser { when?: string }
interface LocWhenUser { [loc: string]: WhenUser[] }
const _mapResourceSchemeValue = (value: string, isRegex: boolean): string => {
// console.log(`_mapResourceSchemeValue: ${value}, ${isRegex}`);
return value.replace(/file/g, 'vscode-remote');
};
const _mapResourceRegExpValue = (value: RegExp): RegExp => {
let flags = '';
flags += value.global ? 'g' : '';
flags += value.ignoreCase ? 'i' : '';
flags += value.multiline ? 'm' : '';
return new RegExp(_mapResourceSchemeValue(value.source, true), flags);
};
const _exprKeyMapper = new class implements IContextKeyExprMapper {
mapDefined(key: string): ContextKeyExpression {
return ContextKeyDefinedExpr.create(key);
}
mapNot(key: string): ContextKeyExpression {
return ContextKeyNotExpr.create(key);
}
mapEquals(key: string, value: any): ContextKeyExpression {
if (key === 'resourceScheme' && typeof value === 'string') {
return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false));
} else {
return ContextKeyEqualsExpr.create(key, value);
}
}
mapNotEquals(key: string, value: any): ContextKeyExpression {
if (key === 'resourceScheme' && typeof value === 'string') {
return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false));
} else {
return ContextKeyNotEqualsExpr.create(key, value);
}
}
mapGreater(key: string, value: any): ContextKeyExpression {
return ContextKeyGreaterExpr.create(key, value);
}
mapGreaterEquals(key: string, value: any): ContextKeyExpression {
return ContextKeyGreaterEqualsExpr.create(key, value);
}
mapSmaller(key: string, value: any): ContextKeyExpression {
return ContextKeySmallerExpr.create(key, value);
}
mapSmallerEquals(key: string, value: any): ContextKeyExpression {
return ContextKeySmallerEqualsExpr.create(key, value);
}
mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr {
if (key === 'resourceScheme' && regexp) {
return ContextKeyRegexExpr.create(key, _mapResourceRegExpValue(regexp));
} else {
return ContextKeyRegexExpr.create(key, regexp);
}
}
mapIn(key: string, valueKey: string): ContextKeyInExpr {
return ContextKeyInExpr.create(key, valueKey);
}
mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr {
return ContextKeyNotInExpr.create(key, valueKey);
}
};
const _massageWhenUser = (element: WhenUser) => {
if (!element || !element.when || !/resourceScheme/.test(element.when)) {
return;
}
const expr = ContextKeyExpr.deserialize(element.when);
if (!expr) {
return;
}
const massaged = expr.map(_exprKeyMapper);
element.when = massaged.serialize();
};
const _massageWhenUserArr = (elements: WhenUser[] | WhenUser) => {
if (Array.isArray(elements)) {
for (const element of elements) {
_massageWhenUser(element);
}
} else {
_massageWhenUser(elements);
}
};
const _massageLocWhenUser = (target: LocWhenUser) => {
for (const loc in target) {
_massageWhenUserArr(target[loc]);
}
};
extensions.forEach((extension) => {
if (extension.contributes) {
if (extension.contributes.menus) {
_massageLocWhenUser(<LocWhenUser>extension.contributes.menus);
}
if (extension.contributes.keybindings) {
_massageWhenUserArr(<WhenUser | WhenUser[]>extension.contributes.keybindings);
}
if (extension.contributes.views) {
_massageLocWhenUser(<LocWhenUser>extension.contributes.views);
}
}
});
}
}
export class RemoteExtensionsScannerChannel implements IServerChannel {
constructor(private service: RemoteExtensionsScannerService, private getUriTransformer: (requestContext: any) => IURITransformer) { }
listen(context: any, event: string): Event<any> {
throw new Error('Invalid listen');
}
async call(context: any, command: string, args?: any): Promise<any> {
const uriTransformer = this.getUriTransformer(context);
switch (command) {
case 'whenExtensionsReady': return await this.service.whenExtensionsReady();
case 'scanExtensions': {
const language = args[0];
const profileLocation = args[1] ? URI.revive(uriTransformer.transformIncoming(args[1])) : undefined;
const workspaceExtensionLocations = Array.isArray(args[2]) ? args[2].map(u => URI.revive(uriTransformer.transformIncoming(u))) : undefined;
const extensionDevelopmentPath = Array.isArray(args[3]) ? args[3].map(u => URI.revive(uriTransformer.transformIncoming(u))) : undefined;
const languagePackId: string | undefined = args[4];
const extensions = await this.service.scanExtensions(
language,
profileLocation,
workspaceExtensionLocations,
extensionDevelopmentPath,
languagePackId
);
return extensions.map(extension => transformOutgoingURIs(extension, uriTransformer));
}
}
throw new Error('Invalid call');
}
}