support enabling extensions through environment

This commit is contained in:
Sandeep Somavarapu 2021-07-19 13:04:59 +02:00
parent 69a882951c
commit f7a979540d
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
8 changed files with 145 additions and 23 deletions

View file

@ -62,6 +62,7 @@ export interface IEnvironmentService {
debugExtensionHost: IExtensionHostDebugParams;
isExtensionDevelopment: boolean;
disableExtensions: boolean | string[];
enableExtensions?: readonly string[];
extensionDevelopmentLocationURI?: URI[];
extensionDevelopmentKind?: ExtensionKind[];
extensionTestsLocationURI?: URI;

View file

@ -1838,7 +1838,8 @@ export class ExtensionStatusLabelAction extends Action implements IExtensionCont
constructor(
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService
) {
super('extensions.action.statusLabel', '', ExtensionStatusLabelAction.DISABLED_CLASS, false);
}
@ -1896,8 +1897,8 @@ export class ExtensionStatusLabelAction extends Action implements IExtensionCont
}
if (currentEnablementState !== null) {
const currentlyEnabled = currentEnablementState === EnablementState.EnabledGlobally || currentEnablementState === EnablementState.EnabledWorkspace;
const enabled = this.enablementState === EnablementState.EnabledGlobally || this.enablementState === EnablementState.EnabledWorkspace;
const currentlyEnabled = this.extensionEnablementService.isEnabledEnablementState(currentEnablementState);
const enabled = this.extensionEnablementService.isEnabledEnablementState(this.enablementState);
if (!currentlyEnabled && enabled) {
return canAddExtension() ? localize('enabled', "Enabled") : null;
}
@ -2108,6 +2109,18 @@ export class ExtensionStatusIconAction extends ExtensionAction {
return;
}
// Extension is disabled by environment
if (this.extension.enablementState === EnablementState.DisabledByEnvironment) {
this.updateStatusMessage(localize('disabled by environment', "This extension is disabled by the environment."));
return;
}
// Extension is enabled by environment
if (this.extension.enablementState === EnablementState.EnabledByEnvironment) {
this.updateStatusMessage(localize('enabled by environment', "This extension is enabled because it is required in the current environment."));
return;
}
// Extension is disabled by virtual workspace
if (this.extension.enablementState === EnablementState.DisabledByVirtualWorkspace) {
const details = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.virtualWorkspaces);

View file

@ -1230,7 +1230,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
if (i === extension) {
return false;
}
if (!(i.enablementState === EnablementState.EnabledWorkspace || i.enablementState === EnablementState.EnabledGlobally)) {
if (!this.extensionEnablementService.isEnabledEnablementState(i.enablementState)) {
return false;
}
if (extensionsToDisable.indexOf(i) !== -1) {

View file

@ -226,6 +226,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
get disableExtensions() { return this.payload?.get('disableExtensions') === 'true'; }
get enableExtensions() { return this.options.enableExtensions; }
@memoize
get webviewExternalEndpoint(): string {
const endpoint = this.options.webviewEndpoint

View file

@ -114,36 +114,26 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
canChangeEnablement(extension: IExtension): boolean {
try {
this.throwErrorIfCannotChangeEnablement(extension);
return true;
} catch (error) {
return false;
}
switch (this.getEnablementState(extension)) {
case EnablementState.DisabledByEnvironment:
case EnablementState.DisabledByVirtualWorkspace:
case EnablementState.DisabledByExtensionKind:
return false;
case EnablementState.DisabledByExtensionDependency:
// Can be changed only when all its dependencies enablements can be changed
return getExtensionDependencies(this.extensionsManager.extensions, extension).every(e => this.canChangeEnablement(e));
}
return true;
}
canChangeWorkspaceEnablement(extension: IExtension): boolean {
if (!this.canChangeEnablement(extension)) {
return false;
}
try {
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
return true;
} catch (error) {
return false;
}
return true;
}
private throwErrorIfCannotChangeEnablement(extension: IExtension): void {
private throwErrorIfCannotChangeEnablement(extension: IExtension, donotCheckDependencies?: boolean): void {
if (isLanguagePackExtension(extension.manifest)) {
throw new Error(localize('cannot disable language pack extension', "Cannot change enablement of {0} extension because it contributes language packs.", extension.manifest.displayName || extension.identifier.id));
}
@ -152,6 +142,32 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
isAuthenticaionProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {
throw new Error(localize('cannot disable auth extension', "Cannot change enablement {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));
}
if (this._isEnabledInEnv(extension)) {
throw new Error(localize('cannot change enablement environment', "Cannot change enablement of {0} extension because it is enabled in environment", extension.manifest.displayName || extension.identifier.id));
}
switch (this.getEnablementState(extension)) {
case EnablementState.DisabledByEnvironment:
throw new Error(localize('cannot change disablement environment', "Cannot change enablement of {0} extension because it is disabled in environment", extension.manifest.displayName || extension.identifier.id));
case EnablementState.DisabledByVirtualWorkspace:
throw new Error(localize('cannot change enablement virtual workspace', "Cannot change enablement of {0} extension because it does not support virtual workspaces", extension.manifest.displayName || extension.identifier.id));
case EnablementState.DisabledByExtensionDependency:
if (donotCheckDependencies) {
break;
}
// Can be changed only when all its dependencies enablements can be changed
for (const dependency of getExtensionDependencies(this.extensionsManager.extensions, extension)) {
if (this.isEnabled(dependency)) {
continue;
}
try {
this.throwErrorIfCannotChangeEnablement(dependency, true);
} catch (error) {
throw new Error(localize('cannot change enablement dependency', "Cannot enable '{0}' extension because it depends on '{1}' extension that cannot be enabled", extension.manifest.displayName || extension.identifier.id, dependency.manifest.displayName || dependency.identifier.id));
}
}
}
}
private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {
@ -227,7 +243,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
isEnabledEnablementState(enablementState: EnablementState): boolean {
return enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
return enablementState === EnablementState.EnabledByEnvironment || enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
}
isDisabledGlobally(extension: IExtension): boolean {
@ -267,6 +283,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
enablementState = EnablementState.DisabledByExtensionDependency;
}
else if (!this.isEnabledEnablementState(enablementState) && this._isEnabledInEnv(extension)) {
enablementState = EnablementState.EnabledByEnvironment;
}
computedEnablementStates.set(extension, enablementState);
return enablementState;
}
@ -289,6 +309,14 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return false;
}
private _isEnabledInEnv(extension: IExtension): boolean {
const enabledExtensions = this.environmentService.enableExtensions;
if (Array.isArray(enabledExtensions)) {
return enabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
}
return false;
}
private _isDisabledByVirtualWorkspace(extension: IExtension, workspaceType: WorkspaceType): boolean {
// Not a virtual workspace
if (!workspaceType.virtual) {

View file

@ -48,6 +48,7 @@ export const enum EnablementState {
DisabledByTrustRequirement,
DisabledByExtensionKind,
DisabledByEnvironment,
EnabledByEnvironment,
DisabledByVirtualWorkspace,
DisabledByExtensionDependency,
DisabledGlobally,

View file

@ -497,6 +497,78 @@ suite('ExtensionEnablementService Test', () => {
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment);
});
test('test extension is enabled globally when enabled in environment', async () => {
const extension = aLocalExtension('pub.a');
installed.push(extension);
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(extension));
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally);
});
test('test extension is enabled workspace when enabled in environment', async () => {
const extension = aLocalExtension('pub.a');
installed.push(extension);
testObject.setEnablement([extension], EnablementState.EnabledWorkspace);
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(extension));
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace);
});
test('test extension is enabled by environment when disabled globally', async () => {
const extension = aLocalExtension('pub.a');
installed.push(extension);
testObject.setEnablement([extension], EnablementState.DisabledGlobally);
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(extension));
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledByEnvironment);
});
test('test extension is enabled by environment when disabled workspace', async () => {
const extension = aLocalExtension('pub.a');
installed.push(extension);
testObject.setEnablement([extension], EnablementState.DisabledWorkspace);
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(extension));
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledByEnvironment);
});
test('test extension is disabled by environment when also enabled in environment', async () => {
const extension = aLocalExtension('pub.a');
installed.push(extension);
testObject.setEnablement([extension], EnablementState.DisabledWorkspace);
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true, enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(!testObject.isEnabled(extension));
assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment);
});
test('test canChangeEnablement return false when the extension is enabled in environment', () => {
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => {
instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: <readonly string[]>['pub.a'] } as IWorkbenchEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a', undefined, ExtensionType.System);
assert.ok(!testObject.canChangeEnablement(extension));
});
test('test extension does not support vitrual workspace is not enabled in virtual workspace', async () => {
const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } });
instantiationService.stub(IWorkspaceContextService, 'getWorkspace', <IWorkspace>{ folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] });
@ -613,11 +685,11 @@ suite('ExtensionEnablementService Test', () => {
assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
test('test canChangeEnablement return false when the local workspace extension is disabled by kind', () => {
test('test canChangeEnablement return true when the local workspace extension is disabled by kind', () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false);
assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true);
});
test('test canChangeEnablement return true for local ui extension', () => {
@ -659,11 +731,11 @@ suite('ExtensionEnablementService Test', () => {
assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
test('test canChangeEnablement return false when the remote ui extension is disabled by kind', () => {
test('test canChangeEnablement return true when the remote ui extension is disabled by kind', () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false);
assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true);
});
test('test canChangeEnablement return true for remote workspace extension', () => {

View file

@ -343,6 +343,11 @@ interface IWorkbenchConstructionOptions {
*/
readonly additionalBuiltinExtensions?: readonly (ExtensionId | UriComponents)[];
/**
* List of extensions to be enabled.
*/
readonly enableExtensions?: readonly ExtensionId[];
/**
* [TEMPORARY]: This will be removed soon.
* Enable inlined extensions.