UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

382 lines (334 loc) 15.6 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var getAttachTargetInfoList = _asyncToGenerator(function* (targetPid) { // Get processes list from ps utility. // -e: include all processes, does not require -ww argument since truncation of process names is // done by the OS, not the ps utility var pidToName = new Map(); var processes = yield (0, (_commonsNodeProcess2 || _commonsNodeProcess()).checkOutput)('ps', ['-e', '-o', 'pid,comm'], {}); processes.stdout.toString().split('\n').slice(1).forEach(function (line) { var words = line.trim().split(' '); var pid = Number(words[0]); var command = words.slice(1).join(' '); var components = command.split('/'); var name = components[components.length - 1]; pidToName.set(pid, name); }); // Get processes list from ps utility. // -e: include all processes // -ww: provides unlimited width for output and prevents the truncating of command names by ps. // -o pid,args: custom format the output to be two columns(pid and command name) var pidToCommand = new Map(); var commands = yield (0, (_commonsNodeProcess2 || _commonsNodeProcess()).checkOutput)('ps', ['-eww', '-o', 'pid,args'], {}); commands.stdout.toString().split('\n').slice(1).forEach(function (line) { var words = line.trim().split(' '); var pid = Number(words[0]); var command = words.slice(1).join(' '); pidToCommand.set(pid, command); }); // Filter out processes that have died in between ps calls and zombiue processes. // Place pid, process, and command info into AttachTargetInfo objects and return in an array. return Array.from(pidToName.entries()).filter(function (entry) { if (targetPid == null) { return true; } var _entry = _slicedToArray(entry, 1); var pid = _entry[0]; return pid === targetPid; }).filter(function (arr) { var _arr = _slicedToArray(arr, 2); var pid = _arr[0]; var name = _arr[1]; if (targetPid != null) { return pid === targetPid; } return pidToCommand.has(pid) && !(name.startsWith('(') && name.endsWith(')')) && (name.length < 9 || name.slice(-9) !== '<defunct>'); }).map(function (arr) { var _arr2 = _slicedToArray(arr, 2); var pid = _arr2[0]; var name = _arr2[1]; var commandName = pidToCommand.get(pid); (0, (_assert2 || _assert()).default)(commandName != null); return { pid: pid, name: name, commandName: commandName }; }); }); exports.getAttachTargetInfoList = getAttachTargetInfoList; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _eventKit2; function _eventKit() { return _eventKit2 = require('event-kit'); } var _child_process2; function _child_process() { return _child_process2 = _interopRequireDefault(require('child_process')); } var _assert2; function _assert() { return _assert2 = _interopRequireDefault(require('assert')); } var _commonsNodeNuclideUri2; function _commonsNodeNuclideUri() { return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri')); } var _utils2; function _utils() { return _utils2 = _interopRequireDefault(require('./utils')); } var _ws2; function _ws() { return _ws2 = _interopRequireDefault(require('ws')); } var _default = (_utils2 || _utils()).default; var log = _default.log; var logTrace = _default.logTrace; var logError = _default.logError; var logInfo = _default.logInfo; var setLogLevel = _default.setLogLevel; var _nuclideDebuggerCommon2; function _nuclideDebuggerCommon() { return _nuclideDebuggerCommon2 = require('../../nuclide-debugger-common'); } var _commonsNodeStream2; function _commonsNodeStream() { return _commonsNodeStream2 = require('../../commons-node/stream'); } var _commonsNodeProcess2; function _commonsNodeProcess() { return _commonsNodeProcess2 = require('../../commons-node/process'); } var DebuggerConnection = (function () { function DebuggerConnection(clientCallback, lldbWebSocket, lldbProcess, subscriptions) { _classCallCheck(this, DebuggerConnection); this._clientCallback = clientCallback; this._lldbWebSocket = lldbWebSocket; this._lldbProcess = lldbProcess; this._subscriptions = subscriptions; lldbWebSocket.on('message', this._handleLLDBMessage.bind(this)); lldbProcess.on('exit', this._handleLLDBExit.bind(this)); } _createClass(DebuggerConnection, [{ key: 'getServerMessageObservable', value: function getServerMessageObservable() { return this._clientCallback.getServerMessageObservable().publish(); } }, { key: '_handleLLDBMessage', value: function _handleLLDBMessage(message) { logTrace('lldb message: ' + message); this._clientCallback.sendChromeMessage(message); } }, { key: '_handleLLDBExit', value: function _handleLLDBExit() { // Fire and forget. this.dispose(); } }, { key: 'sendCommand', value: _asyncToGenerator(function* (message) { var lldbWebSocket = this._lldbWebSocket; if (lldbWebSocket != null) { logTrace('forward client message to lldb: ' + message); lldbWebSocket.send(message); } else { logInfo('Nuclide sent message to LLDB after socket closed: ' + message); } }) }, { key: 'dispose', value: _asyncToGenerator(function* () { log('DebuggerConnection disposed'); this._subscriptions.dispose(); this._lldbWebSocket = null; }) }]); return DebuggerConnection; })(); exports.DebuggerConnection = DebuggerConnection; var NativeDebuggerService = (function () { function NativeDebuggerService(config) { _classCallCheck(this, NativeDebuggerService); this._clientCallback = new (_nuclideDebuggerCommon2 || _nuclideDebuggerCommon()).ClientCallback(); this._config = config; setLogLevel(config.logLevel); this._subscriptions = new (_eventKit2 || _eventKit()).CompositeDisposable(this._clientCallback); } _createClass(NativeDebuggerService, [{ key: 'getOutputWindowObservable', value: function getOutputWindowObservable() { return this._clientCallback.getOutputWindowObservable().publish(); } }, { key: 'attach', value: _asyncToGenerator(function* (attachInfo) { log('attach process: ' + JSON.stringify(attachInfo)); var inferiorArguments = { pid: String(attachInfo.pid), basepath: attachInfo.basepath ? attachInfo.basepath : this._config.buckConfigRootFile }; return yield this._startDebugging(inferiorArguments); }) }, { key: 'launch', value: _asyncToGenerator(function* (launchInfo) { log('launch process: ' + JSON.stringify(launchInfo)); var inferiorArguments = { executable_path: launchInfo.executablePath, launch_arguments: launchInfo.arguments, launch_environment_variables: launchInfo.environmentVariables, working_directory: launchInfo.workingDirectory, basepath: launchInfo.basepath ? launchInfo.basepath : this._config.buckConfigRootFile }; return yield this._startDebugging(inferiorArguments); }) }, { key: '_startDebugging', value: _asyncToGenerator(function* (inferiorArguments) { var lldbProcess = this._spawnPythonBackend(); this._registerIpcChannel(lldbProcess); this._sendArgumentsToPythonBackend(lldbProcess, inferiorArguments); var lldbWebSocket = yield this._connectWithLLDB(lldbProcess); this._subscriptions.add(new (_eventKit2 || _eventKit()).Disposable(function () { return lldbWebSocket.terminate(); })); return new DebuggerConnection(this._clientCallback, lldbWebSocket, lldbProcess, this._subscriptions); }) }, { key: '_registerIpcChannel', value: function _registerIpcChannel(lldbProcess) { var IPC_CHANNEL_FD = 4; /* $FlowFixMe - update Flow defs for ChildProcess */ var ipcStream = lldbProcess.stdio[IPC_CHANNEL_FD]; this._subscriptions.add(new (_commonsNodeStream2 || _commonsNodeStream()).DisposableSubscription((0, (_commonsNodeStream2 || _commonsNodeStream()).splitStream)((0, (_commonsNodeStream2 || _commonsNodeStream()).observeStream)(ipcStream)).subscribe(this._handleIpcMessage.bind(this, ipcStream), function (error) { return logError('ipcStream error: ' + JSON.stringify(error)); }))); } }, { key: '_handleIpcMessage', value: function _handleIpcMessage(ipcStream, message) { logTrace('ipc message: ' + message); var messageJson = JSON.parse(message); if (messageJson.type === 'Nuclide.userOutput') { // Write response message to ipc for sync message. if (messageJson.isSync) { ipcStream.write(JSON.stringify({ message_id: messageJson.id }) + '\n'); } this._clientCallback.sendUserOutputMessage(JSON.stringify(messageJson.message)); } else { logError('Unknown message: ' + message); } } }, { key: '_spawnPythonBackend', value: function _spawnPythonBackend() { var lldbPythonScriptPath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(__dirname, '../scripts/main.py'); var python_args = [lldbPythonScriptPath, '--arguments_in_json']; var options = { cwd: (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(lldbPythonScriptPath), // FD[3] is used for sending arguments JSON blob. // FD[4] is used as a ipc channel for output/atom notifications. stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'], detached: false }; // When Atom is killed, clang_server.py should be killed, too. logInfo('spawn child_process: ' + JSON.stringify(python_args)); var lldbProcess = (_child_process2 || _child_process()).default.spawn(this._config.pythonBinaryPath, python_args, options); this._subscriptions.add(new (_eventKit2 || _eventKit()).Disposable(function () { return lldbProcess.kill(); })); return lldbProcess; } }, { key: '_sendArgumentsToPythonBackend', value: function _sendArgumentsToPythonBackend(child, args) { var ARGUMENT_INPUT_FD = 3; /* $FlowFixMe - update Flow defs for ChildProcess */ var argumentsStream = child.stdio[ARGUMENT_INPUT_FD]; // Make sure the bidirectional communication channel is set up before // sending data. argumentsStream.write('init\n'); this._subscriptions.add(new (_commonsNodeStream2 || _commonsNodeStream()).DisposableSubscription((0, (_commonsNodeStream2 || _commonsNodeStream()).observeStream)(argumentsStream).first().subscribe(function (text) { if (text.startsWith('ready')) { var args_in_json = JSON.stringify(args); logInfo('Sending ' + args_in_json + ' to child_process'); argumentsStream.write(args_in_json + '\n'); } else { logError('Get unknown initial data: ' + text + '.'); child.kill(); } }, function (error) { return logError('argumentsStream error: ' + JSON.stringify(error)); }))); } }, { key: '_connectWithLLDB', value: function _connectWithLLDB(lldbProcess) { var _this = this; log('connecting with lldb'); return new Promise(function (resolve, reject) { // Async handle parsing websocket address from the stdout of the child. lldbProcess.stdout.on('data', function (chunk) { // stdout should hopefully be set to line-buffering, in which case the var block = chunk.toString(); log('child process(' + lldbProcess.pid + ') stdout: ' + block); var result = /Port: (\d+)\n/.exec(block); if (result != null) { (function () { // $FlowIssue - flow has wrong typing for it(t9649946). lldbProcess.stdout.removeAllListeners(['data', 'error', 'exit']); // TODO[jeffreytan]: explicitly use ipv4 address 127.0.0.1 for now. // Investigate if we can use localhost and match protocol version between client/server. var lldbWebSocketAddress = 'ws://127.0.0.1:' + result[1] + '/'; log('Connecting lldb with address: ' + lldbWebSocketAddress); var ws = new (_ws2 || _ws()).default(lldbWebSocketAddress); ws.on('open', function () { // Successfully connected with lldb python process, fulfill the promise. resolve(ws); }); })(); } }); lldbProcess.stderr.on('data', function (chunk) { var errorMessage = chunk.toString(); _this._clientCallback.sendUserOutputMessage(JSON.stringify({ level: 'error', text: errorMessage })); logError('child process(' + lldbProcess.pid + ') stderr: ' + errorMessage); }); lldbProcess.on('error', function () { reject('lldb process error'); }); lldbProcess.on('exit', function () { reject('lldb process exit'); }); }); } }, { key: 'dispose', value: _asyncToGenerator(function* () { logInfo('NativeDebuggerService disposed'); }) }]); return NativeDebuggerService; })(); exports.NativeDebuggerService = NativeDebuggerService; // -o pid,comm: custom format the output to be two columns(pid and process name) // string would come on one line.