makemake.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. /*
  2. * "makemake" Copyright (C) 1994-9, Codemist Ltd
  3. *
  4. * Used to build system-specific makefiles from a common base file
  5. *
  6. *
  7. * Usage:
  8. * makemake key* [-f prototype] [-o newmakefile]
  9. * [-ddata1] [-ddata2] ...
  10. *
  11. * The default prototype file is called "makebase" and the default
  12. * file created is called just "makenew" (note that both those are
  13. * spelt in lower case letters). The "keys" passed as arguments
  14. * should match the options declared at the head of the makebase file.
  15. * Data given after "-d" is used where otherwise interactive input would be
  16. * requested.
  17. */
  18. /* Signature: 55907f19 10-Feb-1999 */
  19. /*
  20. * The base file is a prototype "makefile", but with extra decorations so
  21. * that it can contain information relating to several systems. Lines
  22. * starting "@ " are comment and are ignored. Apart from such comments
  23. * the file should start with a section:
  24. * @menu
  25. * @item(key1) description
  26. * @item(key2) description
  27. * @endmenu
  28. * (the "@" signs should be in column 1, ie no whitespace before them)
  29. * This declares the valid keys. A key can be written as
  30. * @item(key>extra1>extra2) description
  31. * and then if the key is specified the given extra symbols will be defined
  32. * while processing the makebase file. An example use for this might be
  33. * @item(dos386>msdos) Intel 386/486 running DOS
  34. * @item(dos286>msdos>sixteenbit) Intel 286 running DOS
  35. * where subsequent items in the file may test msdos or sixteenbit.
  36. *
  37. * After the menu the makebase file is processed subject to directives
  38. * @if(key)
  39. * @ifnot(key)
  40. * @else
  41. * @elif(key)
  42. * @elifnot(key)
  43. * @endif
  44. * (again no whitespace is permitted, and the "@" must be in column 1)
  45. * which provide for conditional inclusion of text. A condition is TRUE if
  46. * it was either the key specified to mmake, or was set up by a ">"
  47. * in a menu item that matched the key.
  48. * @error (while not skipping)
  49. * terminates processing.
  50. *
  51. */
  52. #include <stdio.h>
  53. #include <stdlib.h>
  54. #include <string.h>
  55. #include <ctype.h>
  56. #ifndef SEEK_SET
  57. #define SEEK_SET 0
  58. #endif
  59. #ifndef EXIT_FAILURE
  60. #define EXIT_FAILURE 1
  61. #endif
  62. static int flags;
  63. static char *special_words[] =
  64. {
  65. "backslash",
  66. #define FLAG_BACKSLASH (flags & 0x001)
  67. "watcom",
  68. #define FLAG_WATCOM (flags & 0x002)
  69. "obj",
  70. #define FLAG_OBJ (flags & 0x004)
  71. "acorn",
  72. #define FLAG_ACORN (flags & 0x008)
  73. "escapequote",
  74. #define FLAG_ESCAPEQUOTE (flags & 0x010)
  75. "blank",
  76. #define FLAG_BLANK (flags & 0x020)
  77. "unix",
  78. #define FLAG_UNIX (flags & 0x040)
  79. "blank1",
  80. #define FLAG_BLANK1 (flags & 0x080)
  81. 0
  82. };
  83. #define N_SPECIAL_WORDS 7
  84. #define LINE_LENGTH 1024
  85. static char line[LINE_LENGTH];
  86. static int EOF_seen = 0;
  87. int line_number, last_live_line;
  88. #define MAX_WORDS 100
  89. static char *defined_words[MAX_WORDS];
  90. static int in_makebase[MAX_WORDS];
  91. static int n_user_words = 0, n_defined_words = 0;
  92. static FILE *fmakebase, *fmakefile;
  93. static int get_line()
  94. /* Read one line from makebase into line buffer, return 1 if OK */
  95. {
  96. int i = 0, c;
  97. if (EOF_seen) return 0;
  98. line_number++;
  99. while ((c = getc(fmakebase)) != '\n' && c != EOF)
  100. if (i < LINE_LENGTH-2) line[i++] = c;
  101. /* I lose trailing blanks from all input lines */
  102. while (i > 0 &&
  103. (line[i-1] == ' ' || line[i-1] == '\t')) i--;
  104. /*
  105. * If the file ended with a line that was not terminated by '\n' I
  106. * insert a '\n' here.
  107. */
  108. if (c == EOF)
  109. { EOF_seen = 1;
  110. if (i != 0)
  111. { line[i++] = '\n';
  112. line[i] = 0;
  113. return 1;
  114. }
  115. else return 0;
  116. }
  117. line[i++] = '\n';
  118. line[i] = 0;
  119. return 1;
  120. }
  121. static int process_item(int show)
  122. {
  123. char menuitem[LINE_LENGTH], *extra_defines[16];
  124. int i, p, n_extra_defines = 0;
  125. for (i=0, p=6; line[p] != ')' && line[p] != '\n'; )
  126. { if (line[p] == '>')
  127. { menuitem[i++] = 0;
  128. extra_defines[n_extra_defines++] = &menuitem[i];
  129. p++;
  130. }
  131. else menuitem[i++] = line[p++];
  132. }
  133. menuitem[i] = 0;
  134. if (line[p] != ')') return 0;
  135. else p++;
  136. while (line[p] == ' ' || line[p] == '\t') p++;
  137. if (show)
  138. { int len = (256 - strlen(menuitem)) % 8;
  139. printf(" %s:%*s %s", menuitem, len, "", &line[p]);
  140. return 1;
  141. }
  142. for (i=0; i<n_defined_words; i++)
  143. if (strcmp(menuitem, defined_words[i]) == 0)
  144. { in_makebase[i] = 1;
  145. break;
  146. }
  147. if (i >= n_defined_words) return 0;
  148. for (i=0; i<n_extra_defines; i++)
  149. { char *s = (char *)malloc(1+strlen(extra_defines[i]));
  150. if (s == NULL) return 0;
  151. strcpy(s, extra_defines[i]);
  152. defined_words[n_defined_words++] = s;
  153. }
  154. return 1;
  155. }
  156. static int process_menu(int show, char *makebase)
  157. {
  158. int success = 0, i;
  159. if (show) printf("The valid keys from \"%s\" are:\n", makebase);
  160. while (get_line())
  161. if (memcmp(line, "@menu", 5) == 0) break;
  162. while (get_line())
  163. { if (memcmp(line, "@endmenu", 8) == 0) break;
  164. if (memcmp(line, "@item(", 6) == 0 &&
  165. process_item(show)) success = 1;
  166. }
  167. if (!show && success)
  168. for (i=0; i<n_user_words; i++)
  169. if (!in_makebase[i])
  170. { printf("\"%s\" not present in base file\n", defined_words[i]);
  171. success = 0;
  172. }
  173. return !success;
  174. }
  175. static int blank_lines = 0;
  176. #ifdef SUPPORT_ACORN
  177. /*
  178. * Support for Acorn's RiscOS is not automatically included - if you need to
  179. * re-instate that you will have to review the following code and make it
  180. * work again.
  181. */
  182. static char *implicit_dirs[] =
  183. /*
  184. * If I find a filename in the makebase which has one of these names as a
  185. * suffix (eg xyz.abcdef.c) then for Acorn purposes I will convert it to
  186. * use a directory instead of a suffix (xyz.c.abcdef). I will make the
  187. * check for things in this list case insensitive. Note that GREAT care is
  188. * needed with files whose real name looks too much like one of these
  189. * special strings - eg "doc.c" or "s.txt" could cause muddle. I view it
  190. * as the business of people creating makecore files to take care of such
  191. * things (typically be avoiding such file names).
  192. */
  193. {
  194. "!", /* Used by ACN as a place for scratch files */
  195. "c", /* C source code */
  196. "f", /* Fortran source code */
  197. "h", /* C header files */
  198. "l", /* Listings generated by the C compiler */
  199. "o", /* Object code */
  200. "p", /* Pascal? */
  201. "s", /* Assembly code */
  202. "lsp", /* Used with CSL */
  203. "sl", /* Used with CSL/REDUCE */
  204. "red", /* REDUCE sources */
  205. "fsl", /* CSL fast-load files (well, not really!) */
  206. "log", /* I guess this is the hardest case */
  207. "tst", /* REDUCE test files */
  208. "doc", /* Another hard case */
  209. "cpp", /* C++ files */
  210. "hpp", /* C++ header files */
  211. "txt", /* to help me with some MSDOS transfers */
  212. "bak", /* Ditto. */
  213. NULL
  214. };
  215. static int acorn_filename(int i)
  216. {
  217. char raw_filename[LINE_LENGTH], extension[20], lc_extension[20];
  218. int j = 0, c;
  219. while ((c = line[i++]) != 0 && c != ' ' && c != '\n' &&
  220. c != '\t')
  221. { if (c == '@')
  222. { c = line[i++];
  223. switch (c)
  224. {
  225. case '^':
  226. case '@': raw_filename[j++] = c; break;
  227. case '/': raw_filename[j++] = '.'; break;
  228. case '~': if (FLAG_BLANK) raw_filename[j++] = ' ';
  229. break;
  230. case '=': if (FLAG_BLANK1) raw_filename[j++] = ' ';
  231. break;
  232. case '"': if (escapequote) raw_filename[j++] = '\\';
  233. raw_filename[j++] = c;
  234. break;
  235. case '!': break;
  236. case 'o': raw_filename[j++] = 'o';
  237. if (FLAG_OBJ)
  238. { raw_filename[j++] = 'b';
  239. raw_filename[j++] = 'j';
  240. }
  241. break;
  242. default: raw_filename[j++] = '@';
  243. raw_filename[j++] = c;
  244. break;
  245. }
  246. }
  247. else raw_filename[j++] = c;
  248. }
  249. if (raw_filename[j-1] == ':') i--, j--;
  250. raw_filename[j] = 0;
  251. while (j >= 0 && raw_filename[j] != '.') j--;
  252. extension[0] = 0;
  253. if (j > 0 & raw_filename[j] == '.' && strlen(&raw_filename[j]) < 16)
  254. { strcpy(extension, &raw_filename[j+1]);
  255. raw_filename[j] = 0;
  256. }
  257. for (j=0; extension[j]!=0; j++)
  258. { c = extension[j];
  259. if (isupper(c)) c = tolower(c);
  260. lc_extension[j] = c;
  261. }
  262. lc_extension[j] = 0;
  263. for (j=0; implicit_dirs[j]!=NULL; j++)
  264. if (strcmp(implicit_dirs[j], extension) == 0) break;
  265. if (implicit_dirs[j] != NULL) /* Match found - flip around */
  266. { j = strlen(raw_filename)-1;
  267. while (j >= 0 && raw_filename[j] != '.') j--;
  268. if (j > 0)
  269. { raw_filename[j] = 0;
  270. fprintf(fmakefile, "%s.%s.%s",
  271. raw_filename, extension, &raw_filename[j+1]);
  272. }
  273. else fprintf(fmakefile, "%s.%s", extension, raw_filename);
  274. }
  275. else if (extension[0] == 0) fprintf(fmakefile, "%s", raw_filename);
  276. else fprintf(fmakefile, "%s.%s", raw_filename, extension);
  277. return i-1;
  278. }
  279. #endif /* SUPPORT_ACORN */
  280. static void put_filename(char *filename)
  281. {
  282. int i, c;
  283. if (FLAG_OBJ)
  284. { i = strlen(filename) - 2;
  285. if (i > 0 && strcmp(&filename[i], ".o") == 0)
  286. strcpy(&filename[i], ".obj");
  287. }
  288. if (FLAG_UNIX)
  289. { i = strlen(filename) - 4;
  290. if (i > 0 && strcmp(&filename[i], ".exe") == 0)
  291. filename[i] = 0;
  292. }
  293. if (FLAG_BACKSLASH)
  294. { for (i=0; (c=filename[i])!=0; i++)
  295. if (c == '/') filename[i] = '\\';
  296. }
  297. for (i=0; (c=filename[i])!=0; i++)
  298. putc(c, fmakefile);
  299. }
  300. static char userinput[256];
  301. static char **data;
  302. static int data_available;
  303. static void put_line()
  304. {
  305. int i = 0, c;
  306. char filename[256], *p;
  307. /*
  308. * A typical problem with expanded files is that blank lines accumulate
  309. * beyond reason. Here I will lose any excessive blank blocks.
  310. */
  311. if (line[0] == '\n')
  312. { if (blank_lines++ > 3) return;
  313. }
  314. else blank_lines = 0;
  315. while ((c = line[i++]) != 0)
  316. {
  317. /*
  318. * The line buffer here has a newline character at its end, but no (other)
  319. * trailing whitespace. For the Watcom "make" utility I need to convert
  320. * any trailing "\" into a "&".
  321. */
  322. if (c == '\\' && FLAG_WATCOM && line[i] == '\n')
  323. { putc('&', fmakefile);
  324. putc('\n', fmakefile);
  325. break;
  326. }
  327. if (c == '@')
  328. { c = line[i++];
  329. switch (c)
  330. {
  331. case '@': putc(c, fmakefile);
  332. continue;
  333. case '~': if (FLAG_BLANK) putc(' ', fmakefile);
  334. continue;
  335. case '=': if (FLAG_BLANK1) putc(' ', fmakefile);
  336. continue;
  337. case '"': if (FLAG_ESCAPEQUOTE) putc('\\', fmakefile);
  338. putc(c, fmakefile);
  339. continue;
  340. case '?': c = line[i++];
  341. if (c != '(') i--;
  342. p = userinput;
  343. while ((c = line[i++]) != ')' && c != 0 && c != '\n')
  344. putchar(c);
  345. if (data_available != 0)
  346. { char *q = *data++;
  347. data_available--;
  348. while ((c = *q++) != 0)
  349. *p++ = c, putc(c, fmakefile);
  350. *p = 0;
  351. printf("\n%s\n", userinput);
  352. }
  353. else
  354. { printf("\nPlease enter value as described above,\n");
  355. printf("file-names to use \"/\" as directory separator: ");
  356. while ((c = getchar()) != '\n' && c != EOF)
  357. *p++ = c, putc(c, fmakefile);
  358. *p = 0;
  359. }
  360. continue;
  361. case '!': put_filename(userinput);
  362. continue;
  363. default: p = filename;
  364. i--;
  365. while ((c = line[i++]) != ' ' && c != 0 &&
  366. c != '\n' && c != '\\' && c != ',' &&
  367. !(c == ':' && isspace(line[i])))
  368. *p++ = c;
  369. *p = 0;
  370. i--; /* unread the character that ended the filename */
  371. put_filename(filename);
  372. continue;
  373. }
  374. }
  375. else putc(c, fmakefile);
  376. }
  377. }
  378. static int eval_condition(char *s)
  379. {
  380. char word[LINE_LENGTH];
  381. int i = 0;
  382. while (s[i] != ')' && s[i] != 0)
  383. { int c = s[i];
  384. if (c == '\n') return 0; /* bad syntax - treat as condition false */
  385. word[i++] = c;
  386. }
  387. word[i] = 0;
  388. for (i=0; i<n_defined_words; i++)
  389. if (strcmp(word, defined_words[i]) == 0) break;
  390. return (i < n_defined_words);
  391. }
  392. void get_flags()
  393. {
  394. int i, j;
  395. flags = 0;
  396. for (i=0; i<n_defined_words; i++)
  397. for (j=0; j<N_SPECIAL_WORDS; j++)
  398. if (strcmp(defined_words[i], special_words[j]) == 0)
  399. flags |= (1 << j);
  400. }
  401. static void create_makefile(char *makebase, char *makefile)
  402. {
  403. /*
  404. * skipping is 0 if text is being included. If is 1 if test is being
  405. * skipped following @if(false). If is 2 following an
  406. * @else clause after an @if(true)
  407. * nesting is a count of the number of @if constructions nested within
  408. * skipped text. It is incremented when @if is seen in skipping
  409. * context, and decremented when @endif is encountered.
  410. */
  411. int skipping = 0, nesting = 0, i;
  412. char *p;
  413. line_number = last_live_line = 0;
  414. if (process_menu(0, makebase))
  415. { printf("\nBad keyword or ill-formed base file\n");
  416. fseek(fmakebase, SEEK_SET, 0L);
  417. line_number = 0;
  418. process_menu(1, makebase);
  419. fprintf(fmakefile, "\nError in creating makefile, re-run mmake\n");
  420. return;
  421. }
  422. get_flags();
  423. userinput[0] = 0;
  424. /*
  425. * By now the makebase file will have had all the menu items at its
  426. * head scanned, so now I mainly have to copy its body to create the
  427. * output makefile.
  428. */
  429. while (get_line())
  430. {
  431. if (skipping == 0) last_live_line = line_number;
  432. if (memcmp(line, "@ ", 2) == 0) continue; /* Comment line */
  433. if (memcmp(line, "@\n", 2) == 0) continue; /* Comment line */
  434. else if (memcmp(line, "@set(", 5) == 0)
  435. { if (skipping == 0)
  436. { i = 5;
  437. while (line[i] != ')' && line[i] != '\n' && line[i] != 0) i++;
  438. line[i] = 0;
  439. p = (char *)malloc(i-4);
  440. strcpy(p, &line[5]);
  441. defined_words[n_defined_words++] = p;
  442. get_flags();
  443. }
  444. }
  445. else if (memcmp(line, "@if(", 4) == 0)
  446. { if (skipping) nesting++;
  447. else if (!eval_condition(&line[4])) skipping = 1;
  448. }
  449. else if (memcmp(line, "@ifnot(", 7) == 0)
  450. { if (skipping) nesting++;
  451. else if (eval_condition(&line[7])) skipping = 1;
  452. }
  453. else if (memcmp(line, "@elif(", 6) == 0)
  454. { if (nesting == 0)
  455. { if (skipping==1)
  456. { if (eval_condition(&line[6])) skipping = 0;
  457. }
  458. else skipping = 2;
  459. }
  460. }
  461. else if (memcmp(line, "@elifnot(", 9) == 0)
  462. { if (nesting == 0)
  463. { if (skipping==1)
  464. { if (!eval_condition(&line[6])) skipping = 0;
  465. }
  466. else skipping = 2;
  467. }
  468. }
  469. else if (memcmp(line, "@else", 5) == 0) /* Like @elif(true) */
  470. { if (nesting == 0)
  471. { if (skipping==1) skipping = 0;
  472. else skipping = 2;
  473. }
  474. }
  475. else if (memcmp(line, "@endif", 6) == 0)
  476. { if (nesting > 0) nesting--;
  477. else skipping = 0;
  478. }
  479. else if (memcmp(line, "@error", 6) == 0)
  480. { if (skipping == 0)
  481. { printf("\nError line detected...\n");
  482. printf("%s", line);
  483. exit(EXIT_FAILURE);
  484. }
  485. }
  486. else if (skipping == 0) put_line();
  487. }
  488. if (skipping != 0)
  489. { printf("Still skipping at end of file after line %d\n",
  490. last_live_line);
  491. }
  492. printf("\"%s\" created as makefile for", makefile);
  493. for (i=0; i<n_user_words; i++)
  494. printf(" \"%s\"", defined_words[i]);
  495. printf("\n");
  496. }
  497. #define DATA_SIZE 20
  498. static char *command_line_data[DATA_SIZE];
  499. /*
  500. * This is the only place where I seem to really need an #ifdef in this
  501. * simple code. It is here because under Microsoft C some user may use
  502. * a register-based calling convention by default, but in such cases
  503. * "main" needs to be made __cdecl. I put the test here in-line rather
  504. * than using the "sys.h" header that other code here does because I expect
  505. * people to need to compile makemake.c before they start to look at the
  506. * rest of my utilities, and so I want makemake.c to be as simple and
  507. * stand-alone as possible.
  508. */
  509. int
  510. #ifdef _MSC_VER
  511. __cdecl
  512. #endif
  513. main(int argc, char *argv[])
  514. {
  515. char *makebase = NULL, *makefile = NULL;
  516. int i, usage = 0;
  517. for (i=0; i<MAX_WORDS; i++) in_makebase[i] = 0;
  518. n_defined_words = 0;
  519. data = command_line_data;
  520. data_available = 0;
  521. for (i=1; i<argc; i++)
  522. { char *arg = argv[i];
  523. if (arg == NULL) continue;
  524. if (arg[0] == '-') switch(arg[1])
  525. {
  526. case 'f': case 'F':
  527. if (++i < argc) makebase = argv[i];
  528. continue;
  529. case 'o': case 'O':
  530. if (++i < argc) makefile = argv[i];
  531. continue;
  532. case 'h': case 'H': case '?':
  533. usage = 1;
  534. printf("Usage:\n");
  535. printf(" mmake key* [-f basefile] [-o outfile]\n");
  536. printf("basefile defaults to \"makebase\"\n");
  537. printf("outfile defaults to \"makenew\"\n");
  538. printf("If no key is given a list of options from basefile ");
  539. printf("is listed\n");
  540. continue;
  541. case 'd': case 'D':
  542. { char *q = (char *)malloc(1+strlen(arg+2));
  543. if (q == NULL)
  544. { printf("malloc failure\n");
  545. continue;
  546. }
  547. strcpy(q, arg+2);
  548. if (data_available >= DATA_SIZE)
  549. printf("Too many \"-d\" options. Ignore this one\n");
  550. else data[data_available++] = q;
  551. }
  552. continue;
  553. default:
  554. printf("Unknown option \"%s\" ignored\n", arg);
  555. continue;
  556. }
  557. else defined_words[n_defined_words++] = arg;
  558. }
  559. if (usage) return 0;
  560. if (makebase == NULL) makebase = "makebase";
  561. if (makefile == NULL) makefile = "makenew";
  562. if ((fmakebase = fopen(makebase, "r")) == NULL)
  563. { printf("Unable to read \"%s\"\n", makebase);
  564. return 0;
  565. }
  566. if (n_defined_words == 0) process_menu(1, makebase);
  567. else
  568. { if ((fmakefile = fopen(makefile, "w")) == NULL)
  569. { printf("Unable to write to file \"%s\"\n", makefile);
  570. fclose(fmakebase);
  571. return 0;
  572. }
  573. n_user_words = n_defined_words;
  574. create_makefile(makebase, makefile);
  575. fclose(fmakefile);
  576. }
  577. fclose(fmakebase);
  578. return 0;
  579. }
  580. /* end of makemake.c */