main.ts 13.5 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
import 'vs/code/electron-main/contributions';
9
import { app, dialog } from 'electron';
J
Joao Moreno 已提交
10 11
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
12
import product from 'vs/platform/node/product';
13
import * as path from 'path';
J
Joao Moreno 已提交
14
import { parseMainProcessArgv } from 'vs/platform/environment/node/argv';
15
import { mkdirp, readdir, rimraf } from 'vs/base/node/pfs';
B
Benjamin Pasero 已提交
16
import { validatePaths } from 'vs/code/node/paths';
17
import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
J
Joao Moreno 已提交
18 19
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
B
Benjamin Pasero 已提交
20
import { ILaunchChannel, LaunchChannelClient } from 'vs/code/electron-main/launch';
J
Joao Moreno 已提交
21 22 23 24
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
S
Sandeep Somavarapu 已提交
25
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
B
Benjamin Pasero 已提交
26 27
import { StateService } from 'vs/platform/state/node/stateService';
import { IStateService } from 'vs/platform/state/common/state';
D
Daniel Imms 已提交
28
import { IBackupMainService } from 'vs/platform/backup/common/backup';
D
Daniel Imms 已提交
29
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
J
Joao Moreno 已提交
30
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
31 32
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
33
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
J
Joao Moreno 已提交
34
import { IRequestService } from 'vs/platform/request/node/request';
J
Joao Moreno 已提交
35
import { RequestService } from 'vs/platform/request/electron-main/requestService';
J
Joao Moreno 已提交
36
import { IURLService } from 'vs/platform/url/common/url';
J
Joao Moreno 已提交
37
import { URLService } from 'vs/platform/url/common/urlService';
B
Benjamin Pasero 已提交
38
import * as fs from 'original-fs';
39 40
import { CodeApplication } from 'vs/code/electron-main/app';
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
B
Benjamin Pasero 已提交
41
import { IHistoryMainService } from 'vs/platform/history/common/history';
42
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
B
Benjamin Pasero 已提交
43
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
44 45
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
46
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
B
Benjamin Pasero 已提交
47
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
48
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
49
import { uploadLogs } from 'vs/code/electron-main/logUploader';
B
Benjamin Pasero 已提交
50
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
51
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
B
Benjamin Pasero 已提交
52
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
B
Benjamin Pasero 已提交
53

54
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
B
Benjamin Pasero 已提交
55 56
	const services = new ServiceCollection();

57
	const environmentService = new EnvironmentService(args, process.execPath);
S
Sandeep Somavarapu 已提交
58
	const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
59
	const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
60 61

	process.once('exit', () => logService.dispose());
62

J
Joao Moreno 已提交
63
	// Eventually cleanup
64
	setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
J
Joao Moreno 已提交
65

66 67
	services.set(IEnvironmentService, environmentService);
	services.set(ILogService, logService);
68
	services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
69
	services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
B
Benjamin Pasero 已提交
70
	services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
B
Benjamin Pasero 已提交
71
	services.set(IStateService, new SyncDescriptor(StateService));
B
Benjamin Pasero 已提交
72 73
	services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
	services.set(IRequestService, new SyncDescriptor(RequestService));
J
Joao Moreno 已提交
74
	services.set(IURLService, new SyncDescriptor(URLService));
B
Benjamin Pasero 已提交
75
	services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
B
Benjamin Pasero 已提交
76
	services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
B
Benjamin Pasero 已提交
77 78 79 80

	return new InstantiationService(services, true);
}

81 82 83 84 85 86 87 88 89 90 91 92 93 94
/**
 * Cleans up older logs, while keeping the 10 most recent ones.
*/
async function cleanupOlderLogs(environmentService: EnvironmentService): TPromise<void> {
	const currentLog = path.basename(environmentService.logsPath);
	const logsRoot = path.dirname(environmentService.logsPath);
	const children = await readdir(logsRoot);
	const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
	const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
	const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));

	await TPromise.join(toDelete.map(name => rimraf(path.join(logsRoot, name))));
}

B
Benjamin Pasero 已提交
95 96 97 98
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
	const paths = [
		environmentService.appSettingsHome,
		environmentService.extensionsPath,
J
Joao Moreno 已提交
99 100
		environmentService.nodeCachedDataDir,
		environmentService.logsPath
B
Benjamin Pasero 已提交
101
	];
B
Benjamin Pasero 已提交
102

B
Benjamin Pasero 已提交
103 104 105
	return TPromise.join(paths.map(p => p && mkdirp(p))) as TPromise<any>;
}

J
Joao Moreno 已提交
106 107 108 109
class ExpectedError extends Error {
	public readonly isExpected = true;
}

