extensionHostMain.ts 8.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8 9
import * as fs from 'fs';
import * as crypto from 'crypto';
E
Erich Gamma 已提交
10 11
import nls = require('vs/nls');
import pfs = require('vs/base/node/pfs');
J
Johannes Rieken 已提交
12
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
13
import paths = require('vs/base/common/paths');
14
import { createApiFactory, defineAPI } from 'vs/workbench/api/node/extHost.api.impl';
J
Johannes Rieken 已提交
15 16 17 18 19
import { IMainProcessExtHostIPC } from 'vs/platform/extensions/common/ipcRemoteCom';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { ExtHostThreadService } from 'vs/workbench/services/thread/common/extHostThreadService';
import { RemoteTelemetryService } from 'vs/workbench/api/node/extHostTelemetry';
import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace';
J
Johannes Rieken 已提交
20
import { IInitData, IEnvironment, MainContext } from 'vs/workbench/api/node/extHost.protocol';
21
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
22

B
Benjamin Pasero 已提交
23
const nativeExit = process.exit.bind(process);
J
Johannes Rieken 已提交
24
process.exit = function () {
25 26 27
	const err = new Error('An extension called process.exit() and this was prevented.');
	console.warn((<any>err).stack);
};
28 29 30 31
export function exit(code?: number) {
	nativeExit(code);
}

E
Erich Gamma 已提交
32
interface ITestRunner {
B
Benjamin Pasero 已提交
33
	run(testsRoot: string, clb: (error: Error, failures?: number) => void): void;
E
Erich Gamma 已提交
34 35
}

