298 lines
7.1 KiB
JavaScript
298 lines
7.1 KiB
JavaScript
/*
|
|
* Jake JavaScript build tool
|
|
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
|
|
let util = require('util'); // Native Node util module
|
|
let spawn = require('child_process').spawn;
|
|
let EventEmitter = require('events').EventEmitter;
|
|
let logger = require('./logger');
|
|
let file = require('./file');
|
|
let Exec;
|
|
|
|
const _UUID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
|
|
|
let parseArgs = function (argumentsObj) {
|
|
let args;
|
|
let arg;
|
|
let cmds;
|
|
let callback;
|
|
let opts = {
|
|
interactive: false,
|
|
printStdout: false,
|
|
printStderr: false,
|
|
breakOnError: true
|
|
};
|
|
|
|
args = Array.prototype.slice.call(argumentsObj);
|
|
|
|
cmds = args.shift();
|
|
// Arrayize if passed a single string command
|
|
if (typeof cmds == 'string') {
|
|
cmds = [cmds];
|
|
}
|
|
// Make a copy if it's an actual list
|
|
else {
|
|
cmds = cmds.slice();
|
|
}
|
|
|
|
// Get optional callback or opts
|
|
while((arg = args.shift())) {
|
|
if (typeof arg == 'function') {
|
|
callback = arg;
|
|
}
|
|
else if (typeof arg == 'object') {
|
|
opts = Object.assign(opts, arg);
|
|
}
|
|
}
|
|
|
|
// Backward-compat shim
|
|
if (typeof opts.stdout != 'undefined') {
|
|
opts.printStdout = opts.stdout;
|
|
delete opts.stdout;
|
|
}
|
|
if (typeof opts.stderr != 'undefined') {
|
|
opts.printStderr = opts.stderr;
|
|
delete opts.stderr;
|
|
}
|
|
|
|
return {
|
|
cmds: cmds,
|
|
opts: opts,
|
|
callback: callback
|
|
};
|
|
};
|
|
|
|
/**
|
|
@name jake
|
|
@namespace jake
|
|
*/
|
|
let utils = new (function () {
|
|
/**
|
|
@name jake.exec
|
|
@static
|
|
@function
|
|
@description Executes shell-commands asynchronously with an optional
|
|
final callback.
|
|
`
|
|
@param {String[]} cmds The list of shell-commands to execute
|
|
@param {Object} [opts]
|
|
@param {Boolean} [opts.printStdout=false] Print stdout from each command
|
|
@param {Boolean} [opts.printStderr=false] Print stderr from each command
|
|
@param {Boolean} [opts.breakOnError=true] Stop further execution on
|
|
the first error.
|
|
@param {Boolean} [opts.windowsVerbatimArguments=false] Don't translate
|
|
arguments on Windows.
|
|
@param {Function} [callback] Callback to run after executing the
|
|
commands
|
|
|
|
@example
|
|
let cmds = [
|
|
'echo "showing directories"'
|
|
, 'ls -al | grep ^d'
|
|
, 'echo "moving up a directory"'
|
|
, 'cd ../'
|
|
]
|
|
, callback = function () {
|
|
console.log('Finished running commands.');
|
|
}
|
|
jake.exec(cmds, {stdout: true}, callback);
|
|
*/
|
|
this.exec = function (a, b, c) {
|
|
let parsed = parseArgs(arguments);
|
|
let cmds = parsed.cmds;
|
|
let opts = parsed.opts;
|
|
let callback = parsed.callback;
|
|
|
|
let ex = new Exec(cmds, opts, callback);
|
|
|
|
ex.addListener('error', function (msg, code) {
|
|
if (opts.breakOnError) {
|
|
fail(msg, code);
|
|
}
|
|
});
|
|
ex.run();
|
|
|
|
return ex;
|
|
};
|
|
|
|
this.createExec = function (a, b, c) {
|
|
return new Exec(a, b, c);
|
|
};
|
|
|
|
// From Math.uuid.js, https://github.com/broofa/node-uuid
|
|
// Robert Kieffer (robert@broofa.com), MIT license
|
|
this.uuid = function (length, radix) {
|
|
var chars = _UUID_CHARS
|
|
, uuid = []
|
|
, r
|
|
, i;
|
|
|
|
radix = radix || chars.length;
|
|
|
|
if (length) {
|
|
// Compact form
|
|
i = -1;
|
|
while (++i < length) {
|
|
uuid[i] = chars[0 | Math.random()*radix];
|
|
}
|
|
} else {
|
|
// rfc4122, version 4 form
|
|
|
|
// rfc4122 requires these characters
|
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
|
uuid[14] = '4';
|
|
|
|
// Fill in random data. At i==19 set the high bits of clock sequence as
|
|
// per rfc4122, sec. 4.1.5
|
|
i = -1;
|
|
while (++i < 36) {
|
|
if (!uuid[i]) {
|
|
r = 0 | Math.random()*16;
|
|
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
|
|
}
|
|
}
|
|
}
|
|
|
|
return uuid.join('');
|
|
};
|
|
|
|
})();
|
|
|
|
Exec = function () {
|
|
let parsed = parseArgs(arguments);
|
|
let cmds = parsed.cmds;
|
|
let opts = parsed.opts;
|
|
let callback = parsed.callback;
|
|
|
|
this._cmds = cmds;
|
|
this._callback = callback;
|
|
this._config = opts;
|
|
};
|
|
|
|
util.inherits(Exec, EventEmitter);
|
|
|
|
Object.assign(Exec.prototype, new (function () {
|
|
|
|
let _run = function () {
|
|
let self = this;
|
|
let sh;
|
|
let cmd;
|
|
let args;
|
|
let next = this._cmds.shift();
|
|
let config = this._config;
|
|
let errData = '';
|
|
let shStdio;
|
|
let handleStdoutData = function (data) {
|
|
self.emit('stdout', data);
|
|
};
|
|
let handleStderrData = function (data) {
|
|
let d = data.toString();
|
|
self.emit('stderr', data);
|
|
// Accumulate the error-data so we can use it as the
|
|
// stack if the process exits with an error
|
|
errData += d;
|
|
};
|
|
|
|
// Keep running as long as there are commands in the array
|
|
if (next) {
|
|
let spawnOpts = {};
|
|
this.emit('cmdStart', next);
|
|
|
|
// Ganking part of Node's child_process.exec to get cmdline args parsed
|
|
if (process.platform == 'win32') {
|
|
cmd = 'cmd';
|
|
args = ['/c', next];
|
|
if (config.windowsVerbatimArguments) {
|
|
spawnOpts.windowsVerbatimArguments = true;
|
|
}
|
|
}
|
|
else {
|
|
cmd = '/bin/sh';
|
|
args = ['-c', next];
|
|
}
|
|
|
|
if (config.interactive) {
|
|
spawnOpts.stdio = 'inherit';
|
|
sh = spawn(cmd, args, spawnOpts);
|
|
}
|
|
else {
|
|
shStdio = [
|
|
process.stdin
|
|
];
|
|
if (config.printStdout) {
|
|
shStdio.push(process.stdout);
|
|
}
|
|
else {
|
|
shStdio.push('pipe');
|
|
}
|
|
if (config.printStderr) {
|
|
shStdio.push(process.stderr);
|
|
}
|
|
else {
|
|
shStdio.push('pipe');
|
|
}
|
|
spawnOpts.stdio = shStdio;
|
|
sh = spawn(cmd, args, spawnOpts);
|
|
if (!config.printStdout) {
|
|
sh.stdout.addListener('data', handleStdoutData);
|
|
}
|
|
if (!config.printStderr) {
|
|
sh.stderr.addListener('data', handleStderrData);
|
|
}
|
|
}
|
|
|
|
// Exit, handle err or run next
|
|
sh.on('exit', function (code) {
|
|
let msg;
|
|
if (code !== 0) {
|
|
msg = errData || 'Process exited with error.';
|
|
msg = msg.trim();
|
|
self.emit('error', msg, code);
|
|
}
|
|
if (code === 0 || !config.breakOnError) {
|
|
self.emit('cmdEnd', next);
|
|
setTimeout(function () { _run.call(self); }, 0);
|
|
}
|
|
});
|
|
|
|
}
|
|
else {
|
|
self.emit('end');
|
|
if (typeof self._callback == 'function') {
|
|
self._callback();
|
|
}
|
|
}
|
|
};
|
|
|
|
this.append = function (cmd) {
|
|
this._cmds.push(cmd);
|
|
};
|
|
|
|
this.run = function () {
|
|
_run.call(this);
|
|
};
|
|
|
|
})());
|
|
|
|
utils.Exec = Exec;
|
|
utils.file = file;
|
|
utils.logger = logger;
|
|
|
|
module.exports = utils;
|
|
|