J
Joao Moreno 已提交
110 111
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
	const logService = accessor.get(ILogService);
112
	const environmentService = accessor.get(IEnvironmentService);
113
	const requestService = accessor.get(IRequestService);
J
Joao Moreno 已提交
114

B
Benjamin Pasero 已提交
115
	function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
116
		let promise = TPromise.wrap<void>(void 0);
B
Benjamin Pasero 已提交
117 118 119
		if (platform.isWindows) {
			promise = service.getMainProcessId()
				.then(processId => {
J
Joao Moreno 已提交
120
					logService.trace('Sending some foreground love to the running instance:', processId);
J
Johannes Rieken 已提交
121

B
Benjamin Pasero 已提交
122 123 124 125 126 127 128 129 130 131 132 133
					try {
						const { allowSetForegroundWindow } = <any>require.__$__nodeRequire('windows-foreground-love');
						allowSetForegroundWindow(processId);
					} catch (e) {
						// noop
					}
				});
		}

		return promise;
	}

E
Erich Gamma 已提交
134
	function setup(retry: boolean): TPromise<Server> {
135
		return serve(environmentService.mainIPCHandle).then(server => {
136

137 138
			// Print --status usage info
			if (environmentService.args.status) {
B
Benjamin Pasero 已提交
139
				logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
B
Benjamin Pasero 已提交
140 141 142
				throw new ExpectedError('Terminating...');
			}

143
			// Log uploader usage info
144
			if (typeof environmentService.args['upload-logs'] !== 'undefined') {
145 146 147 148
				logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.');
				throw new ExpectedError('Terminating...');
			}

B
Benjamin Pasero 已提交
149 150 151
			// dock might be hidden at this case due to a retry
			if (platform.isMacintosh) {
				app.dock.show();
B
Benjamin Pasero 已提交
152 153
			}

154 155 156 157
			// Set the VSCODE_PID variable here when we are sure we are the first
			// instance to startup. Otherwise we would wrongly overwrite the PID
			process.env['VSCODE_PID'] = String(process.pid);

158 159
			return server;
		}, err => {
E
Erich Gamma 已提交
160
			if (err.code !== 'EADDRINUSE') {
161
				return TPromise.wrapError<Server>(err);
E
Erich Gamma 已提交
162 163
			}

164 165 166 167
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
168

169
			// there's a running instance, let's connect to it
170
			return connect(environmentService.mainIPCHandle, 'main').then(
171
				client => {
J
Joao Moreno 已提交
172

B
Benjamin Pasero 已提交
173
					// Tests from CLI require to be the only instance currently
J
Joao Moreno 已提交
174
					if (environmentService.extensionTestsPath && !environmentService.debugExtensionHost.break) {
J
Joao Moreno 已提交
175
						const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
B
Benjamin Pasero 已提交
176
						logService.error(msg);
J
Joao Moreno 已提交
177
						client.dispose();
B
Benjamin Pasero 已提交
178

179
						return TPromise.wrapError<Server>(new Error(msg));
J
Joao Moreno 已提交
180 181
					}

182
					// Show a warning dialog after some timeout if it takes long to talk to the other instance
B
Benjamin Pasero 已提交
183 184
					// Skip this if we are running with --wait where it is expected that we wait for a while.
					// Also skip when gathering diagnostics (--status) which can take a longer time.
185
					let startupWarningDialogHandle: number;
186
					if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) {
187 188 189 190 191 192 193 194
						startupWarningDialogHandle = setTimeout(() => {
							showStartupWarningDialog(
								localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
								localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
							);
						}, 10000);
					}

J
Joao Moreno 已提交
195 196
					const channel = client.getChannel<ILaunchChannel>('launch');
					const service = new LaunchChannelClient(channel);
E
Erich Gamma 已提交
197

B
Benjamin Pasero 已提交
198
					// Process Info
199
					if (environmentService.args.status) {
B
Benjamin Pasero 已提交
200
						return service.getMainProcessInfo().then(info => {
B
Benjamin Pasero 已提交
201
							return printDiagnostics(info).then(() => TPromise.wrapError(new ExpectedError()));
B
Benjamin Pasero 已提交
202 203 204
						});
					}

205
					// Log uploader
206
					if (typeof environmentService.args['upload-logs'] !== 'undefined') {
207
						return uploadLogs(channel, requestService, environmentService)
208 209 210
							.then(() => TPromise.wrapError(new ExpectedError()));
					}

J
Joao Moreno 已提交
211
					logService.trace('Sending env to running instance...');
B
Benjamin Pasero 已提交
212

B
Benjamin Pasero 已提交
213
					return allowSetForegroundWindow(service)
214
						.then(() => service.start(environmentService.args, process.env))
E
Erich Gamma 已提交
215
						.then(() => client.dispose())
216 217 218 219 220 221 222 223 224
						.then(() => {

							// Now that we started, make sure the warning dialog is prevented
							if (startupWarningDialogHandle) {
								clearTimeout(startupWarningDialogHandle);
							}

							return TPromise.wrapError(new ExpectedError('Sent env to running instance. Terminating...'));
						});
E
Erich Gamma 已提交
225 226 227
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
228 229 230 231 232 233 234
						if (err.code === 'EPERM') {
							showStartupWarningDialog(
								localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
								localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
							);
						}

235
						return TPromise.wrapError<Server>(err);
E
Erich Gamma 已提交
236 237 238 239
					}

					// it happens on Linux and OS X that the pipe is left behind
					// let's delete it, since we can't connect to it
S
Shreya Dahal 已提交
240
					// and then retry the whole thing
E
Erich Gamma 已提交
241
					try {
242
						fs.unlinkSync(environmentService.mainIPCHandle);
E
Erich Gamma 已提交
243
					} catch (e) {
J
Joao Moreno 已提交
244
						logService.warn('Could not delete obsolete instance handle', e);
245
						return TPromise.wrapError<Server>(e);
E
Erich Gamma 已提交
246 247 248 249 250 251 252 253 254 255 256
					}

					return setup(false);
				}
			);
		});
	}

	return setup(true);
}

