Merge from vscode 7eaf220cafb9d9e901370ffce02229171cbf3ea6

This commit is contained in:
ADS Merger
2020-09-03 02:34:56 +00:00
committed by Anthony Dresser
parent 39d9eed585
commit a63578e6f7
519 changed files with 14338 additions and 6670 deletions

View File

@@ -12,13 +12,15 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, IExtension, isAuthenticaionProviderExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
import { StorageManager } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
const SOURCE = 'IWorkbenchExtensionEnablementService';
@@ -40,6 +42,8 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
@IConfigurationService private readonly configurationService: IConfigurationService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IProductService private readonly productService: IProductService,
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
) {
super();
this.storageManger = this._register(new StorageManager(storageService));
@@ -66,7 +70,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
canChangeEnablement(extension: IExtension): boolean {
if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
try {
this.throwErrorIfCannotChangeEnablement(extension);
} catch (error) {
return false;
}
const enablementState = this.getEnablementState(extension);
@@ -76,11 +82,47 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return true;
}
private throwErrorIfCannotChangeEnablement(extension: IExtension): void {
if (isLanguagePackExtension(extension.manifest)) {
throw new Error(localize('cannot disable language pack extension', "Cannot disable {0} extension because it contributes language packs.", extension.manifest.displayName || extension.identifier.id));
}
if (this.userDataAutoSyncService.isEnabled() && this.userDataSyncAccountService.account &&
isAuthenticaionProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {
throw new Error(localize('cannot disable auth extension', "Cannot disable {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));
}
}
canChangeWorkspaceEnablement(extension: IExtension): boolean {
if (!this.canChangeEnablement(extension)) {
return false;
}
try {
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
} catch (error) {
return false;
}
return true;
}
private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {
if (!this.hasWorkspace) {
throw new Error(localize('noWorkspace', "No workspace."));
}
if (isAuthenticaionProviderExtension(extension.manifest)) {
throw new Error(localize('cannot disable auth extension in workspace', "Cannot disable {0} extension in workspace because it contributes authentication providers", extension.manifest.displayName || extension.identifier.id));
}
}
async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {
const workspace = newState === EnablementState.DisabledWorkspace || newState === EnablementState.EnabledWorkspace;
if (workspace && !this.hasWorkspace) {
return Promise.reject(new Error(localize('noWorkspace', "No workspace.")));
for (const extension of extensions) {
if (workspace) {
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
} else {
this.throwErrorIfCannotChangeEnablement(extension);
}
}
const result = await Promise.all(extensions.map(e => this._setEnablement(e, newState)));
@@ -316,4 +358,4 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
}
registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService, true);
registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService);

View File

@@ -58,6 +58,11 @@ export interface IWorkbenchExtensionEnablementService {
*/
canChangeEnablement(extension: IExtension): boolean;
/**
* Returns `true` if the enablement can be changed.
*/
canChangeWorkspaceEnablement(extension: IExtension): boolean;
/**
* Returns `true` if the given extension identifier is enabled.
*/
@@ -127,10 +132,11 @@ export interface IExtensionRecommendationsService {
readonly _serviceBrand: undefined;
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
getFileBasedRecommendations(): IExtensionRecommendation[];
getImportantRecommendations(): Promise<IExtensionRecommendation[]>;
getConfigBasedRecommendations(): Promise<IExtensionRecommendation[]>;
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
getFileBasedRecommendations(): IExtensionRecommendation[];
getExeBasedRecommendations(exe?: string): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }>;
getConfigBasedRecommendations(): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }>;
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
getKeymapRecommendations(): IExtensionRecommendation[];
@@ -147,6 +153,7 @@ export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;
scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]>;
scanAndTranslateExtensions(type?: ExtensionType): Promise<ITranslatedScannedExtension[]>;
canAddExtension(galleryExtension: IGalleryExtension): Promise<boolean>;
addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension>;
removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;
}

View File

@@ -5,7 +5,6 @@
import { localize } from 'vs/nls';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
@@ -15,6 +14,10 @@ import { isWeb } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService';
import { IExtension } from 'vs/platform/extensions/common/extensions';
import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IProductService } from 'vs/platform/product/common/productService';
export class ExtensionManagementServerService implements IExtensionManagementServerService {
@@ -27,11 +30,14 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ILabelService labelService: ILabelService,
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
) {
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection!.getChannel<IChannel>('extensions'));
const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel<IChannel>('extensions'), galleryService, configurationService, productService);
this.remoteExtensionManagementServer = {
id: 'remote',
extensionManagementService,

View File

@@ -189,6 +189,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
return Promise.reject('No Servers');
}
async canInstall(gallery: IGalleryExtension): Promise<boolean> {
for (const server of this.servers) {
if (await server.extensionManagementService.canInstall(gallery)) {
return true;
}
}
return false;
}
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
// Only local server, install without any checks

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, IGalleryExtension, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
export class WebRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IExtensionManagementService {
constructor(
channel: IChannel,
@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IProductService protected readonly productService: IProductService
) {
super(channel);
}
async canInstall(extension: IGalleryExtension): Promise<boolean> {
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
return !!manifest && canExecuteOnWorkspace(manifest, this.productService, this.configurationService);
}
}

