mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Initial VS Code 1.19 source merge (#571)
* Initial 1.19 xcopy * Fix yarn build * Fix numerous build breaks * Next batch of build break fixes * More build break fixes * Runtime breaks * Additional post merge fixes * Fix windows setup file * Fix test failures. * Update license header blocks to refer to source eula
This commit is contained in:
@@ -25,12 +25,11 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { generateRandomPipeName, Protocol } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { createServer, Server, Socket } from 'net';
|
||||
import Event, { Emitter, debounceEvent, mapEvent, anyEvent } from 'vs/base/common/event';
|
||||
import { fromEventEmitter } from 'vs/base/node/event';
|
||||
import Event, { Emitter, debounceEvent, mapEvent, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event';
|
||||
import { IInitData, IWorkspaceData, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/common/crashReporterService';
|
||||
import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService';
|
||||
import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-browser/broadcastService';
|
||||
import { isEqual } from 'vs/base/common/paths';
|
||||
import { EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
|
||||
@@ -56,6 +55,7 @@ export class ExtensionHostProcessWorker {
|
||||
|
||||
// Resources, in order they get acquired/created when .start() is called:
|
||||
private _namedPipeServer: Server;
|
||||
private _inspectPort: number;
|
||||
private _extensionHostProcess: ChildProcess;
|
||||
private _extensionHostConnection: Socket;
|
||||
private _messageProtocol: TPromise<IMessagePassingProtocol>;
|
||||
@@ -131,13 +131,12 @@ export class ExtensionHostProcessWorker {
|
||||
}
|
||||
|
||||
if (!this._messageProtocol) {
|
||||
this._messageProtocol = TPromise.join<any>([this._tryListenOnPipe(), this._tryFindDebugPort()]).then((data: [string, number]) => {
|
||||
this._messageProtocol = TPromise.join([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => {
|
||||
const pipeName = data[0];
|
||||
// The port will be 0 if there's no need to debug or if a free port was not found
|
||||
const port = data[1];
|
||||
const portData = data[1];
|
||||
|
||||
const opts = {
|
||||
env: objects.mixin(objects.clone(process.env), {
|
||||
env: objects.mixin(objects.deepClone(process.env), {
|
||||
AMD_ENTRYPOINT: 'vs/workbench/node/extensionHostProcess',
|
||||
PIPE_LOGGING: 'true',
|
||||
VERBOSE_LOGGING: true,
|
||||
@@ -152,12 +151,22 @@ export class ExtensionHostProcessWorker {
|
||||
// We detach because we have noticed that when the renderer exits, its child processes
|
||||
// (i.e. extension host) are taken down in a brutal fashion by the OS
|
||||
detached: !!isWindows,
|
||||
execArgv: port
|
||||
? ['--nolazy', (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + port]
|
||||
: undefined,
|
||||
execArgv: <string[]>undefined,
|
||||
silent: true
|
||||
};
|
||||
|
||||
if (portData.actual) {
|
||||
opts.execArgv = [
|
||||
'--nolazy',
|
||||
(this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portData.actual
|
||||
];
|
||||
if (!portData.expected) {
|
||||
// No one asked for 'inspect' or 'inspect-brk', only us. We add another
|
||||
// option such that the extension host can manipulate the execArgv array
|
||||
opts.env.VSCODE_PREVENT_FOREIGN_INSPECT = true;
|
||||
}
|
||||
}
|
||||
|
||||
const crashReporterOptions = this._crashReporterService.getChildProcessStartOptions('extensionHost');
|
||||
if (crashReporterOptions) {
|
||||
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
|
||||
@@ -170,8 +179,8 @@ export class ExtensionHostProcessWorker {
|
||||
type Output = { data: string, format: string[] };
|
||||
this._extensionHostProcess.stdout.setEncoding('utf8');
|
||||
this._extensionHostProcess.stderr.setEncoding('utf8');
|
||||
const onStdout = fromEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
|
||||
const onStderr = fromEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
|
||||
const onStdout = fromNodeEventEmitter<string>(this._extensionHostProcess.stdout, 'data');
|
||||
const onStderr = fromNodeEventEmitter<string>(this._extensionHostProcess.stderr, 'data');
|
||||
const onOutput = anyEvent(
|
||||
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
|
||||
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
|
||||
@@ -208,15 +217,16 @@ export class ExtensionHostProcessWorker {
|
||||
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 && port) {
|
||||
if (this._isExtensionDevHost && portData.actual) {
|
||||
this._broadcastService.broadcast({
|
||||
channel: EXTENSION_ATTACH_BROADCAST_CHANNEL,
|
||||
payload: {
|
||||
debugId: this._environmentService.debugExtensionHost.debugId,
|
||||
port
|
||||
port: portData.actual
|
||||
}
|
||||
});
|
||||
}
|
||||
this._inspectPort = portData.actual;
|
||||
|
||||
// Help in case we fail to start it
|
||||
let startupTimeoutHandle: number;
|
||||
@@ -260,26 +270,27 @@ export class ExtensionHostProcessWorker {
|
||||
/**
|
||||
* Find a free port if extension host debugging is enabled.
|
||||
*/
|
||||
private _tryFindDebugPort(): TPromise<number> {
|
||||
const extensionHostPort = this._environmentService.debugExtensionHost.port;
|
||||
if (typeof extensionHostPort !== 'number') {
|
||||
return TPromise.wrap<number>(0);
|
||||
private _tryFindDebugPort(): TPromise<{ expected: number; actual: number }> {
|
||||
let expected: number;
|
||||
let startPort = 9333;
|
||||
if (typeof this._environmentService.debugExtensionHost.port === 'number') {
|
||||
startPort = expected = this._environmentService.debugExtensionHost.port;
|
||||
}
|
||||
return new TPromise<number>((c, e) => {
|
||||
findFreePort(extensionHostPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, (port) => {
|
||||
return new TPromise((c, e) => {
|
||||
return findFreePort(startPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => {
|
||||
if (!port) {
|
||||
console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color: black');
|
||||
return c(void 0);
|
||||
}
|
||||
if (port !== extensionHostPort) {
|
||||
console.warn(`%c[Extension Host] %cProvided debugging port ${extensionHostPort} is not free, using ${port} instead.`, 'color: blue', 'color: black');
|
||||
}
|
||||
if (this._isExtensionDevDebugBrk) {
|
||||
console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color: black');
|
||||
} else {
|
||||
console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color: black');
|
||||
if (expected && port !== expected) {
|
||||
console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color: black');
|
||||
}
|
||||
if (this._isExtensionDevDebugBrk) {
|
||||
console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color: black');
|
||||
} else {
|
||||
console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color: black');
|
||||
}
|
||||
}
|
||||
return c(port);
|
||||
return c({ expected, actual: port });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -364,7 +375,10 @@ export class ExtensionHostProcessWorker {
|
||||
extensions: extensionDescriptions,
|
||||
// Send configurations scopes only in development mode.
|
||||
configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes(this._configurationService.keys().default) } : configurationData,
|
||||
telemetryInfo
|
||||
telemetryInfo,
|
||||
args: this._environmentService.args,
|
||||
execPath: this._environmentService.execPath,
|
||||
windowId: this._windowService.getCurrentWindowId()
|
||||
};
|
||||
return r;
|
||||
});
|
||||
@@ -427,6 +441,10 @@ export class ExtensionHostProcessWorker {
|
||||
}
|
||||
}
|
||||
|
||||
public getInspectPort(): number {
|
||||
return this._inspectPort;
|
||||
}
|
||||
|
||||
public terminate(): void {
|
||||
if (this._terminating) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IExtensionService, IExtensionDescription, ProfileSession, IExtensionHostProfile, ProfileSegmentId } from 'vs/platform/extensions/common/extensions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { realpathSync } from 'vs/base/node/extfs';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
import * as path from 'path';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { setTimeout } from 'timers';
|
||||
import { Profile, ProfileNode } from 'v8-inspect-profiler';
|
||||
|
||||
export class ExtensionHostProfiler {
|
||||
|
||||
constructor(private readonly _port: number, @IExtensionService private readonly _extensionService: IExtensionService) {
|
||||
}
|
||||
|
||||
public start(): TPromise<ProfileSession> {
|
||||
return TPromise.wrap(import('v8-inspect-profiler')).then(profiler => {
|
||||
return profiler.startProfiling({ port: this._port }).then(session => {
|
||||
return {
|
||||
stop: () => {
|
||||
return TPromise.wrap(session.stop()).then(profile => {
|
||||
return this._extensionService.getExtensions().then(extensions => {
|
||||
return this.distill(profile.profile, extensions);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private distill(profile: Profile, extensions: IExtensionDescription[]): IExtensionHostProfile {
|
||||
let searchTree = TernarySearchTree.forPaths<IExtensionDescription>();
|
||||
for (let extension of extensions) {
|
||||
searchTree.set(realpathSync(extension.extensionFolderPath), extension);
|
||||
}
|
||||
|
||||
let nodes = profile.nodes;
|
||||
let idsToNodes = new Map<number, ProfileNode>();
|
||||
let idsToSegmentId = new Map<number, ProfileSegmentId>();
|
||||
for (let node of nodes) {
|
||||
idsToNodes.set(node.id, node);
|
||||
}
|
||||
|
||||
function visit(node: ProfileNode, segmentId: ProfileSegmentId) {
|
||||
if (!segmentId) {
|
||||
switch (node.callFrame.functionName) {
|
||||
case '(root)':
|
||||
break;
|
||||
case '(program)':
|
||||
segmentId = 'program';
|
||||
break;
|
||||
case '(garbage collector)':
|
||||
segmentId = 'gc';
|
||||
break;
|
||||
default:
|
||||
segmentId = 'self';
|
||||
break;
|
||||
}
|
||||
} else if (segmentId === 'self' && node.callFrame.url) {
|
||||
let extension = searchTree.findSubstr(node.callFrame.url);
|
||||
if (extension) {
|
||||
segmentId = extension.id;
|
||||
}
|
||||
}
|
||||
idsToSegmentId.set(node.id, segmentId);
|
||||
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
visit(idsToNodes.get(child), segmentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(nodes[0], null);
|
||||
|
||||
let samples = profile.samples;
|
||||
let timeDeltas = profile.timeDeltas;
|
||||
let distilledDeltas: number[] = [];
|
||||
let distilledIds: ProfileSegmentId[] = [];
|
||||
|
||||
let currSegmentTime = 0;
|
||||
let currSegmentId = void 0;
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
let id = samples[i];
|
||||
let segmentId = idsToSegmentId.get(id);
|
||||
if (segmentId !== currSegmentId) {
|
||||
if (currSegmentId) {
|
||||
distilledIds.push(currSegmentId);
|
||||
distilledDeltas.push(currSegmentTime);
|
||||
}
|
||||
currSegmentId = segmentId;
|
||||
currSegmentTime = 0;
|
||||
}
|
||||
currSegmentTime += timeDeltas[i];
|
||||
}
|
||||
if (currSegmentId) {
|
||||
distilledIds.push(currSegmentId);
|
||||
distilledDeltas.push(currSegmentTime);
|
||||
}
|
||||
idsToNodes = null;
|
||||
idsToSegmentId = null;
|
||||
searchTree = null;
|
||||
|
||||
return {
|
||||
startTime: profile.startTime,
|
||||
endTime: profile.endTime,
|
||||
deltas: distilledDeltas,
|
||||
ids: distilledIds,
|
||||
data: profile,
|
||||
getAggregatedTimes: () => {
|
||||
let segmentsToTime = new Map<ProfileSegmentId, number>();
|
||||
for (let i = 0; i < distilledIds.length; i++) {
|
||||
let id = distilledIds[i];
|
||||
segmentsToTime.set(id, (segmentsToTime.get(id) || 0) + distilledDeltas[i]);
|
||||
}
|
||||
return segmentsToTime;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('exthost.profile.start', async accessor => {
|
||||
const statusbarService = accessor.get(IStatusbarService);
|
||||
const extensionService = accessor.get(IExtensionService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
const handle = statusbarService.addEntry({ text: localize('message', "$(zap) Profiling Extension Host...") }, StatusbarAlignment.LEFT);
|
||||
|
||||
extensionService.startExtensionHostProfile().then(session => {
|
||||
setTimeout(() => {
|
||||
session.stop().then(result => {
|
||||
result.getAggregatedTimes().forEach((val, index) => {
|
||||
console.log(`${index} : ${Math.round(val / 1000)} ms`);
|
||||
});
|
||||
let profilePath = path.join(environmentService.userHome, 'extHostProfile.cpuprofile');
|
||||
console.log(`Saving profile at ${profilePath}`);
|
||||
return writeFile(profilePath, JSON.stringify(result.data));
|
||||
}).then(() => {
|
||||
handle.dispose();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
@@ -6,30 +6,25 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import pfs = require('vs/base/node/pfs');
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { groupBy, values } from 'vs/base/common/collections';
|
||||
import { join, normalize, extname } from 'path';
|
||||
import json = require('vs/base/common/json');
|
||||
import Types = require('vs/base/common/types');
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { isValidExtensionDescription } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import * as semver from 'semver';
|
||||
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
|
||||
const MANIFEST_FILE = 'package.json';
|
||||
|
||||
const devMode = !!process.env['VSCODE_DEV'];
|
||||
interface NlsConfiguration {
|
||||
locale: string;
|
||||
pseudo: boolean;
|
||||
export interface NlsConfiguration {
|
||||
readonly devMode: boolean;
|
||||
readonly locale: string;
|
||||
readonly pseudo: boolean;
|
||||
}
|
||||
const nlsConfig: NlsConfiguration = {
|
||||
locale: Platform.locale,
|
||||
pseudo: Platform.locale === 'pseudo'
|
||||
};
|
||||
|
||||
export interface ILog {
|
||||
error(source: string, message: string): void;
|
||||
@@ -39,11 +34,11 @@ export interface ILog {
|
||||
|
||||
abstract class ExtensionManifestHandler {
|
||||
|
||||
protected _ourVersion: string;
|
||||
protected _log: ILog;
|
||||
protected _absoluteFolderPath: string;
|
||||
protected _isBuiltin: boolean;
|
||||
protected _absoluteManifestPath: string;
|
||||
protected readonly _ourVersion: string;
|
||||
protected readonly _log: ILog;
|
||||
protected readonly _absoluteFolderPath: string;
|
||||
protected readonly _isBuiltin: boolean;
|
||||
protected readonly _absoluteManifestPath: string;
|
||||
|
||||
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean) {
|
||||
this._ourVersion = ourVersion;
|
||||
@@ -82,6 +77,13 @@ class ExtensionManifestParser extends ExtensionManifestHandler {
|
||||
|
||||
class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
|
||||
private readonly _nlsConfig: NlsConfiguration;
|
||||
|
||||
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, nlsConfig: NlsConfiguration) {
|
||||
super(ourVersion, log, absoluteFolderPath, isBuiltin);
|
||||
this._nlsConfig = nlsConfig;
|
||||
}
|
||||
|
||||
public replaceNLS(extensionDescription: IExtensionDescription): TPromise<IExtensionDescription> {
|
||||
let extension = extname(this._absoluteManifestPath);
|
||||
let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length);
|
||||
@@ -90,7 +92,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
if (!exists) {
|
||||
return extensionDescription;
|
||||
}
|
||||
return ExtensionManifestNLSReplacer.findMessageBundles(basename).then((messageBundle) => {
|
||||
return ExtensionManifestNLSReplacer.findMessageBundles(this._nlsConfig, basename).then((messageBundle) => {
|
||||
if (!messageBundle.localized) {
|
||||
return extensionDescription;
|
||||
}
|
||||
@@ -106,7 +108,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
return extensionDescription;
|
||||
}
|
||||
|
||||
ExtensionManifestNLSReplacer._replaceNLStrings(extensionDescription, messages, originalMessages, this._log, this._absoluteFolderPath);
|
||||
ExtensionManifestNLSReplacer._replaceNLStrings(this._nlsConfig, extensionDescription, messages, originalMessages, this._log, this._absoluteFolderPath);
|
||||
return extensionDescription;
|
||||
});
|
||||
}, (err) => {
|
||||
@@ -136,7 +138,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
* Finds localized message bundle and the original (unlocalized) one.
|
||||
* If the localized file is not present, returns null for the original and marks original as localized.
|
||||
*/
|
||||
private static findMessageBundles(basename: string): TPromise<{ localized: string, original: string }> {
|
||||
private static findMessageBundles(nlsConfig: NlsConfiguration, basename: string): TPromise<{ localized: string, original: string }> {
|
||||
return new TPromise<{ localized: string, original: string }>((c, e, p) => {
|
||||
function loop(basename: string, locale: string): void {
|
||||
let toCheck = `${basename}.nls.${locale}.json`;
|
||||
@@ -154,7 +156,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
});
|
||||
}
|
||||
|
||||
if (devMode || nlsConfig.pseudo || !nlsConfig.locale) {
|
||||
if (nlsConfig.devMode || nlsConfig.pseudo || !nlsConfig.locale) {
|
||||
return c({ localized: basename + '.nls.json', original: null });
|
||||
}
|
||||
loop(basename, nlsConfig.locale);
|
||||
@@ -165,10 +167,10 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
* This routine makes the following assumptions:
|
||||
* The root element is an object literal
|
||||
*/
|
||||
private static _replaceNLStrings<T>(literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string }, log: ILog, messageScope: string): void {
|
||||
private static _replaceNLStrings<T>(nlsConfig: NlsConfiguration, literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string }, log: ILog, messageScope: string): void {
|
||||
function processEntry(obj: any, key: string | number, command?: boolean) {
|
||||
let value = obj[key];
|
||||
if (Types.isString(value)) {
|
||||
if (types.isString(value)) {
|
||||
let str = <string>value;
|
||||
let length = str.length;
|
||||
if (length > 1 && str[0] === '%' && str[length - 1] === '%') {
|
||||
@@ -184,13 +186,13 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
log.warn(messageScope, nls.localize('missingNLSKey', "Couldn't find message for key {0}.", messageKey));
|
||||
}
|
||||
}
|
||||
} else if (Types.isObject(value)) {
|
||||
} else if (types.isObject(value)) {
|
||||
for (let k in value) {
|
||||
if (value.hasOwnProperty(k)) {
|
||||
k === 'commands' ? processEntry(value, k, true) : processEntry(value, k, command);
|
||||
}
|
||||
}
|
||||
} else if (Types.isArray(value)) {
|
||||
} else if (types.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
processEntry(value, i, command);
|
||||
}
|
||||
@@ -201,7 +203,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
if (literal.hasOwnProperty(key)) {
|
||||
processEntry(literal, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,17 +253,48 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionScannerInput {
|
||||
|
||||
public mtime: number;
|
||||
|
||||
constructor(
|
||||
public readonly ourVersion: string,
|
||||
public readonly commit: string,
|
||||
public readonly locale: string,
|
||||
public readonly devMode: boolean,
|
||||
public readonly absoluteFolderPath: string,
|
||||
public readonly isBuiltin: boolean
|
||||
) {
|
||||
// Keep empty!! (JSON.parse)
|
||||
}
|
||||
|
||||
public static createNLSConfig(input: ExtensionScannerInput): NlsConfiguration {
|
||||
return {
|
||||
devMode: input.devMode,
|
||||
locale: input.locale,
|
||||
pseudo: input.locale === 'pseudo'
|
||||
};
|
||||
}
|
||||
|
||||
public static equals(a: ExtensionScannerInput, b: ExtensionScannerInput): boolean {
|
||||
return (
|
||||
a.ourVersion === b.ourVersion
|
||||
&& a.commit === b.commit
|
||||
&& a.locale === b.locale
|
||||
&& a.devMode === b.devMode
|
||||
&& a.absoluteFolderPath === b.absoluteFolderPath
|
||||
&& a.isBuiltin === b.isBuiltin
|
||||
&& a.mtime === b.mtime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionScanner {
|
||||
|
||||
/**
|
||||
* Read the extension defined in `absoluteFolderPath`
|
||||
*/
|
||||
public static scanExtension(
|
||||
version: string,
|
||||
log: ILog,
|
||||
absoluteFolderPath: string,
|
||||
isBuiltin: boolean
|
||||
): TPromise<IExtensionDescription> {
|
||||
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, nlsConfig: NlsConfiguration): TPromise<IExtensionDescription> {
|
||||
absoluteFolderPath = normalize(absoluteFolderPath);
|
||||
|
||||
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin);
|
||||
@@ -270,7 +303,7 @@ export class ExtensionScanner {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nlsReplacer = new ExtensionManifestNLSReplacer(version, log, absoluteFolderPath, isBuiltin);
|
||||
let nlsReplacer = new ExtensionManifestNLSReplacer(version, log, absoluteFolderPath, isBuiltin, nlsConfig);
|
||||
return nlsReplacer.replaceNLS(extensionDescription);
|
||||
}).then((extensionDescription) => {
|
||||
if (extensionDescription === null) {
|
||||
@@ -285,81 +318,87 @@ export class ExtensionScanner {
|
||||
/**
|
||||
* Scan a list of extensions defined in `absoluteFolderPath`
|
||||
*/
|
||||
public static scanExtensions(
|
||||
version: string,
|
||||
log: ILog,
|
||||
absoluteFolderPath: string,
|
||||
isBuiltin: boolean
|
||||
): TPromise<IExtensionDescription[]> {
|
||||
let obsolete = TPromise.as({});
|
||||
public static async scanExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
const absoluteFolderPath = input.absoluteFolderPath;
|
||||
const isBuiltin = input.isBuiltin;
|
||||
|
||||
if (!isBuiltin) {
|
||||
obsolete = pfs.readFile(join(absoluteFolderPath, '.obsolete'), 'utf8')
|
||||
.then(raw => JSON.parse(raw))
|
||||
.then(null, err => ({}));
|
||||
}
|
||||
try {
|
||||
let obsolete: { [folderName: string]: boolean; } = {};
|
||||
if (!isBuiltin) {
|
||||
try {
|
||||
const obsoleteFileContents = await pfs.readFile(join(absoluteFolderPath, '.obsolete'), 'utf8');
|
||||
obsolete = JSON.parse(obsoleteFileContents);
|
||||
} catch (err) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
|
||||
return obsolete.then(obsolete => {
|
||||
return pfs.readDirsInDir(absoluteFolderPath)
|
||||
.then(folders => {
|
||||
if (isBuiltin) {
|
||||
return folders;
|
||||
const rawFolders = await pfs.readDirsInDir(absoluteFolderPath);
|
||||
let folders: string[] = null;
|
||||
if (isBuiltin) {
|
||||
folders = rawFolders;
|
||||
} else {
|
||||
// TODO: align with extensionsService
|
||||
const nonGallery: string[] = [];
|
||||
const gallery: { folder: string; id: string; version: string; }[] = [];
|
||||
|
||||
rawFolders.forEach(folder => {
|
||||
if (obsolete[folder]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: align with extensionsService
|
||||
const nonGallery: string[] = [];
|
||||
const gallery: { folder: string; id: string; version: string; }[] = [];
|
||||
const { id, version } = getIdAndVersionFromLocalExtensionId(folder);
|
||||
|
||||
folders.forEach(folder => {
|
||||
if (obsolete[folder]) {
|
||||
return;
|
||||
}
|
||||
if (!id || !version) {
|
||||
nonGallery.push(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, version } = getIdAndVersionFromLocalExtensionId(folder);
|
||||
|
||||
if (!id || !version) {
|
||||
nonGallery.push(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
gallery.push({ folder, id, version });
|
||||
});
|
||||
|
||||
const byId = values(groupBy(gallery, p => p.id));
|
||||
const latest = byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0])
|
||||
.map(a => a.folder);
|
||||
|
||||
return [...nonGallery, ...latest];
|
||||
})
|
||||
.then(folders => TPromise.join(folders.map(f => this.scanExtension(version, log, join(absoluteFolderPath, f), isBuiltin))))
|
||||
.then(extensionDescriptions => extensionDescriptions.filter(item => item !== null))
|
||||
.then(null, err => {
|
||||
log.error(absoluteFolderPath, err);
|
||||
return [];
|
||||
gallery.push({ folder, id, version });
|
||||
});
|
||||
});
|
||||
|
||||
const byId = values(groupBy(gallery, p => p.id));
|
||||
const latest = byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0])
|
||||
.map(a => a.folder);
|
||||
|
||||
folders = [...nonGallery, ...latest];
|
||||
}
|
||||
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
let extensionDescriptions = await TPromise.join(folders.map(f => this.scanExtension(input.ourVersion, log, join(absoluteFolderPath, f), isBuiltin, nlsConfig)));
|
||||
extensionDescriptions = extensionDescriptions.filter(item => item !== null);
|
||||
extensionDescriptions.sort((a, b) => {
|
||||
if (a.extensionFolderPath < b.extensionFolderPath) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
return extensionDescriptions;
|
||||
} catch (err) {
|
||||
log.error(absoluteFolderPath, err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combination of scanExtension and scanExtensions: If an extension manifest is found at root, we load just this extension,
|
||||
* otherwise we assume the folder contains multiple extensions.
|
||||
*/
|
||||
public static scanOneOrMultipleExtensions(
|
||||
version: string,
|
||||
log: ILog,
|
||||
absoluteFolderPath: string,
|
||||
isBuiltin: boolean
|
||||
): TPromise<IExtensionDescription[]> {
|
||||
public static scanOneOrMultipleExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
const absoluteFolderPath = input.absoluteFolderPath;
|
||||
const isBuiltin = input.isBuiltin;
|
||||
|
||||
return pfs.fileExists(join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
|
||||
if (exists) {
|
||||
return this.scanExtension(version, log, absoluteFolderPath, isBuiltin).then((extensionDescription) => {
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, nlsConfig).then((extensionDescription) => {
|
||||
if (extensionDescription === null) {
|
||||
return [];
|
||||
}
|
||||
return [extensionDescription];
|
||||
});
|
||||
}
|
||||
return this.scanExtensions(version, log, absoluteFolderPath, isBuiltin);
|
||||
return this.scanExtensions(input, log);
|
||||
}, (err) => {
|
||||
log.error(absoluteFolderPath, err);
|
||||
return [];
|
||||
|
||||
@@ -6,18 +6,21 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
import * as path from 'path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService, ExtensionPointContribution, ActivationTimes } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, getGloballyDisabledExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService, ExtensionPointContribution, ActivationTimes, IExtensionHostInformation, ProfileSession, USER_MANIFEST_CACHE_FILE, BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionEnablementService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { ExtensionScanner, ILog } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ExtensionScanner, ILog, ExtensionScannerInput } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
|
||||
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -26,13 +29,18 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost';
|
||||
import { MainThreadService } from 'vs/workbench/services/thread/electron-browser/threadService';
|
||||
import { Barrier } from 'vs/workbench/services/extensions/node/barrier';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { startTimer } from 'vs/base/node/startupTimers';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { mark, time } from 'vs/base/common/performance';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
|
||||
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
|
||||
import product from 'vs/platform/node/product';
|
||||
|
||||
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
|
||||
|
||||
@@ -48,17 +56,21 @@ function messageWithSource2(source: string, message: string): string {
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = TPromise.as<void>(void 0);
|
||||
const NO_OP_VOID_PROMISE = TPromise.wrap<void>(void 0);
|
||||
|
||||
export class ExtensionService implements IExtensionService {
|
||||
export class ExtensionService extends Disposable implements IExtensionService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _onDidRegisterExtensions: Emitter<IExtensionDescription[]>;
|
||||
|
||||
private _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _barrier: Barrier;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
private readonly _isDev: boolean;
|
||||
private readonly _extensionsStatus: { [id: string]: IExtensionsStatus };
|
||||
private readonly _extensionsMessages: { [id: string]: IMessage[] };
|
||||
private _allRequestedActivateEvents: { [activationEvent: string]: boolean; };
|
||||
|
||||
private readonly _onDidChangeExtensionsStatus: Emitter<string[]> = this._register(new Emitter<string[]>());
|
||||
public readonly onDidChangeExtensionsStatus: Event<string[]> = this._onDidChangeExtensionsStatus.event;
|
||||
|
||||
// --- Members used per extension host process
|
||||
|
||||
@@ -67,6 +79,7 @@ export class ExtensionService implements IExtensionService {
|
||||
*/
|
||||
private _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; };
|
||||
private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; };
|
||||
private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; };
|
||||
private _extensionHostProcessWorker: ExtensionHostProcessWorker;
|
||||
private _extensionHostProcessThreadService: MainThreadService;
|
||||
private _extensionHostProcessCustomers: IDisposable[];
|
||||
@@ -82,23 +95,49 @@ export class ExtensionService implements IExtensionService {
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IWindowService private readonly _windowService: IWindowService
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService
|
||||
) {
|
||||
super();
|
||||
this._registry = null;
|
||||
this._barrier = new Barrier();
|
||||
this._installedExtensionsReady = new Barrier();
|
||||
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
|
||||
this._extensionsStatus = {};
|
||||
this._extensionsMessages = {};
|
||||
this._allRequestedActivateEvents = Object.create(null);
|
||||
|
||||
this._onDidRegisterExtensions = new Emitter<IExtensionDescription[]>();
|
||||
|
||||
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
|
||||
this._extensionHostProcessActivationTimes = Object.create(null);
|
||||
this._extensionHostExtensionRuntimeErrors = Object.create(null);
|
||||
this._extensionHostProcessWorker = null;
|
||||
this._extensionHostProcessThreadService = null;
|
||||
this._extensionHostProcessCustomers = [];
|
||||
this._extensionHostProcessProxy = null;
|
||||
|
||||
this._startExtensionHostProcess([]);
|
||||
this._scanAndHandleExtensions();
|
||||
lifecycleService.when(LifecyclePhase.Running).then(() => {
|
||||
// delay extension host creation and extension scanning
|
||||
// until after workbench is running
|
||||
this._startExtensionHostProcess([]);
|
||||
this._scanAndHandleExtensions();
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public get onDidRegisterExtensions(): Event<IExtensionDescription[]> {
|
||||
return this._onDidRegisterExtensions.event;
|
||||
}
|
||||
|
||||
public getExtensionHostInformation(): IExtensionHostInformation {
|
||||
if (!this._extensionHostProcessWorker) {
|
||||
throw errors.illegalState();
|
||||
}
|
||||
return <IExtensionHostInformation>{
|
||||
inspectPort: this._extensionHostProcessWorker.getInspectPort()
|
||||
};
|
||||
}
|
||||
|
||||
public restartExtensionHost(): void {
|
||||
@@ -115,8 +154,11 @@ export class ExtensionService implements IExtensionService {
|
||||
}
|
||||
|
||||
private _stopExtensionHostProcess(): void {
|
||||
const previouslyActivatedExtensionIds = Object.keys(this._extensionHostProcessActivationTimes);
|
||||
|
||||
this._extensionHostProcessFinishedActivateEvents = Object.create(null);
|
||||
this._extensionHostProcessActivationTimes = Object.create(null);
|
||||
this._extensionHostExtensionRuntimeErrors = Object.create(null);
|
||||
if (this._extensionHostProcessWorker) {
|
||||
this._extensionHostProcessWorker.dispose();
|
||||
this._extensionHostProcessWorker = null;
|
||||
@@ -135,6 +177,8 @@ export class ExtensionService implements IExtensionService {
|
||||
}
|
||||
this._extensionHostProcessCustomers = [];
|
||||
this._extensionHostProcessProxy = null;
|
||||
|
||||
this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds);
|
||||
}
|
||||
|
||||
private _startExtensionHostProcess(initialActivationEvents: string[]): void {
|
||||
@@ -216,7 +260,7 @@ export class ExtensionService implements IExtensionService {
|
||||
// ---- begin IExtensionService
|
||||
|
||||
public activateByEvent(activationEvent: string): TPromise<void> {
|
||||
if (this._barrier.isOpen()) {
|
||||
if (this._installedExtensionsReady.isOpen()) {
|
||||
// Extensions have been scanned and interpreted
|
||||
|
||||
if (!this._registry.containsActivationEvent(activationEvent)) {
|
||||
@@ -234,7 +278,7 @@ export class ExtensionService implements IExtensionService {
|
||||
// Record the fact that this activationEvent was requested (in case of a restart)
|
||||
this._allRequestedActivateEvents[activationEvent] = true;
|
||||
|
||||
return this._barrier.wait().then(() => this._activateByEvent(activationEvent));
|
||||
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,18 +293,18 @@ export class ExtensionService implements IExtensionService {
|
||||
});
|
||||
}
|
||||
|
||||
public onReady(): TPromise<boolean> {
|
||||
return this._barrier.wait();
|
||||
public whenInstalledExtensionsRegistered(): TPromise<boolean> {
|
||||
return this._installedExtensionsReady.wait();
|
||||
}
|
||||
|
||||
public getExtensions(): TPromise<IExtensionDescription[]> {
|
||||
return this.onReady().then(() => {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._registry.getAllExtensionDescriptions();
|
||||
});
|
||||
}
|
||||
|
||||
public readExtensionPointContributions<T>(extPoint: IExtensionPoint<T>): TPromise<ExtensionPointContribution<T>[]> {
|
||||
return this.onReady().then(() => {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
let availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
|
||||
let result: ExtensionPointContribution<T>[] = [], resultLen = 0;
|
||||
@@ -277,11 +321,30 @@ export class ExtensionService implements IExtensionService {
|
||||
}
|
||||
|
||||
public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } {
|
||||
return this._extensionsStatus;
|
||||
let result: { [id: string]: IExtensionsStatus; } = Object.create(null);
|
||||
if (this._registry) {
|
||||
const extensions = this._registry.getAllExtensionDescriptions();
|
||||
for (let i = 0, len = extensions.length; i < len; i++) {
|
||||
const extension = extensions[i];
|
||||
const id = extension.id;
|
||||
result[id] = {
|
||||
messages: this._extensionsMessages[id],
|
||||
activationTimes: this._extensionHostProcessActivationTimes[id],
|
||||
runtimeErrors: this._extensionHostExtensionRuntimeErrors[id],
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getExtensionsActivationTimes(): { [id: string]: ActivationTimes; } {
|
||||
return this._extensionHostProcessActivationTimes;
|
||||
public startExtensionHostProfile(): TPromise<ProfileSession> {
|
||||
if (this._extensionHostProcessWorker) {
|
||||
let port = this._extensionHostProcessWorker.getInspectPort();
|
||||
if (port) {
|
||||
return this._instantiationService.createInstance(ExtensionHostProfiler, port).start();
|
||||
}
|
||||
}
|
||||
throw new Error('Extension host not running or no inspect port available');
|
||||
}
|
||||
|
||||
// ---- end IExtensionService
|
||||
@@ -290,63 +353,100 @@ export class ExtensionService implements IExtensionService {
|
||||
|
||||
private _scanAndHandleExtensions(): void {
|
||||
|
||||
this._getRuntimeExtension()
|
||||
.then(runtimeExtensons => {
|
||||
this._registry = new ExtensionDescriptionRegistry(runtimeExtensons);
|
||||
|
||||
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++) {
|
||||
const clock = time(`handleExtensionPoint:${extensionPoints[i].name}`);
|
||||
try {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
} finally {
|
||||
clock.stop();
|
||||
}
|
||||
}
|
||||
|
||||
mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(availableExtensions);
|
||||
this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id));
|
||||
});
|
||||
}
|
||||
|
||||
private _getRuntimeExtension(): TPromise<IExtensionDescription[]> {
|
||||
const log = new Logger((severity, source, message) => {
|
||||
this._logOrShowMessage(severity, this._isDev ? messageWithSource2(source, message) : message);
|
||||
});
|
||||
|
||||
ExtensionService._scanInstalledExtensions(this._environmentService, log).then((installedExtensions) => {
|
||||
return ExtensionService._scanInstalledExtensions(this._instantiationService, this._messageService, this._environmentService, log)
|
||||
.then(({ system, user, development }) => {
|
||||
this._extensionEnablementService.migrateToIdentifiers(user); // TODO: @sandy Remove it after couple of milestones
|
||||
return this._extensionEnablementService.getDisabledExtensions()
|
||||
.then(disabledExtensions => {
|
||||
let result: { [extensionId: string]: IExtensionDescription; } = {};
|
||||
let extensionsToDisable: IExtensionIdentifier[] = [];
|
||||
let userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }];
|
||||
|
||||
// Migrate enablement service to use identifiers
|
||||
this._extensionEnablementService.migrateToIdentifiers(installedExtensions);
|
||||
system.forEach((systemExtension) => {
|
||||
// Disabling system extensions is not supported
|
||||
result[systemExtension.id] = systemExtension;
|
||||
});
|
||||
|
||||
const disabledExtensions = [
|
||||
...getGloballyDisabledExtensions(this._extensionEnablementService, this._storageService, installedExtensions),
|
||||
...this._extensionEnablementService.getWorkspaceDisabledExtensions()
|
||||
];
|
||||
user.forEach((userExtension) => {
|
||||
if (result.hasOwnProperty(userExtension.id)) {
|
||||
log.warn(userExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionFolderPath, userExtension.extensionFolderPath));
|
||||
}
|
||||
if (disabledExtensions.every(disabled => !areSameExtensions(disabled, userExtension))) {
|
||||
// Check if the extension is changed to system extension
|
||||
let userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: userExtension.id }))[0];
|
||||
if (userMigratedSystemExtension) {
|
||||
extensionsToDisable.push(userMigratedSystemExtension);
|
||||
} else {
|
||||
result[userExtension.id] = userExtension;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* __GDPR__
|
||||
"extensionsScanned" : {
|
||||
"totalCount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
|
||||
"disabledCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('extensionsScanned', {
|
||||
totalCount: installedExtensions.length,
|
||||
disabledCount: disabledExtensions.length
|
||||
development.forEach(developedExtension => {
|
||||
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionFolderPath));
|
||||
if (result.hasOwnProperty(developedExtension.id)) {
|
||||
log.warn(developedExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionFolderPath, developedExtension.extensionFolderPath));
|
||||
}
|
||||
// Do not disable extensions under development
|
||||
result[developedExtension.id] = developedExtension;
|
||||
});
|
||||
|
||||
const runtimeExtensions = Object.keys(result).map(name => result[name]);
|
||||
|
||||
this._telemetryService.publicLog('extensionsScanned', {
|
||||
totalCount: runtimeExtensions.length,
|
||||
disabledCount: disabledExtensions.length
|
||||
});
|
||||
|
||||
if (extensionsToDisable.length) {
|
||||
return TPromise.join(extensionsToDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled)))
|
||||
.then(() => {
|
||||
this._storageService.store(BetterMergeDisabledNowKey, true);
|
||||
return runtimeExtensions;
|
||||
});
|
||||
} else {
|
||||
return runtimeExtensions;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (disabledExtensions.length === 0) {
|
||||
return installedExtensions;
|
||||
}
|
||||
return installedExtensions.filter(e => disabledExtensions.every(disabled => !areSameExtensions(disabled, e)));
|
||||
|
||||
}).then((extensionDescriptions) => {
|
||||
this._registry = new ExtensionDescriptionRegistry(extensionDescriptions);
|
||||
|
||||
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++) {
|
||||
const clock = startTimer(`handleExtensionPoint:${extensionPoints[i].name}`);
|
||||
try {
|
||||
ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler);
|
||||
} finally {
|
||||
clock.stop();
|
||||
}
|
||||
}
|
||||
|
||||
this._barrier.open();
|
||||
});
|
||||
}
|
||||
|
||||
private _handleExtensionPointMessage(msg: IMessage) {
|
||||
|
||||
if (!this._extensionsStatus[msg.source]) {
|
||||
this._extensionsStatus[msg.source] = { messages: [] };
|
||||
if (!this._extensionsMessages[msg.source]) {
|
||||
this._extensionsMessages[msg.source] = [];
|
||||
}
|
||||
this._extensionsStatus[msg.source].messages.push(msg);
|
||||
this._extensionsMessages[msg.source].push(msg);
|
||||
|
||||
if (msg.source === this._environmentService.extensionDevelopmentPath) {
|
||||
// This message is about the extension currently being developed
|
||||
@@ -371,42 +471,157 @@ export class ExtensionService implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
private static _scanInstalledExtensions(environmentService: IEnvironmentService, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
const version = pkg.version;
|
||||
const builtinExtensions = ExtensionScanner.scanExtensions(version, log, SystemExtensionsRoot, true);
|
||||
const userExtensions = environmentService.disableExtensions || !environmentService.extensionsPath ? TPromise.as([]) : ExtensionScanner.scanExtensions(version, log, environmentService.extensionsPath, false);
|
||||
const developedExtensions = environmentService.disableExtensions || !environmentService.isExtensionDevelopment ? TPromise.as([]) : ExtensionScanner.scanOneOrMultipleExtensions(version, log, environmentService.extensionDevelopmentPath, false);
|
||||
private static async _validateExtensionsCache(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise<void> {
|
||||
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
return TPromise.join([builtinExtensions, userExtensions, developedExtensions]).then<IExtensionDescription[]>((extensionDescriptions: IExtensionDescription[][]) => {
|
||||
const builtinExtensions = extensionDescriptions[0];
|
||||
const userExtensions = extensionDescriptions[1];
|
||||
const developedExtensions = extensionDescriptions[2];
|
||||
const expected = await ExtensionScanner.scanExtensions(input, new NullLogger());
|
||||
|
||||
let result: { [extensionId: string]: IExtensionDescription; } = {};
|
||||
builtinExtensions.forEach((builtinExtension) => {
|
||||
result[builtinExtension.id] = builtinExtension;
|
||||
});
|
||||
userExtensions.forEach((userExtension) => {
|
||||
if (result.hasOwnProperty(userExtension.id)) {
|
||||
log.warn(userExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionFolderPath, userExtension.extensionFolderPath));
|
||||
}
|
||||
result[userExtension.id] = userExtension;
|
||||
});
|
||||
developedExtensions.forEach(developedExtension => {
|
||||
log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionFolderPath));
|
||||
if (result.hasOwnProperty(developedExtension.id)) {
|
||||
log.warn(developedExtension.extensionFolderPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionFolderPath, developedExtension.extensionFolderPath));
|
||||
}
|
||||
result[developedExtension.id] = developedExtension;
|
||||
});
|
||||
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
|
||||
const actual = cacheContents.result;
|
||||
|
||||
return Object.keys(result).map(name => result[name]);
|
||||
}).then(null, err => {
|
||||
log.error('', err);
|
||||
return [];
|
||||
if (objects.equals(expected, actual)) {
|
||||
// Cache is valid and running with it is perfectly fine...
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await pfs.del(cacheFile);
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
let message = nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window.");
|
||||
messageService.show(Severity.Info, {
|
||||
message: message,
|
||||
actions: [
|
||||
instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL),
|
||||
CloseAction
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): TPromise<IExtensionCacheData> {
|
||||
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
try {
|
||||
const cacheRawContents = await pfs.readFile(cacheFile, 'utf8');
|
||||
return JSON.parse(cacheRawContents);
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): TPromise<void> {
|
||||
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
try {
|
||||
await pfs.mkdirp(cacheFolder);
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
}
|
||||
|
||||
try {
|
||||
await pfs.writeFile(cacheFile, JSON.stringify(cacheContents));
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
}
|
||||
}
|
||||
|
||||
private static async _scanExtensionsWithCache(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
|
||||
if (input.devMode) {
|
||||
// Do not cache when running out of sources...
|
||||
return ExtensionScanner.scanExtensions(input, log);
|
||||
}
|
||||
|
||||
try {
|
||||
const folderStat = await pfs.stat(input.absoluteFolderPath);
|
||||
input.mtime = folderStat.mtime.getTime();
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
}
|
||||
|
||||
const cacheContents = await this._readExtensionCache(environmentService, cacheKey);
|
||||
if (cacheContents && cacheContents.input && ExtensionScannerInput.equals(cacheContents.input, input)) {
|
||||
// Validate the cache asynchronously after 5s
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this._validateExtensionsCache(instantiationService, messageService, environmentService, cacheKey, input);
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
}
|
||||
}, 5000);
|
||||
return cacheContents.result;
|
||||
}
|
||||
|
||||
const counterLogger = new CounterLogger(log);
|
||||
const result = await ExtensionScanner.scanExtensions(input, counterLogger);
|
||||
if (counterLogger.errorCnt === 0) {
|
||||
// Nothing bad happened => cache the result
|
||||
const cacheContents: IExtensionCacheData = {
|
||||
input: input,
|
||||
result: result
|
||||
};
|
||||
await this._writeExtensionCache(environmentService, cacheKey, cacheContents);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _scanInstalledExtensions(instantiationService: IInstantiationService, messageService: IMessageService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> {
|
||||
const version = pkg.version;
|
||||
const commit = product.commit;
|
||||
const devMode = !!process.env['VSCODE_DEV'];
|
||||
const locale = platform.locale;
|
||||
|
||||
const builtinExtensions = this._scanExtensionsWithCache(
|
||||
instantiationService,
|
||||
messageService,
|
||||
environmentService,
|
||||
BUILTIN_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, SystemExtensionsRoot, true),
|
||||
log
|
||||
);
|
||||
|
||||
const userExtensions = (
|
||||
environmentService.disableExtensions || !environmentService.extensionsPath
|
||||
? TPromise.as([])
|
||||
: this._scanExtensionsWithCache(
|
||||
instantiationService,
|
||||
messageService,
|
||||
environmentService,
|
||||
USER_MANIFEST_CACHE_FILE,
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false),
|
||||
log
|
||||
)
|
||||
);
|
||||
|
||||
// Always load developed extensions while extensions development
|
||||
const developedExtensions = (
|
||||
environmentService.isExtensionDevelopment
|
||||
? ExtensionScanner.scanOneOrMultipleExtensions(
|
||||
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionDevelopmentPath, false), log
|
||||
)
|
||||
: TPromise.as([])
|
||||
);
|
||||
|
||||
return TPromise.join([builtinExtensions, userExtensions, developedExtensions])
|
||||
.then((extensionDescriptions: IExtensionDescription[][]) => {
|
||||
const system = extensionDescriptions[0];
|
||||
const user = extensionDescriptions[1];
|
||||
const development = extensionDescriptions[2];
|
||||
return { system, user, development };
|
||||
}).then(null, err => {
|
||||
log.error('', err);
|
||||
return { system: [], user: [], development: [] };
|
||||
});
|
||||
}
|
||||
|
||||
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++) {
|
||||
@@ -452,9 +667,37 @@ export class ExtensionService implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number): void {
|
||||
this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime);
|
||||
public _onExtensionActivated(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
|
||||
this._extensionHostProcessActivationTimes[extensionId] = new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionRuntimeError(extensionId: string, err: Error): void {
|
||||
if (!this._extensionHostExtensionRuntimeErrors[extensionId]) {
|
||||
this._extensionHostExtensionRuntimeErrors[extensionId] = [];
|
||||
}
|
||||
this._extensionHostExtensionRuntimeErrors[extensionId].push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _addMessage(extensionId: string, severity: Severity, message: string): void {
|
||||
if (!this._extensionsMessages[extensionId]) {
|
||||
this._extensionsMessages[extensionId] = [];
|
||||
}
|
||||
this._extensionsMessages[extensionId].push({
|
||||
type: severity,
|
||||
message: message,
|
||||
source: null,
|
||||
extensionId: null,
|
||||
extensionPointId: null
|
||||
});
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
}
|
||||
|
||||
interface IExtensionCacheData {
|
||||
input: ExtensionScannerInput;
|
||||
result: IExtensionDescription[];
|
||||
}
|
||||
|
||||
export class Logger implements ILog {
|
||||
@@ -479,3 +722,34 @@ export class Logger implements ILog {
|
||||
this._messageHandler(Severity.Info, source, message);
|
||||
}
|
||||
}
|
||||
|
||||
class CounterLogger implements ILog {
|
||||
|
||||
public errorCnt = 0;
|
||||
public warnCnt = 0;
|
||||
public infoCnt = 0;
|
||||
|
||||
constructor(private readonly _actual: ILog) {
|
||||
}
|
||||
|
||||
public error(source: string, message: string): void {
|
||||
this._actual.error(source, message);
|
||||
}
|
||||
|
||||
public warn(source: string, message: string): void {
|
||||
this._actual.warn(source, message);
|
||||
}
|
||||
|
||||
public info(source: string, message: string): void {
|
||||
this._actual.info(source, message);
|
||||
}
|
||||
}
|
||||
|
||||
class NullLogger implements ILog {
|
||||
public error(source: string, message: string): void {
|
||||
}
|
||||
public warn(source: string, message: string): void {
|
||||
}
|
||||
public info(source: string, message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
/**
|
||||
* A barrier that is initially closed and then becomes opened permanently.
|
||||
*/
|
||||
export class Barrier {
|
||||
|
||||
private _isOpen: boolean;
|
||||
private _promise: TPromise<boolean>;
|
||||
private _completePromise: (v: boolean) => void;
|
||||
|
||||
constructor() {
|
||||
this._isOpen = false;
|
||||
this._promise = new TPromise<boolean>((c, e, p) => {
|
||||
this._completePromise = c;
|
||||
}, () => {
|
||||
console.warn('You should really not try to cancel this ready promise!');
|
||||
});
|
||||
}
|
||||
|
||||
public isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this._isOpen = true;
|
||||
this._completePromise(true);
|
||||
}
|
||||
|
||||
public wait(): TPromise<boolean> {
|
||||
return this._promise;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export class LazyPromise {
|
||||
export class LazyPromise implements TPromise<any> {
|
||||
|
||||
private _onCancel: () => void;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user