builder.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183
  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. /*@flow*/
  16. 'use strict';
  17. const Os = require('os');
  18. const Fs = require('fs');
  19. const Spawn = require('child_process').spawn;
  20. const nThen = require('nthen');
  21. const Crypto = require('crypto');
  22. const Saferphore = require('saferphore');
  23. var seed = process.env.SOURCE_DATE_EPOCH || Crypto.randomBytes(32).toString('hex');
  24. /*::
  25. export type Builder_File_t = {|
  26. includes: string[],
  27. links: string[],
  28. cflags: string[],
  29. ldflags: string[],
  30. mtime: number,
  31. |};
  32. export type Builder_Hfile_t = {|
  33. mtime: number,
  34. |};
  35. export type Builder_Compiler_t = {|
  36. isLLVM: bool,
  37. isClang: bool,
  38. isGCC: bool,
  39. version: string
  40. |};
  41. export type Builder_State_t = {|
  42. compilerType: Builder_Compiler_t,
  43. cFiles: { [string]: Builder_File_t },
  44. hFiles: { [string]: Builder_Hfile_t },
  45. |};
  46. export type Builder_Linter_t = (string, string, (string, bool)=>void)=>void;
  47. export type Builder_TestRunnerCb_t = (string, bool)=>void;
  48. export type Builder_t = {|
  49. cc: (string[], (number, string, string)=>void)=>void,
  50. buildLibrary: (string)=>void,
  51. buildExecutable: (string, ?string)=>void,
  52. buildTest: (string)=>string,
  53. runTest: (string, (string, Builder_TestRunnerCb_t)=>void)=>void,
  54. lintFiles: (Builder_Linter_t)=>void,
  55. config: Builder_Config_t,
  56. tmpFile: (?string)=>string,
  57. compilerType: () => Builder_Compiler_t,
  58. fileCflags: {[string]: string[]},
  59. |};
  60. export type Builder_BaseConfig_t = {|
  61. systemName?: ?string,
  62. gcc?: ?string,
  63. buildDir?: ?string,
  64. |};
  65. export type Builder_Config_t = {
  66. systemName: string,
  67. gcc: string,
  68. buildDir: string,
  69. includeDirs: string[],
  70. cflags: string[],
  71. ldflags: string[],
  72. libs: string[],
  73. jobs: number,
  74. } & {[string]:any};
  75. import type { Nthen_WaitFor_t } from 'nthen';
  76. import type { Saferphore_t } from 'saferphore';
  77. export type Builder_Stage_t = (Builder_t, Nthen_WaitFor_t)=>void;
  78. export type Builder_CompileJob_t = {
  79. cFile: string,
  80. outputFile: ?string,
  81. type: 'exe'|'lib'
  82. };
  83. export type Builder_Pub_t = {|
  84. build: (Builder_Stage_t)=>Builder_Pub_t,
  85. test: (Builder_Stage_t)=>Builder_Pub_t,
  86. pack: (Builder_Stage_t)=>Builder_Pub_t,
  87. failure: (Builder_Stage_t)=>Builder_Pub_t,
  88. success: (Builder_Stage_t)=>Builder_Pub_t,
  89. complete: (Builder_Stage_t)=>Builder_Pub_t,
  90. |}
  91. export type Builder_PreCtx_t = {
  92. buildStage: Builder_Stage_t,
  93. testStage: Builder_Stage_t,
  94. packStage: Builder_Stage_t,
  95. failureStage: Builder_Stage_t,
  96. successStage: Builder_Stage_t,
  97. completeStage: Builder_Stage_t,
  98. failure: bool,
  99. linters: Builder_Linter_t[],
  100. executables: Array<Builder_CompileJob_t>,
  101. tests: Array<(Builder_TestRunnerCb_t)=>void>,
  102. toCompile: { [string]: Builder_File_t },
  103. config: Builder_Config_t,
  104. sema: Saferphore_t,
  105. };
  106. export type Builder_Ctx_t = Builder_PreCtx_t & {
  107. builder: Builder_t,
  108. state: Builder_State_t,
  109. };
  110. */
  111. const error = function (message) /*:Error*/ {
  112. try {
  113. throw new Error(message);
  114. } catch (e) {
  115. return e;
  116. }
  117. };
  118. const expandArgs = function (args) {
  119. const out = [];
  120. for (let i = 0; i < args.length; i++) {
  121. if (typeof(args[i]) === 'object') {
  122. if (Array.isArray(args[i])) {
  123. out.push.apply(out, expandArgs(args[i]));
  124. } else {
  125. throw new Error("object in arguments [" + args.join() + "]");
  126. }
  127. } else {
  128. out.push(args[i]);
  129. }
  130. }
  131. return out;
  132. };
  133. const compiler = function (
  134. ctx /*:Builder_Ctx_t*/,
  135. args /*:string[]*/,
  136. callback /*:(number, string, string)=>bool|void*/,
  137. content /*:string*/
  138. ) {
  139. let stop = false;
  140. args = expandArgs(args);
  141. ctx.sema.take(function (returnAfter) {
  142. if (stop) {
  143. return void returnAfter(function (ret) {
  144. callback(1, '', 'interrupted');
  145. });
  146. }
  147. if (process.env.VERBOSE) {
  148. console.log(ctx.config.gcc + ' ' + args.join(' '));
  149. }
  150. const gcc = Spawn(ctx.config.gcc, args);
  151. let err = '';
  152. let out = '';
  153. gcc.stdout.on('data', function (dat) { out += dat.toString(); });
  154. gcc.stderr.on('data', function (dat) { err += dat.toString(); });
  155. gcc.on('close', returnAfter(function (ret) {
  156. if (callback(ret, out, err)) { stop = true; }
  157. }));
  158. gcc.on('error', function (err) {
  159. if (err.code === 'ENOENT') {
  160. console.error('\x1b[1;31mError: ' + ctx.config.gcc + ' is required!\x1b[0m');
  161. } else {
  162. console.error(
  163. '\x1b[1;31mFail run ' + process.cwd() + ': ' + ctx.config.gcc + ' '
  164. + args.join(' ') + '\x1b[0m'
  165. );
  166. console.error('Message:' + err);
  167. }
  168. // handle the error safely
  169. console.log(args);
  170. });
  171. if (content) {
  172. gcc.stdin.write(content, function (err) {
  173. if (err) { throw err; }
  174. gcc.stdin.end();
  175. });
  176. }
  177. });
  178. };
  179. const cc = function (
  180. ctx /*:Builder_Ctx_t*/,
  181. args /*:string[]*/,
  182. callback /*:(?Error, ?string)=>bool|void*/,
  183. content /*:string*/
  184. ) {
  185. compiler(ctx, args, function (ret, out, err) {
  186. if (ret) {
  187. return callback(error(ctx.config.gcc + " " + args.map(String).join(' ') + "\n\n" + err));
  188. }
  189. if (err !== '') {
  190. //process.stdout.write(err);
  191. }
  192. return callback(undefined, out);
  193. }, content);
  194. };
  195. const getStatePrototype = function () /*:Builder_State_t*/ {
  196. return {
  197. compilerType: {
  198. isLLVM: false,
  199. isClang: false,
  200. isGCC: false,
  201. version: ''
  202. },
  203. cFiles: {},
  204. hFiles: {},
  205. };
  206. };
  207. const tmpFile = function (ctx /*:Builder_Ctx_t*/, name) {
  208. name = name || '';
  209. return ctx.config.buildDir + '/tmp/' + name + Crypto.pseudoRandomBytes(10).toString('hex');
  210. };
  211. const finalizeCtx = function (
  212. state /*:Builder_State_t*/,
  213. pctx /*:Builder_PreCtx_t*/
  214. ) /*:Builder_Ctx_t*/ {
  215. const ctx = ((pctx /*:any*/) /*:Builder_Ctx_t*/);
  216. ctx.state = state;
  217. ctx.builder = (Object.freeze({
  218. cc: function (args, callback) {
  219. compiler(ctx, args, callback, '');
  220. },
  221. buildLibrary: function (cFile) {
  222. ctx.executables.push({ cFile, outputFile: null, type: 'lib' });
  223. },
  224. buildExecutable: function (cFile, outputFile) {
  225. ctx.executables.push({ cFile, outputFile, type: 'exe' });
  226. },
  227. buildTest: function (cFile) {
  228. const outputFile = getTempExe(ctx, cFile);
  229. ctx.executables.push({ cFile, outputFile, type: 'exe' });
  230. return outputFile;
  231. },
  232. runTest: function (outFile, testRunner) {
  233. ctx.tests.push(function (cb) { testRunner(outFile, cb); });
  234. },
  235. lintFiles: function (linter) {
  236. ctx.linters.push(linter);
  237. },
  238. config: ctx.config,
  239. tmpFile: function (name) {
  240. return tmpFile(ctx, name);
  241. },
  242. compilerType: () => JSON.parse(JSON.stringify(ctx.state.compilerType)),
  243. fileCflags: {},
  244. }) /*:Builder_t*/);
  245. return ctx;
  246. };
  247. // You Were Warned
  248. const execJs = function (js, ctx, file, fileName, callback, thisObj) {
  249. let res;
  250. let x;
  251. let err;
  252. // # 74 "./wire/Message.h"
  253. js = js.replace(/\n#.*\n/g, '');
  254. // Js_SQ Js_DQ
  255. const qs = js.split('Js_Q');
  256. if (qs.length && (qs.length % 2) === 0) {
  257. throw new Error("Uneven number of Js_Q, content: [" + js + "]");
  258. }
  259. for (let i = 1; i < qs.length; i += 2) {
  260. // escape nested quotes, they'll come back out in the final .i file
  261. qs[i] = qs[i].replace(/\'/g, '\\u0027');
  262. }
  263. js = '"use strict";' + qs.join("'");
  264. const to = setTimeout(function () {
  265. throw new Error("Inline JS did not return after 120 seconds [" + js + "]");
  266. }, 120000);
  267. nThen(function (waitFor) {
  268. try {
  269. /* jshint -W054 */ // Suppress jshint warning on Function being a form of eval
  270. const func = new Function('require', 'js', 'console', 'builder', js);
  271. const jsObj = Object.freeze({
  272. async: function () {
  273. return waitFor(function (result) {
  274. res = result;
  275. });
  276. },
  277. linkerDependency: (cFile) => file.links.push(cFile),
  278. currentFile: fileName,
  279. });
  280. x = func.call(thisObj,
  281. require,
  282. jsObj,
  283. console,
  284. ctx.builder);
  285. } catch (e) {
  286. clearTimeout(to);
  287. console.error("Error executing: [" + js + "] in File [" + fileName + "]");
  288. throw e;
  289. }
  290. }).nThen(function (waitFor) {
  291. if (err) { return; }
  292. res = res || x || '';
  293. clearTimeout(to);
  294. process.nextTick(function () { callback(undefined, res); });
  295. });
  296. };
  297. const debug = console.log;
  298. const preprocessBlock = function (block, ctx, fileObj, fileName, callback, thisObj) {
  299. // a block is an array of strings and arrays, any inside arrays must be
  300. // preprocessed first. deep first top to bottom.
  301. let nt = nThen;
  302. block.forEach(function (elem, i) {
  303. if (typeof(elem) === 'string') { return; }
  304. nt = nt(function (waitFor) {
  305. preprocessBlock(elem, ctx, fileObj, fileName, waitFor(function (err, ret) {
  306. if (err) { throw err; }
  307. block[i] = ret;
  308. }), thisObj);
  309. }).nThen;
  310. });
  311. nt(function (waitFor) {
  312. const capture = block.join('');
  313. execJs(capture, ctx, fileObj, fileName, waitFor(function (err, ret) {
  314. if (err) { throw err; }
  315. callback(undefined, ret);
  316. }), thisObj);
  317. });
  318. };
  319. const preprocess = function (content /*:string*/, ctx, fileObj, fileName, callback) {
  320. // <?js file.Test_mainFunc = "<?js return 'RootTest_'+file.RootTest_mainFunc; ?>" ?>
  321. // worse:
  322. // <?js file.Test_mainFunc = "<?js const done = this.async(); process.nextTick(done); ?>" ?>
  323. const flatArray = content.split(/(<\?js|\?>)/);
  324. const elems = [];
  325. const unflatten = function (array, startAt, out) {
  326. let i = startAt;
  327. for (; i < array.length; i++) {
  328. if (((i - startAt) % 2) === 0) {
  329. out.push(array[i]);
  330. } else if (array[i] === '<?js') {
  331. const next = [];
  332. out.push(next);
  333. i = unflatten(array, i+1, next);
  334. } else if (array[i] === '?>') {
  335. return i;
  336. }
  337. }
  338. return i;
  339. };
  340. if (unflatten(flatArray, 0, elems) !== flatArray.length) {
  341. throw new Error();
  342. }
  343. const thisObj = {};
  344. let nt = nThen;
  345. elems.forEach(function (elem, i) {
  346. if (typeof(elem) === 'string') { return; }
  347. nt = nt(function (waitFor) {
  348. preprocessBlock(elem, ctx, fileObj, fileName, waitFor(function (err, ret) {
  349. if (err) { throw err; }
  350. elems[i] = ret;
  351. }), thisObj);
  352. }).nThen;
  353. });
  354. nt(function (waitFor) {
  355. callback(undefined, elems.join(''));
  356. });
  357. };
  358. const mkFile = function () /*:Builder_File_t*/ {
  359. return {
  360. includes: [],
  361. links: [],
  362. cflags: [],
  363. ldflags: [],
  364. mtime: 0,
  365. };
  366. };
  367. const getOFile = function (ctx, cFile) {
  368. return ctx.config.buildDir + '/' + cFile.replace(/[^a-zA-Z0-9_-]/g, '_') + '.o';
  369. };
  370. const getIFile = function (ctx, cFile) {
  371. return ctx.config.buildDir + '/' + cFile.replace(/[^a-zA-Z0-9_-]/g, '_') + '.i';
  372. };
  373. const getTempExe = function (ctx, cFile) {
  374. return ctx.config.buildDir + '/' + cFile.replace(/[^a-zA-Z0-9_-]/g, '_');
  375. };
  376. const getExeFile = function (ctx, exe /*:Builder_CompileJob_t*/) {
  377. let outputFile = exe.outputFile;
  378. if (!outputFile) {
  379. outputFile = exe.cFile.replace(/^.*\/([^\/\.]*).*$/, (a, b) => b);
  380. }
  381. if (ctx.config.systemName === 'win32' && !(/\.exe$/.test(outputFile))) {
  382. outputFile += '.exe';
  383. }
  384. return outputFile;
  385. };
  386. var randomHex = function (bytes, fileName) {
  387. var material = new Crypto.Hash('sha512').update(seed).update(fileName).digest();
  388. if (bytes > 64) { throw new Error("meh, randomHex of over 64 bytes is unimplemented"); }
  389. return material.slice(0, bytes).toString('hex');
  390. };
  391. const getFlags = function (ctx, cFile, includeDirs) {
  392. const flags = [];
  393. if (cFile.indexOf('node_build/dependencies/libuv') > -1) {
  394. //console.log('cargo:warning=' + cFile);
  395. for (const f of ctx.config.cflags) {
  396. if (f !== '-Werror') {
  397. flags.push(f);
  398. }
  399. }
  400. } else {
  401. flags.push.apply(flags, ctx.config.cflags);
  402. }
  403. flags.push.apply(flags, ctx.builder.fileCflags[cFile] || []);
  404. flags.push('-DCJDNS_RAND_U64_PER_FILE=0x' + randomHex(8, cFile) + 'ull');
  405. if (includeDirs) {
  406. for (let i = 0; i < ctx.config.includeDirs.length; i++) {
  407. if (flags[flags.indexOf(ctx.config.includeDirs[i])-1] === '-I') {
  408. continue;
  409. }
  410. flags.push('-I');
  411. flags.push(ctx.config.includeDirs[i]);
  412. }
  413. }
  414. return flags;
  415. };
  416. const preprocessFile = function (cFile, ctx, callback)
  417. {
  418. if (ctx.state.cFiles[cFile]) {
  419. return void callback();
  420. }
  421. const state = ctx.state;
  422. //debug(' preprocessing ' + cFile);
  423. //debug('\x1b[2;32mCompiling ' + cFile + '\x1b[0m');
  424. const fileObj = mkFile();
  425. let fileContent = '';
  426. const cflags = getFlags(ctx, cFile, true);
  427. fileObj.cflags = getFlags(ctx, cFile, false);
  428. nThen((w) => {
  429. //debug("CPP");
  430. cc(ctx, ['-E', ...cflags, cFile], w(function (err, output) {
  431. if (err) { throw err; }
  432. fileContent = output;
  433. return false;
  434. }), '');
  435. // Stat the C file
  436. Fs.stat(cFile, w(function (err, st) {
  437. if (err) { throw err; }
  438. fileObj.mtime = st.mtime.getTime();
  439. }));
  440. }).nThen((w) => {
  441. //debug("Preprocess");
  442. preprocess(fileContent, ctx, fileObj, cFile, w(function (err, output) {
  443. if (err) { throw err; }
  444. Fs.writeFile(getIFile(ctx, cFile), output, w(function (err) {
  445. if (err) { throw err; }
  446. }));
  447. }));
  448. // Also snatch the local includes
  449. const includes = fileContent.match(/# [0-9]+ "\.\/[^"]*"/g) || [];
  450. const uniqIncl = {};
  451. for (const incl of includes) {
  452. uniqIncl[incl.replace(/^.* "\.\//, '').slice(0,-1)] = 1;
  453. }
  454. fileObj.includes = Object.keys(uniqIncl);
  455. fileObj.includes.forEach((incl) => {
  456. if (ctx.state.hFiles[incl]) { return; }
  457. Fs.stat(incl, w((err, st) => {
  458. if (err) { throw err; }
  459. ctx.state.hFiles[incl] = {
  460. mtime: st.mtime.getTime()
  461. };
  462. }));
  463. });
  464. }).nThen(function (_) {
  465. debug('\x1b[2;36mPreprocessing ' + cFile + ' complete\x1b[0m');
  466. state.cFiles[cFile] = fileObj;
  467. ctx.toCompile[cFile] = fileObj;
  468. callback();
  469. });
  470. };
  471. const preprocessFiles = function (ctx, files, callback) {
  472. const added = {};
  473. for (const f of files) { added[f] = 1; }
  474. const doMore = () => {
  475. if (files.length === 0) {
  476. return void callback();
  477. }
  478. const filez = files;
  479. files = [];
  480. nThen((w) => {
  481. filez.forEach((file) => {
  482. preprocessFile(file, ctx, w(() => {
  483. ctx.state.cFiles[file].links.forEach(function (link) {
  484. if (link === file || added[link]) {
  485. return;
  486. }
  487. added[link] = 1;
  488. files.push(link);
  489. });
  490. }));
  491. });
  492. }).nThen((w) => {
  493. doMore();
  494. });
  495. };
  496. doMore();
  497. };
  498. const getLinkOrder = function (cFile, files) /*:string[]*/ {
  499. const completeFiles = [];
  500. const getFile = function (name) {
  501. const f = files[name];
  502. //debug('Resolving links for ' + name);
  503. for (let i = 0; i < f.links.length; i++) {
  504. if (f.links[i] === name) {
  505. continue;
  506. }
  507. if (completeFiles.indexOf(f.links[i]) > -1) {
  508. continue;
  509. }
  510. getFile(f.links[i]);
  511. }
  512. completeFiles.push(name);
  513. };
  514. getFile(cFile);
  515. return completeFiles;
  516. };
  517. // Called on the .c file with a main() function which corrisponds to
  518. // an executable.
  519. // We kick the file entries right out of the state object when they
  520. // or an #include get dirty, so we just need to traverse links to
  521. // make sure everything is present.
  522. const needsToLink = function (ctx, cFile) {
  523. const nlCache = {};
  524. const nll = [];
  525. const nl = (cFile) => {
  526. if (nlCache[cFile]) { return false; }
  527. if (nll.indexOf(cFile) > -1) {
  528. return false;
  529. //throw new Error(`File ${cFile} is self-referencial:\n${nll.join('\n')}\n\n`);
  530. }
  531. nll.push(cFile);
  532. const out = (() => {
  533. //debug(' ' + cFile);
  534. if (typeof(ctx.state.cFiles[cFile]) !== 'object') {
  535. return true;
  536. }
  537. for (const l of ctx.state.cFiles[cFile].links) {
  538. if (l !== cFile && nl(l)) {
  539. return true;
  540. }
  541. }
  542. nlCache[cFile] = true;
  543. return false;
  544. })();
  545. if (nll.pop() !== cFile) { throw new Error(); }
  546. return out;
  547. };
  548. return nl(cFile);
  549. };
  550. const makeTime = function () {
  551. let time = 0;
  552. return function () {
  553. const oldTime = time;
  554. time = new Date().getTime();
  555. return time - oldTime;
  556. };
  557. };
  558. const link = function (cFile, callback, ctx /*:Builder_Ctx_t*/) {
  559. const state = ctx.state;
  560. const temp = getTempExe(ctx, cFile);
  561. nThen((waitFor) => {
  562. const linkOrder = getLinkOrder(cFile, state.cFiles);
  563. for (let i = 0; i < linkOrder.length; i++) {
  564. linkOrder[i] = getOFile(ctx, linkOrder[i]);
  565. }
  566. const fileObj = state.cFiles[cFile];
  567. const ldArgs = []
  568. .concat(ctx.config.ldflags)
  569. .concat(fileObj.ldflags)
  570. .concat(['-o', temp])
  571. .concat(linkOrder)
  572. .concat(ctx.config.libs);
  573. debug('\x1b[1;31mLinking C executable ' + cFile + '\x1b[0m');
  574. cc(ctx, ldArgs, waitFor(function (err, ret) {
  575. if (err) { throw err; }
  576. return false;
  577. }), '');
  578. }).nThen((_) => callback());
  579. };
  580. const compile = function (ctx, cFile, done) {
  581. //debug("CC");
  582. const file = ctx.state.cFiles[cFile];
  583. const oFile = getOFile(ctx, cFile);
  584. const iFile = getIFile(ctx, cFile);
  585. cc(ctx, ['-c', '-x', 'cpp-output', '-o', oFile, ...file.cflags, iFile], (err) => {
  586. done(err);
  587. return typeof(err) !== 'undefined';
  588. }, '');
  589. };
  590. /**
  591. * Get a copy of process.env with a few entries which are constantly changing removed.
  592. * This prevents isStaleState from returning true every time one builds in a different
  593. * window.
  594. */
  595. const normalizedProcessEnv = function () {
  596. const out = process.env;
  597. delete out.WINDOWID;
  598. delete out.OLDPWD;
  599. return out;
  600. };
  601. const getRebuildIfChangesHash = function (rebuildIfChanges, callback) {
  602. const hash = Crypto.createHash('sha256');
  603. const rebIfChg = [];
  604. nThen(function (waitFor) {
  605. rebuildIfChanges.forEach(function (fileName, i) {
  606. Fs.readFile(fileName, waitFor(function (err, ret) {
  607. if (err) { throw err; }
  608. rebIfChg[i] = ret;
  609. }));
  610. });
  611. hash.update(JSON.stringify(normalizedProcessEnv()));
  612. }).nThen(function (waitFor) {
  613. rebIfChg.forEach(function (data) {
  614. hash.update(data);
  615. });
  616. callback(hash.digest('hex'));
  617. });
  618. };
  619. const probeCompiler = function (ctx /*:Builder_Ctx_t*/, callback) {
  620. nThen(function (waitFor) {
  621. const compilerType = ctx.state.compilerType = {
  622. isLLVM: false,
  623. isClang: false,
  624. isGCC: false,
  625. version: ''
  626. };
  627. compiler(ctx, ['-v'], waitFor(function (ret, out, err) {
  628. // TODO(cjd): afl-clang-fast errors when called with -v
  629. //if (ret !== 0) { throw new Error("Failed to probe compiler ret[" + ret + "]\n" + err); }
  630. if (/Apple LLVM version /.test(err)) {
  631. compilerType.isLLVM = true;
  632. if (/clang/.test(err)) {
  633. // Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
  634. // Target: x86_64-apple-darwin14.4.0
  635. // Thread model: posix
  636. compilerType.isClang = true;
  637. compilerType.version = err.match(/Apple LLVM version ([^ ]+) /)[1];
  638. } else if (/gcc version /.test(err)) {
  639. // Using built-in specs.
  640. // Target: i686-apple-darwin11
  641. // Configured with: /private/const/tmp/llvmgcc42/llvmgcc42.......
  642. // Thread model: posix
  643. // gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
  644. compilerType.isGCC = true;
  645. compilerType.version = err.match(/gcc version ([^ ]+) /)[1];
  646. }
  647. } else if (/clang version /.test(err)) {
  648. // FreeBSD clang version 3.0 (tags/RELEASE_30/final 145349) 20111210
  649. // Target: x86_64-unknown-freebsd10.0
  650. // Thread model: posix
  651. // clang version 3.2 (trunk)
  652. // Target: x86_64-unknown-linux-gnu
  653. // Thread model: posix
  654. compilerType.isLLVM = true;
  655. compilerType.isClang = true;
  656. compilerType.version = err.match(/clang version ([^ ]+) /)[1];
  657. } else if (/gcc version /.test(err)) {
  658. compilerType.isGCC = true;
  659. compilerType.version = err.match(/gcc version ([^ ]+) /)[1];
  660. }
  661. //console.log(JSON.stringify(compilerType));
  662. }), '');
  663. }).nThen(callback);
  664. };
  665. process.on('exit', function () {
  666. console.log("Total build time: " + Math.floor(process.uptime() * 1000) + "ms.");
  667. });
  668. const deepFreeze = (obj) => {
  669. Object.freeze(obj);
  670. for (const k in obj) {
  671. if (typeof(obj[k]) === 'object') { deepFreeze(obj[k]); }
  672. }
  673. };
  674. const sweep = (path, done) => {
  675. let files = [];
  676. nThen((w) => {
  677. Fs.readdir(path, w((err, fls) => {
  678. if (err) { throw err; }
  679. files = fls;
  680. }));
  681. }).nThen((w) => {
  682. files.forEach((f) => {
  683. const file = path + '/' + f;
  684. Fs.stat(file, w((err, st) => {
  685. if (err) { throw err; }
  686. if (st.isDirectory()) {
  687. sweep(file, w(() => {
  688. Fs.rmdir(file, w((err) => {
  689. if (err) { throw err; }
  690. }));
  691. }));
  692. } else {
  693. Fs.unlink(file, w((err) => {
  694. if (err) { throw err; }
  695. }));
  696. }
  697. }));
  698. });
  699. }).nThen((_) => done());
  700. };
  701. module.exports.configure = function (
  702. params /*:Builder_BaseConfig_t*/,
  703. configFunc /*:(Builder_t, Nthen_WaitFor_t)=>void*/
  704. ) /*:Builder_Pub_t*/ {
  705. // Track time taken for various steps
  706. const time = makeTime();
  707. time();
  708. const systemName = params.systemName || process.platform;
  709. const buildDir = params.buildDir || 'build_' + systemName;
  710. let gcc;
  711. if (params.gcc) {
  712. gcc = params.gcc;
  713. } else if (systemName === 'openbsd') {
  714. gcc = 'egcc';
  715. } else if (systemName === 'freebsd') {
  716. gcc = 'clang';
  717. } else {
  718. gcc = 'gcc';
  719. }
  720. // Since many of the compile operations are short, the best
  721. // performance seems to be when running 1.25x the number of jobs as
  722. // cpu cores. On BSD and iphone systems, os.cpus() is not reliable so
  723. // if it returns undefined let's just assume 1
  724. // workaround, nodejs seems to be broken on openbsd (undefined result after second call)
  725. const cpus = Os.cpus();
  726. const jobs = Math.floor((typeof cpus === 'undefined' ? 1 : cpus.length) * 1.25);
  727. const pctx /*:Builder_PreCtx_t*/ = {
  728. buildStage: (_x,_y)=>{},
  729. testStage: (_x,_y)=>{},
  730. packStage: (_x,_y)=>{},
  731. failureStage: (_x,_y)=>{},
  732. successStage: (_x,_y)=>{},
  733. completeStage: (_x,_y)=>{},
  734. failure: false,
  735. linters: [],
  736. executables: [],
  737. tests: [],
  738. toCompile: {},
  739. sema: Saferphore.create(1),
  740. config: {
  741. buildDir,
  742. gcc,
  743. systemName,
  744. version: '',
  745. includeDirs: ['.'],
  746. cflags: [],
  747. ldflags: [],
  748. libs: [],
  749. jobs,
  750. },
  751. };
  752. let state = getStatePrototype();
  753. let ctx;
  754. let hasState = false;
  755. nThen(function (waitFor) {
  756. // make the build directory
  757. Fs.exists(buildDir, waitFor(function (exists) {
  758. if (exists) { return; }
  759. Fs.mkdir(buildDir, {}, waitFor(function (err) {
  760. if (err) { throw err; }
  761. }));
  762. }));
  763. }).nThen(function (waitFor) {
  764. Fs.exists(buildDir + '/tmp', waitFor(function (exists) {
  765. if (exists) {
  766. sweep(buildDir + '/tmp', waitFor());
  767. } else {
  768. Fs.mkdir(buildDir + '/tmp', {}, waitFor(function (err) {
  769. if (err) { throw err; }
  770. }));
  771. }
  772. }));
  773. }).nThen(function (waitFor) {
  774. if (process.env['CJDNS_FULL_REBUILD']) {
  775. debug("CJDNS_FULL_REBUILD set, non-incremental build");
  776. return;
  777. }
  778. // read out the state if it exists
  779. Fs.exists(buildDir + '/state.json', waitFor(function (exists) {
  780. if (!exists) { return; }
  781. Fs.readFile(buildDir + '/state.json', waitFor(function (err, ret) {
  782. if (err) { throw err; }
  783. state = ( JSON.parse(ret) /*:Builder_State_t*/ );
  784. hasState = true;
  785. debug("Loaded state file");
  786. }));
  787. }));
  788. }).nThen(function (waitFor) {
  789. debug("Initialize " + time() + "ms");
  790. // Do the configuration step
  791. if (hasState) {
  792. ctx = finalizeCtx(state, pctx);
  793. return;
  794. }
  795. state = getStatePrototype();
  796. ctx = finalizeCtx(state, pctx);
  797. probeCompiler(ctx, waitFor());
  798. }).nThen(function (waitFor) {
  799. //if (!ctx.builder) { throw new Error(); }
  800. configFunc(ctx.builder, waitFor);
  801. }).nThen(function (_) {
  802. ctx.sema = Saferphore.create(ctx.config.jobs);
  803. if (ctx.config.systemName !== systemName) {
  804. throw new Error("systemName cannot be changed in configure phase " +
  805. "it must be specified in the initial configuration " +
  806. `initial systemName = ${systemName}, changed to ${ctx.config.systemName}`);
  807. }
  808. if (ctx.config.gcc !== gcc) {
  809. throw new Error("gcc cannot be changed in configure phase " +
  810. "it must be specified in the initial configuration " +
  811. `initial gcc = ${gcc}, changed to ${ctx.config.gcc}`);
  812. }
  813. deepFreeze(ctx.config);
  814. debug("Configure " + time() + "ms");
  815. if (!ctx) { throw new Error(); }
  816. postConfigure(ctx, time);
  817. });
  818. const out = Object.freeze({
  819. build: function (x /*:Builder_Stage_t*/) { pctx.buildStage = x; return out; },
  820. test: function (x /*:Builder_Stage_t*/) { pctx.testStage = x; return out; },
  821. pack: function (x /*:Builder_Stage_t*/) { pctx.packStage = x; return out; },
  822. failure: function (x /*:Builder_Stage_t*/) { pctx.failureStage = x; return out; },
  823. success: function (x /*:Builder_Stage_t*/) { pctx.successStage = x; return out; },
  824. complete: function (x /*:Builder_Stage_t*/) { pctx.completeStage = x; return out; },
  825. });
  826. return out;
  827. };
  828. const checkFileMtime = (fileName, done) => {
  829. Fs.stat(fileName, function (err, stat) {
  830. if (err) {
  831. if (err.code === 'ENOENT') {
  832. done(-1);
  833. } else {
  834. throw err;
  835. }
  836. } else {
  837. done(stat.mtime.getTime());
  838. }
  839. });
  840. };
  841. const removeStaleFiles = (ctx, done) => {
  842. const stales = {};
  843. // Transient dependencies are provided by gcc -MM so there's no need to resolve them
  844. const dependents = {};
  845. nThen((w) => {
  846. Object.keys(ctx.state.cFiles).forEach(function (cFile) {
  847. const file = ctx.state.cFiles[cFile];
  848. for (const incl of file.includes) {
  849. if (!ctx.state.hFiles[incl]) {
  850. // Missing the header entirely, definitely stale
  851. debug(`\x1b[1;34m${cFile} stale (header ${incl} deleted)\x1b[0m`);
  852. stales[cFile] = 1;
  853. return;
  854. }
  855. (dependents[incl] = dependents[incl] || []).push(cFile);
  856. }
  857. const cflags = getFlags(ctx, cFile, false);
  858. if (JSON.stringify(cflags) !== JSON.stringify(file.cflags)) {
  859. debug(`\x1b[1;34m${cFile} stale (change of cflags)\x1b[0m`);
  860. stales[cFile] = 1;
  861. return;
  862. }
  863. checkFileMtime(cFile, w((mtime) => {
  864. if (mtime !== file.mtime) {
  865. debug(`\x1b[1;34m${cFile} stale\x1b[0m`);
  866. stales[cFile] = 1;
  867. } else {
  868. Fs.access(getOFile(ctx, cFile), Fs.constants.F_OK, w((err) => {
  869. if (err && err.code !== 'ENOENT') {
  870. throw err;
  871. } else if (err) {
  872. // Not stale but needs to be compiled
  873. ctx.toCompile[cFile] = file;
  874. }
  875. }));
  876. }
  877. }));
  878. });
  879. }).nThen((w) => {
  880. Object.keys(ctx.state.hFiles).forEach(function (hFile) {
  881. const file = ctx.state.hFiles[hFile];
  882. checkFileMtime(hFile, w((mtime) => {
  883. if (mtime === file.mtime) {
  884. return;
  885. } else if (mtime === -1) {
  886. debug(`\x1b[1;34m${hFile} stale (deleted)\x1b[0m`);
  887. delete ctx.state.hFiles[hFile];
  888. } else {
  889. debug(`\x1b[1;34m${hFile} stale\x1b[0m`);
  890. file.mtime = mtime;
  891. }
  892. for (const cFile of (dependents[hFile] || [])) {
  893. debug(`\x1b[1;34m${cFile} stale (includes ${hFile})\x1b[0m`);
  894. stales[cFile] = 1;
  895. }
  896. }));
  897. });
  898. }).nThen((w) => {
  899. Object.keys(stales).forEach((cFile) => {
  900. const file = ctx.state.cFiles[cFile];
  901. if (typeof(file) === 'undefined') { return; }
  902. delete ctx.state.cFiles[cFile];
  903. // Sweep up relevant files
  904. [getIFile(ctx, cFile), getOFile(ctx, cFile)].forEach((deleteMe) => {
  905. if (!deleteMe) { return; }
  906. Fs.unlink(deleteMe, w(function (err) {
  907. if (err && err.code !== 'ENOENT') {
  908. throw err;
  909. }
  910. }));
  911. });
  912. });
  913. }).nThen((w) => {
  914. done();
  915. });
  916. };
  917. const postConfigure = (ctx /*:Builder_Ctx_t*/, time) => {
  918. const state = ctx.state;
  919. nThen((waitFor) => {
  920. removeStaleFiles(ctx, waitFor());
  921. }).nThen(function (waitFor) {
  922. debug("Scan for out of date files " + time() + "ms");
  923. ctx.buildStage(ctx.builder, waitFor);
  924. }).nThen(function (waitFor) {
  925. ctx.executables = ctx.executables.filter((exe) => {
  926. if (!needsToLink(ctx, exe.cFile)) {
  927. debug(`\x1b[1;31m${getExeFile(ctx, exe)} up to date\x1b[0m`);
  928. return false;
  929. }
  930. return true;
  931. });
  932. preprocessFiles(ctx, ctx.executables.map(exe => exe.cFile), waitFor());
  933. }).nThen(function (w) {
  934. debug("Preprocess " + time() + "ms");
  935. // save state
  936. const stateJson = JSON.stringify(state, null, '\t');
  937. Fs.writeFile(ctx.config.buildDir + '/state.json', stateJson, w(function (err) {
  938. if (err) { throw err; }
  939. //debug("Saved state " + time() + "ms");
  940. deepFreeze(state);
  941. }));
  942. }).nThen(function (w) {
  943. Object.keys(ctx.toCompile).forEach((cFile) => {
  944. compile(ctx, cFile, w((err) => {
  945. if (err) {
  946. throw err;
  947. }
  948. debug('\x1b[2;32mCompiling ' + cFile + ' complete\x1b[0m');
  949. }));
  950. });
  951. }).nThen(function (waitFor) {
  952. debug("Compile " + time() + "ms");
  953. for (const exe of ctx.executables) {
  954. if (exe.type === 'exe') {
  955. link(exe.cFile, waitFor(), ctx);
  956. }
  957. }
  958. }).nThen((w) => {
  959. debug("Link " + time() + "ms");
  960. ctx.tests.forEach(function (test) {
  961. test(w(function (output, failure) {
  962. debug(output);
  963. if (failure) {
  964. ctx.failure = true;
  965. }
  966. }));
  967. });
  968. }).nThen(function (waitFor) {
  969. if (ctx.linters.length === 0) {
  970. return;
  971. }
  972. debug("Checking codestyle");
  973. const sema = Saferphore.create(64);
  974. Object.keys(ctx.toCompile).forEach(function (cFile) {
  975. sema.take(waitFor(function (returnAfter) {
  976. Fs.readFile(cFile, waitFor(function (err, ret) {
  977. if (err) { throw err; }
  978. ret = ret.toString('utf8');
  979. nThen(function (waitFor) {
  980. ctx.linters.forEach(function (linter) {
  981. linter(cFile, ret, waitFor(function (out, isErr) {
  982. if (isErr) {
  983. debug("\x1b[1;31m" + out + "\x1b[0m");
  984. ctx.failure = true;
  985. }
  986. }));
  987. });
  988. }).nThen(returnAfter(waitFor()));
  989. }));
  990. }));
  991. });
  992. }).nThen(function (waitFor) {
  993. ctx.testStage(ctx.builder, waitFor);
  994. }).nThen(function (waitFor) {
  995. if (ctx.failure) { return; }
  996. debug("Test " + time() + "ms");
  997. ctx.executables.forEach(function (exe) {
  998. const temp = getTempExe(ctx, exe.cFile);
  999. if (exe.outputFile === temp) { return; }
  1000. const outputFile = getExeFile(ctx, exe);
  1001. Fs.rename(temp, outputFile, waitFor(function (err) {
  1002. // TODO(cjd): It would be better to know in advance whether to expect the file.
  1003. if (err && err.code !== 'ENOENT') {
  1004. throw err;
  1005. }
  1006. }));
  1007. });
  1008. }).nThen(function (waitFor) {
  1009. if (ctx.failure) { return; }
  1010. ctx.packStage(ctx.builder, waitFor);
  1011. }).nThen(function (waitFor) {
  1012. if (ctx.failure) { return; }
  1013. debug("Pack " + time() + "ms");
  1014. }).nThen(function (waitFor) {
  1015. if (ctx.failure) { return; }
  1016. ctx.successStage(ctx.builder, waitFor);
  1017. }).nThen(function (waitFor) {
  1018. if (!ctx.failure) { return; }
  1019. ctx.failureStage(ctx.builder, waitFor);
  1020. }).nThen(function (waitFor) {
  1021. ctx.completeStage(ctx.builder, waitFor);
  1022. sweep(ctx.config.buildDir + '/tmp', waitFor());
  1023. });
  1024. };