builder.js 34 KB

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