View File

@@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
export class WebExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -40,16 +41,25 @@ export class WebExtensionManagementService extends Disposable implements IExtens
return Promise.all(extensions.map(e => this.toLocalExtension(e)));
}
async canInstall(gallery: IGalleryExtension): Promise<boolean> {
return this.webExtensionsScannerService.canAddExtension(gallery);
}
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
if (!(await this.canInstall(gallery))) {
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", gallery.displayName || gallery.name));
error.name = INSTALL_ERROR_NOT_SUPPORTED;
throw error;
}
this.logService.info('Installing extension:', gallery.identifier.id);
this._onInstallExtension.fire({ identifier: gallery.identifier, gallery });
try {
const existingExtension = await this.getUserExtension(gallery.identifier);
const scannedExtension = await this.webExtensionsScannerService.addExtension(gallery);
const local = await this.toLocalExtension(scannedExtension);
if (existingExtension && existingExtension.manifest.version !== gallery.version) {
await this.webExtensionsScannerService.removeExtension(existingExtension.identifier, existingExtension.manifest.version);
}
const scannedExtension = await this.webExtensionsScannerService.addExtension(gallery);
const local = await this.toLocalExtension(scannedExtension);
this._onDidInstallExtension.fire({ local, identifier: gallery.identifier, operation: InstallOperation.Install, gallery });
return local;
} catch (error) {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as semver from 'semver-umd';
import { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType, IExtensionIdentifier, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -17,18 +16,19 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { asText, isSuccess, IRequestService } from 'vs/platform/request/common/request';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IGalleryExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStaticExtension } from 'vs/workbench/workbench.web.api';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { localize } from 'vs/nls';
interface IUserExtension {
identifier: IExtensionIdentifier;
version: string;
uri: URI;
location: URI;
readmeUri?: URI;
changelogUri?: URI;
packageNLSUri?: URI;
@@ -37,16 +37,12 @@ interface IUserExtension {
interface IStoredUserExtension {
identifier: IExtensionIdentifier;
version: string;
uri: UriComponents;
location: UriComponents;
readmeUri?: UriComponents;
changelogUri?: UriComponents;
packageNLSUri?: UriComponents;
}
const AssetTypeWebResource = 'Microsoft.VisualStudio.Code.WebResources';
function getExtensionLocation(assetUri: URI): URI { return joinPath(assetUri, AssetTypeWebResource, 'extension'); }
export class WebExtensionsScannerService extends Disposable implements IWebExtensionsScannerService {
declare readonly _serviceBrand: undefined;
@@ -87,29 +83,45 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
*/
private getStaticExtensions(builtin: boolean): IScannedExtension[] {
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
return (
staticExtensions
.filter(e => Boolean(e.isBuiltin) === builtin)
.map(e => ({
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: e.isBuiltin ? ExtensionType.System : ExtensionType.User,
packageJSON: e.packageJSON,
}))
);
const result: IScannedExtension[] = [];
for (const e of staticExtensions) {
if (Boolean(e.isBuiltin) === builtin) {
const scannedExtension = this.parseStaticExtension(e, builtin);
if (scannedExtension) {
result.push(scannedExtension);
}
}
}
return result;
}
private async readDefaultExtensions(): Promise<IScannedExtension[]> {
const defaultUserWebExtensions = await this.readDefaultUserWebExtensions();
const extensions = defaultUserWebExtensions.map(e => ({
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: ExtensionType.User,
packageJSON: e.packageJSON,
}));
const extensions: IScannedExtension[] = [];
for (const e of defaultUserWebExtensions) {
const scannedExtension = this.parseStaticExtension(e, false);
if (scannedExtension) {
extensions.push(scannedExtension);
}
}
return extensions.concat(this.getStaticExtensions(false));
}
private parseStaticExtension(e: IStaticExtension, builtin: boolean): IScannedExtension | null {
try {
return {
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
location: e.extensionLocation,
type: builtin ? ExtensionType.System : ExtensionType.User,
packageJSON: e.packageJSON,
};
} catch (error) {
this.logService.error(`Error while parsing extension ${e.extensionLocation.toString()}`);
this.logService.error(error);
}
return null;
}
private async readDefaultUserWebExtensions(): Promise<IStaticExtension[]> {
const result: IStaticExtension[] = [];
const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions') || [];
@@ -190,12 +202,19 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
};
}
async canAddExtension(galleryExtension: IGalleryExtension): Promise<boolean> {
return !!galleryExtension.properties.webExtension && !!galleryExtension.webResource;
}
async addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension> {
if (!galleryExtension.assetTypes.some(type => type.startsWith(AssetTypeWebResource))) {
throw new Error(`Missing ${AssetTypeWebResource} asset type`);
if (!(await this.canAddExtension(galleryExtension))) {
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", galleryExtension.displayName || galleryExtension.name));
error.name = INSTALL_ERROR_NOT_SUPPORTED;
throw error;
}
const packageNLSUri = joinPath(getExtensionLocation(galleryExtension.assetUri), 'package.nls.json');
const extensionLocation = galleryExtension.webResource!;
const packageNLSUri = joinPath(extensionLocation, 'package.nls.json');
const context = await this.requestService.request({ type: 'GET', url: packageNLSUri.toString() }, CancellationToken.None);
const packageNLSExists = isSuccess(context);
@@ -203,7 +222,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
const userExtension: IUserExtension = {
identifier: galleryExtension.identifier,
version: galleryExtension.version,
uri: galleryExtension.assetUri,
location: extensionLocation,
readmeUri: galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined,
changelogUri: galleryExtension.assets.changelog ? URI.parse(galleryExtension.assets.changelog.uri) : undefined,
packageNLSUri: packageNLSExists ? packageNLSUri : undefined
@@ -225,6 +244,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
private async scanUserExtensions(): Promise<IScannedExtension[]> {
const semver = await import('semver-umd');
let userExtensions = await this.readUserExtensions();
const byExtension: IUserExtension[][] = groupByExtension(userExtensions, e => e.identifier);
userExtensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
@@ -243,14 +263,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
private async toScannedExtension(userExtension: IUserExtension): Promise<IScannedExtension | null> {
const context = await this.requestService.request({ type: 'GET', url: joinPath(userExtension.uri, 'Microsoft.VisualStudio.Code.Manifest').toString() }, CancellationToken.None);
const context = await this.requestService.request({ type: 'GET', url: joinPath(userExtension.location, 'package.json').toString() }, CancellationToken.None);
if (isSuccess(context)) {
const content = await asText(context);
if (content) {
const packageJSON = JSON.parse(content);
return {
identifier: userExtension.identifier,
location: getExtensionLocation(userExtension.uri),
location: userExtension.location,
packageJSON,
type: ExtensionType.User,
readmeUrl: userExtension.readmeUri,
@@ -269,11 +289,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return this.userExtensionsResourceLimiter.queue(async () => {
try {
const content = await this.fileService.readFile(this.extensionsResource!);
const storedUserExtensions: IStoredUserExtension[] = JSON.parse(content.value.toString());
const storedUserExtensions: IStoredUserExtension[] = this.parseExtensions(content.value.toString());
return storedUserExtensions.map(e => ({
identifier: e.identifier,
version: e.version,
uri: URI.revive(e.uri),
location: URI.revive(e.location),
readmeUri: URI.revive(e.readmeUri),
changelogUri: URI.revive(e.changelogUri),
packageNLSUri: URI.revive(e.packageNLSUri),
@@ -291,7 +311,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
const storedUserExtensions: IStoredUserExtension[] = userExtensions.map(e => ({
identifier: e.identifier,
version: e.version,
uri: e.uri.toJSON(),
location: e.location.toJSON(),
readmeUri: e.readmeUri?.toJSON(),
changelogUri: e.changelogUri?.toJSON(),
packageNLSUri: e.packageNLSUri?.toJSON(),
@@ -302,6 +322,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
});
}
private parseExtensions(content: string): IStoredUserExtension[] {
const storedUserExtensions: (IStoredUserExtension & { uri?: UriComponents })[] = JSON.parse(content.toString());
return storedUserExtensions.map(e => {
const location = e.uri ? joinPath(URI.revive(e.uri), 'Microsoft.VisualStudio.Code.WebResources', 'extension') : e.location;
return { ...e, location };
});
}
}
registerSingleton(IWebExtensionsScannerService, WebExtensionsScannerService);

View File

@@ -5,7 +5,6 @@
import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -13,12 +12,13 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { RemoteExtensionManagementChannelClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
import { NativeRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionManagementServerService implements IExtensionManagementServerService {
@@ -32,18 +32,18 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@IConfigurationService configurationService: IConfigurationService,
@IProductService productService: IProductService,
@ILogService logService: ILogService,
@ILabelService labelService: ILabelService,
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@ILogService logService: ILogService,
) {
const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions'));
this._localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") };
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new RemoteExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer.extensionManagementService, galleryService, logService, configurationService, productService);
const extensionManagementService = new NativeRemoteExtensionManagementService(remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer, logService, galleryService, configurationService, productService);
this.remoteExtensionManagementServer = {
id: 'remote',
extensionManagementService,

View File

@@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { tmpdir } from 'os';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { prefersExecuteOnUI } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { generateUuid } from 'vs/base/common/uuid';
import { joinPath } from 'vs/base/common/resources';
import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService';
import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
export class NativeRemoteExtensionManagementService extends WebRemoteExtensionManagementService implements IExtensionManagementService {
private readonly localExtensionManagementService: IExtensionManagementService;
constructor(
channel: IChannel,
localExtensionManagementServer: IExtensionManagementServer,
@ILogService private readonly logService: ILogService,
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@IConfigurationService configurationService: IConfigurationService,
@IProductService productService: IProductService
) {
super(channel, galleryService, configurationService, productService);
this.localExtensionManagementService = localExtensionManagementServer.extensionManagementService;
}
async install(vsix: URI): Promise<ILocalExtension> {
const local = await super.install(vsix);
await this.installUIDependenciesAndPackedExtensions(local);
return local;
}
async installFromGallery(extension: IGalleryExtension): Promise<ILocalExtension> {
const local = await this.doInstallFromGallery(extension);
await this.installUIDependenciesAndPackedExtensions(local);
return local;
}
private async doInstallFromGallery(extension: IGalleryExtension): Promise<ILocalExtension> {
if (this.configurationService.getValue<boolean>('remote.downloadExtensionsLocally')) {
this.logService.trace(`Download '${extension.identifier.id}' extension locally and install`);
return this.downloadCompatibleAndInstall(extension);
}
try {
const local = await super.installFromGallery(extension);
return local;
} catch (error) {
try {
this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error));
this.logService.info(`Trying to download '${extension.identifier.id}' extension locally and install`);
const local = await this.downloadCompatibleAndInstall(extension);
this.logService.info(`Successfully installed '${extension.identifier.id}' extension`);
return local;
} catch (e) {
this.logService.error(e);
throw error;
}
}
}
private async downloadCompatibleAndInstall(extension: IGalleryExtension): Promise<ILocalExtension> {
const installed = await this.getInstalled(ExtensionType.User);
const compatible = await this.galleryService.getCompatibleExtension(extension);
if (!compatible) {
return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version)));
}
const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None);
if (manifest) {
const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(manifest, CancellationToken.None);
await Promise.all(workspaceExtensions.map(e => this.downloadAndInstall(e, installed)));
}
return this.downloadAndInstall(extension, installed);
}
private async downloadAndInstall(extension: IGalleryExtension, installed: ILocalExtension[]): Promise<ILocalExtension> {
const location = joinPath(URI.file(tmpdir()), generateUuid());
await this.galleryService.download(extension, location, installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install);
return super.install(location);
}
private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise<void> {
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None);
const installed = await this.localExtensionManagementService.getInstalled();
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
await Promise.all(toInstall.map(d => this.localExtensionManagementService.installFromGallery(d)));
}
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token);
return [...result.values()];
}
private async getAllWorkspaceDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token);
return [...result.values()];
}
private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, uiExtension: boolean, token: CancellationToken): Promise<void> {
if (toGet.length === 0) {
return Promise.resolve();
}
const extensions = (await this.galleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token)));
const extensionsManifests: IExtensionManifest[] = [];
for (let idx = 0; idx < extensions.length; idx++) {
const extension = extensions[idx];
const manifest = manifests[idx];
if (manifest && prefersExecuteOnUI(manifest, this.productService, this.configurationService) === uiExtension) {
result.set(extension.identifier.id.toLowerCase(), extension);
extensionsManifests.push(manifest);
}
}
toGet = [];
for (const extensionManifest of extensionsManifests) {
if (isNonEmptyArray(extensionManifest.extensionDependencies)) {
for (const id of extensionManifest.extensionDependencies) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
if (isNonEmptyArray(extensionManifest.extensionPack)) {
for (const id of extensionManifest.extensionPack) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
}
return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token);
}
}

View File

@@ -23,6 +23,8 @@ import { assign } from 'vs/base/common/objects';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { productService } from 'vs/workbench/test/browser/workbenchTestServices';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
function createStorageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@@ -51,7 +53,9 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
extensionManagementService,
instantiationService.get(IConfigurationService),
extensionManagementServerService,
productService
productService,
instantiationService.get(IUserDataAutoSyncService) || instantiationService.stub(IUserDataAutoSyncService, <Partial<IUserDataAutoSyncService>>{ isEnabled() { return false; } }),
instantiationService.get(IUserDataSyncAccountService) || instantiationService.stub(IUserDataSyncAccountService, UserDataSyncAccountService)
);
}
@@ -371,6 +375,48 @@ suite('ExtensionEnablementService Test', () => {
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false);
});
test('test canChangeEnablement return true for auth extension', () => {
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
});
test('test canChangeEnablement return true for auth extension when user data sync account does not depends on it', () => {
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
account: { authenticationProviderId: 'b' }
});
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
});
test('test canChangeEnablement return true for auth extension when user data sync account depends on it but auto sync is off', () => {
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
account: { authenticationProviderId: 'a' }
});
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
});
test('test canChangeEnablement return false for auth extension and user data sync account depends on it and auto sync is on', () => {
instantiationService.stub(IUserDataAutoSyncService, <Partial<IUserDataAutoSyncService>>{ isEnabled() { return true; } });
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
account: { authenticationProviderId: 'a' }
});
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false);
});
test('test canChangeWorkspaceEnablement return true', () => {
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), true);
});
test('test canChangeWorkspaceEnablement return false if there is no workspace', () => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeWorkspaceEnablement return false for auth extension', () => {
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false);
});
test('test canChangeEnablement return false when extensions are disabled in environment', () => {
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);