289 lines
9.6 KiB
JavaScript
289 lines
9.6 KiB
JavaScript
//
|
|
// The Dojo Build System
|
|
//
|
|
// This is application is implemented as an AMD package intended to be loaded and executed by dojo. It is known to work correctly
|
|
// with node.js (fast!) and rhino (slow!). The program may be started from a command prompt as follows:
|
|
//
|
|
// node.js:
|
|
// >node path/to/dojotoolkit/dojo/dojo.js load=build <arguments>
|
|
//
|
|
// rhino:
|
|
// >java -jar path/to/js.jar path/to/dojotoolkit/dojo/dojo.js baseUrl=path/to/dojotoolkit/dojo load=build <arguments>
|
|
//
|
|
// * notice that, owing to the defective design of rhino, it is impossible for a script to know the location from
|
|
// which it was executed; therefore, the baseUrl must be provided.
|
|
//
|
|
// util/buildscripts/bng:
|
|
// TODOC
|
|
//
|
|
// The application proceeds as follows:
|
|
//
|
|
// 1. Process the command line and then process the build control script(s)/profile as specified by the command line.
|
|
// 2. Discover all resources as instructed by the build control script
|
|
// 3. Move the resources through an ordered set of gates. Zero to many synchronous and/or asynchronous transforms may be applied to various
|
|
// resources as specified by the build control script. Different resources can be subject to different transforms. Resources are allowed
|
|
// to move through gates without stopping until a "synchronized" gate is encountered. All transforms must complete for the previous gate before
|
|
// any transform is allowed on the synchronized gate.
|
|
// 4. After the last gate has been completed, print a done message and terminate.
|
|
//
|
|
// See also:
|
|
//
|
|
// project home: http://bdframework.org/bdBuild/index
|
|
// fossil: http://bdframework.org/bdBuild/repo
|
|
// github: https://github.com/altoviso/bdBuild
|
|
// docs: http://bdframework.org/bdBuild/docs
|
|
|
|
define(["require", "dojo/has"], function(require, has){
|
|
|
|
// host-dependent environment initialization
|
|
if(has("host-node")){
|
|
define("commandLineArgs", function(){
|
|
//arg[0] is node; argv[1] is dojo.js; therefore, start with argv[2]
|
|
return process.argv.slice(2);
|
|
});
|
|
|
|
// helps during dev or heavily async node...
|
|
var util = require.nodeRequire("util");
|
|
debug = function(it, depth, inspect){
|
|
util.debug(inspect ? util.inspect(it, false, depth) : it);
|
|
};
|
|
|
|
has.add("is-windows", process.platform == "win32");
|
|
}else if(has("host-rhino")){
|
|
define("commandLineArgs", [], function(){
|
|
var result = [];
|
|
require.rawConfig.commandLineArgs.forEach(function(item){
|
|
var parts = item.split("=");
|
|
if(parts[0]!="baseUrl"){
|
|
result.push(item);
|
|
}
|
|
});
|
|
return result;
|
|
});
|
|
// TODO: make this real
|
|
has.add("is-windows", /indows/.test(environment["os.name"]));
|
|
}else{
|
|
console.log("unknown environment; terminating.");
|
|
return 0;
|
|
}
|
|
|
|
this.require.scopeify = function(moduleList){
|
|
for(var p, mid, module, text = "", contextRequire = this, args = moduleList.split(","), i = 0; i<args.length;){
|
|
mid = args[i++].match(/\S+/)[0];
|
|
module = contextRequire(mid);
|
|
mid = mid.match(/[^\/]+$/)[0];
|
|
for(p in module){
|
|
text+= "var " + p + "=" + mid + "." + p + ";\n";
|
|
}
|
|
}
|
|
return text;
|
|
};
|
|
|
|
// run the build program
|
|
require(["./buildControl", "./process"], function(bc, process){
|
|
var
|
|
gateListeners = bc.gateListeners = [],
|
|
|
|
transforms = bc.transforms,
|
|
transformJobs = bc.transformJobs,
|
|
transformJobsLength = transformJobs.length,
|
|
|
|
// all discovered resources
|
|
resources = [],
|
|
|
|
reportError = function(resource, err){
|
|
bc.log("transformFailed", ["resource", resource.src, "transform", resource.jobPos, "error", err]);
|
|
resource.error = true;
|
|
},
|
|
|
|
returnFromAsyncProc = function(resource, err){
|
|
bc.waiting--;
|
|
if(err){
|
|
// notice reportError can decide to continue or panic
|
|
reportError(resource, err);
|
|
}
|
|
advance(resource, true);
|
|
},
|
|
|
|
advance = function(resource, continuingSameGate){
|
|
if(resource.error){
|
|
return;
|
|
}
|
|
if(!continuingSameGate){
|
|
// first time trying to advance through the current gate
|
|
bc.waiting++;
|
|
}
|
|
|
|
// apply all transforms with a gateId <= the current gate for resource that have not yet been applied
|
|
var err, nextJobPos, candidate;
|
|
while(1){
|
|
nextJobPos = resource.jobPos + 1;
|
|
candidate = nextJobPos<resource.job.length && resource.job[nextJobPos];
|
|
// candidate (if any) is a [transformProc, gateId] pair
|
|
if(candidate && candidate[1]<=bc.currentGate){
|
|
resource.jobPos++;
|
|
bc.waiting++;
|
|
err = candidate[0](resource, returnFromAsyncProc);
|
|
if(err===returnFromAsyncProc){
|
|
// the transform proc must call returnFromAsyncProc when complete
|
|
return;
|
|
}
|
|
bc.waiting--;
|
|
if(err){
|
|
// notice we reportError can decide to continue or panic
|
|
reportError(resource, err);
|
|
// if reportError didn't panic, then this break will cause this resource to clear the next
|
|
// gate; when all resources have cleared the next gate, passGate will notice error count and
|
|
// quit
|
|
break;
|
|
}
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// got through the gate; advise passGate which will decrement the lock we set at top of this function
|
|
passGate();
|
|
},
|
|
|
|
advanceGate = function(currentGate){
|
|
while(1){
|
|
bc.currentGate = ++currentGate;
|
|
bc.log("pacify", "starting " + bc.gates[bc.currentGate][2] + "...");
|
|
gateListeners.forEach(function(listener){
|
|
listener(bc.gates[bc.currentGate][1]);
|
|
});
|
|
if(currentGate==bc.gates.length-1 || bc.gates[currentGate+1][0]){
|
|
// if we've either advanced to the last gate or the next gate is a synchronized gate, then hold at the current gate
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
passGate = bc.passGate = function(){
|
|
if(--bc.waiting){
|
|
return;
|
|
} // else all processes have passed through bc.currentGate
|
|
|
|
if(bc.checkDiscovery){
|
|
//passing the first gate which is dicovery and just echoing discovery; therefore
|
|
process.exit(0);
|
|
}
|
|
|
|
if(bc.currentGate<bc.gates.length-1){
|
|
advanceGate(bc.currentGate);
|
|
// hold the next gate until all resources have been advised
|
|
bc.waiting++;
|
|
resources.forEach(function(resource){ advance(resource, 0); });
|
|
// release the hold placed above
|
|
passGate();
|
|
}else{
|
|
if(!resources.length){
|
|
bc.log("discoveryFailed");
|
|
}
|
|
bc.log("pacify", "Process finished normally.\n\terrors: " + bc.getErrorCount() + "\n\twarnings: " + bc.getWarnCount() + "\n\tbuild time: " + ((new Date()).getTime() - bc.startTimestamp.getTime()) / 1000 + " seconds");
|
|
if(!bc.exitCode && bc.getErrorCount()){
|
|
bc.exitCode = 1;
|
|
}
|
|
process.exit(bc.exitCode);
|
|
// that's all, folks...
|
|
}
|
|
};
|
|
|
|
bc.start = function(resource){
|
|
// check for collisions
|
|
var
|
|
src = resource.src,
|
|
dest = resource.dest;
|
|
if(bc.resourcesByDest[src]){
|
|
// a dest is scheduled to overwrite a source
|
|
bc.log("overwrite", ["input", src, "resource destined for same location: ", bc.resourcesByDest[src].src]);
|
|
return;
|
|
}
|
|
if(bc.resourcesByDest[dest]){
|
|
// multiple srcs scheduled to write into a single dest
|
|
if(src !== bc.resourcesByDest[dest].src){ // don't squawk if same file
|
|
bc.log("outputCollide", ["source-1", src, "source-2", bc.resourcesByDest[dest].src]);
|
|
}
|
|
return;
|
|
}
|
|
// remember the resources in the global maps
|
|
bc.resources[resource.src] = resource;
|
|
bc.resourcesByDest[resource.dest] = resource;
|
|
|
|
if(bc.checkDiscovery){
|
|
bc.log("pacify", src + "-->" + dest);
|
|
return;
|
|
}
|
|
|
|
// find the transformJob and start it...
|
|
for(var i = 0; i<transformJobsLength; i++){
|
|
if(transformJobs[i][0](resource, bc)){
|
|
// job gives a vector of functions to apply to the resource
|
|
// jobPos says the index in the job vector that has been applied
|
|
resources.push(resource);
|
|
resource.job = transformJobs[i][1];
|
|
resource.jobPos = -1;
|
|
advance(resource);
|
|
return;
|
|
}
|
|
}
|
|
bc.log("noTransform", ["resoures", resource.src]);
|
|
};
|
|
|
|
function doBuild(){
|
|
var
|
|
transformNames = [],
|
|
pluginNames = [],
|
|
deps = [];
|
|
bc.discoveryProcs.forEach(function(mid){ deps.push(mid); });
|
|
for(var p in bc.transforms){
|
|
// each item is a [AMD-MID, gateId] pair
|
|
transformNames.push(p);
|
|
deps.push(bc.transforms[p][0]);
|
|
}
|
|
for(p in bc.plugins){
|
|
pluginNames.push(p);
|
|
deps.push(bc.plugins[p]);
|
|
}
|
|
bc.plugins = {};
|
|
require(deps, function(){
|
|
// pull out the discovery procedures
|
|
for(var discoveryProcs = [], argsPos = 0; argsPos<bc.discoveryProcs.length; discoveryProcs.push(arguments[argsPos++]));
|
|
|
|
// replace the transformIds in the transformJobs with the actual transform procs; similarly for plugins
|
|
for(var id, proc, i=0; i<transformNames.length;){
|
|
id = transformNames[i++];
|
|
proc = arguments[argsPos++];
|
|
// replace every occurence of id with proc
|
|
transformJobs.forEach(function(item){
|
|
// item is a [predicate, vector of [transformId, gateId] pairs] pairs
|
|
for(var transforms=item[1], i = 0; i<transforms.length; i++){
|
|
if(transforms[i][0]==id){
|
|
transforms[i][0] = proc;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
for(i=0; i<pluginNames.length;){
|
|
bc.plugins[bc.getSrcModuleInfo(pluginNames[i++]).mid] = arguments[argsPos++];
|
|
}
|
|
|
|
// start the transform engine: initialize bc.currentGate and bc.waiting, then discover and start each resource.
|
|
// note: discovery procs will call bc.start with each discovered resource, which will call advance, which will
|
|
// enter each resource in a race to the next gate, which will result in many bc.waiting incs/decs
|
|
bc.waiting = 1; // matches *1*
|
|
bc.log("pacify", "discovering resources...");
|
|
advanceGate(-1);
|
|
discoveryProcs.forEach(function(proc){ proc(); });
|
|
passGate(); // matched *1*
|
|
});
|
|
}
|
|
|
|
if(!bc.getErrorCount() && bc.release){
|
|
doBuild();
|
|
}
|
|
});
|
|
});
|