| 1 | /*
|
|---|
| 2 | * Jake JavaScript build tool
|
|---|
| 3 | * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
|---|
| 4 | *
|
|---|
| 5 | * Licensed under the Apache License, Version 2.0 (the "License");
|
|---|
| 6 | * you may not use this file except in compliance with the License.
|
|---|
| 7 | * You may obtain a copy of the License at
|
|---|
| 8 | *
|
|---|
| 9 | * http://www.apache.org/licenses/LICENSE-2.0
|
|---|
| 10 | *
|
|---|
| 11 | * Unless required by applicable law or agreed to in writing, software
|
|---|
| 12 | * distributed under the License is distributed on an "AS IS" BASIS,
|
|---|
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|---|
| 14 | * See the License for the specific language governing permissions and
|
|---|
| 15 | * limitations under the License.
|
|---|
| 16 | *
|
|---|
| 17 | */
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 | let util = require('util'); // Native Node util module
|
|---|
| 21 | let spawn = require('child_process').spawn;
|
|---|
| 22 | let EventEmitter = require('events').EventEmitter;
|
|---|
| 23 | let logger = require('./logger');
|
|---|
| 24 | let file = require('./file');
|
|---|
| 25 | let Exec;
|
|---|
| 26 |
|
|---|
| 27 | const _UUID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
|---|
| 28 |
|
|---|
| 29 | let parseArgs = function (argumentsObj) {
|
|---|
| 30 | let args;
|
|---|
| 31 | let arg;
|
|---|
| 32 | let cmds;
|
|---|
| 33 | let callback;
|
|---|
| 34 | let opts = {
|
|---|
| 35 | interactive: false,
|
|---|
| 36 | printStdout: false,
|
|---|
| 37 | printStderr: false,
|
|---|
| 38 | breakOnError: true
|
|---|
| 39 | };
|
|---|
| 40 |
|
|---|
| 41 | args = Array.prototype.slice.call(argumentsObj);
|
|---|
| 42 |
|
|---|
| 43 | cmds = args.shift();
|
|---|
| 44 | // Arrayize if passed a single string command
|
|---|
| 45 | if (typeof cmds == 'string') {
|
|---|
| 46 | cmds = [cmds];
|
|---|
| 47 | }
|
|---|
| 48 | // Make a copy if it's an actual list
|
|---|
| 49 | else {
|
|---|
| 50 | cmds = cmds.slice();
|
|---|
| 51 | }
|
|---|
| 52 |
|
|---|
| 53 | // Get optional callback or opts
|
|---|
| 54 | while((arg = args.shift())) {
|
|---|
| 55 | if (typeof arg == 'function') {
|
|---|
| 56 | callback = arg;
|
|---|
| 57 | }
|
|---|
| 58 | else if (typeof arg == 'object') {
|
|---|
| 59 | opts = Object.assign(opts, arg);
|
|---|
| 60 | }
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | // Backward-compat shim
|
|---|
| 64 | if (typeof opts.stdout != 'undefined') {
|
|---|
| 65 | opts.printStdout = opts.stdout;
|
|---|
| 66 | delete opts.stdout;
|
|---|
| 67 | }
|
|---|
| 68 | if (typeof opts.stderr != 'undefined') {
|
|---|
| 69 | opts.printStderr = opts.stderr;
|
|---|
| 70 | delete opts.stderr;
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | return {
|
|---|
| 74 | cmds: cmds,
|
|---|
| 75 | opts: opts,
|
|---|
| 76 | callback: callback
|
|---|
| 77 | };
|
|---|
| 78 | };
|
|---|
| 79 |
|
|---|
| 80 | /**
|
|---|
| 81 | @name jake
|
|---|
| 82 | @namespace jake
|
|---|
| 83 | */
|
|---|
| 84 | let utils = new (function () {
|
|---|
| 85 | /**
|
|---|
| 86 | @name jake.exec
|
|---|
| 87 | @static
|
|---|
| 88 | @function
|
|---|
| 89 | @description Executes shell-commands asynchronously with an optional
|
|---|
| 90 | final callback.
|
|---|
| 91 | `
|
|---|
| 92 | @param {String[]} cmds The list of shell-commands to execute
|
|---|
| 93 | @param {Object} [opts]
|
|---|
| 94 | @param {Boolean} [opts.printStdout=false] Print stdout from each command
|
|---|
| 95 | @param {Boolean} [opts.printStderr=false] Print stderr from each command
|
|---|
| 96 | @param {Boolean} [opts.breakOnError=true] Stop further execution on
|
|---|
| 97 | the first error.
|
|---|
| 98 | @param {Boolean} [opts.windowsVerbatimArguments=false] Don't translate
|
|---|
| 99 | arguments on Windows.
|
|---|
| 100 | @param {Function} [callback] Callback to run after executing the
|
|---|
| 101 | commands
|
|---|
| 102 |
|
|---|
| 103 | @example
|
|---|
| 104 | let cmds = [
|
|---|
| 105 | 'echo "showing directories"'
|
|---|
| 106 | , 'ls -al | grep ^d'
|
|---|
| 107 | , 'echo "moving up a directory"'
|
|---|
| 108 | , 'cd ../'
|
|---|
| 109 | ]
|
|---|
| 110 | , callback = function () {
|
|---|
| 111 | console.log('Finished running commands.');
|
|---|
| 112 | }
|
|---|
| 113 | jake.exec(cmds, {stdout: true}, callback);
|
|---|
| 114 | */
|
|---|
| 115 | this.exec = function (a, b, c) {
|
|---|
| 116 | let parsed = parseArgs(arguments);
|
|---|
| 117 | let cmds = parsed.cmds;
|
|---|
| 118 | let opts = parsed.opts;
|
|---|
| 119 | let callback = parsed.callback;
|
|---|
| 120 |
|
|---|
| 121 | let ex = new Exec(cmds, opts, callback);
|
|---|
| 122 |
|
|---|
| 123 | ex.addListener('error', function (msg, code) {
|
|---|
| 124 | if (opts.breakOnError) {
|
|---|
| 125 | fail(msg, code);
|
|---|
| 126 | }
|
|---|
| 127 | });
|
|---|
| 128 | ex.run();
|
|---|
| 129 |
|
|---|
| 130 | return ex;
|
|---|
| 131 | };
|
|---|
| 132 |
|
|---|
| 133 | this.createExec = function (a, b, c) {
|
|---|
| 134 | return new Exec(a, b, c);
|
|---|
| 135 | };
|
|---|
| 136 |
|
|---|
| 137 | // From Math.uuid.js, https://github.com/broofa/node-uuid
|
|---|
| 138 | // Robert Kieffer (robert@broofa.com), MIT license
|
|---|
| 139 | this.uuid = function (length, radix) {
|
|---|
| 140 | var chars = _UUID_CHARS
|
|---|
| 141 | , uuid = []
|
|---|
| 142 | , r
|
|---|
| 143 | , i;
|
|---|
| 144 |
|
|---|
| 145 | radix = radix || chars.length;
|
|---|
| 146 |
|
|---|
| 147 | if (length) {
|
|---|
| 148 | // Compact form
|
|---|
| 149 | i = -1;
|
|---|
| 150 | while (++i < length) {
|
|---|
| 151 | uuid[i] = chars[0 | Math.random()*radix];
|
|---|
| 152 | }
|
|---|
| 153 | } else {
|
|---|
| 154 | // rfc4122, version 4 form
|
|---|
| 155 |
|
|---|
| 156 | // rfc4122 requires these characters
|
|---|
| 157 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
|---|
| 158 | uuid[14] = '4';
|
|---|
| 159 |
|
|---|
| 160 | // Fill in random data. At i==19 set the high bits of clock sequence as
|
|---|
| 161 | // per rfc4122, sec. 4.1.5
|
|---|
| 162 | i = -1;
|
|---|
| 163 | while (++i < 36) {
|
|---|
| 164 | if (!uuid[i]) {
|
|---|
| 165 | r = 0 | Math.random()*16;
|
|---|
| 166 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
|
|---|
| 167 | }
|
|---|
| 168 | }
|
|---|
| 169 | }
|
|---|
| 170 |
|
|---|
| 171 | return uuid.join('');
|
|---|
| 172 | };
|
|---|
| 173 |
|
|---|
| 174 | })();
|
|---|
| 175 |
|
|---|
| 176 | Exec = function () {
|
|---|
| 177 | let parsed = parseArgs(arguments);
|
|---|
| 178 | let cmds = parsed.cmds;
|
|---|
| 179 | let opts = parsed.opts;
|
|---|
| 180 | let callback = parsed.callback;
|
|---|
| 181 |
|
|---|
| 182 | this._cmds = cmds;
|
|---|
| 183 | this._callback = callback;
|
|---|
| 184 | this._config = opts;
|
|---|
| 185 | };
|
|---|
| 186 |
|
|---|
| 187 | util.inherits(Exec, EventEmitter);
|
|---|
| 188 |
|
|---|
| 189 | Object.assign(Exec.prototype, new (function () {
|
|---|
| 190 |
|
|---|
| 191 | let _run = function () {
|
|---|
| 192 | let self = this;
|
|---|
| 193 | let sh;
|
|---|
| 194 | let cmd;
|
|---|
| 195 | let args;
|
|---|
| 196 | let next = this._cmds.shift();
|
|---|
| 197 | let config = this._config;
|
|---|
| 198 | let errData = '';
|
|---|
| 199 | let shStdio;
|
|---|
| 200 | let handleStdoutData = function (data) {
|
|---|
| 201 | self.emit('stdout', data);
|
|---|
| 202 | };
|
|---|
| 203 | let handleStderrData = function (data) {
|
|---|
| 204 | let d = data.toString();
|
|---|
| 205 | self.emit('stderr', data);
|
|---|
| 206 | // Accumulate the error-data so we can use it as the
|
|---|
| 207 | // stack if the process exits with an error
|
|---|
| 208 | errData += d;
|
|---|
| 209 | };
|
|---|
| 210 |
|
|---|
| 211 | // Keep running as long as there are commands in the array
|
|---|
| 212 | if (next) {
|
|---|
| 213 | let spawnOpts = {};
|
|---|
| 214 | this.emit('cmdStart', next);
|
|---|
| 215 |
|
|---|
| 216 | // Ganking part of Node's child_process.exec to get cmdline args parsed
|
|---|
| 217 | if (process.platform == 'win32') {
|
|---|
| 218 | cmd = 'cmd';
|
|---|
| 219 | args = ['/c', next];
|
|---|
| 220 | if (config.windowsVerbatimArguments) {
|
|---|
| 221 | spawnOpts.windowsVerbatimArguments = true;
|
|---|
| 222 | }
|
|---|
| 223 | }
|
|---|
| 224 | else {
|
|---|
| 225 | cmd = '/bin/sh';
|
|---|
| 226 | args = ['-c', next];
|
|---|
| 227 | }
|
|---|
| 228 |
|
|---|
| 229 | if (config.interactive) {
|
|---|
| 230 | spawnOpts.stdio = 'inherit';
|
|---|
| 231 | sh = spawn(cmd, args, spawnOpts);
|
|---|
| 232 | }
|
|---|
| 233 | else {
|
|---|
| 234 | shStdio = [
|
|---|
| 235 | process.stdin
|
|---|
| 236 | ];
|
|---|
| 237 | if (config.printStdout) {
|
|---|
| 238 | shStdio.push(process.stdout);
|
|---|
| 239 | }
|
|---|
| 240 | else {
|
|---|
| 241 | shStdio.push('pipe');
|
|---|
| 242 | }
|
|---|
| 243 | if (config.printStderr) {
|
|---|
| 244 | shStdio.push(process.stderr);
|
|---|
| 245 | }
|
|---|
| 246 | else {
|
|---|
| 247 | shStdio.push('pipe');
|
|---|
| 248 | }
|
|---|
| 249 | spawnOpts.stdio = shStdio;
|
|---|
| 250 | sh = spawn(cmd, args, spawnOpts);
|
|---|
| 251 | if (!config.printStdout) {
|
|---|
| 252 | sh.stdout.addListener('data', handleStdoutData);
|
|---|
| 253 | }
|
|---|
| 254 | if (!config.printStderr) {
|
|---|
| 255 | sh.stderr.addListener('data', handleStderrData);
|
|---|
| 256 | }
|
|---|
| 257 | }
|
|---|
| 258 |
|
|---|
| 259 | // Exit, handle err or run next
|
|---|
| 260 | sh.on('exit', function (code) {
|
|---|
| 261 | let msg;
|
|---|
| 262 | if (code !== 0) {
|
|---|
| 263 | msg = errData || 'Process exited with error.';
|
|---|
| 264 | msg = msg.trim();
|
|---|
| 265 | self.emit('error', msg, code);
|
|---|
| 266 | }
|
|---|
| 267 | if (code === 0 || !config.breakOnError) {
|
|---|
| 268 | self.emit('cmdEnd', next);
|
|---|
| 269 | setTimeout(function () { _run.call(self); }, 0);
|
|---|
| 270 | }
|
|---|
| 271 | });
|
|---|
| 272 |
|
|---|
| 273 | }
|
|---|
| 274 | else {
|
|---|
| 275 | self.emit('end');
|
|---|
| 276 | if (typeof self._callback == 'function') {
|
|---|
| 277 | self._callback();
|
|---|
| 278 | }
|
|---|
| 279 | }
|
|---|
| 280 | };
|
|---|
| 281 |
|
|---|
| 282 | this.append = function (cmd) {
|
|---|
| 283 | this._cmds.push(cmd);
|
|---|
| 284 | };
|
|---|
| 285 |
|
|---|
| 286 | this.run = function () {
|
|---|
| 287 | _run.call(this);
|
|---|
| 288 | };
|
|---|
| 289 |
|
|---|
| 290 | })());
|
|---|
| 291 |
|
|---|
| 292 | utils.Exec = Exec;
|
|---|
| 293 | utils.file = file;
|
|---|
| 294 | utils.logger = logger;
|
|---|
| 295 |
|
|---|
| 296 | module.exports = utils;
|
|---|
| 297 |
|
|---|