A
Alex Dima 已提交
36
export class ExtensionHostMain {
E
Erich Gamma 已提交
37

38
	private _isTerminating: boolean;
A
Alex Dima 已提交
39
	private _contextService: IWorkspaceContextService;
B
Benjamin Pasero 已提交
40
	private _environment: IEnvironment;
A
Alex Dima 已提交
41
	private _extensionService: ExtHostExtensionService;
42

43
	constructor(remoteCom: IMainProcessExtHostIPC, initData: IInitData) {
44
		this._isTerminating = false;
45

B
Benjamin Pasero 已提交
46 47
		this._environment = initData.environment;

48
		this._contextService = new WorkspaceContextService(initData.contextService.workspace);
49
		const workspaceStoragePath = this._getOrCreateWorkspaceStoragePath();
50 51 52 53 54

		const threadService = new ExtHostThreadService(remoteCom);

		const telemetryService = new RemoteTelemetryService('pluginHostTelemetry', threadService);

55
		this._extensionService = new ExtHostExtensionService(initData.extensions, threadService, telemetryService, { _serviceBrand: 'optionalArgs', workspaceStoragePath });
56

J
Johannes Rieken 已提交
57 58 59 60
		// Error forwarding
		const mainThreadErrors = threadService.get(MainContext.MainThreadErrors);
		errors.setUnexpectedErrorHandler(err => mainThreadErrors.onUnexpectedExtHostError(errors.transformErrorForSerialization(err)));

61
		// Create the ext host API
62
		const factory = createApiFactory(initData, threadService, this._extensionService, this._contextService);
63
		defineAPI(factory, this._extensionService);
64 65
	}

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
	private _getOrCreateWorkspaceStoragePath(): string {
		let workspaceStoragePath: string;

		const workspace = this._contextService.getWorkspace();

		function rmkDir(directory: string): boolean {
			try {
				fs.mkdirSync(directory);
				return true;
			} catch (err) {
				if (err.code === 'ENOENT') {
					if (rmkDir(paths.dirname(directory))) {
						fs.mkdirSync(directory);
						return true;
					}
				} else {
					return fs.statSync(directory).isDirectory();
				}
			}
		}

		if (workspace) {
			const hash = crypto.createHash('md5');
			hash.update(workspace.resource.fsPath);
			if (workspace.uid) {
				hash.update(workspace.uid.toString());
			}
			workspaceStoragePath = paths.join(this._environment.appSettingsHome, 'workspaceStorage', hash.digest('hex'));
			if (!fs.existsSync(workspaceStoragePath)) {
				try {
					if (rmkDir(workspaceStoragePath)) {
						fs.writeFileSync(paths.join(workspaceStoragePath, 'meta.json'), JSON.stringify({
							workspacePath: workspace.resource.fsPath,
							uid: workspace.uid ? workspace.uid : null
						}, null, 4));
					} else {
						workspaceStoragePath = undefined;
					}
				} catch (err) {
					workspaceStoragePath = undefined;
				}
			}
		}

		return workspaceStoragePath;
	}

E
Erich Gamma 已提交
113
	public start(): TPromise<void> {
114
		return this.handleEagerExtensions().then(() => this.handleExtensionTests());
E
Erich Gamma 已提交
115 116
	}

117 118 119 120 121 122 123
	public terminate(): void {
		if (this._isTerminating) {
			// we are already shutting down...
			return;
		}
		this._isTerminating = true;

124 125 126 127 128
		errors.setUnexpectedErrorHandler((err) => {
			// TODO: write to log once we have one
		});

		let allPromises: TPromise<void>[] = [];
129
		try {
130
			let allExtensions = this._extensionService.getAllExtensionDescriptions();
131
			let allExtensionsIds = allExtensions.map(ext => ext.id);
A
Alex Dima 已提交
132
			let activatedExtensions = allExtensionsIds.filter(id => this._extensionService.isActivated(id));
133

134 135
			allPromises = activatedExtensions.map((extensionId) => {
				return this._extensionService.deactivate(extensionId);
136
			});
B
Benjamin Pasero 已提交
137
		} catch (err) {
138 139 140
			// TODO: write to log once we have one
		}

141 142
		let extensionsDeactivated = TPromise.join(allPromises).then<void>(() => void 0);

143 144
		// Give extensions 1 second to wrap up any async dispose, then exit
		setTimeout(() => {
145
			TPromise.any<void>([TPromise.timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
146 147 148
		}, 1000);
	}

A
Alex Dima 已提交
149 150
	// Handle "eager" activation extensions
	private handleEagerExtensions(): TPromise<void> {
A
Alex Dima 已提交
151
		this._extensionService.activateByEvent('*').then(null, (err) => {
E
Erich Gamma 已提交
152 153
			console.error(err);
		});
A
Alex Dima 已提交
154
		return this.handleWorkspaceContainsEagerExtensions();
E
Erich Gamma 已提交
155 156
	}

A
Alex Dima 已提交
157
	private handleWorkspaceContainsEagerExtensions(): TPromise<void> {
A
Alex Dima 已提交
158
		let workspace = this._contextService.getWorkspace();
E
Erich Gamma 已提交
159 160 161 162
		if (!workspace || !workspace.resource) {
			return TPromise.as(null);
		}

J
Joao Moreno 已提交
163
		const folderPath = workspace.resource.fsPath;
E
Erich Gamma 已提交
164

J
Joao Moreno 已提交
165
		const desiredFilesMap: {
E
Erich Gamma 已提交
166 167 168
			[filename: string]: boolean;
		} = {};

169
		this._extensionService.getAllExtensionDescriptions().forEach((desc) => {
E
Erich Gamma 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182
			let activationEvents = desc.activationEvents;
			if (!activationEvents) {
				return;
			}

			for (let i = 0; i < activationEvents.length; i++) {
				if (/^workspaceContains:/.test(activationEvents[i])) {
					let fileName = activationEvents[i].substr('workspaceContains:'.length);
					desiredFilesMap[fileName] = true;
				}
			}
		});

J
Joao Moreno 已提交
183 184 185 186 187 188 189
		const fileNames = Object.keys(desiredFilesMap);

		return TPromise.join(fileNames.map(f => pfs.exists(paths.join(folderPath, f)))).then(exists => {
			fileNames
				.filter((f, i) => exists[i])
				.forEach(fileName => {
					const activationEvent = `workspaceContains:${fileName}`;
E
Erich Gamma 已提交
190

J
Joao Moreno 已提交
191 192
					this._extensionService.activateByEvent(activationEvent)
						.done(null, err => console.error(err));
E
Erich Gamma 已提交
193 194 195 196
				});
		});
	}

A
Alex Dima 已提交
197
	private handleExtensionTests(): TPromise<void> {
B
Benjamin Pasero 已提交
198
		if (!this._environment.extensionTestsPath || !this._environment.extensionDevelopmentPath) {
E
Erich Gamma 已提交
199 200 201 202
			return TPromise.as(null);
		}

		// Require the test runner via node require from the provided path
B
Benjamin Pasero 已提交
203 204
		let testRunner: ITestRunner;
		let requireError: Error;
E
Erich Gamma 已提交
205
		try {
B
Benjamin Pasero 已提交
206
			testRunner = <any>require.__$__nodeRequire(this._environment.extensionTestsPath);
E
Erich Gamma 已提交
207 208 209 210 211 212 213
		} catch (error) {
			requireError = error;
		}

		// Execute the runner if it follows our spec
		if (testRunner && typeof testRunner.run === 'function') {
			return new TPromise<void>((c, e) => {
B
Benjamin Pasero 已提交
214
				testRunner.run(this._environment.extensionTestsPath, (error, failures) => {
E
Erich Gamma 已提交
215 216 217 218 219 220
					if (error) {
						e(error.toString());
					} else {
						c(null);
					}

221
					// after tests have run, we shutdown the host
222
					this.gracefulExit(failures && failures > 0 ? 1 /* ERROR */ : 0 /* OK */);
E
Erich Gamma 已提交
223 224 225 226
				});
			});
		}

227 228
		// Otherwise make sure to shutdown anyway even in case of an error
		else {
229
			this.gracefulExit(1 /* ERROR */);
230
		}
E
Erich Gamma 已提交
231

B
Benjamin Pasero 已提交
232
		return TPromise.wrapError<void>(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", this._environment.extensionTestsPath));
E
Erich Gamma 已提交
233
	}
234

235
	private gracefulExit(code: number): void {
236
		// to give the PH process a chance to flush any outstanding console
237
		// messages to the main process, we delay the exit() by some time
238
		setTimeout(() => exit(code), 500);
239
	}
240
}