Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)

* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973

* disable strict null check
This commit is contained in:
Anthony Dresser
2019-07-15 22:35:46 -07:00
committed by GitHub
parent f720ec642f
commit 0b7e7ddbf9
2406 changed files with 59140 additions and 35464 deletions

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IProductService } from 'vs/platform/product/common/product';
import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@INotificationService notificationService: INotificationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionEnablementService extensionEnablementService: IExtensionEnablementService,
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
) {
super(
instantiationService,
notificationService,
environmentService,
telemetryService,
extensionEnablementService,
fileService,
productService,
);
this._remoteExtensionsEnvironmentData = null;
this._initialize();
}
private _createProvider(remoteAuthority: string): IInitDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: () => {
return this.whenInstalledExtensionsRegistered().then(() => {
return this._remoteExtensionsEnvironmentData!;
});
}
};
}
protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] {
const result: ExtensionHostProcessManager[] = [];
const remoteAgentConnection = this._remoteAgentService.getConnection()!;
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), browserWebSocketFactory);
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
result.push(remoteExtHostProcessManager);
return result;
}
protected async _scanAndHandleExtensions(): Promise<void> {
// fetch the remote environment
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
// enable or disable proposed API per extension
this._checkEnableProposedApi(remoteEnv.extensions);
// remove disabled extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension));
// save for remote extension's init data
this._remoteExtensionsEnvironmentData = remoteEnv;
// this._handleExtensionPoints((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
const result = this._registry.deltaExtensions(remoteEnv.extensions, []);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
}
public _onExtensionHostExit(code: number): void {
console.log(`vscode:exit`, code);
// ipc.send('vscode:exit', code);
}
}
registerSingleton(IExtensionService, ExtensionService);

View File

@@ -0,0 +1,498 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IProductService } from 'vs/platform/product/common/product';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: any;
protected readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
protected readonly _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
protected readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
protected readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
protected readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
protected readonly _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
protected readonly _isDev: boolean;
private readonly _extensionsMessages: Map<string, IMessage[]>;
protected readonly _allRequestedActivateEvents = new Set<string>();
private readonly _proposedApiController: ProposedApiController;
private readonly _isExtensionDevHost: boolean;
protected readonly _isExtensionDevTestFromCli: boolean;
// --- Members used per extension host process
protected _extensionHostProcessManagers: ExtensionHostProcessManager[];
protected _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
constructor(
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@INotificationService protected readonly _notificationService: INotificationService,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
@IExtensionEnablementService protected readonly _extensionEnablementService: IExtensionEnablementService,
@IFileService protected readonly _fileService: IFileService,
@IProductService protected readonly _productService: IProductService
) {
super();
// help the file service to activate providers by activating extensions by file system event
this._register(this._fileService.onWillActivateFileSystemProvider(e => {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = new Map<string, IMessage[]>();
this._proposedApiController = new ProposedApiController(this._environmentService, this._productService);
this._extensionHostProcessManagers = [];
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
protected async _initialize(): Promise<void> {
perf.mark('willLoadExtensions');
this._startExtensionHostProcess(true, []);
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
await this._scanAndHandleExtensions();
this._releaseBarrier();
}
private _releaseBarrier(): void {
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(undefined);
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
private _stopExtensionHostProcess(): void {
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
this._extensionHostActiveExtensions.forEach((value) => {
previouslyActivatedExtensionIds.push(value);
});
for (const manager of this._extensionHostProcessManagers) {
manager.dispose();
}
this._extensionHostProcessManagers = [];
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
if (previouslyActivatedExtensionIds.length > 0) {
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
}
}
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
const processManagers = this._createExtensionHosts(isInitialStart, initialActivationEvents);
processManagers.forEach((processManager) => {
processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal));
processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(processManager);
});
}
private _onExtensionHostCrashOrExit(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onExtensionHostCrashed(extensionHost, code, signal);
return;
}
this._onExtensionHostExit(code);
}
protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
}
//#region IExtensionService
public canAddExtension(extension: IExtensionDescription): boolean {
return false;
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
return false;
}
public restartExtensionHost(): void {
this._stopExtensionHostProcess();
this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys()));
}
public startExtensionHost(): void {
this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys()));
}
public stopExtensionHost(): void {
this._stopExtensionHostProcess();
}
public activateByEvent(activationEvent: string): Promise<void> {
if (this._installedExtensionsReady.isOpen()) {
// Extensions have been scanned and interpreted
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents.add(activationEvent);
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return NO_OP_VOID_PROMISE;
}
return this._activateByEvent(activationEvent);
} else {
// Extensions have not been scanned yet.
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents.add(activationEvent);
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent));
}
}
private _activateByEvent(activationEvent: string): Promise<void> {
const result = Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent))
).then(() => { });
this._onWillActivateByEvent.fire({
event: activationEvent,
activation: result
});
return result;
}
public whenInstalledExtensionsRegistered(): Promise<boolean> {
return this._installedExtensionsReady.wait();
}
public getExtensions(): Promise<IExtensionDescription[]> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getAllExtensionDescriptions();
});
}
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getExtensionDescription(id);
});
}
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
return this._installedExtensionsReady.wait().then(() => {
const availableExtensions = this._registry.getAllExtensionDescriptions();
const result: ExtensionPointContribution<T>[] = [];
for (const desc of availableExtensions) {
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
result.push(new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name])); // {{SQL CARBON EDIT}} strict-null-checks
}
}
return result;
});
}
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
if (this._registry) {
const extensions = this._registry.getAllExtensionDescriptions();
for (const extension of extensions) {
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
result[extension.identifier.value] = {
messages: this._extensionsMessages.get(extensionKey) || [],
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [],
};
}
}
return result;
}
public getInspectPort(): number {
return 0;
}
//#endregion
// --- impl
protected _checkEnableProposedApi(extensions: IExtensionDescription[]): void {
for (let extension of extensions) {
this._proposedApiController.updateEnableProposedApi(extension);
}
}
private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
if (this._environmentService.isExtensionDevelopment) {
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
if (extDevLocs) {
const extLocation = extension.extensionLocation;
for (let p of extDevLocs) {
if (isEqualOrParent(extLocation, p)) {
return true;
}
}
}
}
return false;
}
protected _isEnabled(extension: IExtensionDescription): boolean {
if (this._isExtensionUnderDevelopment(extension)) {
// Never disable extensions under development
return true;
}
if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) {
// Check if this is the better merge extension which was migrated to a built-in extension
return false;
}
return this._extensionEnablementService.isEnabled(toExtension(extension));
}
protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void {
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
for (let extensionDescription of affectedExtensions) {
if (extensionDescription.contributes) {
for (let extPointName in extensionDescription.contributes) {
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
affectedExtensionPoints[extPointName] = true;
}
}
}
}
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
const availableExtensions = this._registry.getAllExtensionDescriptions();
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
for (const extensionPoint of extensionPoints) {
if (affectedExtensionPoints[extensionPoint.name]) {
AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler);
}
}
}
private _handleExtensionPointMessage(msg: IMessage) {
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages.get(extensionKey)!.push(msg);
const extension = this._registry.getExtensionDescription(msg.extensionId);
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
if (extension && extension.isUnderDevelopment) {
// This message is about the extension currently being developed
this._showMessageToUser(msg.type, strMsg);
} else {
this._logMessageInConsole(msg.type, strMsg);
}
if (!this._isDev && msg.extensionId) {
const { type, extensionId, extensionPointId, message } = msg;
type ExtensionsMessageClassification = {
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
extensionPointId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
message: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
type ExtensionsMessageEvent = {
type: Severity;
extensionId: string;
extensionPointId: string;
message: string;
};
this._telemetryService.publicLog2<ExtensionsMessageEvent, ExtensionsMessageClassification>('extensionsMessage', {
type, extensionId: extensionId.value, extensionPointId, message
});
}
}
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
const users: IExtensionPointUser<T>[] = [];
for (const desc of availableExtensions) {
if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
users.push({
description: desc,
value: desc.contributes[extensionPoint.name], // {{SQL CARBON EDIT}} strict-null-checks
collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
});
}
}
perf.mark(`willHandleExtensionPoint/${extensionPoint.name}`);
extensionPoint.acceptUsers(users);
perf.mark(`didHandleExtensionPoint/${extensionPoint.name}`);
}
private _showMessageToUser(severity: Severity, msg: string): void {
if (severity === Severity.Error || severity === Severity.Warning) {
this._notificationService.notify({ severity, message: msg });
} else {
this._logMessageInConsole(severity, msg);
}
}
private _logMessageInConsole(severity: Severity, msg: string): void {
if (severity === Severity.Error) {
console.error(msg);
} else if (severity === Severity.Warning) {
console.warn(msg);
} else {
console.log(msg);
}
}
//#region Called by extension host
public _logOrShowMessage(severity: Severity, msg: string): void {
if (this._isDev) {
this._showMessageToUser(severity, msg);
} else {
this._logMessageInConsole(severity, msg);
}
}
public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void> {
const results = await Promise.all(
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent))
);
const activated = results.some(e => e);
if (!activated) {
throw new Error(`Unknown extension ${extensionId.value}`);
}
}
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
}
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
}
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
//#endregion
protected abstract _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[];
protected abstract _scanAndHandleExtensions(): Promise<void>;
public abstract _onExtensionHostExit(code: number): void;
}
class ProposedApiController {
private readonly enableProposedApiFor: string | string[];
private readonly enableProposedApiForAll: boolean;
private readonly productAllowProposedApi: Set<string>;
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService productService: IProductService
) {
this.enableProposedApiFor = environmentService.args['enable-proposed-api'] || [];
if (this.enableProposedApiFor.length) {
// Make enabled proposed API be lowercase for case insensitive comparison
if (Array.isArray(this.enableProposedApiFor)) {
this.enableProposedApiFor = this.enableProposedApiFor.map(id => id.toLowerCase());
} else {
this.enableProposedApiFor = this.enableProposedApiFor.toLowerCase();
}
}
this.enableProposedApiForAll = !environmentService.isBuilt ||
(!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') ||
(this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args);
this.productAllowProposedApi = new Set<string>();
if (isNonEmptyArray(productService.extensionAllowedProposedApi)) {
productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
}
}
public updateEnableProposedApi(extension: IExtensionDescription): void {
if (this._allowProposedApiFromProduct(extension.identifier)) {
// fast lane -> proposed api is available to all extensions
// that are listed in product.json-files
extension.enableProposedApi = true;
} else if (extension.enableProposedApi && !extension.isBuiltin) {
if (
!this.enableProposedApiForAll &&
this.enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
) {
extension.enableProposedApi = false;
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
} else {
// proposed api is available when developing or when an extension was explicitly
// spelled out via a command line argument
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
}
}
}
private _allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
return this.productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
}
}

