cli.ts 5.9 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { spawn, ChildProcess } from 'child_process';
J
Joao Moreno 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
8
import { assign } from 'vs/base/common/objects';
J
Joao Moreno 已提交
9 10
import { parseCLIProcessArgv, buildHelpMessage } from 'vs/platform/environment/node/argv';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
11 12
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
J
Joao Moreno 已提交
13

14 15
import * as paths from 'path';
import * as os from 'os';
B
Benjamin Pasero 已提交
16
import { whenDeleted } from 'vs/base/node/pfs';
17
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
18
import { findFreePort } from 'vs/base/node/ports';
19

J
Joao Moreno 已提交
20
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
21 22 23 24
	return !!argv['install-source']
		|| !!argv['list-extensions']
		|| !!argv['install-extension']
		|| !!argv['uninstall-extension'];
J
Joao Moreno 已提交
25 26 27 28 29 30
}

interface IMainCli {
	main: (argv: ParsedArgs) => TPromise<void>;
}

31
export async function main(argv: string[]): TPromise<any> {
J
Joao Moreno 已提交
32
	let args: ParsedArgs;
J
Joao Moreno 已提交
33

J
Joao Moreno 已提交
34 35 36 37 38 39 40 41
	try {
		args = parseCLIProcessArgv(argv);
	} catch (err) {
		console.error(err.message);
		return TPromise.as(null);
	}

	if (args.help) {
J
Joao Moreno 已提交
42
		console.log(buildHelpMessage(product.nameLong, product.applicationName, pkg.version));
J
Joao Moreno 已提交
43
	} else if (args.version) {
44
		console.log(`${pkg.version}\n${product.commit}\n${process.arch}`);
J
Joao Moreno 已提交
45
	} else if (shouldSpawnCliProcess(args)) {
J
Joao Moreno 已提交
46
		const mainCli = new TPromise<IMainCli>(c => require(['vs/code/node/cliProcessMain'], c));
J
Joao Moreno 已提交
47
		return mainCli.then(cli => cli.main(args));
J
Joao Moreno 已提交
48
	} else {
49 50
		const env = assign({}, process.env, {
			// this will signal Code that it was spawned from this module
J
Joao Moreno 已提交
51 52
			'VSCODE_CLI': '1',
			'ELECTRON_NO_ATTACH_CONSOLE': '1'
53
		});
B
Benjamin Pasero 已提交
54

B
Benjamin Pasero 已提交
55
		delete env['ELECTRON_RUN_AS_NODE'];
J
Joao Moreno 已提交
56

57 58
		let processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];

B
Benjamin Pasero 已提交
59 60
		if (args.verbose) {
			env['ELECTRON_ENABLE_LOGGING'] = '1';
61 62 63 64 65 66

			processCallbacks.push(child => {
				child.stdout.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
				child.stderr.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
				return new TPromise<void>(c => child.once('exit', () => c(null)));
			});
B
Benjamin Pasero 已提交
67 68
		}

69 70 71 72 73 74 75 76 77
		// If we are started with --wait create a random temporary file
		// and pass it over to the starting instance. We can use this file
		// to wait for it to be deleted to monitor that the edited file
		// is closed and then exit the waiting process.
		let waitMarkerFilePath: string;
		if (args.wait) {
			let waitMarkerError: Error;
			const randomTmpFile = paths.join(os.tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10));
			try {
78
				writeFileAndFlushSync(randomTmpFile, '');
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
				waitMarkerFilePath = randomTmpFile;
				argv.push('--waitMarkerFilePath', waitMarkerFilePath);
			} catch (error) {
				waitMarkerError = error;
			}

			if (args.verbose) {
				if (waitMarkerError) {
					console.error(`Failed to create marker file for --wait: ${waitMarkerError.toString()}`);
				} else {
					console.log(`Marker file for --wait created: ${waitMarkerFilePath}`);
				}
			}
		}

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
		// If we have been started with `--prof-startup` we need to find free ports to profile
		// the main process, the renderer, and the extension host. We also disable v8 cached data
		// to get better profile traces. Last, we listen on stdout for a signal that tells us to
		// stop profiling.
		if (args['prof-startup']) {

			const portMain = await findFreePort(9222, 10, 6000);
			const portRenderer = await findFreePort(portMain + 1, 10, 6000);
			const portExthost = await findFreePort(portRenderer + 1, 10, 6000);

			if (!portMain || !portRenderer || !portExthost) {
				console.error('Failed to find free ports for profiler to connect to do.');
				return;
			}

			const filenamePrefix = paths.join(os.homedir(), Math.random().toString(16).slice(-4));

			argv.push(`--inspect-brk=${portMain}`);
			argv.push(`--remote-debugging-port=${portRenderer}`);
			argv.push(`--inspect-brk-extensions=${portExthost}`);
			argv.push(`--prof-startup-prefix`, filenamePrefix);
			argv.push(`--no-cached-data`);

			writeFileAndFlushSync(filenamePrefix, argv.slice(-6).join('|'));

			processCallbacks.push(async child => {

				// load and start profiler
				const profiler = await import('v8-inspect-profiler');
				const main = await profiler.startProfiling({ port: portMain });
				const renderer = await profiler.startProfiling({ port: portRenderer, tries: 200 });
				const extHost = await profiler.startProfiling({ port: portExthost, tries: 300 });

				// wait for the renderer to delete the
				// marker file
				whenDeleted(filenamePrefix);

				// finally stop profiling and save profiles to disk
				await profiler.writeProfile(await main.stop(), `${filenamePrefix}-main.cpuprofile`);
				await profiler.writeProfile(await renderer.stop(), `${filenamePrefix}-renderer.cpuprofile`);
				await profiler.writeProfile(await extHost.stop(), `${filenamePrefix}-exthost.cpuprofile`);
			});
		}

B
Benjamin Pasero 已提交
138
		const options = {
D
Daniel Imms 已提交
139
			detached: true,
140
			env
141
		};
B
Benjamin Pasero 已提交
142

J
Joao Moreno 已提交
143
		if (!args.verbose) {
144 145 146
			options['stdio'] = 'ignore';
		}

J
Joao Moreno 已提交
147
		const child = spawn(process.execPath, argv.slice(2), options);
148

149 150 151 152 153 154 155
		if (args.wait && waitMarkerFilePath) {
			return new TPromise<void>(c => {

				// Complete when process exits
				child.once('exit', () => c(null));

				// Complete when wait marker file is deleted
B
Benjamin Pasero 已提交
156
				whenDeleted(waitMarkerFilePath).done(c, c);
157 158
			});
		}
159 160

		return TPromise.join(processCallbacks.map(callback => callback(child)));
J
Joao Moreno 已提交
161 162
	}

J
Joao Moreno 已提交
163
	return TPromise.as(null);
J
Joao Moreno 已提交
164 165
}

166 167 168 169
function eventuallyExit(code: number): void {
	setTimeout(() => process.exit(code), 0);
}

J
Joao Moreno 已提交
170
main(process.argv)
171
	.then(() => eventuallyExit(0))
J
Joao Moreno 已提交
172 173
	.then(null, err => {
		console.error(err.stack ? err.stack : err);
174
		eventuallyExit(1);
J
Joao Moreno 已提交
175
	});