#include #include #include "make.h" /* * MAKE - Maintain separate source files * * SYNOPSIS * MK [-f file] [-a] [-n] [-d] [-i] [-k] [name] ... * f: use 'file' instead of default makefile * a: assume all modules are obsolete (recompile everything) * n: don't recompile, just list steps to recompile * d: debugging (print tree, file info) * i: ignore return statuses from execution * k: if errors occur, propagate error status up tree; continue. * name: module name to recompile * * AUTHOR * Landon M. Dyer, Atari Inc. * * INCREDIBLY HACKED OVER BY * Eric C. Brown University of Utah. * Fred Fish, UniSoft Systems Inc (for Commodore AMIGA) * * HACKS * Added object library support (dummy names that inherit dates) * Added source library support (real parents) * Added direct execution capability. * Removed script file. * Added support for my macro based debugging package (fnf) * Ran through "indent" to change formatting (fnf) */ #define INIT "~INIT" /* initialization macro */ #define DEINIT "~DEINIT" /* de-init macro */ #define BEFORE "~BEFORE" /* the per-root 'startup' method */ #define AFTER "~AFTER" /* the per-root 'wrapup' method */ char *mfiles[] = { /* default makefiles */ "makefile", "Makefile", #ifdef VAXVMS "[-]makefile", "sys$login:makefile", #endif #ifdef MSDOS "..\makefile", #endif "" }; MACRO *mroot = (MACRO *) NULL; /* root of macro-list */ FILENODE *froot = (FILENODE *) NULL; /* root of filenode-list */ FILENODE *firstf = (FILENODE *) NULL; /* the very first filenode */ char *modnames[MAXMODS]; /* module-names mentioned in commandline */ int execstat = 0; /* nonzero if started executing */ int modcount = 0; /* #of module-names */ int debug = 0; /* nonzero: turn on debugging */ int obsolete = 0; /* nonzero: every file should be recompiled */ int noscript = 0; /* nonzero: print methods on stdout */ int ignore_errors = 0; /* nonzero: ignore error return codes */ int prop_errors = 0; /* nonzero: propagate error status up tree */ DATE bigbang; /* a date, the very earliest possible */ DATE endoftime; /* a date, the very last possible */ /* * The following are used to save macro definitions given on the * command line. In the unix make, it is very common to override * a definition in the makefile via a definition on the command line. * However, this make expands macros in each line as they are read, * which conflicts with the need to wait until the entire file is read * before adding command line macros to the table (to override those * from the file). Thus the code needs to be modified to delay macro * expansions until the last minute, before shipping the line off to * be executed. The alternative is to implement a macro locking or * precedence scheme. Either solution requires more work than I can * do at the moment. Fred Fish 28-Nov-85 */ char *cmdmacros[MAXCMDMACS]; /* Macro defs given on command line */ int cmdmcount = 0; /* Number of macro defs on command line */ static void fparse (); static void yankdependents (); static void addmanydepend (); static void determ (); static void recomp (); main (argc, argv) int argc; char *argv[]; { register int arg; register int i; register char *mfile = NULL; extern DATE adate (); extern void initrootdates (); extern void prtree (); DBUG_ENTER ("main"); ENABLE_ABORT; initrootdates (); for (arg = 1; arg < argc; ++arg) { if (*argv[arg] == '-') { switch (tolower (argv[arg][1])) { case '#': DBUG_PUSH (&argv[arg][2]); break; case 'f': if (++arg >= argc) { fputs ("-f needs filename argument.\n", stderr); DBUG_RETURN (1); } mfile = argv[arg]; break; case 'a': obsolete = 1; break; case 'n': noscript = 1; break; case 'd': debug = 1; break; case 'i': ignore_errors = 1; break; case 'k': prop_errors = 1; break; default: fputs ("Unknown switch: ", stderr); fputc (argv[arg][1], stderr); fputc ('\n', stderr); break; } } else { if (strchr (argv[arg], '=') != NULL) { if (cmdmcount < MAXCMDMACS) { cmdmacros[cmdmcount++] = argv[arg]; } else { fputs ("Too many command line macros.\n", stderr); DBUG_RETURN (1); } } else { if (modcount < MAXMODS) { modnames[modcount++] = argv[arg]; } else { fputs ("Too many module names.\n", stderr); DBUG_RETURN (1); } } } } if (mfile != NULL) { if (fmake (mfile) == -1) { fputs ("Cannot open makefile '", stderr); fputs (mfile, stderr); fputs ("'.\n", stderr); } } else { for (i = 0; *mfiles[i]; ++i) { if (fmake (mfiles[i]) != -1) { break; } } if (!*mfiles[i]) { fputs ("Cannot open makefile.\n", stderr); } } if (debug) { prtree (); } DBUG_RETURN (0); } /* * Construct dependency tree from the makefile 'fn'. * Figure out what has to be recompiled, and write a script file to do that. */ fmake (fn) char *fn; { FILE * fp; DBUG_ENTER ("fmake"); if ((fp = fopen (fn, "r")) == (FILE *) NULL) { DBUG_RETURN (-1); } fparse (fp); determ (); fclose (fp); DBUG_RETURN (0); } /* * Parse the input file, defining macros and building the dependency tree. */ static void fparse (fp) FILE *fp; { auto char ibuf[STRSIZ]; auto char ebuf[STRSIZ]; auto char *strp; register char *tok1; register char *tok2; register char *s; extern char *fgets (); register FILENODE *lastf = (FILENODE *)NULL; extern FILENODE *addfile (); extern void defmac (); extern void AddToLibrary (); extern void escape (); extern void addmeth (); DBUG_ENTER ("fparse"); for (;;) { if (fgets (ibuf, STRSIZ, fp) == NULL) { break; } DBUG_3 ("inline", "got line '%s'", ibuf); mexpand (ibuf, ebuf, STRSIZ, MACCHAR); escape (ebuf, COMCHAR); s = ebuf + strlen (ebuf) - 1; /* clobber last newline in string */ if (s >= ebuf && *s == '\n') { *s = '\0'; } DBUG_3 ("inline2", "after macro and excape processing is '%s'", ebuf); if (ebuf[0] == '\t' || ebuf[0] == ' ') { DBUG_2 ("meth", "looks like a method line to me"); addmeth (lastf, ebuf); continue; } strp = ebuf; if ((tok1 = token (&strp)) == NULL) { continue; } if ((tok2 = token (&strp)) != NULL) { if (STRSAME (tok2, DEFMAC)) { DBUG_2 ("mac", "looks like a macro definition to me"); if (*strp) { defmac (tok1, strp); } #if VAXVMS || MSDOS /* Preserve old behavior. Unix and amiga behavior is */ /* to never undefine any macros, in the sense used here. */ /* This also allows macros with null expansions, which */ /* are very useful. Fred Fish */ if (!*strp) { if (undefmac (tok1) < 0) { fputs ("Can't undefine macro '", stderr); fputs (tok1, stderr); fputs ("'.\n", stderr); } } #endif continue; } else if (STRSAME (tok2, DEPEND)) { DBUG_2 ("mac", "looks like a dependency line to me"); addmeth (lastf, gmacro (AFTER)); /* terminate last method */ lastf = filenode (tok1); /* init lastf */ if (firstf == (FILENODE *) NULL) { firstf = lastf; } lastf -> fmake = NULL; addmeth (lastf, gmacro (BEFORE)); lastf -> fflag |= ROOTP; addmanydepend (strp, lastf); continue; #ifndef FUNNYLIBS } else if (STRSAME (tok2, ISLIB)) { addmeth (lastf, gmacro (AFTER)); lastf = filenode (tok1); if (firstf == (FILENODE *) NULL) { firstf = lastf; } lastf -> fmake = NULL; addmeth (lastf, gmacro (BEFORE)); lastf -> fflag |= LIBRARY; lastf -> fflag |= ROOTP; AddToLibrary (lastf); /* no archives here */ /* archives and libraries are mutually exclusive */ while ((tok1 = token (&strp)) != NULL) { (void) addfile (lastf, tok1); } continue; #endif } else { DBUG_2 ("uh", "what kinda line is this?"); addmanydepend (strp, lastf); } } } addmeth (lastf, gmacro (AFTER)); DBUG_VOID_RETURN; } /* * scan tokens from strbuf and search for libraries and archives. * libraries look like foo [ bar baz mumble ] * archives look like foo ( bar baz mumble ) * in either case, bar, baz, and mumble have parents of foo. * foo is added to the parentlist, if not already on the list. * bar, baz, and mumble are added to the dependency list of depend. * the command *cannot* be split across newlines without causing errors. * if you don't like that, well, life's a bitch and then you die. */ static void addmanydepend (strbuf, depend) char *strbuf; FILENODE *depend; { register char *tok1; register char *tok2; register FILENODE *parent; register FILENODE *child; extern FILENODE *addfile (); extern FILENODE *addparent (); extern void exit (); DBUG_ENTER ("addmanydepend"); DBUG_4 ("dep", "add dependencies '%s' to '%s'", strbuf, depend -> fname); tok1 = token (&strbuf); if (tok1 == NULL) { DBUG_VOID_RETURN; } tok2 = token (&strbuf); while (tok2 != NULL) { #ifdef FUNNYLIBS if (STRSAME (tok2, BGNLIB)) { parent = addparent (tok1); /* add tok1 to parent list */ for (tok1 = token (&strbuf); /* skip over token in tok2 */ tok1 != NULL && strcmp (tok1, ENDLIB); /* go eol or end */ tok1 = token (&strbuf)) { /* get next token */ if (tok1 == NULL) { fputs ("MAKE: Error in library defn.\n", stderr); exit (2); } child = addfile (depend, tok1); child -> fflag = LIBRARY; child -> parent = parent; } /* for */ tok1 = token (&strbuf); tok2 = token (&strbuf); continue; /* the while */ } /* if islib */ #endif if (STRSAME (tok2, BGNARC)) { parent = addparent (tok1); /* add tok1 to parent list */ for (tok1 = token (&strbuf); /* skip over token in tok2 */ tok1 != NULL && strcmp (tok1, ENDARC); /* go eol or end */ tok1 = token (&strbuf)) { /* get next token */ if (tok1 == NULL) { fputs ("MAKE: Error in archive defn.\n", stderr); exit (2); } child = addfile (depend, tok1); child -> fflag = ARCHIVE; child -> parent = parent; } /* for */ tok1 = token (&strbuf);/* get current token */ tok2 = token (&strbuf);/* get lookahead token */ continue; /* the while */ } /* if isarc */ else { /* nothing special -- */ (void) addfile (depend, tok1);/* add dependency */ tok1 = tok2; /* shift token */ tok2 = token (&strbuf); } } /* while */ if (tok2 == NULL && tok1 != NULL) { /* last token = not special */ (void) addfile (depend, tok1); } DBUG_VOID_RETURN; } /* * Determine sequence of recompiles from the creation dates. * If have anything to recompile, then create a script file full of commands. */ static void determ () { register FILENODE *f; register int i; register char *m; extern void cleanuparchives (); DBUG_ENTER ("determ"); if (firstf == (FILENODE *) NULL) { /* empty tree */ puts ("No changes."); DBUG_VOID_RETURN; } if (modcount == 0) { examine (firstf, endoftime); } else { for (i = 0; i < modcount; ++i) { if ((f = gfile (modnames[i])) == (FILENODE *) NULL) { fputs ("Don't know how to make ", stderr); fputs (modnames[i], stderr); fputs (".\n", stderr); continue; } if ((f -> fflag & ROOTP) == 0) { fputc ('\'', stderr); fputs (f -> fname, stderr); fputs ("' is not a root!\n", stderr); continue; } examine (f, endoftime); } } if (execstat) { if ((m = gmacro (DEINIT)) != NULL) { execute (m, noscript); } cleanuparchives (); } else { puts ("No changes."); } DBUG_VOID_RETURN; } /* * Examine filenode 'fnd' and see if it has to be recompiled. * 'date' is the last-touched date of the node's father * (or 'endoftime' if its a root file.) * Root files with NO dependencies are assumed not to be up to date. */ examine (fnd, date) FILENODE *fnd; DATE date; { register int rebuildp = 0; register int rval; register int errcode = 0; register NODE *n; extern void getdate (); extern char *printdate (); DBUG_ENTER ("examine"); DBUG_3 ("ex", "parent node date '%s'", printdate (date)); DBUG_3 ("ex", "examine node '%s'", fnd -> fname); getdate (fnd); DBUG_3 ("ex", "modification date '%s'", printdate (fnd -> fdate)); DBUG_3 ("ex", "parent node date '%s'", printdate (date)); if (fnd -> fnode == (NODE *) NULL && fnd -> fflag & ROOTP) { DBUG_2 ("root", "node, is rootnode with no dependents, rebuild"); rebuildp = 1; } else { /* see if dependents need to be recompiled */ for (n = fnd -> fnode; n != (NODE *) NULL; n = n -> nnext) { if ((rval = examine (n -> nfile, fnd -> fdate)) != 0) { if (rval == ERROR) { errcode = ERROR;/* if error occurred, propagate up */ fnd -> fflag |= ERROR; fputs ("Couldn't remake ", stderr); fputs (fnd -> fname, stderr); fputs (" because of errors.\n", stderr); } rebuildp = 1; } } } DBUG_3 ("ex", "parent node date '%s'", printdate (date)); DBUG_3 ("rebuildp", "rebuild flag is %d", rebuildp); /* if ancestor recompiled or root, recompile, */ /* but not if error in ancestor */ if (rebuildp && (fnd -> fflag & ERROR) == 0) { DBUG_3 ("rebuild", "'%s' needs remaking", fnd -> fname); recomp (fnd); if (fnd -> fflag & ERROR) { DBUG_3 ("err", "got an error remaking %s", fnd -> fname); DBUG_RETURN (ERROR); } } DBUG_3 ("ex", "current node date now '%s'", printdate (fnd -> fdate)); DBUG_3 ("ex", "parent node date '%s'", printdate (date)); if (obsolete || laterdt (fnd -> fdate, date) >= 0) { DBUG_2 ("date", "looks like parent needs remaking now"); rebuildp = 1; } if (errcode) { DBUG_RETURN (errcode); } else { DBUG_RETURN (rebuildp); } } /* * Make sure a filenode gets recompiled. */ static void recomp (f) FILENODE *f; { register char *m; DBUG_ENTER ("recomp"); if (!execstat) { execstat = 1; if ((m = gmacro (INIT)) != NULL) { execute (m, noscript); } } if (f -> fflag & REBUILT) { DBUG_VOID_RETURN; } if (!noscript) { /* don't extract if printing steps */ yankdependents (f); } if (f -> fmake != NULL) { if (execute (f -> fmake, noscript) != 0) { if (!ignore_errors && !prop_errors) { exit (2); } else if (prop_errors) { f -> fflag |= ERROR; } } } f -> fflag |= REBUILT; DBUG_VOID_RETURN; } static void yankdependents (fnd) FILENODE *fnd; { register NODE *n; extern int extract (); DBUG_ENTER ("yankdependents"); for (n = fnd -> fnode; n != (NODE *) NULL; n = n -> nnext) { #ifdef YANKDESCENDANTS yankdependents (n -> nfile); #endif DBUG_3 ("dep", "yanking %s", n -> nfile -> fname); DBUG_3 ("dep", "flags %d", n -> nfile -> fflag); if ((n -> nfile -> fflag & ARCHIVE) && ((n -> nfile -> fflag & EXTRACT) == 0)) { /* if archived and not extracted */ fputs ("Extracting ", stdout); puts (n -> nfile -> fname); if (!noscript) { #ifdef LAR if (extract (n -> nfile) == FAILURE) { fputs ("Extract failed -- I think I'll die now.\n", stderr); exit (1); } #else fputs ("No support for archives, bye!\n", stderr); exit (1); #endif } n -> nfile -> fflag |= EXTRACT; } } DBUG_VOID_RETURN; } /* * Complain about being out of memory, and then die. */ allerr () { fputs ("Can't alloc -- no space left (I give up!)\n", stderr); exit (1); }