View File

@@ -35,7 +35,7 @@ const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export class ExtensionHostProcessManager extends Disposable {
public readonly onDidCrash: Event<[number, string | null]>;
public readonly onDidExit: Event<[number, string | null]>;
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
@@ -54,6 +54,7 @@ export class ExtensionHostProcessManager extends Disposable {
private _resolveAuthorityAttempt: number;
constructor(
public readonly isLocal: boolean,
extensionHostProcessWorker: IExtensionHostStarter,
private readonly _remoteAuthority: string,
initialActivationEvents: string[],
@@ -66,7 +67,7 @@ export class ExtensionHostProcessManager extends Disposable {
this._extensionHostProcessCustomers = [];
this._extensionHostProcessWorker = extensionHostProcessWorker;
this.onDidCrash = this._extensionHostProcessWorker.onCrashed;
this.onDidExit = this._extensionHostProcessWorker.onExit;
this._extensionHostProcessProxy = this._extensionHostProcessWorker.start()!.then(
(protocol) => {
return { value: this._createExtensionHostCustomers(protocol) };
@@ -203,7 +204,8 @@ export class ExtensionHostProcessManager extends Disposable {
// Check that no named customers are missing
// {{SQL CARBON EDIT}} filter out services we don't expose
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]).filter(v => v !== MainContext.MainThreadDebugService);
const filtered: ProxyIdentifier<any>[] = [MainContext.MainThreadDebugService, MainContext.MainThreadTask];
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]).filter(v => !filtered.includes(v));
this._extensionHostProcessRPCProtocol.assertRegistered(expected);
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);

View File

@@ -12,6 +12,7 @@ export interface IExtHostReadyMessage {
export interface IExtHostSocketMessage {
type: 'VSCODE_EXTHOST_IPC_SOCKET';
initialDataChunk: string;
skipWebSocketFrames: boolean;
}
export const enum MessageType {

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Severity } from 'vs/platform/notification/common/notification';
export interface Translations {
[id: string]: string;
}
export namespace Translations {
export function equals(a: Translations, b: Translations): boolean {
if (a === b) {
return true;
}
let aKeys = Object.keys(a);
let bKeys: Set<string> = new Set<string>();
for (let key of Object.keys(b)) {
bKeys.add(key);
}
if (aKeys.length !== bKeys.size) {
return false;
}
for (let key of aKeys) {
if (a[key] !== b[key]) {
return false;
}
bKeys.delete(key);
}
return bKeys.size === 0;
}
}
export interface ILog {
error(source: string, message: string): void;
warn(source: string, message: string): void;
info(source: string, message: string): void;
}
export class Logger implements ILog {
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
constructor(
messageHandler: (severity: Severity, source: string, message: string) => void
) {
this._messageHandler = messageHandler;
}
public error(source: string, message: string): void {
this._messageHandler(Severity.Error, source, message);
}
public warn(source: string, message: string): void {
this._messageHandler(Severity.Warning, source, message);
}
public info(source: string, message: string): void {
this._messageHandler(Severity.Info, source, message);
}
}

View File

@@ -84,7 +84,8 @@ export interface IExtensionHostProfile {
}
export interface IExtensionHostStarter {
readonly onCrashed: Event<[number, string | null]>;
readonly onExit: Event<[number, string | null]>;
start(): Promise<IMessagePassingProtocol> | null;
getInspectPort(): number | undefined;
dispose(): void;

View File

@@ -206,7 +206,7 @@ export const schema = {
type: 'object',
properties: {
// extensions will fill in
},
} as { [key: string]: any },
default: {}
},
preview: {
@@ -341,6 +341,19 @@ export const schema = {
pattern: EXTENSION_IDENTIFIER_PATTERN
}
},
extensionKind: {
description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."),
type: 'string',
enum: [
'ui',
'workspace'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
],
default: 'workspace'
},
scripts: {
type: 'object',
properties: {

View File

@@ -8,9 +8,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import product from 'vs/platform/product/node/product';
import { IProductService } from 'vs/platform/product/common/product';
export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name);
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
const extensionKind = getExtensionKind(manifest, configurationService);
@@ -19,7 +19,7 @@ export function isUIExtension(manifest: IExtensionManifest, configurationService
case 'workspace': return false;
default: {
// Tagged as UI extension in product
if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
// Not an UI extension if it has main

View File

@@ -5,7 +5,7 @@
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -70,10 +70,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true);
}
this.disposable = combinedDisposable([
this.disposable = combinedDisposable(
urlService.registerHandler(this),
toDisposable(() => clearInterval(interval))
]);
);
}
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {

View File

@@ -3,17 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { Emitter, Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@@ -28,8 +24,9 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
import { IProductService } from 'vs/platform/product/common/product';
import { ISignService } from 'vs/platform/sign/common/sign';
export interface IInitDataProvider {
readonly remoteAuthority: string;
@@ -38,28 +35,29 @@ export interface IInitDataProvider {
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
public readonly onExit: Event<[number, string | null]> = this._onExit.event;
private _protocol: PersistentProtocol | null;
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevTestFromCli: boolean;
private _terminating: boolean;
constructor(
private readonly _allExtensions: Promise<IExtensionDescription[]>,
private readonly _initDataProvider: IInitDataProvider,
private readonly _webSocketFactory: IWebSocketFactory,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWindowService private readonly _windowService: IWindowService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
@IProductService private readonly _productService: IProductService,
@ISignService private readonly _signService: ISignService
) {
super();
this._protocol = null;
@@ -69,20 +67,20 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
}
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
isBuilt: this._environmentService.isBuilt,
commit: product.commit,
webSocketFactory: nodeWebSocketFactory,
commit: this._productService.commit,
webSocketFactory: this._webSocketFactory,
addressProvider: {
getAddress: async () => {
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
return { host, port };
}
}
},
signService: this._signService
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
@@ -168,20 +166,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
return;
}
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onCrashed.fire([0, null]);
}
// Expected development extension termination: When the extension host goes down we also shutdown the window
else if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
else {
ipc.send('vscode:exit', 0);
}
this._onExit.fire([0, null]);
}
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
@@ -191,26 +176,32 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: product.commit,
version: pkg.version,
commit: this._productService.commit,
version: this._productService.version,
parentPid: remoteExtensionHostData.pid,
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteExtensionHostData.appRoot,
appSettingsHome: remoteExtensionHostData.appSettingsHome,
appName: product.nameLong,
appUriScheme: product.urlProtocol,
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteExtensionHostData.globalStorageHome,
userHome: remoteExtensionHostData.userHome
userHome: remoteExtensionHostData.userHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
},
remote: {
isRemote: true,
authority: this._initDataProvider.remoteAuthority
},
resolvedExtensions: resolvedExtensions,
hostExtensions: hostExtensions,
extensions: remoteExtensionHostData.extensions,
@@ -218,7 +209,6 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
logLevel: this._logService.getLevel(),
logsLocation: remoteExtensionHostData.extensionHostLogsPath,
autoStart: true,
remoteAuthority: this._initDataProvider.remoteAuthority,
};
return r;
});

