builder.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. /* vim: set expandtab ts=4 sw=4: */
  2. /*
  3. * You may redistribute this program and/or modify it under the terms of
  4. * the GNU General Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. *
  12. * You should have received a copy of the GNU General Public License
  13. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. */
  15. var Os = require('os');
  16. var Fs = require('fs');
  17. var Spawn = require('child_process').spawn;
  18. var nThen = require('nthen');
  19. var Crypto = require('crypto');
  20. var Semaphore = require('../tools/lib/Semaphore');
  21. var GetVersion = require('./GetVersion');
  22. /*
  23. * Why hello dear packager,
  24. *
  25. * I suppose you have found this place as you are trying to figure out how to work this into your
  26. * build system. You're probably faced with a decision between getting node.js into your build and
  27. * "fixing" this build process so it doesn't need such a silly thing. A 500 line script is certainly
  28. * not unapproachable, right?
  29. * The reason why I am speaking to you now is because I care about you. I want you to be happy
  30. * and live a carefree life, and because you are standing on the precipice of a cavern so dark and
  31. * deep that while you may well make it out alive, your personal pride and innocence almost
  32. * certainly will not. Imagine yourself after months of sleepless nights wallowing in the quicksand,
  33. * forever trying to slay the dragon which is always so close yet and so far away. Imagine the deep
  34. * hatred you will have for humanity, code, and the creator of this doomsday machine. I beg you to
  35. * turn back now while there is still hope. You need not die here, your life is important, and
  36. * whether you close this file now or not, in the end you will still end up including node.js in
  37. * your build.
  38. *
  39. * The Creator
  40. */
  41. // Since many of the compile operations are short, the best
  42. // performance seems to be when running 1.25x the number of jobs as
  43. // cpu cores. On BSD and iphone systems, os.cpus() is not reliable so
  44. // if it returns undefined let's just assume 1
  45. var cpus = Os.cpus(); // workaround, nodejs seems to be broken on openbsd (undefined result after second call)
  46. var PROCESSORS = Math.floor((typeof cpus === 'undefined' ? 1 : cpus.length) * 1.25);
  47. var error = function (message)
  48. {
  49. try {
  50. throw new Error(message);
  51. } catch (e) {
  52. return e;
  53. }
  54. };
  55. var throwIfErr = function(err) {
  56. if (err) {
  57. throw new Error(err);
  58. }
  59. };
  60. var expandArgs = function (args) {
  61. var out = [];
  62. for (var i = 0; i < args.length; i++) {
  63. if (typeof(args[i]) === 'object') {
  64. if (Array.isArray(args[i])) {
  65. out.push.apply(out, expandArgs(args[i]));
  66. } else {
  67. throw new Error("object in arguments [" + args + "]");
  68. }
  69. } else {
  70. out.push(args[i]);
  71. }
  72. }
  73. return out;
  74. };
  75. var sema = Semaphore.create(PROCESSORS);
  76. var compiler = function (compilerPath, args, callback, content) {
  77. args = expandArgs(args);
  78. sema.take(function (returnAfter) {
  79. var gcc = Spawn(compilerPath, args);
  80. var err = '';
  81. var out = '';
  82. gcc.stdout.on('data', function (dat) { out += dat.toString(); });
  83. gcc.stderr.on('data', function (dat) { err += dat.toString(); });
  84. gcc.on('close', returnAfter(function (ret) {
  85. callback(ret, out, err);
  86. }));
  87. gcc.on('error', function (err) {
  88. if (err.code === 'ENOENT') {
  89. console.error('\033[1;31mError: ' + compilerPath + ' is required!\033[0m');
  90. } else {
  91. console.error(
  92. '\033[1;31mFail run ' + process.cwd() + ': ' + compilerPath + ' '
  93. + args.join(' ') + '\033[0m'
  94. );
  95. console.error('Message:' + err);
  96. }
  97. // handle the error safely
  98. console.log(args);
  99. });
  100. if (content) {
  101. gcc.stdin.write(content, function (err) {
  102. if (err) { throw err; }
  103. gcc.stdin.end();
  104. });
  105. }
  106. });
  107. };
  108. var cc = function (gcc, args, callback, content) {
  109. compiler(gcc, args, function (ret, out, err) {
  110. if (ret) {
  111. callback(error("gcc " + args.join(' ') + "\n\n" + err));
  112. }
  113. if (err !== '') {
  114. debug(err);
  115. }
  116. callback(undefined, out);
  117. }, content);
  118. };
  119. var tmpFile = function (state, name) {
  120. name = name || '';
  121. return state.tempDir + '/jsmake-' + name + Crypto.pseudoRandomBytes(10).toString('hex');
  122. };
  123. var mkBuilder = function (state) {
  124. var builder = {
  125. cc: function (args, callback) {
  126. compiler(builder.config.gcc, args, callback);
  127. },
  128. buildExecutable: function (cFile, outputFile) {
  129. if (!outputFile) {
  130. outputFile = cFile.replace(/^.*\/([^\/\.]*).*$/, function (a, b) { return b; });
  131. }
  132. if (state.systemName === 'win32' && !(/\.exe$/.test(outputFile))) {
  133. outputFile += '.exe';
  134. }
  135. var temp = state.buildDir + '/' + getExecutableFile(cFile);
  136. compile(cFile, temp, builder, builder.waitFor());
  137. builder.executables.push([temp, outputFile]);
  138. return temp;
  139. },
  140. buildTest: function (cFile) {
  141. return builder.buildExecutable(cFile, state.buildDir + '/' + getExecutableFile(cFile));
  142. },
  143. runTest: function (outFile, testRunner) {
  144. builder.tests.push(function (cb) { testRunner(outFile, cb); });
  145. },
  146. lintFiles: function (linter) {
  147. builder.linters.push(linter);
  148. },
  149. config: state,
  150. tmpFile: function (name) {
  151. return tmpFile(state, name);
  152. },
  153. rebuiltFiles: [],
  154. // Test executables (arrays containing name in build dir and test runner)
  155. tests: [],
  156. // Executables (arrays containing name in build dir and final name)
  157. executables: [],
  158. linters: [],
  159. // Concurrency...
  160. processors: PROCESSORS
  161. };
  162. return builder;
  163. };
  164. // You Were Warned
  165. var execJs = function (js, builder, file, fileName, callback) {
  166. var res;
  167. var x;
  168. var err;
  169. // # 74 "./wire/Message.h"
  170. js = js.replace(/\n#.*\n/g, '');
  171. var to = setTimeout(function () {
  172. throw new Error("Inline JS did not return after 120 seconds [" + js + "]");
  173. }, 120000);
  174. var REQUIRE = function (str) {
  175. if (typeof(str) !== 'string') {
  176. throw new Error("must be a string");
  177. }
  178. try { return require(str); } catch (e) { }
  179. return require(process.cwd() + '/' + str);
  180. };
  181. nThen(function (waitFor) {
  182. try {
  183. /* jshint -W054 */ // Suppress jshint warning on Function being a form of eval
  184. var func = new Function('file', 'require', 'fileName', 'console', 'builder', js);
  185. func.async = function () {
  186. return waitFor(function (result) {
  187. res = result;
  188. });
  189. };
  190. x = func.call(func,
  191. file,
  192. REQUIRE,
  193. fileName,
  194. console,
  195. builder);
  196. } catch (e) {
  197. err = e;
  198. err.message += "\nContent: [" + js + "]";
  199. clearTimeout(to);
  200. throw err;
  201. }
  202. }).nThen(function (waitFor) {
  203. if (err) { return; }
  204. res = res || x || '';
  205. clearTimeout(to);
  206. process.nextTick(function () { callback(undefined, res); });
  207. });
  208. };
  209. var debug = console.log;
  210. var preprocessBlock = function (block, builder, fileObj, fileName, callback) {
  211. // a block is an array of strings and arrays, any inside arrays must be
  212. // preprocessed first. deep first top to bottom.
  213. var error = false;
  214. var nt = nThen;
  215. block.forEach(function (elem, i) {
  216. if (typeof(elem) === 'string') { return; }
  217. nt = nt(function (waitFor) {
  218. preprocessBlock(elem, builder, fileObj, fileName, waitFor(function (err, ret) {
  219. if (err) { throw err; }
  220. block[i] = ret;
  221. }));
  222. }).nThen;
  223. });
  224. nt(function (waitFor) {
  225. if (error) { return; }
  226. var capture = block.join('');
  227. execJs(capture, builder, fileObj, fileName, waitFor(function (err, ret) {
  228. if (err) { throw err; }
  229. callback(undefined, ret);
  230. }));
  231. });
  232. };
  233. var preprocess = function (content, builder, fileObj, fileName, callback) {
  234. // <?js file.Test_mainFunc = "<?js return 'RootTest_'+file.RootTest_mainFunc; ?>" ?>
  235. // worse:
  236. // <?js file.Test_mainFunc = "<?js var done = this.async(); process.nextTick(done); ?>" ?>
  237. var flatArray = content.split(/(<\?js|\?>)/);
  238. var elems = [];
  239. var unflatten = function (array, startAt, out) {
  240. for (var i = startAt; i < array.length; i++) {
  241. /* jshint -W018 */ // Suppress jshint warning on ! being confusing
  242. if (!((i - startAt) % 2)) {
  243. out.push(array[i]);
  244. } else if (array[i] === '<?js') {
  245. var next = [];
  246. out.push(next);
  247. i = unflatten(array, i+1, next);
  248. } else if (array[i] === '?>') {
  249. return i;
  250. }
  251. }
  252. return i;
  253. };
  254. if (unflatten(flatArray, 0, elems) !== flatArray.length) {
  255. throw new Error();
  256. }
  257. var nt = nThen;
  258. elems.forEach(function (elem, i) {
  259. if (typeof(elem) === 'string') { return; }
  260. nt = nt(function (waitFor) {
  261. preprocessBlock(elem, builder, fileObj, fileName, waitFor(function (err, ret) {
  262. if (err) { throw err; }
  263. elems[i] = ret;
  264. }));
  265. }).nThen;
  266. });
  267. nt(function (waitFor) {
  268. callback(undefined, elems.join(''));
  269. });
  270. };
  271. var getFile = function ()
  272. {
  273. return {
  274. includes: [],
  275. links: [],
  276. cflags: [],
  277. oldmtime: 0
  278. };
  279. };
  280. var getObjectFile = function (cFile) {
  281. return cFile.replace(/[^a-zA-Z0-9_-]/g, '_') + '.o';
  282. };
  283. var getExecutableFile = function (cFile) {
  284. return cFile.replace(/[^a-zA-Z0-9_-]/g, '_');
  285. };
  286. var getFlags = function (state, fileName, includeDirs) {
  287. var flags = [];
  288. flags.push.apply(flags, state.cflags);
  289. flags.push.apply(flags, state['cflags'+fileName]);
  290. if (includeDirs) {
  291. for (var i = 0; i < state.includeDirs.length; i++) {
  292. if (flags[flags.indexOf(state.includeDirs[i])-1] === '-I') {
  293. continue;
  294. }
  295. flags.push('-I');
  296. flags.push(state.includeDirs[i]);
  297. }
  298. }
  299. for (var ii = flags.length-1; ii >= 0; ii--) {
  300. // might be undefined because splicing causes us to be off the end of the array
  301. if (typeof(flags[ii]) === 'string' && flags[ii][0] === '!') {
  302. var f = flags[ii].substring(1);
  303. flags.splice(ii, 1);
  304. var index;
  305. while ((index = flags.indexOf(f)) > -1) {
  306. flags.splice(index, 1);
  307. }
  308. }
  309. }
  310. return flags;
  311. };
  312. var currentlyCompiling = {};
  313. var compileFile = function (fileName, builder, tempDir, callback)
  314. {
  315. var state = builder.config;
  316. if (typeof(state.files[fileName]) !== 'undefined') {
  317. callback();
  318. return;
  319. }
  320. if (typeof(currentlyCompiling[fileName]) !== 'undefined') {
  321. currentlyCompiling[fileName].push(callback);
  322. return;
  323. } else {
  324. currentlyCompiling[fileName] = [];
  325. }
  326. currentlyCompiling[fileName].push(callback);
  327. //debug('\033[2;32mCompiling ' + fileName + '\033[0m');
  328. var preprocessed = state.buildDir + '/' + getObjectFile(fileName) + '.i';
  329. var outFile = state.buildDir + '/' + getObjectFile(fileName);
  330. var fileContent;
  331. var fileObj = getFile();
  332. nThen(function (waitFor) {
  333. (function () {
  334. //debug("CPP -MM");
  335. var flags = ['-E', '-MM'];
  336. flags.push.apply(flags, getFlags(state, fileName, true));
  337. flags.push(fileName);
  338. cc(state.gcc, flags, waitFor(function (err, output) {
  339. if (err) { throw err; }
  340. // replace the escapes and newlines
  341. output = output.replace(/ \\|\n/g, '').split(' ');
  342. // first 2 entries are crap
  343. output.splice(0, 2);
  344. for (var i = output.length-1; i >= 0; i--) {
  345. //console.log('Removing empty dependency [' +
  346. // state.gcc + ' ' + flags.join(' ') + ']');
  347. if (output[i] === '') {
  348. output.splice(i, 1);
  349. }
  350. }
  351. fileObj.includes = output;
  352. }));
  353. })();
  354. (function () {
  355. //debug("CPP");
  356. var flags = ['-E'];
  357. flags.push.apply(flags, getFlags(state, fileName, true));
  358. flags.push(fileName);
  359. cc(state.gcc, flags, waitFor(function (err, output) {
  360. if (err) { throw err; }
  361. fileContent = output;
  362. }));
  363. })();
  364. }).nThen(function (waitFor) {
  365. Fs.exists(preprocessed, waitFor(function (exists) {
  366. if (!exists) { return; }
  367. Fs.unlink(preprocessed, waitFor(function (err) {
  368. if (err) { throw err; }
  369. }));
  370. }));
  371. }).nThen(function (waitFor) {
  372. //debug("Preprocess");
  373. preprocess(fileContent, builder, fileObj, fileName, waitFor(function (err, output) {
  374. if (err) { throw err; }
  375. Fs.writeFile(preprocessed, output, waitFor(function (err) {
  376. if (err) { throw err; }
  377. }));
  378. // important, this will prevent the file from also being piped to gcc.
  379. fileContent = undefined;
  380. }));
  381. Fs.exists(outFile, waitFor(function (exists) {
  382. if (!exists) { return; }
  383. Fs.unlink(outFile, waitFor(function (err) {
  384. if (err) { throw err; }
  385. }));
  386. }));
  387. }).nThen(function (waitFor) {
  388. //debug("CC");
  389. var flags = ['-c', '-x', 'cpp-output', '-o', outFile];
  390. flags.push.apply(flags, getFlags(state, fileName, false));
  391. flags.push(preprocessed);
  392. cc(state.gcc, flags, waitFor(function (err) {
  393. if (err) { throw err; }
  394. fileObj.obj = outFile;
  395. }), fileContent);
  396. }).nThen(function (waitFor) {
  397. debug('\033[2;32mBuilding C object ' + fileName + ' complete\033[0m');
  398. state.files[fileName] = fileObj;
  399. var callbacks = currentlyCompiling[fileName];
  400. delete currentlyCompiling[fileName];
  401. callbacks.forEach(function (cb) { cb(); });
  402. });
  403. };
  404. /**
  405. * @param files state.files
  406. * @param mtimes a mapping of files to times for files for which the times are known
  407. * @param callback when done.
  408. */
  409. var getMTimes = function (files, mtimes, callback)
  410. {
  411. nThen(function (waitFor) {
  412. Object.keys(files).forEach(function (fileName) {
  413. mtimes[fileName] = mtimes[fileName] || 0;
  414. files[fileName].includes.forEach(function (incl) {
  415. mtimes[incl] = mtimes[incl] || 0;
  416. });
  417. });
  418. Object.keys(mtimes).forEach(function (fileName) {
  419. if (mtimes[fileName] !== 0) { return; }
  420. Fs.stat(fileName, waitFor(function (err, stat) {
  421. if (err) {
  422. waitFor.abort();
  423. callback(err);
  424. return;
  425. }
  426. mtimes[fileName] = stat.mtime.getTime();
  427. }));
  428. });
  429. }).nThen(function (waitFor) {
  430. callback(undefined, mtimes);
  431. });
  432. };
  433. var removeFile = function (state, fileName, callback)
  434. {
  435. //debug("remove " + fileName);
  436. nThen(function (waitFor) {
  437. // And every file which includes it
  438. Object.keys(state.files).forEach(function (file) {
  439. // recursion could remove it
  440. if (typeof(state.files[file]) === 'undefined') {
  441. return;
  442. }
  443. if (state.files[file].includes.indexOf(fileName) !== -1) {
  444. removeFile(state, file, waitFor());
  445. }
  446. });
  447. // we'll set the oldmtime on the file to 0 since it's getting rebuilt.
  448. state.oldmtimes[fileName] = 0;
  449. var f = state.files[fileName];
  450. if (typeof(f) === 'undefined') {
  451. return;
  452. }
  453. delete state.files[fileName];
  454. if (typeof(f.obj) === 'string') {
  455. Fs.unlink(f.obj, waitFor(function (err) {
  456. if (err && err.code !== 'ENOENT') {
  457. throw err;
  458. }
  459. }));
  460. }
  461. }).nThen(function (waitFor) {
  462. callback();
  463. });
  464. };
  465. var recursiveCompile = function (fileName, builder, tempDir, callback)
  466. {
  467. // Recursive compilation
  468. var state = builder.config;
  469. var doCycle = function (toCompile, parentStack, callback) {
  470. if (toCompile.length === 0) {
  471. callback();
  472. return;
  473. }
  474. nThen(function (waitFor) {
  475. var filefunc = function (file) {
  476. var stack = [];
  477. stack.push.apply(stack, parentStack);
  478. //debug("compiling " + file);
  479. stack.push(file);
  480. if (stack.indexOf(file) !== stack.length - 1) {
  481. throw new Error("Dependency loops are bad and you should feel bad\n" +
  482. "Dependency stack:\n" + stack.reverse().join('\n'));
  483. }
  484. compileFile(file, builder, tempDir, waitFor(function () {
  485. var toCompile = [];
  486. state.files[file].links.forEach(function (link) {
  487. if (link === file) {
  488. return;
  489. }
  490. toCompile.push(link);
  491. });
  492. doCycle(toCompile, stack, waitFor(function () {
  493. if (stack[stack.length - 1] !== file) {
  494. throw new Error();
  495. }
  496. stack.pop();
  497. }));
  498. }));
  499. };
  500. for (var file = toCompile.pop(); file; file = toCompile.pop()) {
  501. filefunc(file);
  502. }
  503. }).nThen(function (waitFor) {
  504. callback();
  505. });
  506. };
  507. doCycle([fileName], [], callback);
  508. };
  509. var getLinkOrder = function (fileName, files) {
  510. var completeFiles = [];
  511. var getFile = function (name) {
  512. var f = files[name];
  513. //debug('Resolving links for ' + name + ' ' + f);
  514. for (var i = 0; i < f.links.length; i++) {
  515. if (f.links[i] === name) {
  516. continue;
  517. }
  518. if (completeFiles.indexOf(f.links[i]) > -1) {
  519. continue;
  520. }
  521. getFile(f.links[i]);
  522. }
  523. completeFiles.push(name);
  524. };
  525. getFile(fileName);
  526. return completeFiles;
  527. };
  528. var needsToLink = function (fileName, state) {
  529. if (typeof(state.oldmtimes[fileName]) !== 'number') {
  530. return true;
  531. }
  532. if (state.oldmtimes[fileName] !== state.mtimes[fileName]) {
  533. return true;
  534. }
  535. var links = state.files[fileName].links;
  536. for (var i = 0; i < links.length; i++) {
  537. if (links[i] !== fileName && needsToLink(links[i], state)) {
  538. return true;
  539. }
  540. }
  541. return false;
  542. };
  543. var makeTime = function () {
  544. return function () {
  545. var oldTime = this.time || 0;
  546. var newTime = this.time = new Date().getTime();
  547. return newTime - oldTime;
  548. };
  549. };
  550. var compile = function (file, outputFile, builder, callback) {
  551. var state = builder.config;
  552. var tempDir;
  553. if (!needsToLink(file, state)) {
  554. process.nextTick(callback);
  555. return;
  556. }
  557. nThen(function (waitFor) {
  558. tempDir = tmpFile(state);
  559. Fs.mkdir(tempDir, waitFor(function (err) {
  560. if (err) { throw err; }
  561. }));
  562. }).nThen(function (waitFor) {
  563. recursiveCompile(file, builder, tempDir, waitFor());
  564. }).nThen(function (waitFor) {
  565. var linkOrder = getLinkOrder(file, state.files);
  566. for (var i = 0; i < linkOrder.length; i++) {
  567. linkOrder[i] = state.buildDir + '/' + getObjectFile(linkOrder[i]);
  568. }
  569. var ldArgs = [state.ldflags, '-o', outputFile, linkOrder, state.libs];
  570. debug('\033[1;31mLinking C executable ' + file + '\033[0m');
  571. cc(state.gcc, ldArgs, waitFor(function (err, ret) {
  572. if (err) { throw err; }
  573. }));
  574. }).nThen(function (waitFor) {
  575. Fs.readdir(tempDir, waitFor(function (err, files) {
  576. if (err) { throw err; }
  577. files.forEach(function (file) {
  578. Fs.unlink(tempDir + '/' + file, waitFor(function (err) {
  579. if (err) { throw err; }
  580. }));
  581. });
  582. }));
  583. }).nThen(function (waitFor) {
  584. Fs.rmdir(tempDir, waitFor(function (err) {
  585. if (err) { throw err; }
  586. }));
  587. }).nThen(function (waitFor) {
  588. if (callback) {
  589. callback();
  590. }
  591. });
  592. };
  593. var getStatePrototype = function (params) {
  594. var base = {
  595. includeDirs: ['.'],
  596. files: {},
  597. mtimes: {},
  598. cflags: [],
  599. ldflags: [],
  600. libs: [],
  601. rebuildIfChanges: [],
  602. rebuildIfChangesHash: undefined,
  603. tempDir: '/tmp',
  604. systemName: 'linux'
  605. };
  606. for (var key in params) {
  607. if (params.hasOwnProperty(key)) {
  608. if (typeof params[key] !== 'object') {
  609. base[key] = params[key];
  610. }
  611. }
  612. }
  613. return base;
  614. };
  615. /**
  616. * Get a copy of process.env with a few entries which are constantly changing removed.
  617. * This prevents isStaleState from returning true every time one builds in a different
  618. * window.
  619. */
  620. var normalizedProcessEnv = function () {
  621. var out = process.env;
  622. delete out.WINDOWID;
  623. delete out.OLDPWD;
  624. return out;
  625. };
  626. var getRebuildIfChangesHash = function (rebuildIfChanges, callback) {
  627. var ret = false;
  628. var hash = Crypto.createHash('sha256');
  629. var rebIfChg = [];
  630. nThen(function (waitFor) {
  631. rebuildIfChanges.forEach(function (fileName, i) {
  632. Fs.readFile(fileName, waitFor(function (err, ret) {
  633. if (err) { throw err; }
  634. rebIfChg[i] = ret;
  635. }));
  636. });
  637. hash.update(JSON.stringify(normalizedProcessEnv()));
  638. }).nThen(function (waitFor) {
  639. rebIfChg.forEach(function (data) {
  640. hash.update(data);
  641. });
  642. callback(hash.digest('hex'));
  643. });
  644. };
  645. var throwIfErr = function (err) { if (err) { throw err; } };
  646. var probeCompiler = function (state, callback) {
  647. nThen(function (waitFor) {
  648. var compilerType = state.compilerType = {
  649. isLLVM: false,
  650. isClang: false,
  651. isGCC: false,
  652. version: undefined
  653. };
  654. compiler(state.gcc, ['-v'], waitFor(function (ret, out, err) {
  655. if (ret !== 0) { throw new Error("Failed to probe compiler ret[" + ret + "]\n" + err); }
  656. if (/Apple LLVM version /.test(err)) {
  657. compilerType.isLLVM = true;
  658. if (/clang/.test(err)) {
  659. // Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
  660. // Target: x86_64-apple-darwin14.4.0
  661. // Thread model: posix
  662. compilerType.isClang = true;
  663. compilerType.version = err.match(/Apple LLVM version ([^ ]+) /)[1];
  664. } else if (/gcc version /.test(err)) {
  665. // Using built-in specs.
  666. // Target: i686-apple-darwin11
  667. // Configured with: /private/var/tmp/llvmgcc42/llvmgcc42.......
  668. // Thread model: posix
  669. // gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
  670. compilerType.isGCC = true;
  671. compilerType.version = err.match(/gcc version ([^ ]+) /)[1];
  672. }
  673. } else if (/clang version /.test(err)) {
  674. // FreeBSD clang version 3.0 (tags/RELEASE_30/final 145349) 20111210
  675. // Target: x86_64-unknown-freebsd10.0
  676. // Thread model: posix
  677. // clang version 3.2 (trunk)
  678. // Target: x86_64-unknown-linux-gnu
  679. // Thread model: posix
  680. compilerType.isLLVM = true;
  681. compilerType.isClang = true;
  682. compilerType.version = err.match(/clang version ([^ ]+) /)[1];
  683. } else if (/gcc version /.test(err)) {
  684. compilerType.isGCC = true;
  685. compilerType.version = err.match(/gcc version ([^ ]+) /)[1];
  686. }
  687. console.log(JSON.stringify(compilerType));
  688. }));
  689. }).nThen(callback);
  690. };
  691. process.on('exit', function () {
  692. console.log("Total build time: " + Math.floor(process.uptime() * 1000) + "ms.");
  693. });
  694. var stage = function (st, builder, waitFor) {
  695. builder.waitFor = waitFor;
  696. st(builder, waitFor);
  697. };
  698. var configure = module.exports.configure = function (params, configFunc) {
  699. // Track time taken for various steps
  700. var time = makeTime();
  701. time();
  702. if (typeof(params.systemName) !== 'string') {
  703. throw new Error("system not specified");
  704. }
  705. params.buildDir = params.buildDir || 'build_' + params.systemName;
  706. var version;
  707. var state;
  708. var builder;
  709. var buildStage = function () {};
  710. var testStage = function () {};
  711. var packStage = function () {};
  712. var successStage = function () {};
  713. var failureStage = function () {};
  714. var completeStage = function () {};
  715. nThen(function (waitFor) {
  716. // make the build directory
  717. Fs.exists(params.buildDir, waitFor(function (exists) {
  718. if (exists) { return; }
  719. Fs.mkdir(params.buildDir, waitFor(function (err) {
  720. if (err) { throw err; }
  721. }));
  722. }));
  723. }).nThen(function (waitFor) {
  724. // read out the state if it exists
  725. Fs.exists(params.buildDir + '/state.json', waitFor(function (exists) {
  726. if (!exists) { return; }
  727. Fs.readFile(params.buildDir + '/state.json', waitFor(function (err, ret) {
  728. if (err) { throw err; }
  729. state = JSON.parse(ret);
  730. // cflags, ldflags and libs are setup by make.js and should not be restored.
  731. state.cflags = [];
  732. state.ldflags = [];
  733. state.libs = [];
  734. state.includeDirs = ['.'];
  735. }));
  736. }));
  737. }).nThen(function (waitFor) {
  738. if (process.env["CJDNS_RELEASE_VERSION"]) {
  739. version = '' + process.env["CJDNS_RELEASE_VERSION"];
  740. } else {
  741. GetVersion(waitFor(function(err, data) {
  742. if (err === null) {
  743. version = '' + data;
  744. version = version.replace(/(\r\n|\n|\r)/gm, "");
  745. } else {
  746. version = 'unknown';
  747. }
  748. }));
  749. }
  750. }).nThen(function (waitFor) {
  751. if (!state || !state.rebuildIfChanges) {
  752. // no state
  753. state = undefined;
  754. } else {
  755. getRebuildIfChangesHash(state.rebuildIfChanges, waitFor(function (rich) {
  756. if (rich !== state.rebuildIfChangesHash) {
  757. debug("rebuildIfChanges changed, rebuilding");
  758. state = undefined;
  759. }
  760. }));
  761. }
  762. }).nThen(function (waitFor) {
  763. debug("Initialize " + time() + "ms");
  764. // Do the configuration step
  765. if (state) {
  766. builder = mkBuilder(state);
  767. builder.config.version = version;
  768. return;
  769. }
  770. state = getStatePrototype(params);
  771. builder = mkBuilder(state);
  772. builder.config.version = version;
  773. probeCompiler(state, waitFor());
  774. }).nThen(function (waitFor) {
  775. configFunc(builder, waitFor);
  776. }).nThen(function (waitFor) {
  777. debug("Configure " + time() + "ms");
  778. if (state.rebuildIfChangesHash) {
  779. return;
  780. }
  781. if (state.rebuildIfChanges.indexOf(module.parent.filename) === -1) {
  782. // Always always rebuild if the makefile was changed.
  783. state.rebuildIfChanges.push(module.parent.filename);
  784. }
  785. getRebuildIfChangesHash(state.rebuildIfChanges, waitFor(function (rich) {
  786. state.rebuildIfChangesHash = rich;
  787. }));
  788. }).nThen(function (waitFor) {
  789. state.oldmtimes = state.mtimes;
  790. state.mtimes = {};
  791. Object.keys(state.oldmtimes).forEach(function (fileName) {
  792. Fs.stat(fileName, waitFor(function (err, stat) {
  793. if (err) {
  794. if (err.code === 'ENOENT') {
  795. // Doesn't matter as long as it's not referenced...
  796. debug("File [" + fileName + "] was removed");
  797. delete state.files[fileName];
  798. return;
  799. } else {
  800. throw err;
  801. }
  802. }
  803. state.mtimes[fileName] = stat.mtime.getTime();
  804. if (state.oldmtimes[fileName] !== stat.mtime.getTime()) {
  805. debug(fileName + ' is out of date, rebuilding');
  806. removeFile(state, fileName, waitFor());
  807. }
  808. }));
  809. });
  810. }).nThen(function (waitFor) {
  811. debug("Scan for out of date files " + time() + "ms");
  812. }).nThen(function (waitFor) {
  813. stage(buildStage, builder, waitFor);
  814. }).nThen(function (waitFor) {
  815. debug("Compile " + time() + "ms");
  816. var allFiles = {};
  817. Object.keys(state.files).forEach(function (fileName) {
  818. allFiles[fileName] = 1;
  819. state.files[fileName].includes.forEach(function (fileName) {
  820. allFiles[fileName] = 1;
  821. });
  822. });
  823. Object.keys(allFiles).forEach(function (fileName) {
  824. var omt = state.oldmtimes[fileName];
  825. if (omt > 0 && omt === state.mtimes[fileName]) {
  826. return;
  827. }
  828. builder.rebuiltFiles.push(fileName);
  829. });
  830. }).nThen(function (waitFor) {
  831. builder.tests.forEach(function (test) {
  832. test(waitFor(function (output, failure) {
  833. debug(output);
  834. if (failure) {
  835. builder.failure = true;
  836. }
  837. }));
  838. });
  839. }).nThen(function (waitFor) {
  840. if (builder.linters.length === 0) {
  841. return;
  842. }
  843. debug("Checking codestyle");
  844. var sema = Semaphore.create(64);
  845. builder.rebuiltFiles.forEach(function (fileName) {
  846. sema.take(waitFor(function (returnAfter) {
  847. Fs.readFile(fileName, waitFor(function (err, ret) {
  848. if (err) { throw err; }
  849. ret = ret.toString('utf8');
  850. nThen(function (waitFor) {
  851. builder.linters.forEach(function (linter) {
  852. linter(fileName, ret, waitFor(function (out, isErr) {
  853. if (isErr) {
  854. debug("\033[1;31m" + out + "\033[0m");
  855. builder.failure = true;
  856. }
  857. }));
  858. });
  859. }).nThen(returnAfter(waitFor()));
  860. }));
  861. }));
  862. });
  863. }).nThen(function (waitFor) {
  864. stage(testStage, builder, waitFor);
  865. }).nThen(function (waitFor) {
  866. if (builder.failure) { return; }
  867. debug("Test " + time() + "ms");
  868. builder.executables.forEach(function (array) {
  869. if (array[1] === array[0]) { return; }
  870. Fs.rename(array[0], array[1], waitFor(function (err) {
  871. // TODO(cjd): It would be better to know in advance whether to expect the file.
  872. if (err && err.code !== 'ENOENT') {
  873. throw err;
  874. }
  875. }));
  876. });
  877. }).nThen(function (waitFor) {
  878. if (builder.failure) { return; }
  879. stage(packStage, builder, waitFor);
  880. }).nThen(function (waitFor) {
  881. if (builder.failure) { return; }
  882. debug("Pack " + time() + "ms");
  883. getMTimes(state.files, state.mtimes, waitFor(function (err, mtimes) {
  884. if (err) { throw err; }
  885. state.mtimes = mtimes;
  886. debug("Get mtimes " + time() + "ms");
  887. }));
  888. }).nThen(function (waitFor) {
  889. if (builder.failure) { return; }
  890. // save state
  891. var stateJson = JSON.stringify(state, null, ' ');
  892. Fs.writeFile(state.buildDir + '/state.json', stateJson, waitFor(function (err) {
  893. if (err) { throw err; }
  894. debug("Save State " + time() + "ms");
  895. }));
  896. }).nThen(function (waitFor) {
  897. if (builder.failure) { return; }
  898. stage(successStage, builder, waitFor);
  899. }).nThen(function (waitFor) {
  900. if (!builder.failure) { return; }
  901. stage(failureStage, builder, waitFor);
  902. }).nThen(function (waitFor) {
  903. stage(completeStage, builder, waitFor);
  904. });
  905. var out = {
  906. build: function (x) { buildStage = x; return out; },
  907. test: function (x) { testStage = x; return out; },
  908. pack: function (x) { packStage = x; return out; },
  909. failure: function (x) { failureStage = x; return out; },
  910. success: function (x) { successStage = x; return out; },
  911. complete: function (x) { completeStage = x; return out; },
  912. };
  913. return out;
  914. };