UNPKG

@luminati-io/jake

Version:

JavaScript build tool, similar to Make or Rake

429 lines (394 loc) 11.7 kB
/* * 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. * */ var util = require('util') // Native Node util module , exec = require('child_process').exec , spawn = require('child_process').spawn , EventEmitter = require('events').EventEmitter , utils = require('utilities') , logger = require('./logger') //, tarjan = require('./tarjan') , fs = require('fs') , Exec; var parseArgs = function (argumentsObj) { var args , arg , cmds , callback , 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') { utils.mixin(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 */ utils.mixin(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 var 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) { var parsed = parseArgs(arguments) , cmds = parsed.cmds , opts = parsed.opts , callback = parsed.callback; var ex = new Exec(cmds, opts, callback); if (!opts.interactive) { if (opts.printStdout) { ex.addListener('stdout', function (data) { process.stdout.write(data); }); } if (opts.printStderr) { ex.addListener('stderr', function (data) { process.stderr.write(data); }); } } 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); }; function reduce_uniq(p, c){ p[c] = 1; return p; } this.uniq = function(arr){ if (!arr) return []; // Order may be lost return Object.keys(arr.reduce(reduce_uniq, {})); }; })()); Exec = function () { var parsed = parseArgs(arguments) , cmds = parsed.cmds , opts = parsed.opts , callback = parsed.callback; this._cmds = cmds; this._callback = callback; this._config = opts; }; util.inherits(Exec, EventEmitter); utils.mixin(Exec.prototype, new (function () { var _run = function () { var self = this , sh , cmd , args , next = this._cmds.shift() , config = this._config , errData = ''; // Keep running as long as there are commands in the array if (next) { var 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 { spawnOpts.stdio = [process.stdin, 'pipe', 'pipe']; sh = spawn(cmd, args, spawnOpts); // Out sh.stdout.on('data', function (data) { self.emit('stdout', data); }); // Err sh.stderr.on('data', function (data) { var 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; }); } // Exit, handle err or run next sh.on('exit', function (code) { var msg; if (code !== 0) { msg = errData || 'Process exited with error.'; msg = utils.string.trim(msg); self.emit('error', msg, code); } if (code === 0 || !config.breakOnError) { self.emit('cmdEnd', next); _run.call(self); } }); } 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.logger = logger; // tarjan.js function Tarjan(){ this.index = 0; this.stack = []; this.scc = []; } function Vertex(name){ this.name = name || null; this.connections = []; this.index= -1; this.lowlink = -1; } var TJ = {}; TJ.process = function(tasks, list){ // convert all tasks to graph var cache = {}, vertices = []; function to_vertex(task){ if (!task) return; var name = task.fullName; if (name in cache) return cache[name]; var vertex = cache[name] = new Vertex(name); vertices.push(vertex); for (var p=0; p<task.prereqs.length; p++) { var child = to_vertex(tasks[task.prereqs[p]]); if (child) vertex.connections.push(child); } return vertex; }; var tlist = list||Object.keys(tasks); for (var t=0, tt=tlist.length; t<tt; t++) to_vertex(tasks[tlist[t]]); return TJ.run(vertices); }; TJ.run = function(vertices){ var T = new Tarjan(); var S = {cache: {}, stack: []}; for (var i=0; i<vertices.length; i++) { var vertex = vertices[i]; if (vertex.index<0) TJ.strong(T, S, vertices[i]); } return T; }; TJ.strong = function(T, S, vertex){ // Set the depth index for v to the smallest unused index vertex.index = T.index; vertex.lowlink = T.index; T.index = T.index + 1; S.stack.push(vertex); if (vertex.name in S.cache) throw new Error('Duplicated vertex '+vertex.name+' found'); S.cache[vertex.name] = vertex; // Consider successors of v // aka... consider each vertex in vertex.connections for (var c=0; c<vertex.connections.length; c++){ var v = vertex, w = vertex.connections[c]; if (w.index<0){ // Successor w has not yet been visited; recurse on it TJ.strong(T, S, w); v.lowlink = Math.min(v.lowlink, w.lowlink); } else if (w.name in S.cache){ // Successor w is in stack S and hence in the current SCC v.lowlink = Math.min(v.lowlink, w.index); } } // If v is a root node, pop the stack and generate an SCC if (vertex.lowlink==vertex.index){ // start a new strongly connected component var vertices = []; var w = null; if (T.stack.length>0){ do { w = T.stack.pop(); delete S.cache[w.name] // add w to current strongly connected component vertices.push(w); } while (vertex.name != w.name); } // output the current strongly connected components // only if 2 or more components strongly connected if (vertices.length>1) T.scc.push(vertices); } }; utils.tarjan = TJ; // file.js var E = {}; var path = require('path'); E.is_win = /^win/.test(process.platform); E.exists = function(path){ return fs.existsSync(path); }; E.is_file = function(path){ var stat; try { stat = fs.statSync(path); } catch(e){ return false; } return stat.isFile(); }; E.is_dir = function(path){ var stat; try { stat = fs.statSync(path); } catch(e){ return false; } return stat.isDirectory(); }; if (E.is_win) { E.cygwin_root = E.is_dir('C:/cygwin64') ? 'C:/cygwin64' : E.is_dir('C:/cygwin') ? 'C:/cygwin' : E.is_dir('D:/cygwin') ? 'D:/cygwin' : null; } E.cyg2unix = function(path){ if (!E.is_win) return path; // /cygdrive/X/yyy --> X:/yyy path = path.replace(/^\/cygdrive\/(.)(\/(.*))?$/, "$1:/$3"); // /xxx --> c:/cygwin/xxx path = path.replace(/^\//, E.cygwin_root.toLowerCase()+'/'); return path; }; E.unix2win = function(path){ if (!E.is_win) return path; // c:/xxx -> C:/xxx path = path.replace(/^[cd]:/, function(s){ return s.toUpperCase(); }); // C:/xxx/yyy -> C:\xxx\yyy path = path.replace(/\//g, '\\'); return path; }; E.win2unix = function(path, force) { if (!force && !E.is_win) return path; // C:\xxx\yyy --> C:/xxx/yyy path = path.replace(/\\/g, '/'); // C:/ --> c:/ path = path.replace(/^[cd]:/i, function(s){ return s.toLowerCase(); }); return path; }; E.win2cyg = function(path){ if (!E.is_win) return path; path = E.win2unix(path); var escaped_root = E.cygwin_root.replace(/([\?\\\/\[\]+*])/g, '\\$1'); path = path.replace(new RegExp("^"+escaped_root+"/?", "i"), '/'); path = path.replace(/^[cd]:/i, function(s){ return "/cygdrive/"+s[0].toLowerCase(); }); return path; }; E.is_absolute = function(path){ return /^(\/|([cd]:))/i.test(path); }; E.absolutize = function(p, d1, d2){ if (!p||E.is_absolute(p)) return p; if (d2&&E.exists(d2+'/'+p)) return d2+'/'+p; d1 = d1||process.cwd(); return d1+'/'+p; }; E.normalize = function(p){ return E.cyg2unix(E.win2unix(path.normalize(p))); }; utils.mixin(utils.file, E); module.exports = utils;