View File

@@ -21,7 +21,8 @@ import pkg from 'vs/platform/product/node/package';
import product from 'vs/platform/product/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints';
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints';
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
interface IExtensionCacheData {
input: ExtensionScannerInput;
@@ -387,26 +388,3 @@ class NullLogger implements ILog {
public info(source: string, message: string): void {
}
}
export class Logger implements ILog {
private readonly _messageHandler: (severity: Severity, source: string, message: string) => void;
constructor(
messageHandler: (severity: Severity, source: string, message: string) => void
) {
this._messageHandler = messageHandler;
}
public error(source: string, message: string): void {
this._messageHandler(Severity.Error, source, message);
}
public warn(source: string, message: string): void {
this._messageHandler(Severity.Warning, source, message);
}
public info(source: string, message: string): void {
this._messageHandler(Severity.Info, source, message);
}
}

View File

@@ -5,13 +5,12 @@
import * as nls from 'vs/nls';
import { ChildProcess, fork } from 'child_process';
import { ipcRenderer as ipc } from 'electron';
import { Server, Socket, createServer } from 'net';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { timeout } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import pkg from 'vs/platform/product/node/package';
@@ -42,10 +41,10 @@ import { isEqualOrParent } from 'vs/base/common/resources';
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
public readonly onCrashed: Event<[number, string]> = this._onCrashed.event;
private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>();
public readonly onExit: Event<[number, string]> = this._onExit.event;
private readonly _toDispose: IDisposable[];
private readonly _toDispose = new DisposableStore();
private readonly _isExtensionDevHost: boolean;
private readonly _isExtensionDevDebug: boolean;
@@ -58,7 +57,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
// Resources, in order they get acquired/created when .start() is called:
private _namedPipeServer: Server | null;
private _inspectPort: number;
private _inspectPort: number | null;
private _extensionHostProcess: ChildProcess | null;
private _extensionHostConnection: Socket | null;
private _messageProtocol: Promise<PersistentProtocol> | null;
@@ -92,16 +91,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._extensionHostConnection = null;
this._messageProtocol = null;
this._toDispose = [];
this._toDispose.push(this._onCrashed);
this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate()));
this._toDispose.push(this._extensionHostDebugService.onClose(event => {
this._toDispose.add(this._onExit);
this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e)));
this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate()));
this._toDispose.add(this._extensionHostDebugService.onClose(event => {
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
this._windowService.closeWindow();
}
}));
this._toDispose.push(this._extensionHostDebugService.onReload(event => {
this._toDispose.add(this._extensionHostDebugService.onReload(event => {
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
this._windowService.reloadWindow();
}
@@ -109,7 +107,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
const globalExitListener = () => this.terminate();
process.once('exit', globalExitListener);
this._toDispose.push(toDisposable(() => {
this._toDispose.add(toDisposable(() => {
process.removeListener('exit', globalExitListener);
}));
}
@@ -125,7 +123,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
}
if (!this._messageProtocol) {
this._messageProtocol = Promise.all([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => {
this._messageProtocol = Promise.all([
this._tryListenOnPipe(),
!this._environmentService.args['disable-inspect'] ? this._tryFindDebugPort() : Promise.resolve(null)
]).then(data => {
const pipeName = data[0];
const portData = data[1];
@@ -148,7 +149,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
silent: true
};
if (portData.actual) {
if (portData && portData.actual) {
opts.execArgv = [
'--nolazy',
(this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portData.actual
@@ -215,10 +216,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal));
// Notify debugger that we are ready to attach to the process if we run a development extension
if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portData.actual);
if (portData) {
if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portData.actual);
}
this._inspectPort = portData.actual;
}
this._inspectPort = portData.actual;
// Help in case we fail to start it
let startupTimeoutHandle: any;
@@ -389,14 +392,16 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined,
appName: product.nameLong,
appUriScheme: product.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: URI.file(this._environmentService.globalStorageHome),
userHome: URI.file(this._environmentService.userHome)
userHome: URI.file(this._environmentService.userHome),
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
configuration: withNullAsUndefined(workspace.configuration),
@@ -404,6 +409,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
name: this._labelService.getWorkspaceLabel(workspace),
isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false
},
remote: {
authority: this._environmentService.configuration.remoteAuthority,
isRemote: false
},
resolvedExtensions: [],
hostExtensions: [],
extensions: extensionDescriptions,
@@ -451,36 +460,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
return;
}
// Unexpected termination
if (!this._isExtensionDevHost) {
this._onCrashed.fire([code, signal]);
}
// Expected development extension termination: When the extension host goes down we also shutdown the window
else if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
else {
ipc.send('vscode:exit', code);
}
this._onExit.fire([code, signal]);
}
public enableInspector(): Promise<void> {
if (this._inspectPort) {
return Promise.resolve();
}
// send SIGUSR1 and wait a little the actual port is read from the process stdout which we
// scan here: https://github.com/Microsoft/vscode/blob/67ffab8dcd1a6752d8b62bcd13d7020101eef568/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts#L225-L240
if (this._extensionHostProcess) {
this._extensionHostProcess.kill('SIGUSR1');
}
return timeout(1000);
}
public getInspectPort(): number {
return this._inspectPort;
public getInspectPort(): number | undefined {
return withNullAsUndefined(this._inspectPort);
}
public terminate(): void {
@@ -489,7 +473,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
}
this._terminating = true;
dispose(this._toDispose);
this._toDispose.dispose();
if (!this._messageProtocol) {
// .start() was not called

View File

@@ -6,13 +6,17 @@
import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/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';
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/product';
const localExtensionManagementServerAuthority: string = 'vscode-local';
@@ -25,14 +29,18 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@IConfigurationService configurationService: IConfigurationService,
@IProductService productService: IProductService,
@ILogService logService: ILogService
) {
const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions'));
this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") };
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'));
const extensionManagementService = new RemoteExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer.extensionManagementService, galleryService, logService, configurationService, productService);
this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: localize('remote', "Remote") };
}
}

View File

@@ -3,62 +3,37 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer as ipc } from 'electron';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import { ipcRenderer as ipc } from 'electron';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier, runWhenIdle } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { runWhenIdle } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionEnablementService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import pkg from 'vs/platform/product/node/package';
import product from 'vs/platform/product/node/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
schema.properties.engines.properties.vscode.default = `^${pkg.version}`;
let productAllowProposedApi: Set<string> | null = null;
function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean {
// create set if needed
if (!productAllowProposedApi) {
productAllowProposedApi = new Set<string>();
if (isNonEmptyArray(product.extensionAllowedProposedApi)) {
product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi!.add(ExtensionIdentifier.toKey(id)));
}
}
return productAllowProposedApi.has(ExtensionIdentifier.toKey(id));
}
import { IProductService } from 'vs/platform/product/common/product';
import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints';
class DeltaExtensionsQueueItem {
constructor(
@@ -67,80 +42,38 @@ class DeltaExtensionsQueueItem {
) { }
}
export class ExtensionService extends Disposable implements IExtensionService {
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
public _serviceBrand: any;
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
private readonly _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
private readonly _extensionHostLogsLocation: URI;
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _installedExtensionsReady: Barrier;
private readonly _isDev: boolean;
private readonly _extensionsMessages: Map<string, IMessage[]>;
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
private readonly _extensionScanner: CachedExtensionScanner;
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
private readonly _onDidRegisterExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
private readonly _onDidChangeExtensionsStatus: Emitter<ExtensionIdentifier[]> = this._register(new Emitter<ExtensionIdentifier[]>());
public readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = this._onDidChangeExtensionsStatus.event;
private readonly _onDidChangeExtensions: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeExtensions: Event<void> = this._onDidChangeExtensions.event;
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent: Event<IWillActivateEvent> = this._onWillActivateByEvent.event;
private readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
// --- Members used per extension host process
private _extensionHostProcessManagers: ExtensionHostProcessManager[];
private _extensionHostActiveExtensions: Map<string, ExtensionIdentifier>;
private _extensionHostProcessActivationTimes: Map<string, ActivationTimes>;
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@INotificationService private readonly _notificationService: INotificationService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IInstantiationService instantiationService: IInstantiationService,
@INotificationService notificationService: INotificationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionEnablementService extensionEnablementService: IExtensionEnablementService,
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IWindowService private readonly _windowService: IWindowService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IFileService fileService: IFileService
@IWindowService protected readonly _windowService: IWindowService,
) {
super();
// help the file service to activate providers by activating extensions by file system event
this._register(fileService.onWillActivateFileSystemProvider(e => {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}));
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
this._registry = new ExtensionDescriptionRegistry([]);
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = new Map<string, IMessage[]>();
this._allRequestedActivateEvents = Object.create(null);
this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner);
this._deltaExtensionsQueue = [];
this._extensionHostProcessManagers = [];
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
this._startDelayed(this._lifecycleService);
super(
instantiationService,
notificationService,
environmentService,
telemetryService,
extensionEnablementService,
fileService,
productService,
);
if (this._extensionEnablementService.allUserExtensionsDisabled) {
this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{
@@ -151,6 +84,12 @@ export class ExtensionService extends Disposable implements IExtensionService {
}]);
}
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
this._deltaExtensionsQueue = [];
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
@@ -181,8 +120,24 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}
}));
// delay extension host creation and extension scanning
// until the workbench is running. we cannot defer the
// extension host more (LifecyclePhase.Restored) because
// some editors require the extension host to restore
// and this would result in a deadlock
// see https://github.com/Microsoft/vscode/issues/41322
this._lifecycleService.when(LifecyclePhase.Ready).then(() => {
// reschedule to ensure this runs after restoring viewlets, panels, and editors
runWhenIdle(() => {
this._initialize();
}, 50 /*max delay*/);
});
}
//#region deltaExtensions
private _inHandleDeltaExtensions = false;
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
this._deltaExtensionsQueue.push(item);
@@ -211,13 +166,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
if (extension.location.scheme !== Schemas.file) {
continue;
}
const existingExtensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (existingExtensionDescription) {
// this extension is already running (most likely at a different version)
if (!this._canAddExtension(extension)) {
continue;
}
@@ -258,6 +207,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
// enable or disable proposed API per extension
this._checkEnableProposedApi(toAdd);
// Update extension points
this._rehandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
@@ -274,48 +226,36 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void {
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
for (let extensionDescription of extensionDescriptions) {
if (extensionDescription.contributes) {
for (let extPointName in extensionDescription.contributes) {
if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) {
affectedExtensionPoints[extPointName] = true;
}
}
}
}
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
const availableExtensions = this._registry.getAllExtensionDescriptions();
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
for (let i = 0, len = extensionPoints.length; i < len; i++) {
if (affectedExtensionPoints[extensionPoints[i].name]) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
}
this._doHandleExtensionPoints(extensionDescriptions);
}
public canAddExtension(extension: IExtensionDescription): boolean {
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
return this._canAddExtension(toExtension(extensionDescription));
}
public _canAddExtension(extension: IExtension): boolean {
if (this._environmentService.configuration.remoteAuthority) {
return false;
}
if (extension.extensionLocation.scheme !== Schemas.file) {
if (extension.location.scheme !== Schemas.file) {
return false;
}
// {{ SQL CARBON EDIT }}
if (extension.forceReload) {
if (extension.manifest.forceReload) {
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (extensionDescription) {
// ignore adding an extension which is already running and cannot be removed
if (!this._canRemoveExtension(extensionDescription)) {
return false;
}
// this extension is already running (most likely at a different version)
return false;
}
// Check if extension is renamed
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
return false;
}
return true;
@@ -359,7 +299,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
if (this._allRequestedActivateEvents[activationEvent]) {
if (this._allRequestedActivateEvents.has(activationEvent)) {
// This activation event was fired before the extension was added
shouldActivate = true;
shouldActivateReason = activationEvent;
@@ -388,108 +328,50 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private _startDelayed(lifecycleService: ILifecycleService): void {
// delay extension host creation and extension scanning
// until the workbench is running. we cannot defer the
// extension host more (LifecyclePhase.Restored) because
// some editors require the extension host to restore
// and this would result in a deadlock
// see https://github.com/Microsoft/vscode/issues/41322
lifecycleService.when(LifecyclePhase.Ready).then(() => {
// reschedule to ensure this runs after restoring viewlets, panels, and editors
runWhenIdle(() => {
perf.mark('willLoadExtensions');
this._startExtensionHostProcess(true, []);
this._scanAndHandleExtensions();
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
}, 50 /*max delay*/);
});
}
public dispose(): void {
super.dispose();
this._onWillActivateByEvent.dispose();
this._onDidChangeResponsiveChange.dispose();
}
public restartExtensionHost(): void {
this._stopExtensionHostProcess();
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
}
public startExtensionHost(): void {
this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents));
}
public stopExtensionHost(): void {
this._stopExtensionHostProcess();
}
private _stopExtensionHostProcess(): void {
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
this._extensionHostActiveExtensions.forEach((value) => {
previouslyActivatedExtensionIds.push(value);
});
for (const manager of this._extensionHostProcessManagers) {
manager.dispose();
}
this._extensionHostProcessManagers = [];
this._extensionHostActiveExtensions = new Map<string, ExtensionIdentifier>();
this._extensionHostProcessActivationTimes = new Map<string, ActivationTimes>();
this._extensionHostExtensionRuntimeErrors = new Map<string, Error[]>();
if (previouslyActivatedExtensionIds.length > 0) {
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
}
}
//#endregion
private _createProvider(remoteAuthority: string): IInitDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: () => {
return this._installedExtensionsReady.wait().then(() => {
return this.whenInstalledExtensionsRegistered().then(() => {
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
});
}
};
}
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
this._stopExtensionHostProcess();
protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] {
let autoStart: boolean;
let extensions: Promise<IExtensionDescription[]>;
if (isInitialStart) {
autoStart = false;
extensions = this._extensionScanner.scannedExtensions;
extensions = this._extensionScanner.scannedExtensions.then(extensions => extensions.filter(extension => this._isEnabled(extension))); // remove disabled extensions
} else {
// restart case
autoStart = true;
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
}
const result: ExtensionHostProcessManager[] = [];
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(extHostProcessManager);
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents);
result.push(extHostProcessManager);
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), nodeWebSocketFactory);
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
result.push(remoteExtHostProcessManager);
}
return result;
}
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
this._stopExtensionHostProcess();
protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void {
super._onExtensionHostCrashed(extensionHost, code, signal);
if (showNotification) {
if (extensionHost.isLocal) {
if (code === 55) {
this._notificationService.prompt(
Severity.Error,
@@ -507,118 +389,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
return;
}
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
if (code === 87) {
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
}
this._notificationService.prompt(Severity.Error, message,
this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."),
[{
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
run: () => this.startExtensionHost()
}]
);
}
}
// ---- begin IExtensionService
public activateByEvent(activationEvent: string): Promise<void> {
if (this._installedExtensionsReady.isOpen()) {
// Extensions have been scanned and interpreted
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents[activationEvent] = true;
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return NO_OP_VOID_PROMISE;
}
return this._activateByEvent(activationEvent);
} else {
// Extensions have not been scanned yet.
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents[activationEvent] = true;
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent));
}
}
private _activateByEvent(activationEvent: string): Promise<void> {
const result = Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent))
).then(() => { });
this._onWillActivateByEvent.fire({
event: activationEvent,
activation: result
});
return result;
}
public whenInstalledExtensionsRegistered(): Promise<boolean> {
return this._installedExtensionsReady.wait();
}
public getExtensions(): Promise<IExtensionDescription[]> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getAllExtensionDescriptions();
});
}
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getExtensionDescription(id);
});
}
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
return this._installedExtensionsReady.wait().then(() => {
let availableExtensions = this._registry.getAllExtensionDescriptions();
let result: ExtensionPointContribution<T>[] = [], resultLen = 0;
for (let i = 0, len = availableExtensions.length; i < len; i++) {
let desc = availableExtensions[i];
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
result[resultLen++] = new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name]);
}
}
return result;
});
}
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
if (this._registry) {
const extensions = this._registry.getAllExtensionDescriptions();
for (const extension of extensions) {
const extensionKey = ExtensionIdentifier.toKey(extension.identifier);
result[extension.identifier.value] = {
messages: this._extensionsMessages.get(extensionKey) || [],
activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey),
runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [],
};
}
}
return result;
}
public getInspectPort(): number {
if (this._extensionHostProcessManagers.length > 0) {
return this._extensionHostProcessManagers[0].getInspectPort();
}
return 0;
}
// ---- end IExtensionService
// --- impl
private createLogger(): Logger {
@@ -647,7 +430,7 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
}
private async _scanAndHandleExtensions(): Promise<void> {
protected async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions(this.createLogger());
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
@@ -655,6 +438,12 @@ export class ExtensionService extends Disposable implements IExtensionService {
let localExtensions = await this._extensionScanner.scannedExtensions;
// enable or disable proposed API per extension
this._checkEnableProposedApi(localExtensions);
// remove disabled extensions
localExtensions = localExtensions.filter(extension => this._isEnabled(extension));
if (remoteAuthority) {
let resolvedAuthority: ResolvedAuthority;
@@ -698,37 +487,28 @@ export class ExtensionService extends Disposable implements IExtensionService {
// fetch the remote environment
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
// revive URIs
remoteEnv.extensions.forEach((extension) => {
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
});
// enable or disable proposed API per extension
this._checkEnableProposedApi(remoteEnv.extensions);
// remove disabled extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension));
// remove UI extensions from the remote extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService));
// remove non-UI extensions from the local extensions
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService));
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
// compute enabled extensions
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
// remove disabled extensions
const isEnabled = new Set<string>();
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
// save for remote extension's init data
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
this._handleExtensionPoints(enabledExtensions);
this._handleExtensionPoints((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
extensionHost.start(localExtensions.map(extension => extension.identifier));
this._releaseBarrier();
} else {
await this._startLocalExtensionHost(extensionHost, localExtensions);
@@ -736,11 +516,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
}
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
this._handleExtensionPoints(enabledExtensions);
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
this._releaseBarrier();
this._handleExtensionPoints(localExtensions);
extensionHost.start(localExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
}
private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void {
@@ -749,234 +526,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
let availableExtensions = this._registry.getAllExtensionDescriptions();
let extensionPoints = ExtensionsRegistry.getExtensionPoints();
let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
for (let i = 0, len = extensionPoints.length; i < len; i++) {
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
}
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
}
private _releaseBarrier(): void {
perf.mark('extensionHostReady');
this._installedExtensionsReady.open();
this._onDidRegisterExtensions.fire(undefined);
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
if (this._environmentService.isExtensionDevelopment) {
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
if (extDevLocs) {
const extLocation = extension.extensionLocation;
for (let p of extDevLocs) {
if (isEqualOrParent(extLocation, p)) {
return true;
}
}
}
public getInspectPort(): number {
if (this._extensionHostProcessManagers.length > 0) {
return this._extensionHostProcessManagers[0].getInspectPort();
}
return false;
}
private async _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise<IExtensionDescription[]> {
const runtimeExtensions: IExtensionDescription[] = [];
const extensionsToDisable: IExtensionDescription[] = [];
const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || [];
const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id);
if (enableProposedApiFor.length) {
let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]);
allProposed.forEach(id => {
if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) {
console.error(notFound(id));
}
});
// Make enabled proposed API be lowercase for case insensitive comparison
if (Array.isArray(enableProposedApiFor)) {
enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase());
} else {
enableProposedApiFor = enableProposedApiFor.toLowerCase();
}
}
const enableProposedApiForAll = !this._environmentService.isBuilt ||
(!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') ||
(enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args);
for (const extension of allExtensions) {
// Do not disable extensions under development
if (!this.isExtensionUnderDevelopment(extension)) {
if (!this._extensionEnablementService.isEnabled(toExtension(extension))) {
continue;
}
}
if (!extension.isBuiltin) {
// Check if the extension is changed to system extension
const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0];
if (userMigratedSystemExtension) {
extensionsToDisable.push(extension);
continue;
}
}
runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor));
}
this._telemetryService.publicLog('extensionsScanned', {
totalCount: runtimeExtensions.length,
disabledCount: allExtensions.length - runtimeExtensions.length
});
if (extensionsToDisable.length) {
return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled)
.then(() => runtimeExtensions);
} else {
return runtimeExtensions;
}
}
private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription {
if (allowProposedApiFromProduct(extension.identifier)) {
// fast lane -> proposed api is available to all extensions
// that are listed in product.json-files
extension.enableProposedApi = true;
} else if (extension.enableProposedApi && !extension.isBuiltin) {
if (
!enableProposedApiForAll &&
enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0
) {
extension.enableProposedApi = false;
console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`);
} else {
// proposed api is available when developing or when an extension was explicitly
// spelled out via a command line argument
console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`);
}
}
return extension;
}
private _handleExtensionPointMessage(msg: IMessage) {
const extensionKey = ExtensionIdentifier.toKey(msg.extensionId);
if (!this._extensionsMessages.has(extensionKey)) {
this._extensionsMessages.set(extensionKey, []);
}
this._extensionsMessages.get(extensionKey)!.push(msg);
const extension = this._registry.getExtensionDescription(msg.extensionId);
const strMsg = `[${msg.extensionId.value}]: ${msg.message}`;
if (extension && extension.isUnderDevelopment) {
// This message is about the extension currently being developed
this._showMessageToUser(msg.type, strMsg);
} else {
this._logMessageInConsole(msg.type, strMsg);
}
if (!this._isDev && msg.extensionId) {
const { type, extensionId, extensionPointId, message } = msg;
/* __GDPR__
"extensionsMessage" : {
"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
this._telemetryService.publicLog('extensionsMessage', {
type, extensionId: extensionId.value, extensionPointId, message
});
}
}
private static _handleExtensionPoint<T>(extensionPoint: ExtensionPoint<T>, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void {
let users: IExtensionPointUser<T>[] = [], usersLen = 0;
for (let i = 0, len = availableExtensions.length; i < len; i++) {
let desc = availableExtensions[i];
if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) {
users[usersLen++] = {
description: desc,
value: desc.contributes[extensionPoint.name],
collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name)
};
}
}
extensionPoint.acceptUsers(users);
}
private _showMessageToUser(severity: Severity, msg: string): void {
if (severity === Severity.Error || severity === Severity.Warning) {
this._notificationService.notify({ severity, message: msg });
} else {
this._logMessageInConsole(severity, msg);
}
}
private _logMessageInConsole(severity: Severity, msg: string): void {
if (severity === Severity.Error) {
console.error(msg);
} else if (severity === Severity.Warning) {
console.warn(msg);
} else {
console.log(msg);
}
}
// -- called by extension host
public _logOrShowMessage(severity: Severity, msg: string): void {
if (this._isDev) {
this._showMessageToUser(severity, msg);
} else {
this._logMessageInConsole(severity, msg);
}
}
public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void> {
const results = await Promise.all(
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent))
);
const activated = results.some(e => e);
if (!activated) {
throw new Error(`Unknown extension ${extensionId.value}`);
}
}
public _onWillActivateExtension(extensionId: ExtensionIdentifier): void {
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
}
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void {
const extensionKey = ExtensionIdentifier.toKey(extensionId);
if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) {
this._extensionHostExtensionRuntimeErrors.set(extensionKey, []);
}
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
this._onDidChangeExtensionsStatus.fire([extensionId]);
return 0;
}
public _onExtensionHostExit(code: number): void {
// Expected development extension termination: When the extension host goes down we also shutdown the window
const devOpts = parseExtensionDevOptions(this._environmentService);
if (!devOpts.isExtensionDevTestFromCli) {
if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
}

View File

@@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* 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 { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { IProductService } from 'vs/platform/product/common/product';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc';
export class RemoteExtensionManagementChannelClient extends ExtensionManagementChannelClient {
_serviceBrand: any;
constructor(
channel: IChannel,
private readonly localExtensionManagementService: IExtensionManagementService,
private readonly galleryService: IExtensionGalleryService,
private readonly logService: ILogService,
private readonly configurationService: IConfigurationService,
private readonly productService: IProductService
) {
super(channel);
}
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> {
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 = await this.galleryService.download(extension, URI.file(tmpdir()), 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 values(result);
}
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 values(result);
}
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 && isUIExtension(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

@@ -19,7 +19,6 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
// we don't (yet) throw when extensions parse
// uris that have no scheme
@@ -53,9 +52,7 @@ export class ExtensionHostMain {
hostUtils: IHostUtils,
consolePatchFn: IConsolePatchFn,
logServiceFn: ILogServiceFn,
uriTransformer: IURITransformer | null,
schemeTransformer: ISchemeTransformer | null,
outputChannelName: string,
uriTransformer: IURITransformer | null
) {
this._isTerminating = false;
this._hostUtils = hostUtils;
@@ -86,8 +83,7 @@ export class ExtensionHostMain {
extHostConfiguraiton,
initData.environment,
this._extHostLogService,
schemeTransformer,
outputChannelName
uriTransformer
);
// error forwarding and stack trace scanning

View File

@@ -3,11 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup';
startExtensionHostProcess(
_ => null,
_ => null,
_ => nls.localize('extension host Log', "Extension Host")
).catch((err) => console.log(err));
startExtensionHostProcess().catch((err) => console.log(err));

View File

@@ -5,23 +5,33 @@
import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net';
import * as minimist from 'minimist';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/product/node/product';
import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
import { exists } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
interface ParsedExtHostArgs {
uriTransformerPath?: string;
}
const args = minimist(process.argv.slice(2), {
string: [
'uriTransformerPath'
]
}) as ParsedExtHostArgs;
// With Electron 2.x and node.js 8.x the "natives" module
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
@@ -72,7 +82,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
};
}
const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
const createLogService: ILogServiceFn = initData => new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel);
interface IRendererConnection {
protocol: IMessagePassingProtocol;
@@ -101,27 +111,41 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
let socket: NodeSocket | WebSocketNodeSocket;
if (msg.skipWebSocketFrames) {
socket = new NodeSocket(handle);
} else {
socket = new WebSocketNodeSocket(new NodeSocket(handle));
}
if (protocol) {
// reconnection case
if (disconnectWaitTimer) {
clearTimeout(disconnectWaitTimer);
disconnectWaitTimer = null;
}
protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk);
protocol.beginAcceptReconnection(socket, initialDataChunk);
protocol.endAcceptReconnection();
} else {
clearTimeout(timer);
protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk);
protocol = new PersistentProtocol(socket, initialDataChunk);
protocol.onClose(() => onTerminate());
resolve(protocol);
protocol.onSocketClose(() => {
// The socket has closed, let's give the renderer a certain amount of time to reconnect
disconnectWaitTimer = setTimeout(() => {
disconnectWaitTimer = null;
if (msg.skipWebSocketFrames) {
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
// The socket has closed, let's give the renderer a certain amount of time to reconnect
disconnectWaitTimer = setTimeout(() => {
disconnectWaitTimer = null;
onTerminate();
}, ProtocolConstants.ReconnectionGraceTime);
});
} else {
// Do not wait for web companion to reconnect
protocol.onSocketClose(() => {
onTerminate();
}, ProtocolConstants.ReconnectionGraceTime);
});
});
}
}
}
});
@@ -155,16 +179,22 @@ async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
return new class implements IMessagePassingProtocol {
private _terminating = false;
private readonly _onMessage = new Emitter<VSBuffer>();
readonly onMessage: Event<VSBuffer> = createBufferedEvent(this._onMessage.event);
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
if (!isMessageOfType(msg, MessageType.Terminate)) {
return true;
}
this._terminating = true;
onTerminate();
return false;
});
private _terminating: boolean;
constructor() {
this._terminating = false;
protocol.onMessage((msg) => {
if (isMessageOfType(msg, MessageType.Terminate)) {
this._terminating = true;
onTerminate();
} else {
this._onMessage.fire(msg);
}
});
}
send(msg: any): void {
if (!this._terminating) {
@@ -272,11 +302,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
}
})();
export async function startExtensionHostProcess(
uriTransformerFn: (initData: IInitData) => IURITransformer | null,
schemeTransformerFn: (initData: IInitData) => ISchemeTransformer | null,
outputChannelNameFn: (initData: IInitData) => string,
): Promise<void> {
export async function startExtensionHostProcess(): Promise<void> {
const protocol = await createExtHostProtocol();
const renderer = await connectToRenderer(protocol);
@@ -291,6 +317,17 @@ export async function startExtensionHostProcess(
realpath(path: string) { return realpath(path); }
};
// Attempt to load uri transformer
let uriTransformer: IURITransformer | null = null;
if (initData.remote.authority && args.uriTransformerPath) {
try {
const rawURITransformerFactory = <any>require.__$__nodeRequire(args.uriTransformerPath);
const rawURITransformer = <IRawURITransformer>rawURITransformerFactory(initData.remote.authority);
uriTransformer = new URITransformer(rawURITransformer);
} catch (e) {
console.error(e);
}
}
const extensionHostMain = new ExtensionHostMain(
renderer.protocol,
@@ -298,9 +335,7 @@ export async function startExtensionHostProcess(
hostUtils,
patchPatchedConsole,
createLogService,
uriTransformerFn(initData),
schemeTransformerFn(initData),
outputChannelNameFn(initData)
uriTransformer
);
// rewrite onTerminate-function to be a proper shutdown

View File

@@ -12,40 +12,13 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/common/extensionValidator';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
const MANIFEST_FILE = 'package.json';
export interface Translations {
[id: string]: string;
}
namespace Translations {
export function equals(a: Translations, b: Translations): boolean {
if (a === b) {
return true;
}
let aKeys = Object.keys(a);
let bKeys: Set<string> = new Set<string>();
for (let key of Object.keys(b)) {
bKeys.add(key);
}
if (aKeys.length !== bKeys.size) {
return false;
}
for (let key of aKeys) {
if (a[key] !== b[key]) {
return false;
}
bKeys.delete(key);
}
return bKeys.size === 0;
}
}
export interface NlsConfiguration {
readonly devMode: boolean;
readonly locale: string | undefined;
@@ -53,12 +26,6 @@ export interface NlsConfiguration {
readonly translations: Translations;
}
export interface ILog {
error(source: string, message: string): void;
warn(source: string, message: string): void;
info(source: string, message: string): void;
}
abstract class ExtensionManifestHandler {
protected readonly _ourVersion: string;

View File

@@ -8,7 +8,7 @@ import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { ExtensionType, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -16,10 +16,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localize } from 'vs/nls';
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
import { IProductService } from 'vs/platform/product/common/product';
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -35,7 +34,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte
constructor(
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
) {
super();
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
@@ -85,7 +85,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise<void> {
if (server === this.extensionManagementServerService.localExtensionManagementServer) {
const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User);
const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.configurationService)
const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService)
&& i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
if (dependentNonUIExtensions.length) {
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));
@@ -132,44 +132,38 @@ export class MultiExtensionManagementService extends Disposable implements IExte
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(([extensionIdentifier]) => extensionIdentifier);
}
async install(vsix: URI): Promise<IExtensionIdentifier> {
async install(vsix: URI): Promise<ILocalExtension> {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const manifest = await getManifest(vsix.fsPath);
if (isLanguagePackExtension(manifest)) {
// Install on both servers
const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix)));
return extensionIdentifier;
const [local] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix)));
return local;
}
if (isUIExtension(manifest, this.configurationService)) {
if (isUIExtension(manifest, this.productService, this.configurationService)) {
// Install only on local server
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
// Install UI Dependencies on local server
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
}
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
async installFromGallery(gallery: IGalleryExtension): Promise<void> {
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
if (manifest) {
if (isLanguagePackExtension(manifest)) {
// Install on both servers
return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined);
return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local);
}
if (isUIExtension(manifest, this.configurationService)) {
if (isUIExtension(manifest, this.productService, this.configurationService)) {
// Install only on local server
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
// Install only on remote server
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
// Install UI dependencies and packed extensions on local server
await this.installUIDependenciesAndPackedExtensions(manifest);
return promise;
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
} else {
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
}
@@ -177,13 +171,6 @@ export class MultiExtensionManagementService extends Disposable implements IExte
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise<void> {
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None);
const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled();
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
@@ -191,49 +178,6 @@ export class MultiExtensionManagementService extends Disposable implements IExte
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token);
return values(result);
}
private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {
if (toGet.length === 0) {
return Promise.resolve();
}
const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token)));
const uiExtensionsManifests: IExtensionManifest[] = [];
for (let idx = 0; idx < extensions.length; idx++) {
const extension = extensions[idx];
const manifest = manifests[idx];
if (manifest && isUIExtension(manifest, this.configurationService)) {
result.set(extension.identifier.id.toLowerCase(), extension);
uiExtensionsManifests.push(manifest);
}
}
toGet = [];
for (const uiExtensionManifest of uiExtensionsManifests) {
if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) {
for (const id of uiExtensionManifest.extensionDependencies) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
if (isNonEmptyArray(uiExtensionManifest.extensionPack)) {
for (const id of uiExtensionManifest.extensionPack) {
if (!result.has(id.toLowerCase())) {
toGet.push(id);
}
}
}
}
return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token);
}
}
registerSingleton(IExtensionManagementService, MultiExtensionManagementService);

View File

@@ -103,22 +103,33 @@ function setupProxyResolution(
let results: ConnectionResult[] = [];
function logEvent() {
timeout = undefined;
/* __GDPR__
"resolveProxy" : {
"count": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cacheRolls": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"envNoProxyCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, envNoProxyCount, results });
type ResolveProxyClassification = {
count: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
errorCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheSize: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
cacheRolls: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
envCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
settingsCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
localhostCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
envNoProxyCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
results: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
type ResolveProxyEvent = {
count: number;
duration: number;
errorCount: number;
cacheCount: number;
cacheSize: number;
cacheRolls: number;
envCount: number;
settingsCount: number;
localhostCount: number;
envNoProxyCount: number;
results: ConnectionResult[];
};
mainThreadTelemetry.$publicLog2<ResolveProxyEvent, ResolveProxyClassification>('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, envNoProxyCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = envNoProxyCount = 0;
results = [];
}
@@ -403,7 +414,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku
const modules = lookup[request];
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
if (ext && ext.enableProposedApi) {
return modules[(<any>ext).proxySupport] || modules.onRequest;
return (modules as any)[(<any>ext).proxySupport] || modules.onRequest;
}
return modules.default;
};
@@ -457,8 +468,8 @@ async function readCaCertificates() {
return undefined;
}
function readWindowsCaCertificates() {
const winCA = require.__$__nodeRequire<any>('vscode-windows-ca-certs');
async function readWindowsCaCertificates() {
const winCA = await import('vscode-windows-ca-certs');
let ders: any[] = [];
const store = winCA();
@@ -471,22 +482,26 @@ function readWindowsCaCertificates() {
store.done();
}
const seen = {};
const certs = ders.map(derToPem)
.filter(pem => !seen[pem] && (seen[pem] = true));
const certs = new Set(ders.map(derToPem));
return {
certs,
certs: Array.from(certs),
append: true
};
}
async function readMacCaCertificates() {
const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8', maxBuffer: 1024 * 1024 })).stdout;
const seen = {};
const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true));
const stdout = await new Promise<string>((resolve, reject) => {
const child = cp.spawn('/usr/bin/security', ['find-certificate', '-a', '-p']);
const stdout: string[] = [];
child.stdout.setEncoding('utf8');
child.stdout.on('data', str => stdout.push(str));
child.on('error', reject);
child.on('exit', code => code ? reject(code) : resolve(stdout.join('')));
});
const certs = new Set(stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length));
return {
certs,
certs: Array.from(certs),
append: true
};
}
@@ -500,11 +515,10 @@ async function readLinuxCaCertificates() {
for (const certPath of linuxCaCertificatePaths) {
try {
const content = await promisify(fs.readFile)(certPath, { encoding: 'utf8' });
const seen = {};
const certs = content.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true));
const certs = new Set(content.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length));
return {
certs,
certs: Array.from(certs),
append: false
};
} catch (err) {