| var common = require('./common'); |
| var _tempDir = require('./tempdir'); |
| var _pwd = require('./pwd'); |
| var path = require('path'); |
| var fs = require('fs'); |
| var child = require('child_process'); |
| |
| // Hack to run child_process.exec() synchronously (sync avoids callback hell) |
| // Uses a custom wait loop that checks for a flag file, created when the child process is done. |
| // (Can't do a wait loop that checks for internal Node variables/messages as |
| // Node is single-threaded; callbacks and other internal state changes are done in the |
| // event loop). |
| function execSync(cmd, opts) { |
| var tempDir = _tempDir(); |
| var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()), |
| codeFile = path.resolve(tempDir+'/'+common.randomFileName()), |
| scriptFile = path.resolve(tempDir+'/'+common.randomFileName()), |
| sleepFile = path.resolve(tempDir+'/'+common.randomFileName()); |
| |
| var options = common.extend({ |
| silent: common.config.silent |
| }, opts); |
| |
| var previousStdoutContent = ''; |
| // Echoes stdout changes from running process, if not silent |
| function updateStdout() { |
| if (options.silent || !fs.existsSync(stdoutFile)) |
| return; |
| |
| var stdoutContent = fs.readFileSync(stdoutFile, 'utf8'); |
| // No changes since last time? |
| if (stdoutContent.length <= previousStdoutContent.length) |
| return; |
| |
| process.stdout.write(stdoutContent.substr(previousStdoutContent.length)); |
| previousStdoutContent = stdoutContent; |
| } |
| |
| function escape(str) { |
| return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0"); |
| } |
| |
| cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix |
| |
| var script = |
| "var child = require('child_process')," + |
| " fs = require('fs');" + |
| "child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {" + |
| " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');" + |
| "});"; |
| |
| if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile); |
| if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile); |
| if (fs.existsSync(codeFile)) common.unlinkSync(codeFile); |
| |
| fs.writeFileSync(scriptFile, script); |
| child.exec('"'+process.execPath+'" '+scriptFile, { |
| env: process.env, |
| cwd: _pwd(), |
| maxBuffer: 20*1024*1024 |
| }); |
| |
| // The wait loop |
| // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage |
| // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing |
| // CPU usage, though apparently not so much on Windows) |
| while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); } |
| while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); } |
| |
| // At this point codeFile exists, but it's not necessarily flushed yet. |
| // Keep reading it until it is. |
| var code = parseInt('', 10); |
| while (isNaN(code)) { |
| code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10); |
| } |
| |
| var stdout = fs.readFileSync(stdoutFile, 'utf8'); |
| |
| // No biggie if we can't erase the files now -- they're in a temp dir anyway |
| try { common.unlinkSync(scriptFile); } catch(e) {} |
| try { common.unlinkSync(stdoutFile); } catch(e) {} |
| try { common.unlinkSync(codeFile); } catch(e) {} |
| try { common.unlinkSync(sleepFile); } catch(e) {} |
| |
| // some shell return codes are defined as errors, per http://tldp.org/LDP/abs/html/exitcodes.html |
| if (code === 1 || code === 2 || code >= 126) { |
| common.error('', true); // unix/shell doesn't really give an error message after non-zero exit codes |
| } |
| // True if successful, false if not |
| var obj = { |
| code: code, |
| output: stdout |
| }; |
| return obj; |
| } // execSync() |
| |
| // Wrapper around exec() to enable echoing output to console in real time |
| function execAsync(cmd, opts, callback) { |
| var output = ''; |
| |
| var options = common.extend({ |
| silent: common.config.silent |
| }, opts); |
| |
| var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) { |
| if (callback) |
| callback(err ? err.code : 0, output); |
| }); |
| |
| c.stdout.on('data', function(data) { |
| output += data; |
| if (!options.silent) |
| process.stdout.write(data); |
| }); |
| |
| c.stderr.on('data', function(data) { |
| output += data; |
| if (!options.silent) |
| process.stdout.write(data); |
| }); |
| |
| return c; |
| } |
| |
| //@ |
| //@ ### exec(command [, options] [, callback]) |
| //@ Available options (all `false` by default): |
| //@ |
| //@ + `async`: Asynchronous execution. Defaults to true if a callback is provided. |
| //@ + `silent`: Do not echo program output to console. |
| //@ |
| //@ Examples: |
| //@ |
| //@ ```javascript |
| //@ var version = exec('node --version', {silent:true}).output; |
| //@ |
| //@ var child = exec('some_long_running_process', {async:true}); |
| //@ child.stdout.on('data', function(data) { |
| //@ /* ... do something with data ... */ |
| //@ }); |
| //@ |
| //@ exec('some_long_running_process', function(code, output) { |
| //@ console.log('Exit code:', code); |
| //@ console.log('Program output:', output); |
| //@ }); |
| //@ ``` |
| //@ |
| //@ Executes the given `command` _synchronously_, unless otherwise specified. |
| //@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's |
| //@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and |
| //@ the `callback` gets the arguments `(code, output)`. |
| //@ |
| //@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as |
| //@ the current synchronous implementation uses a lot of CPU. This should be getting |
| //@ fixed soon. |
| function _exec(command, options, callback) { |
| if (!command) |
| common.error('must specify command'); |
| |
| // Callback is defined instead of options. |
| if (typeof options === 'function') { |
| callback = options; |
| options = { async: true }; |
| } |
| |
| // Callback is defined with options. |
| if (typeof options === 'object' && typeof callback === 'function') { |
| options.async = true; |
| } |
| |
| options = common.extend({ |
| silent: common.config.silent, |
| async: false |
| }, options); |
| |
| if (options.async) |
| return execAsync(command, options, callback); |
| else |
| return execSync(command, options); |
| } |
| module.exports = _exec; |