123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- /*
- * "makemake" Copyright (C) 1994-9, Codemist Ltd
- *
- * Used to build system-specific makefiles from a common base file
- *
- *
- * Usage:
- * makemake key* [-f prototype] [-o newmakefile]
- * [-ddata1] [-ddata2] ...
- *
- * The default prototype file is called "makebase" and the default
- * file created is called just "makenew" (note that both those are
- * spelt in lower case letters). The "keys" passed as arguments
- * should match the options declared at the head of the makebase file.
- * Data given after "-d" is used where otherwise interactive input would be
- * requested.
- */
- /* Signature: 55907f19 10-Feb-1999 */
- /*
- * The base file is a prototype "makefile", but with extra decorations so
- * that it can contain information relating to several systems. Lines
- * starting "@ " are comment and are ignored. Apart from such comments
- * the file should start with a section:
- * @menu
- * @item(key1) description
- * @item(key2) description
- * @endmenu
- * (the "@" signs should be in column 1, ie no whitespace before them)
- * This declares the valid keys. A key can be written as
- * @item(key>extra1>extra2) description
- * and then if the key is specified the given extra symbols will be defined
- * while processing the makebase file. An example use for this might be
- * @item(dos386>msdos) Intel 386/486 running DOS
- * @item(dos286>msdos>sixteenbit) Intel 286 running DOS
- * where subsequent items in the file may test msdos or sixteenbit.
- *
- * After the menu the makebase file is processed subject to directives
- * @if(key)
- * @ifnot(key)
- * @else
- * @elif(key)
- * @elifnot(key)
- * @endif
- * (again no whitespace is permitted, and the "@" must be in column 1)
- * which provide for conditional inclusion of text. A condition is TRUE if
- * it was either the key specified to mmake, or was set up by a ">"
- * in a menu item that matched the key.
- * @error (while not skipping)
- * terminates processing.
- *
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #ifndef SEEK_SET
- #define SEEK_SET 0
- #endif
- #ifndef EXIT_FAILURE
- #define EXIT_FAILURE 1
- #endif
- static int flags;
- static char *special_words[] =
- {
- "backslash",
- #define FLAG_BACKSLASH (flags & 0x001)
- "watcom",
- #define FLAG_WATCOM (flags & 0x002)
- "obj",
- #define FLAG_OBJ (flags & 0x004)
- "acorn",
- #define FLAG_ACORN (flags & 0x008)
- "escapequote",
- #define FLAG_ESCAPEQUOTE (flags & 0x010)
- "blank",
- #define FLAG_BLANK (flags & 0x020)
- "unix",
- #define FLAG_UNIX (flags & 0x040)
- "blank1",
- #define FLAG_BLANK1 (flags & 0x080)
- 0
- };
- #define N_SPECIAL_WORDS 7
- #define LINE_LENGTH 1024
- static char line[LINE_LENGTH];
- static int EOF_seen = 0;
- int line_number, last_live_line;
- #define MAX_WORDS 100
- static char *defined_words[MAX_WORDS];
- static int in_makebase[MAX_WORDS];
- static int n_user_words = 0, n_defined_words = 0;
- static FILE *fmakebase, *fmakefile;
- static int get_line()
- /* Read one line from makebase into line buffer, return 1 if OK */
- {
- int i = 0, c;
- if (EOF_seen) return 0;
- line_number++;
- while ((c = getc(fmakebase)) != '\n' && c != EOF)
- if (i < LINE_LENGTH-2) line[i++] = c;
- /* I lose trailing blanks from all input lines */
- while (i > 0 &&
- (line[i-1] == ' ' || line[i-1] == '\t')) i--;
- /*
- * If the file ended with a line that was not terminated by '\n' I
- * insert a '\n' here.
- */
- if (c == EOF)
- { EOF_seen = 1;
- if (i != 0)
- { line[i++] = '\n';
- line[i] = 0;
- return 1;
- }
- else return 0;
- }
- line[i++] = '\n';
- line[i] = 0;
- return 1;
- }
- static int process_item(int show)
- {
- char menuitem[LINE_LENGTH], *extra_defines[16];
- int i, p, n_extra_defines = 0;
- for (i=0, p=6; line[p] != ')' && line[p] != '\n'; )
- { if (line[p] == '>')
- { menuitem[i++] = 0;
- extra_defines[n_extra_defines++] = &menuitem[i];
- p++;
- }
- else menuitem[i++] = line[p++];
- }
- menuitem[i] = 0;
- if (line[p] != ')') return 0;
- else p++;
- while (line[p] == ' ' || line[p] == '\t') p++;
- if (show)
- { int len = (256 - strlen(menuitem)) % 8;
- printf(" %s:%*s %s", menuitem, len, "", &line[p]);
- return 1;
- }
- for (i=0; i<n_defined_words; i++)
- if (strcmp(menuitem, defined_words[i]) == 0)
- { in_makebase[i] = 1;
- break;
- }
- if (i >= n_defined_words) return 0;
- for (i=0; i<n_extra_defines; i++)
- { char *s = (char *)malloc(1+strlen(extra_defines[i]));
- if (s == NULL) return 0;
- strcpy(s, extra_defines[i]);
- defined_words[n_defined_words++] = s;
- }
- return 1;
- }
- static int process_menu(int show, char *makebase)
- {
- int success = 0, i;
- if (show) printf("The valid keys from \"%s\" are:\n", makebase);
- while (get_line())
- if (memcmp(line, "@menu", 5) == 0) break;
- while (get_line())
- { if (memcmp(line, "@endmenu", 8) == 0) break;
- if (memcmp(line, "@item(", 6) == 0 &&
- process_item(show)) success = 1;
- }
- if (!show && success)
- for (i=0; i<n_user_words; i++)
- if (!in_makebase[i])
- { printf("\"%s\" not present in base file\n", defined_words[i]);
- success = 0;
- }
- return !success;
- }
- static int blank_lines = 0;
- #ifdef SUPPORT_ACORN
- /*
- * Support for Acorn's RiscOS is not automatically included - if you need to
- * re-instate that you will have to review the following code and make it
- * work again.
- */
- static char *implicit_dirs[] =
- /*
- * If I find a filename in the makebase which has one of these names as a
- * suffix (eg xyz.abcdef.c) then for Acorn purposes I will convert it to
- * use a directory instead of a suffix (xyz.c.abcdef). I will make the
- * check for things in this list case insensitive. Note that GREAT care is
- * needed with files whose real name looks too much like one of these
- * special strings - eg "doc.c" or "s.txt" could cause muddle. I view it
- * as the business of people creating makecore files to take care of such
- * things (typically be avoiding such file names).
- */
- {
- "!", /* Used by ACN as a place for scratch files */
- "c", /* C source code */
- "f", /* Fortran source code */
- "h", /* C header files */
- "l", /* Listings generated by the C compiler */
- "o", /* Object code */
- "p", /* Pascal? */
- "s", /* Assembly code */
- "lsp", /* Used with CSL */
- "sl", /* Used with CSL/REDUCE */
- "red", /* REDUCE sources */
- "fsl", /* CSL fast-load files (well, not really!) */
- "log", /* I guess this is the hardest case */
- "tst", /* REDUCE test files */
- "doc", /* Another hard case */
- "cpp", /* C++ files */
- "hpp", /* C++ header files */
- "txt", /* to help me with some MSDOS transfers */
- "bak", /* Ditto. */
- NULL
- };
- static int acorn_filename(int i)
- {
- char raw_filename[LINE_LENGTH], extension[20], lc_extension[20];
- int j = 0, c;
- while ((c = line[i++]) != 0 && c != ' ' && c != '\n' &&
- c != '\t')
- { if (c == '@')
- { c = line[i++];
- switch (c)
- {
- case '^':
- case '@': raw_filename[j++] = c; break;
- case '/': raw_filename[j++] = '.'; break;
- case '~': if (FLAG_BLANK) raw_filename[j++] = ' ';
- break;
- case '=': if (FLAG_BLANK1) raw_filename[j++] = ' ';
- break;
- case '"': if (escapequote) raw_filename[j++] = '\\';
- raw_filename[j++] = c;
- break;
- case '!': break;
- case 'o': raw_filename[j++] = 'o';
- if (FLAG_OBJ)
- { raw_filename[j++] = 'b';
- raw_filename[j++] = 'j';
- }
- break;
- default: raw_filename[j++] = '@';
- raw_filename[j++] = c;
- break;
- }
- }
- else raw_filename[j++] = c;
- }
- if (raw_filename[j-1] == ':') i--, j--;
- raw_filename[j] = 0;
- while (j >= 0 && raw_filename[j] != '.') j--;
- extension[0] = 0;
- if (j > 0 & raw_filename[j] == '.' && strlen(&raw_filename[j]) < 16)
- { strcpy(extension, &raw_filename[j+1]);
- raw_filename[j] = 0;
- }
- for (j=0; extension[j]!=0; j++)
- { c = extension[j];
- if (isupper(c)) c = tolower(c);
- lc_extension[j] = c;
- }
- lc_extension[j] = 0;
- for (j=0; implicit_dirs[j]!=NULL; j++)
- if (strcmp(implicit_dirs[j], extension) == 0) break;
- if (implicit_dirs[j] != NULL) /* Match found - flip around */
- { j = strlen(raw_filename)-1;
- while (j >= 0 && raw_filename[j] != '.') j--;
- if (j > 0)
- { raw_filename[j] = 0;
- fprintf(fmakefile, "%s.%s.%s",
- raw_filename, extension, &raw_filename[j+1]);
- }
- else fprintf(fmakefile, "%s.%s", extension, raw_filename);
- }
- else if (extension[0] == 0) fprintf(fmakefile, "%s", raw_filename);
- else fprintf(fmakefile, "%s.%s", raw_filename, extension);
- return i-1;
- }
- #endif /* SUPPORT_ACORN */
- static void put_filename(char *filename)
- {
- int i, c;
- if (FLAG_OBJ)
- { i = strlen(filename) - 2;
- if (i > 0 && strcmp(&filename[i], ".o") == 0)
- strcpy(&filename[i], ".obj");
- }
- if (FLAG_UNIX)
- { i = strlen(filename) - 4;
- if (i > 0 && strcmp(&filename[i], ".exe") == 0)
- filename[i] = 0;
- }
- if (FLAG_BACKSLASH)
- { for (i=0; (c=filename[i])!=0; i++)
- if (c == '/') filename[i] = '\\';
- }
- for (i=0; (c=filename[i])!=0; i++)
- putc(c, fmakefile);
- }
- static char userinput[256];
- static char **data;
- static int data_available;
- static void put_line()
- {
- int i = 0, c;
- char filename[256], *p;
- /*
- * A typical problem with expanded files is that blank lines accumulate
- * beyond reason. Here I will lose any excessive blank blocks.
- */
- if (line[0] == '\n')
- { if (blank_lines++ > 3) return;
- }
- else blank_lines = 0;
- while ((c = line[i++]) != 0)
- {
- /*
- * The line buffer here has a newline character at its end, but no (other)
- * trailing whitespace. For the Watcom "make" utility I need to convert
- * any trailing "\" into a "&".
- */
- if (c == '\\' && FLAG_WATCOM && line[i] == '\n')
- { putc('&', fmakefile);
- putc('\n', fmakefile);
- break;
- }
- if (c == '@')
- { c = line[i++];
- switch (c)
- {
- case '@': putc(c, fmakefile);
- continue;
- case '~': if (FLAG_BLANK) putc(' ', fmakefile);
- continue;
- case '=': if (FLAG_BLANK1) putc(' ', fmakefile);
- continue;
- case '"': if (FLAG_ESCAPEQUOTE) putc('\\', fmakefile);
- putc(c, fmakefile);
- continue;
- case '?': c = line[i++];
- if (c != '(') i--;
- p = userinput;
- while ((c = line[i++]) != ')' && c != 0 && c != '\n')
- putchar(c);
- if (data_available != 0)
- { char *q = *data++;
- data_available--;
- while ((c = *q++) != 0)
- *p++ = c, putc(c, fmakefile);
- *p = 0;
- printf("\n%s\n", userinput);
- }
- else
- { printf("\nPlease enter value as described above,\n");
- printf("file-names to use \"/\" as directory separator: ");
- while ((c = getchar()) != '\n' && c != EOF)
- *p++ = c, putc(c, fmakefile);
- *p = 0;
- }
- continue;
- case '!': put_filename(userinput);
- continue;
- default: p = filename;
- i--;
- while ((c = line[i++]) != ' ' && c != 0 &&
- c != '\n' && c != '\\' && c != ',' &&
- !(c == ':' && isspace(line[i])))
- *p++ = c;
- *p = 0;
- i--; /* unread the character that ended the filename */
- put_filename(filename);
- continue;
- }
- }
- else putc(c, fmakefile);
- }
- }
- static int eval_condition(char *s)
- {
- char word[LINE_LENGTH];
- int i = 0;
- while (s[i] != ')' && s[i] != 0)
- { int c = s[i];
- if (c == '\n') return 0; /* bad syntax - treat as condition false */
- word[i++] = c;
- }
- word[i] = 0;
- for (i=0; i<n_defined_words; i++)
- if (strcmp(word, defined_words[i]) == 0) break;
- return (i < n_defined_words);
- }
- void get_flags()
- {
- int i, j;
- flags = 0;
- for (i=0; i<n_defined_words; i++)
- for (j=0; j<N_SPECIAL_WORDS; j++)
- if (strcmp(defined_words[i], special_words[j]) == 0)
- flags |= (1 << j);
- }
- static void create_makefile(char *makebase, char *makefile)
- {
- /*
- * skipping is 0 if text is being included. If is 1 if test is being
- * skipped following @if(false). If is 2 following an
- * @else clause after an @if(true)
- * nesting is a count of the number of @if constructions nested within
- * skipped text. It is incremented when @if is seen in skipping
- * context, and decremented when @endif is encountered.
- */
- int skipping = 0, nesting = 0, i;
- char *p;
- line_number = last_live_line = 0;
- if (process_menu(0, makebase))
- { printf("\nBad keyword or ill-formed base file\n");
- fseek(fmakebase, SEEK_SET, 0L);
- line_number = 0;
- process_menu(1, makebase);
- fprintf(fmakefile, "\nError in creating makefile, re-run mmake\n");
- return;
- }
- get_flags();
- userinput[0] = 0;
- /*
- * By now the makebase file will have had all the menu items at its
- * head scanned, so now I mainly have to copy its body to create the
- * output makefile.
- */
- while (get_line())
- {
- if (skipping == 0) last_live_line = line_number;
- if (memcmp(line, "@ ", 2) == 0) continue; /* Comment line */
- if (memcmp(line, "@\n", 2) == 0) continue; /* Comment line */
- else if (memcmp(line, "@set(", 5) == 0)
- { if (skipping == 0)
- { i = 5;
- while (line[i] != ')' && line[i] != '\n' && line[i] != 0) i++;
- line[i] = 0;
- p = (char *)malloc(i-4);
- strcpy(p, &line[5]);
- defined_words[n_defined_words++] = p;
- get_flags();
- }
- }
- else if (memcmp(line, "@if(", 4) == 0)
- { if (skipping) nesting++;
- else if (!eval_condition(&line[4])) skipping = 1;
- }
- else if (memcmp(line, "@ifnot(", 7) == 0)
- { if (skipping) nesting++;
- else if (eval_condition(&line[7])) skipping = 1;
- }
- else if (memcmp(line, "@elif(", 6) == 0)
- { if (nesting == 0)
- { if (skipping==1)
- { if (eval_condition(&line[6])) skipping = 0;
- }
- else skipping = 2;
- }
- }
- else if (memcmp(line, "@elifnot(", 9) == 0)
- { if (nesting == 0)
- { if (skipping==1)
- { if (!eval_condition(&line[6])) skipping = 0;
- }
- else skipping = 2;
- }
- }
- else if (memcmp(line, "@else", 5) == 0) /* Like @elif(true) */
- { if (nesting == 0)
- { if (skipping==1) skipping = 0;
- else skipping = 2;
- }
- }
- else if (memcmp(line, "@endif", 6) == 0)
- { if (nesting > 0) nesting--;
- else skipping = 0;
- }
- else if (memcmp(line, "@error", 6) == 0)
- { if (skipping == 0)
- { printf("\nError line detected...\n");
- printf("%s", line);
- exit(EXIT_FAILURE);
- }
- }
- else if (skipping == 0) put_line();
- }
- if (skipping != 0)
- { printf("Still skipping at end of file after line %d\n",
- last_live_line);
- }
- printf("\"%s\" created as makefile for", makefile);
- for (i=0; i<n_user_words; i++)
- printf(" \"%s\"", defined_words[i]);
- printf("\n");
- }
- #define DATA_SIZE 20
- static char *command_line_data[DATA_SIZE];
- /*
- * This is the only place where I seem to really need an #ifdef in this
- * simple code. It is here because under Microsoft C some user may use
- * a register-based calling convention by default, but in such cases
- * "main" needs to be made __cdecl. I put the test here in-line rather
- * than using the "sys.h" header that other code here does because I expect
- * people to need to compile makemake.c before they start to look at the
- * rest of my utilities, and so I want makemake.c to be as simple and
- * stand-alone as possible.
- */
- int
- #ifdef _MSC_VER
- __cdecl
- #endif
- main(int argc, char *argv[])
- {
- char *makebase = NULL, *makefile = NULL;
- int i, usage = 0;
- for (i=0; i<MAX_WORDS; i++) in_makebase[i] = 0;
- n_defined_words = 0;
- data = command_line_data;
- data_available = 0;
- for (i=1; i<argc; i++)
- { char *arg = argv[i];
- if (arg == NULL) continue;
- if (arg[0] == '-') switch(arg[1])
- {
- case 'f': case 'F':
- if (++i < argc) makebase = argv[i];
- continue;
- case 'o': case 'O':
- if (++i < argc) makefile = argv[i];
- continue;
- case 'h': case 'H': case '?':
- usage = 1;
- printf("Usage:\n");
- printf(" mmake key* [-f basefile] [-o outfile]\n");
- printf("basefile defaults to \"makebase\"\n");
- printf("outfile defaults to \"makenew\"\n");
- printf("If no key is given a list of options from basefile ");
- printf("is listed\n");
- continue;
- case 'd': case 'D':
- { char *q = (char *)malloc(1+strlen(arg+2));
- if (q == NULL)
- { printf("malloc failure\n");
- continue;
- }
- strcpy(q, arg+2);
- if (data_available >= DATA_SIZE)
- printf("Too many \"-d\" options. Ignore this one\n");
- else data[data_available++] = q;
- }
- continue;
- default:
- printf("Unknown option \"%s\" ignored\n", arg);
- continue;
- }
- else defined_words[n_defined_words++] = arg;
- }
- if (usage) return 0;
- if (makebase == NULL) makebase = "makebase";
- if (makefile == NULL) makefile = "makenew";
- if ((fmakebase = fopen(makebase, "r")) == NULL)
- { printf("Unable to read \"%s\"\n", makebase);
- return 0;
- }
- if (n_defined_words == 0) process_menu(1, makebase);
- else
- { if ((fmakefile = fopen(makefile, "w")) == NULL)
- { printf("Unable to write to file \"%s\"\n", makefile);
- fclose(fmakebase);
- return 0;
- }
- n_user_words = n_defined_words;
- create_makefile(makebase, makefile);
- fclose(fmakefile);
- }
- fclose(fmakebase);
- return 0;
- }
- /* end of makemake.c */
|