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; |