builder.js 35 KB

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