257
function showStartupWarningDialog(message: string, detail: string): void {
258
	dialog.showMessageBox({
259 260 261 262 263 264 265 266 267
		title: product.nameLong,
		type: 'warning',
		buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
		message,
		detail,
		noLink: true
	});
}

B
Benjamin Pasero 已提交
268
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
269 270
	const logService = accessor.get(ILogService);
	const lifecycleService = accessor.get(ILifecycleService);
271

272
	let exitCode = 0;
J
Joao Moreno 已提交
273

B
Benjamin Pasero 已提交
274 275
	if (reason) {
		if ((reason as ExpectedError).isExpected) {
B
Benjamin Pasero 已提交
276 277 278
			if (reason.message) {
				logService.trace(reason.message);
			}
279
		} else {
J
Joao Moreno 已提交
280 281
			exitCode = 1; // signal error to the outside

B
Benjamin Pasero 已提交
282
			if (reason.stack) {
B
Benjamin Pasero 已提交
283
				logService.error(reason.stack);
J
Joao Moreno 已提交
284
			} else {
B
Benjamin Pasero 已提交
285
				logService.error(`Startup error: ${reason.toString()}`);
J
Joao Moreno 已提交
286
			}
287
		}
J
Joao Moreno 已提交
288 289
	}

290
	lifecycleService.kill(exitCode);
J
Joao Moreno 已提交
291 292 293 294
}

function main() {

B
Benjamin Pasero 已提交
295 296 297 298 299
	// Set the error handler early enough so that we are not getting the
	// default electron error dialog popping up
	setUnexpectedErrorHandler(err => console.error(err));

	let args: ParsedArgs;
J
Joao Moreno 已提交
300 301 302 303 304 305 306 307 308 309
	try {
		args = parseMainProcessArgv(process.argv);
		args = validatePaths(args);
	} catch (err) {
		console.error(err.message);
		app.exit(1);

		return;
	}

310 311 312 313 314 315
	// We need to buffer the spdlog logs until we are sure
	// we are the only instance running, otherwise we'll have concurrent
	// log file access on Windows
	// https://github.com/Microsoft/vscode/issues/41218
	const bufferLogService = new BufferLogService();
	const instantiationService = createServices(args, bufferLogService);
J
Joao Moreno 已提交
316 317 318 319 320 321 322

	return instantiationService.invokeFunction(accessor => {

		// Patch `process.env` with the instance's environment
		const environmentService = accessor.get(IEnvironmentService);
		const instanceEnv: typeof process.env = {
			VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
J
Joao Moreno 已提交
323 324
			VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
			VSCODE_LOGS: process.env['VSCODE_LOGS']
J
Joao Moreno 已提交
325 326 327 328 329 330
		};
		assign(process.env, instanceEnv);

		// Startup
		return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
			.then(() => instantiationService.invokeFunction(setupIPC))
331
			.then(mainIpcServer => {
S
Sandeep Somavarapu 已提交
332
				bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
333 334
				return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
			});
J
Joao Moreno 已提交
335 336 337
	}).done(null, err => instantiationService.invokeFunction(quit, err));
}

S
Shreya Dahal 已提交
338
main();