/* * "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 */