Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService.ts
3296 views
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 { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
7
import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, ExtensionManagementError, ExtensionManagementErrorCode, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, IAllowedExtensionsService, VerifyExtensionSignatureConfigKey } from '../../../../platform/extensionManagement/common/extensionManagement.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import { ExtensionType, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';
10
import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
11
import { ILogService } from '../../../../platform/log/common/log.js';
12
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
13
import { isNonEmptyArray } from '../../../../base/common/arrays.js';
14
import { CancellationToken } from '../../../../base/common/cancellation.js';
15
import { localize } from '../../../../nls.js';
16
import { IProductService } from '../../../../platform/product/common/productService.js';
17
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
18
import { IExtensionManagementServer } from '../common/extensionManagement.js';
19
import { Promises } from '../../../../base/common/async.js';
20
import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';
21
import { IFileService } from '../../../../platform/files/common/files.js';
22
import { RemoteExtensionManagementService } from '../common/remoteExtensionManagementService.js';
23
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
24
import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';
25
import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/remoteUserDataProfiles.js';
26
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
27
import { areApiProposalsCompatible } from '../../../../platform/extensions/common/extensionValidator.js';
28
import { isBoolean, isUndefined } from '../../../../base/common/types.js';
29
30
export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService {
31
32
constructor(
33
channel: IChannel,
34
private readonly localExtensionManagementServer: IExtensionManagementServer,
35
@IProductService productService: IProductService,
36
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
37
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
38
@IRemoteUserDataProfilesService remoteUserDataProfilesService: IRemoteUserDataProfilesService,
39
@IUriIdentityService uriIdentityService: IUriIdentityService,
40
@ILogService private readonly logService: ILogService,
41
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
42
@IConfigurationService private readonly configurationService: IConfigurationService,
43
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
44
@IFileService private readonly fileService: IFileService,
45
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
46
) {
47
super(channel, productService, allowedExtensionsService, userDataProfileService, userDataProfilesService, remoteUserDataProfilesService, uriIdentityService);
48
}
49
50
override async install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension> {
51
const local = await super.install(vsix, options);
52
await this.installUIDependenciesAndPackedExtensions(local);
53
return local;
54
}
55
56
override async installFromGallery(extension: IGalleryExtension, installOptions: InstallOptions = {}): Promise<ILocalExtension> {
57
if (isUndefined(installOptions.donotVerifySignature)) {
58
const value = this.configurationService.getValue(VerifyExtensionSignatureConfigKey);
59
installOptions.donotVerifySignature = isBoolean(value) ? !value : undefined;
60
}
61
const local = await this.doInstallFromGallery(extension, installOptions);
62
await this.installUIDependenciesAndPackedExtensions(local);
63
return local;
64
}
65
66
private async doInstallFromGallery(extension: IGalleryExtension, installOptions: InstallOptions): Promise<ILocalExtension> {
67
if (installOptions.downloadExtensionsLocally || this.configurationService.getValue('remote.downloadExtensionsLocally')) {
68
return this.downloadAndInstall(extension, installOptions);
69
}
70
try {
71
const clientTargetPlatform = await this.localExtensionManagementServer.extensionManagementService.getTargetPlatform();
72
return await super.installFromGallery(extension, { ...installOptions, context: { ...installOptions?.context, [EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]: clientTargetPlatform } });
73
} catch (error) {
74
switch (error.name) {
75
case ExtensionManagementErrorCode.Download:
76
case ExtensionManagementErrorCode.DownloadSignature:
77
case ExtensionManagementErrorCode.Gallery:
78
case ExtensionManagementErrorCode.Internal:
79
case ExtensionManagementErrorCode.Unknown:
80
try {
81
this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error));
82
return await this.downloadAndInstall(extension, installOptions);
83
} catch (e) {
84
this.logService.error(e);
85
throw e;
86
}
87
default:
88
this.logService.debug('Remote Install Error Name', error.name);
89
throw error;
90
}
91
}
92
}
93
94
private async downloadAndInstall(extension: IGalleryExtension, installOptions: InstallOptions): Promise<ILocalExtension> {
95
this.logService.info(`Downloading the '${extension.identifier.id}' extension locally and install`);
96
const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);
97
installOptions = { ...installOptions, donotIncludePackAndDependencies: true };
98
const installed = await this.getInstalled(ExtensionType.User, undefined, installOptions.productVersion);
99
const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(compatible, CancellationToken.None);
100
if (workspaceExtensions.length) {
101
this.logService.info(`Downloading the workspace dependencies and packed extensions of '${compatible.identifier.id}' locally and install`);
102
for (const workspaceExtension of workspaceExtensions) {
103
await this.downloadCompatibleAndInstall(workspaceExtension, installed, installOptions);
104
}
105
}
106
return await this.downloadCompatibleAndInstall(compatible, installed, installOptions);
107
}
108
109
private async downloadCompatibleAndInstall(extension: IGalleryExtension, installed: ILocalExtension[], installOptions: InstallOptions): Promise<ILocalExtension> {
110
const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);
111
this.logService.trace('Downloading extension:', compatible.identifier.id);
112
const location = await this.localExtensionManagementServer.extensionManagementService.download(compatible, installed.filter(i => areSameExtensions(i.identifier, compatible.identifier))[0] ? InstallOperation.Update : InstallOperation.Install, !!installOptions.donotVerifySignature);
113
this.logService.info('Downloaded extension:', compatible.identifier.id, location.path);
114
try {
115
const local = await super.install(location, { ...installOptions, keepExisting: true });
116
this.logService.info(`Successfully installed '${compatible.identifier.id}' extension`);
117
return local;
118
} finally {
119
try {
120
await this.fileService.del(location);
121
} catch (error) {
122
this.logService.error(error);
123
}
124
}
125
}
126
127
private async checkAndGetCompatible(extension: IGalleryExtension, includePreRelease: boolean): Promise<IGalleryExtension> {
128
const targetPlatform = await this.getTargetPlatform();
129
let compatibleExtension: IGalleryExtension | null = null;
130
131
if (extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {
132
compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true }, CancellationToken.None))[0] || null;
133
}
134
135
if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
136
compatibleExtension = extension;
137
}
138
139
if (!compatibleExtension) {
140
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform);
141
}
142
143
if (!compatibleExtension) {
144
const incompatibleApiProposalsMessages: string[] = [];
145
if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [], incompatibleApiProposalsMessages)) {
146
throw new ExtensionManagementError(localize('incompatibleAPI', "Can't install '{0}' extension. {1}", extension.displayName ?? extension.identifier.id, incompatibleApiProposalsMessages[0]), ExtensionManagementErrorCode.IncompatibleApi);
147
}
148
/** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */
149
if (!includePreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) {
150
throw new ExtensionManagementError(localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound);
151
}
152
throw new ExtensionManagementError(localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
153
}
154
155
return compatibleExtension;
156
}
157
158
private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise<void> {
159
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None);
160
const installed = await this.localExtensionManagementServer.extensionManagementService.getInstalled();
161
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
162
if (toInstall.length) {
163
this.logService.info(`Installing UI dependencies and packed extensions of '${local.identifier.id}' locally`);
164
await Promises.settled(toInstall.map(d => this.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));
165
}
166
}
167
168
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
169
const result = new Map<string, IGalleryExtension>();
170
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
171
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token);
172
return [...result.values()];
173
}
174
175
private async getAllWorkspaceDependenciesAndPackedExtensions(extension: IGalleryExtension, token: CancellationToken): Promise<IGalleryExtension[]> {
176
const result = new Map<string, IGalleryExtension>();
177
result.set(extension.identifier.id.toLowerCase(), extension);
178
const manifest = await this.galleryService.getManifest(extension, token);
179
if (manifest) {
180
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
181
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token);
182
}
183
result.delete(extension.identifier.id);
184
return [...result.values()];
185
}
186
187
private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, uiExtension: boolean, token: CancellationToken): Promise<void> {
188
if (toGet.length === 0) {
189
return Promise.resolve();
190
}
191
192
const extensions = await this.galleryService.getExtensions(toGet.map(id => ({ id })), token);
193
const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token)));
194
const extensionsManifests: IExtensionManifest[] = [];
195
for (let idx = 0; idx < extensions.length; idx++) {
196
const extension = extensions[idx];
197
const manifest = manifests[idx];
198
if (manifest && this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest) === uiExtension) {
199
result.set(extension.identifier.id.toLowerCase(), extension);
200
extensionsManifests.push(manifest);
201
}
202
}
203
toGet = [];
204
for (const extensionManifest of extensionsManifests) {
205
if (isNonEmptyArray(extensionManifest.extensionDependencies)) {
206
for (const id of extensionManifest.extensionDependencies) {
207
if (!result.has(id.toLowerCase())) {
208
toGet.push(id);
209
}
210
}
211
}
212
if (isNonEmptyArray(extensionManifest.extensionPack)) {
213
for (const id of extensionManifest.extensionPack) {
214
if (!result.has(id.toLowerCase())) {
215
toGet.push(id);
216
}
217
}
218
}
219
}
220
return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token);
221
}
222
}
223
224