|
@@ -16,6 +16,7 @@ var Os = require('os');
|
|
|
var Fs = require('fs');
|
|
|
var Spawn = require('child_process').spawn;
|
|
|
var nThen = require('nthen');
|
|
|
+var Extend = require('node.extend');
|
|
|
var Crypto = require('crypto');
|
|
|
|
|
|
/*
|
|
@@ -53,8 +54,7 @@ var error = function (message)
|
|
|
|
|
|
var toCompile = [];
|
|
|
var compilers = 0;
|
|
|
-var cc = function (args, callback, noArg) {
|
|
|
- if (noArg) { throw new Error(); }
|
|
|
+var cc = function (args, callback, content) {
|
|
|
toCompile.push(function() {
|
|
|
compilers++;
|
|
|
var gcc = Spawn('gcc', args);
|
|
@@ -65,7 +65,7 @@ var cc = function (args, callback, noArg) {
|
|
|
gcc.on('close', function(ret) {
|
|
|
compilers--;
|
|
|
if (ret) {
|
|
|
- callback(error("\n" + err));
|
|
|
+ callback(error("gcc " + args.join(' ') + "\n\n" + err));
|
|
|
}
|
|
|
while (compilers < WORKERS && toCompile.length > 0) {
|
|
|
toCompile.shift()();
|
|
@@ -75,6 +75,12 @@ var cc = function (args, callback, noArg) {
|
|
|
}
|
|
|
callback(undefined, out);
|
|
|
});
|
|
|
+ if (content) {
|
|
|
+ gcc.stdin.write(content, function (err) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ gcc.stdin.end();
|
|
|
+ });
|
|
|
+ }
|
|
|
});
|
|
|
while (compilers < WORKERS && toCompile.length > 0) {
|
|
|
toCompile.shift()();
|
|
@@ -82,45 +88,56 @@ var cc = function (args, callback, noArg) {
|
|
|
};
|
|
|
|
|
|
// You Were Warned
|
|
|
-var execJs = function (js, state, fileObj, callback) {
|
|
|
+var execJs = function (js, state, file, callback) {
|
|
|
var x;
|
|
|
var err;
|
|
|
- try {
|
|
|
- var func = eval('func = function(file, state) { ' + js + ' };');
|
|
|
- x = func(fileObj, state) || '';
|
|
|
- } catch (e) {
|
|
|
- err = e;
|
|
|
- err.message += "Content: [" + js + "]";
|
|
|
- }
|
|
|
- process.nextTick(function() { callback(err, x); });
|
|
|
+ // # 74 "./wire/Message.h"
|
|
|
+ js = js.replace(/\n#.*\n/g, '');
|
|
|
+ var to = setTimeout(function () {
|
|
|
+ throw new Error("Inline JS did not return after 10 seconds [" + js + "]");
|
|
|
+ }, 10000);
|
|
|
+ nThen(function (waitFor) {
|
|
|
+ try {
|
|
|
+ var func = new Function('file','state','require',js);
|
|
|
+ func.async = function () {
|
|
|
+ return waitFor(function (result) {
|
|
|
+ x = result || '';
|
|
|
+ });
|
|
|
+ };
|
|
|
+ x = func.call(func,file,state,require) || '';
|
|
|
+ } catch (e) {
|
|
|
+ err = e;
|
|
|
+ err.message += "\nContent: [" + js + "]";
|
|
|
+ }
|
|
|
+ }).nThen(function (waitFor) {
|
|
|
+ clearTimeout(to);
|
|
|
+ process.nextTick(function() { callback(err, x); });
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
var debug = console.log;
|
|
|
|
|
|
var preprocess = function (content, state, fileObj, callback) {
|
|
|
- var captures = [];
|
|
|
+ var elems;
|
|
|
nThen(function (waitFor) {
|
|
|
- content = content.replace(/<\?js(.*)\?>/g, function (x, capture) {
|
|
|
- captures.push(capture);
|
|
|
- return '<?js?>';
|
|
|
- });
|
|
|
- captures.forEach(function (capture, i) {
|
|
|
+ elems = content.split('<?js');
|
|
|
+ elems.forEach(function (elem, i) {
|
|
|
+ if (!i) { return; }
|
|
|
+ var capture = elem.substring(0,elem.indexOf('?>'));
|
|
|
+ var remainder = elem.substring(capture.length+2);
|
|
|
execJs(capture, state, fileObj, waitFor(function (err, ret) {
|
|
|
if (err) {
|
|
|
callback(err);
|
|
|
callback = function() {};
|
|
|
return;
|
|
|
}
|
|
|
- //debug('[' + capture + '] --> [' + ret + ']');
|
|
|
- captures[i] = ret;
|
|
|
+ //debug('[' + capture + '] --> [' + ret + '] [' + remainder.substring(0,100) + ']');
|
|
|
+ elems[i] = ret + remainder;
|
|
|
+ //if (elems[i].indexOf('?>') !== -1) { throw new Error(); }
|
|
|
}));
|
|
|
});
|
|
|
}).nThen(function (waitFor) {
|
|
|
- content = content.replace(/<\?js\?>/g, function (x) {
|
|
|
- return captures.shift();
|
|
|
- });
|
|
|
-
|
|
|
- callback(undefined, content);
|
|
|
+ callback(undefined, elems.join(''));
|
|
|
});
|
|
|
};
|
|
|
|
|
@@ -153,33 +170,16 @@ var compileFile = function (fileName, state, tempDir, callback)
|
|
|
currentlyCompiling[fileName].push(callback);
|
|
|
|
|
|
//debug('\033[2;32mCompiling ' + fileName + '\033[0m');
|
|
|
- var processedOne = tempDir + '/' + getObjectFile(fileName) + '.c';
|
|
|
- var processedTwo = tempDir + '/' + getObjectFile(fileName) + '.i';
|
|
|
+ var preprocessed = tempDir + '/' + getObjectFile(fileName) + '.i';
|
|
|
var outFile = state.buildDir+'/'+getObjectFile(fileName);
|
|
|
var fileContent;
|
|
|
var fileObj = getFile();
|
|
|
nThen(function (waitFor) {
|
|
|
-
|
|
|
- //debug("Load file");
|
|
|
- Fs.readFile(fileName, waitFor(function (err, ret) {
|
|
|
- if (err) { throw err; }
|
|
|
- fileContent = ret.toString('utf8');
|
|
|
- }));
|
|
|
-
|
|
|
- }).nThen(function (waitFor) {
|
|
|
-
|
|
|
- //debug("Preprocess 1");
|
|
|
- preprocess(fileContent, state, fileObj, waitFor(function (err, output) {
|
|
|
- if (err) { throw err; }
|
|
|
- Fs.writeFile(processedOne, output, waitFor());
|
|
|
- }));
|
|
|
-
|
|
|
- }).nThen(function (waitFor) {
|
|
|
(function() {
|
|
|
//debug("CPP -MM");
|
|
|
var flags = ['-E', '-MM'];
|
|
|
flags.push.apply(flags, state.cflags);
|
|
|
- flags.push(processedOne);
|
|
|
+ flags.push(fileName);
|
|
|
cc(flags, waitFor(function (err, output) {
|
|
|
if (err) { throw err; }
|
|
|
// replace the escapes and newlines
|
|
@@ -194,7 +194,7 @@ var compileFile = function (fileName, state, tempDir, callback)
|
|
|
//debug("CPP");
|
|
|
var flags = ['-E'];
|
|
|
flags.push.apply(flags, state.cflags);
|
|
|
- flags.push(processedOne);
|
|
|
+ flags.push(fileName);
|
|
|
cc(flags, waitFor(function (err, output) {
|
|
|
if (err) { throw err; }
|
|
|
fileContent = output;
|
|
@@ -202,12 +202,19 @@ var compileFile = function (fileName, state, tempDir, callback)
|
|
|
})();
|
|
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
- //debug("Preprocess 2");
|
|
|
+
|
|
|
+ //debug("Preprocess");
|
|
|
preprocess(fileContent, state, fileObj, waitFor(function (err, output) {
|
|
|
if (err) { throw err; }
|
|
|
- Fs.writeFile(processedTwo, output, waitFor(function (err) {
|
|
|
- if (err) { throw err; }
|
|
|
- }));
|
|
|
+ if (state.useTempFiles) {
|
|
|
+ Fs.writeFile(preprocessed, output, waitFor(function (err) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ }));
|
|
|
+ // important, this will prevent the file from also being piped to gcc.
|
|
|
+ fileContent = undefined;
|
|
|
+ } else {
|
|
|
+ fileContent = output;
|
|
|
+ }
|
|
|
}));
|
|
|
|
|
|
Fs.exists(outFile, waitFor(function (exists) {
|
|
@@ -221,13 +228,17 @@ var compileFile = function (fileName, state, tempDir, callback)
|
|
|
}).nThen(function (waitFor) {
|
|
|
|
|
|
//debug("CC");
|
|
|
- var flags = ['-c','-o',outFile];
|
|
|
+ var flags = ['-c','-x','cpp-output','-o',outFile];
|
|
|
flags.push.apply(flags, state.cflags);
|
|
|
- flags.push(processedTwo);
|
|
|
+ if (state.useTempFiles) {
|
|
|
+ flags.push(preprocessed);
|
|
|
+ } else {
|
|
|
+ flags.push('-');
|
|
|
+ }
|
|
|
cc(flags, waitFor(function (err) {
|
|
|
if (err) { throw err; }
|
|
|
fileObj.obj = outFile;
|
|
|
- }));
|
|
|
+ }), fileContent);
|
|
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
debug('\033[2;32mBuilding C object ' + fileName + ' complete\033[0m');
|
|
@@ -274,6 +285,8 @@ var removeFile = function (state, fileName, callback)
|
|
|
nThen(function (waitFor) {
|
|
|
// And every file which includes it
|
|
|
Object.keys(state.files).forEach(function (file) {
|
|
|
+ // recursion could remove it
|
|
|
+ if (typeof(state.files[file]) === 'undefined') { return; }
|
|
|
if (state.files[file].includes.indexOf(fileName) !== -1) {
|
|
|
removeFile(state, file, waitFor());
|
|
|
}
|
|
@@ -345,25 +358,136 @@ var getLinkOrder = function (fileName, files) {
|
|
|
return completeFiles;
|
|
|
};
|
|
|
|
|
|
-var setUp = function (config, callback) {
|
|
|
- var configStr = JSON.stringify(config);
|
|
|
- var state = JSON.parse(configStr);
|
|
|
- var configHash = Crypto.createHash('sha256').update(configStr).digest('hex');
|
|
|
- state.includeDirs = state.includeDirs || [];
|
|
|
- state.files = state.files || {};
|
|
|
- state.includeDirs.unshift('.');
|
|
|
- state.mtimes = state.mtimes || {};
|
|
|
-
|
|
|
- for (var i = 0; i < state.includeDirs.length; i++) {
|
|
|
- state.cflags.push('-I');
|
|
|
- state.cflags.push(state.includeDirs[i]);
|
|
|
+var needsToLink = function (fileName, state) {
|
|
|
+ if (typeof(state.oldmtimes[fileName]) !== 'number') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (state.oldmtimes[fileName] !== state.mtimes[fileName]) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ var links = state.files[fileName].links;
|
|
|
+ for (var i = 0; i < links.length; i++) {
|
|
|
+ if (links[i] !== fileName && needsToLink(links[i], state)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+};
|
|
|
+
|
|
|
+var makeTime = function () {
|
|
|
+ return function () {
|
|
|
+ var oldTime = this.time || 0;
|
|
|
+ var newTime = this.time = new Date().getTime();
|
|
|
+ return newTime - oldTime;
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+var compile = function (file, outputFile, state, callback) {
|
|
|
+
|
|
|
+ var tempDir;
|
|
|
+ if (!needsToLink(file, state)) {
|
|
|
+ callback();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ nThen(function(waitFor) {
|
|
|
+
|
|
|
+ if (!state.useTempFiles) { return; }
|
|
|
+ tempDir = state.tempDir+'/jsmake-' + Crypto.pseudoRandomBytes(10).toString('hex');
|
|
|
+ Fs.mkdir(tempDir, waitFor(function (err) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ }));
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ recursiveCompile(file, state, tempDir, waitFor());
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ var linkOrder = getLinkOrder(file, state.files);
|
|
|
+ for (var i = 0; i < linkOrder.length; i++) {
|
|
|
+ linkOrder[i] = state.buildDir + '/' + getObjectFile(linkOrder[i]);
|
|
|
+ }
|
|
|
+ var ldArgs = [];
|
|
|
+ ldArgs.push.apply(ldArgs, state.ldflags);
|
|
|
+ ldArgs.push.apply(ldArgs, ['-o', outputFile]);
|
|
|
+ ldArgs.push.apply(ldArgs, linkOrder);
|
|
|
+ ldArgs.push.apply(ldArgs, state.libs);
|
|
|
+ debug('\033[1;31mLinking C executable ' + outputFile + '\033[0m');
|
|
|
+
|
|
|
+ cc(ldArgs, waitFor(function (err, ret) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ }));
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ if (!state.useTempFiles) { return; }
|
|
|
+ Fs.readdir(tempDir, waitFor(function(err, files) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ files.forEach(function(file) {
|
|
|
+ Fs.unlink(tempDir + '/' + file, waitFor(function(err) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ }));
|
|
|
+ });
|
|
|
+ }));
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ if (!state.useTempFiles) { return; }
|
|
|
+ Fs.rmdir(tempDir, waitFor(function(err) {
|
|
|
+ if (err) { throw err; }
|
|
|
+ }));
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ if (callback) { callback(); }
|
|
|
+
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+var getStatePrototype = function () {
|
|
|
+ return {
|
|
|
+ includeDirs: ['.'],
|
|
|
+ files: {},
|
|
|
+ mtimes: {},
|
|
|
+
|
|
|
+ cflags: [],
|
|
|
+ ldflags: [],
|
|
|
+ libs: [],
|
|
|
+
|
|
|
+ // Using temp files instead of pipes shaves about 400ms off a clean build.
|
|
|
+ // TODO: Understand why our use of pipes is not good.
|
|
|
+ tempDir: '/tmp',
|
|
|
+ useTempFiles: true,
|
|
|
+
|
|
|
+ systemName: 'Linux'
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+module.exports.configure = function (params, configure) {
|
|
|
+
|
|
|
+ // Track time taken for various steps
|
|
|
+ var time = makeTime();
|
|
|
+ time();
|
|
|
+
|
|
|
+ if (typeof(params.buildDir) !== 'string') {
|
|
|
+ throw new Error("buildDir not specified");
|
|
|
}
|
|
|
|
|
|
+ var rebuildIfChangesHash = '';
|
|
|
+ if (typeof(params.rebuildIfChanges) !== 'undefined') {
|
|
|
+ rebuildIfChangesHash =
|
|
|
+ Crypto.createHash('sha256').update(params.rebuildIfChanges).digest('hex');
|
|
|
+ }
|
|
|
+
|
|
|
+ var state;
|
|
|
+ var buildStage;
|
|
|
+
|
|
|
nThen(function(waitFor) {
|
|
|
// make the build directory
|
|
|
- Fs.exists(state.buildDir, waitFor(function (exists) {
|
|
|
+ Fs.exists(params.buildDir, waitFor(function (exists) {
|
|
|
if (exists) { return; }
|
|
|
- Fs.mkdir(state.buildDir, waitFor(function (err) {
|
|
|
+ Fs.mkdir(params.buildDir, waitFor(function (err) {
|
|
|
if (err) { throw err; }
|
|
|
}));
|
|
|
}));
|
|
@@ -371,22 +495,44 @@ var setUp = function (config, callback) {
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
|
// read out the state if it exists
|
|
|
- Fs.exists(state.buildDir + '/state.json', waitFor(function (exists) {
|
|
|
+ Fs.exists(params.buildDir + '/state.json', waitFor(function (exists) {
|
|
|
if (!exists) { return; }
|
|
|
- Fs.readFile(state.buildDir + '/state.json', waitFor(function (err, ret) {
|
|
|
+ Fs.readFile(params.buildDir + '/state.json', waitFor(function (err, ret) {
|
|
|
if (err) { throw err; }
|
|
|
var storedState = JSON.parse(ret);
|
|
|
- if (configHash === storedState.configHash) {
|
|
|
+ if (storedState.rebuildIfChangesHash === rebuildIfChangesHash) {
|
|
|
state = storedState;
|
|
|
} else {
|
|
|
- debug("Config file changed, rebuilding");
|
|
|
+ debug("rebuildIfChanges changed, rebuilding");
|
|
|
}
|
|
|
}));
|
|
|
}));
|
|
|
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
|
- state.configHash = configHash;
|
|
|
+ debug("Initialize " + time() + "ms");
|
|
|
+
|
|
|
+ // Do the configuration step
|
|
|
+ if (state) { return; }
|
|
|
+ state = getStatePrototype();
|
|
|
+ configure({
|
|
|
+ config: state
|
|
|
+ }, waitFor);
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ state.buildDir = params.buildDir;
|
|
|
+ for (var i = 0; i < state.includeDirs.length; i++) {
|
|
|
+ state.cflags.push('-I');
|
|
|
+ state.cflags.push(state.includeDirs[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ debug("Configure " + time() + "ms");
|
|
|
+
|
|
|
+ }).nThen(function(waitFor) {
|
|
|
+
|
|
|
+ state.rebuildIfChangesHash = rebuildIfChangesHash;
|
|
|
state.oldmtimes = state.mtimes;
|
|
|
state.mtimes = {};
|
|
|
|
|
@@ -402,62 +548,20 @@ var setUp = function (config, callback) {
|
|
|
});
|
|
|
|
|
|
}).nThen(function(waitFor) {
|
|
|
- callback(state);
|
|
|
- });
|
|
|
-};
|
|
|
|
|
|
-var needsToLink = function (fileName, state) {
|
|
|
- if (typeof(state.oldmtimes[fileName]) !== 'number') {
|
|
|
- return true;
|
|
|
- }
|
|
|
- if (state.oldmtimes[fileName] !== state.mtimes[fileName]) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- var links = state.files[fileName].links;
|
|
|
- for (var i = 0; i < links.length; i++) {
|
|
|
- if (links[i] !== fileName && needsToLink(links[i], state)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
-};
|
|
|
-
|
|
|
-var compile = function (file, outputFile, state) {
|
|
|
-
|
|
|
- var tempDir = '/tmp/jsmake-' + Crypto.pseudoRandomBytes(10).toString('hex');
|
|
|
-
|
|
|
- if (typeof(state.files[file]) !== 'undefined') {
|
|
|
- // return;
|
|
|
- }
|
|
|
-
|
|
|
- nThen(function(waitFor) {
|
|
|
-
|
|
|
- Fs.mkdir(tempDir, waitFor(function (err) {
|
|
|
- if (err) { throw err; }
|
|
|
- }));
|
|
|
+ debug("Scan for out of date files " + time() + "ms");
|
|
|
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
|
- recursiveCompile(file, state, tempDir, waitFor());
|
|
|
+ buildStage({
|
|
|
+ compile: function (cFile, outputFile) {
|
|
|
+ compile(cFile, outputFile, state, waitFor());
|
|
|
+ }
|
|
|
+ }, waitFor);
|
|
|
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
|
- if (needsToLink(file, state)) {
|
|
|
- var linkOrder = getLinkOrder(file, state.files);
|
|
|
- for (var i = 0; i < linkOrder.length; i++) {
|
|
|
- linkOrder[i] = state.buildDir + '/' + getObjectFile(linkOrder[i]);
|
|
|
- }
|
|
|
- var ldArgs = [];
|
|
|
- ldArgs.push.apply(ldArgs, state.ldflags);
|
|
|
- ldArgs.push.apply(ldArgs, ['-o', outputFile]);
|
|
|
- ldArgs.push.apply(ldArgs, linkOrder);
|
|
|
- ldArgs.push.apply(ldArgs, state.libs);
|
|
|
- debug('\033[1;31mLinking C executable ' + outputFile + '\033[0m');
|
|
|
-
|
|
|
- cc(ldArgs, waitFor(function (err, ret) {
|
|
|
- if (err) { throw err; }
|
|
|
- }));
|
|
|
- }
|
|
|
+ debug("Compile " + time() + "ms");
|
|
|
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
@@ -469,38 +573,17 @@ var compile = function (file, outputFile, state) {
|
|
|
}).nThen(function(waitFor) {
|
|
|
|
|
|
// save state
|
|
|
- Fs.writeFile(state.buildDir+'/state.json', JSON.stringify(state, null, ' '), waitFor(function(err) {
|
|
|
- if (err) { throw err; }
|
|
|
- }));
|
|
|
-
|
|
|
- }).nThen(function(waitFor) {
|
|
|
-
|
|
|
- // clear the temp dir
|
|
|
- Fs.readdir(tempDir, waitFor(function(err, files) {
|
|
|
- if (err) { throw err; }
|
|
|
- files.forEach(function(file) {
|
|
|
- Fs.unlink(tempDir + '/' + file, waitFor(function(err) {
|
|
|
- if (err) { throw err; }
|
|
|
- }));
|
|
|
- });
|
|
|
- }));
|
|
|
-
|
|
|
- }).nThen(function(waitFor) {
|
|
|
-
|
|
|
- // remove the temp dir
|
|
|
- Fs.rmdir(tempDir, waitFor(function(err) {
|
|
|
+ debug("Saving state");
|
|
|
+ var stateJson = JSON.stringify(state, null, ' ');
|
|
|
+ Fs.writeFile(state.buildDir+'/state.json', stateJson, waitFor(function(err) {
|
|
|
if (err) { throw err; }
|
|
|
}));
|
|
|
|
|
|
});
|
|
|
-};
|
|
|
|
|
|
-module.exports.setUp = function (config, callback) {
|
|
|
- var state;
|
|
|
- setUp(config, function (s) {
|
|
|
- state = s;
|
|
|
- callback({
|
|
|
- makeExecutable: function (cFile, outputFile) { compile(cFile, outputFile, state); }
|
|
|
- });
|
|
|
- });
|
|
|
+ return {
|
|
|
+ build: function (build) {
|
|
|
+ buildStage = build;
|
|
|
+ }
|
|
|
+ };
|
|
|
};
|