builder.js 35 KB

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