home *** CD-ROM | disk | FTP | other *** search
- /*
- * make.c An imitation of the Unix MAKE facility
- *
- * 88-10-01 v1.0 created by greg yachuk, placed in the public domain
- * 88-10-06 v1.1 changed prerequisite list handling
- * 88-11-11 v1.2 fixed some bugs and added environment variables
- * 89-07-12 v1.3 stop appending shell commands, and flush output
- * 89-08-01 v1.4 AB lots of new options and code
- * 89-10-30 v1.5 -f -S -q options, took some changes from v1.4
- * 90-04-18 v1.6 -b -- -W options, emulate <<, non-BSD cleanup
- */
-
- #include <stdio.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <time.h>
- #ifdef MSDOS
- #include <stdlib.h>
- #endif
-
- #include "make.h"
- #include "tstring.h"
- #include "decl.h"
-
-
- targptr target_list = NULL; /* list of target nodes */
- fileptr file_list = NULL; /* list of file nodes */
- symptr symbol_list = NULL; /* list of symbol nodes */
- shellptr shell_list = NULL; /* list of shell nodes */
-
- char **shell_cmds = NULL; /* commands which force a SHELL */
-
- int make_level = 0; /* for counting new_make()'s */
-
- targptr first_targ = NULL; /* first target, in case nothing explicit */
- targptr suffix_targ = NULL; /* .SUFFIXES target pointer */
-
- char **tlist = NULL; /* command line targets */
- char **flist = NULL; /* command line make files */
- char **mlist = NULL; /* command line macros */
-
- int tmax = 0; /* max size of tlist */
- int fmax = 0; /* max size of flist */
- int mmax = 0; /* max size of mlist */
-
- optnode opts; /* all the options */
- int readdef = 1; /* -r option */
- int dispcount = 0; /* used for -D option */
-
- long now; /* time at startup */
- char *makeflags; /* value to update the MAKEFLAGS macro with */
-
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- int i;
- targptr targp;
- int mk;
- symptr symp;
- char *envp;
- char **envv;
-
- /* initialize the various global lists */
-
- opts.depend = 0;
- dispcount = 0;
-
- target_list = NULL;
- file_list = NULL;
- shell_list = NULL;
- /* don't set symbol_list to NULL, or recursive makes won't work */
-
- /* allocate space for command line targets, files and macros */
-
- tlist = grow_list(NULL, &tmax);
- flist = grow_list(NULL, &fmax);
- mlist = grow_list(NULL, &mmax);
-
- /* process MAKEFLAGS environment variable, first */
-
- symp = get_symbol("MAKEFLAGS", 0);
- if (symp->svalue != NULL)
- {
- /* chop up the MAKEFLAGS and feed them to to make_args() */
-
- envp = tstrcpy(symp->svalue);
- envv = tokenize(envp);
- for (i = 0; envv[i] != NULL; i++);
- make_args(i, envv);
-
- /* free the vector of pointers, and the string itself, */
- /* since you cannot have macros, targets or makefiles */
- /* in the MAKEFLAGS macro. */
-
- tfree(envv);
- tfree(envp);
- tfree(makeflags); /* ignore this, since we just read it */
- }
-
- make_args(--argc, ++argv); /* process command line options */
-
- add_macro(makeflags, 0);/* update the MAKEFLAGS macro */
- tfree(makeflags);
-
- /* add command line macros, so they DON'T get overridden */
-
- for (i = 0; mlist[i] != NULL; i++)
- add_macro(mlist[i], 1);
-
- tfree(mlist); /* all done with macros */
-
- if (opts.query) /* -q never executes anything */
- opts.noexec = 1;
-
- if (opts.noexec)
- opts.touch = 0; /* -n never touches */
-
- if (dispcount > 1) /* display `default.mk' on -DD */
- opts.display = 1;
-
- first_targ = NULL; /* used in parse() */
-
- if (readdef) /* read in `default.mk' */
- parse(fopenp(MAKEINI, "r"));
-
- if (dispcount > 0) /* display makefile's on -D */
- opts.display = 1;
-
- first_targ = NULL; /* get first target in `makefile' */
-
- /* parse the makefiles given on command line */
- for (i = 0; flist[i] != NULL; i++)
- {
- parse(equal(flist[i], "-") ? fdopen(dup(fileno(stdin)), "r")
- : fopen(flist[i], "r"));
- }
-
- /* no makefiles specified, so use "makefile" or "Makefile" */
- if (i == 0)
- {
- if (parse(fopen("makefile", "r")) == 0)
- {
- #ifndef MSDOS
- parse(fopen("Makefile", "r"));
- #endif
- }
- }
-
- tfree(flist); /* all done with makefile's */
-
- /* find the current value of the $(MAKE) macro */
- symp = get_symbol("MAKE", 0);
- opts.make = (symp->svalue == NULL) ? "make" : symp->svalue;
-
- /* get list of commands which will force usage of SHELL */
- symp = get_symbol("SHELLCMD", 0);
- shell_cmds = (symp->svalue) ? tokenize(tstrcpy(symp->svalue)) : NULL;
-
- if ((targp = get_target(".INIT")) != NULL)
- build(targp->tshell); /* process the .INIT rule */
-
- mk = 0;
-
- for (i = 0; tlist[i] != NULL; i++)
- {
- /* process command line arguments */
- mk |= (make(tlist[i], 1) > 0) ? 1 : 0;
- }
-
- tfree(tlist); /* all done with targets */
-
- /* if no targets specified, make the first one */
- if (i == 0 && first_targ)
- mk |= (make(first_targ->tfile->fname, 1) > 0) ? 1 : 0;
-
- if ((targp = get_target(".DONE")) != NULL)
- build(targp->tshell); /* process the .DONE rule */
-
- /* all done with the shell commands, so clean up */
- if (shell_cmds)
- {
- if (*shell_cmds)
- tfree(*shell_cmds);
- tfree(shell_cmds);
- }
-
- return (mk & opts.query); /* not exit(); see new_make() */
- }
-
-
- /*
- * make_args - process the command line arguments
- */
- make_args(argc, argv)
- int argc;
- char **argv;
- {
- int tlen;
- int flen;
- int mlen;
- int no_k = 0; /* override the -k option */
- char *tmf;
- int addflag;
- fileptr fp;
-
- now = time(NULL); /* get current date & time */
-
- makeflags = tstrcpy("MAKEFLAGS+=");
-
- tlen = flen = mlen = 0;
-
- for (; argc != 0; ++argv, --argc)
- {
- if (**argv != '-')
- {
- /* doesn't start with '-'; must be macro or target */
-
- if (strchr(*argv, '='))
- { /* store as a macro */
- if (mlen == mmax)
- mlist = grow_list(mlist, &mmax);
- mlist[mlen++] = *argv;
- }
- else
- { /* store as a target */
- if (tlen == tmax)
- tlist = grow_list(tlist, &tmax);
- tlist[tlen++] = *argv;
- }
- continue;
- }
-
- /* must be an option */
-
- tmf = tstrcat(makeflags, *argv);
-
- while (*argv && *++*argv)
- {
- addflag = 1; /* add to MAKEFLAGS */
- switch (**argv)
- {
- case 'b': /* backwards compatibility */
- case '-': /* SCO Xenix compatibility */
- addflag = 0; /* don't add to MAKEFLAGS */
- break;
-
- case 'd': /* show dependencies */
- addflag = 0; /* don't add to MAKEFLAGS */
- opts.depend++;
- break;
-
- case 'D': /* display makefiles */
- dispcount++;
- break;
-
- case 'e': /* don't override environment */
- opts.envirn = 1;
- break;
-
- case 'f': /* new makefile name */
- addflag = 0; /* don't add to MAKEFLAGS */
- if (argc < 2)
- usage();
- if (flen == fmax)
- flist = grow_list(flist, &fmax);
- ++argv, --argc;
- flist[flen++] = *argv;
-
- *argv = NULL;
- break;
-
- case 'i': /* ignore errors */
- opts.ignore = 1;
- break;
-
- case 'k': /* give up on current target on error */
- opts.keepon = 1;
- break;
-
- case 'n': /* don't execute commands */
- opts.noexec = 1;
- break;
-
- case 'q': /* question mode */
- opts.query = 1;
- break;
-
- case 'r': /* don't read default.mk */
- readdef = 0;
- break;
-
- case 's': /* don't echo commands */
- opts.silent = 1;
- break;
-
- case 'S': /* Undo -k option */
- no_k = 1;
- break;
-
- case 't': /* touch files, don't build */
- opts.touch = 1;
- break;
-
- case 'W': /* What-if file is touched? */
- if (argc < 2)
- usage();
- ++argv, --argc;
- fp = add_file(*argv);
- fp->ftime = now;
-
- *argv = NULL;
- break;
-
- default:
- usage(); /* never returns */
- }
- }
-
- if (addflag)
- {
- tfree(makeflags);
- makeflags = tstrcat(tmf, " ");
- }
-
- tfree(tmf);
- }
-
- /* terminate all lists with a NULL pointer */
-
- tlist[tlen] = NULL;
- flist[flen] = NULL;
- mlist[mlen] = NULL;
-
- /* check for -S over-riding -k option */
- if (no_k)
- opts.keepon = 0;
-
- /* let the caller update the makeflags macro */
- }
-
-
- /*
- * grow_list - expand the list of pointers by a factor of two
- */
- char **grow_list(list, len)
- char **list;
- int *len;
- {
- int l;
-
- /* if list is NULL, start off with a default list */
-
- if (list == NULL)
- list = (char **) talloc(((l = 1) + 1) * sizeof(char *));
- else
- {
- l = *len; /* get current length */
-
- list = (char **) trealloc((char *) list,
- ((l <<= 1) + 1) * sizeof(char *));
- }
-
- if (list == NULL)
- terror(1, "too many options");
-
- /* if we are initially allocating it, set first pointer to NULL */
-
- if (l == 1)
- *list = NULL;
-
- *len = l; /* update current length */
- return (list);
- }
-
-
- /*
- * fopenp - open file in current directory or along PATH
- */
- FILE *fopenp(fname, type)
- char *fname;
- char *type;
- {
- int len;
- char *fpath;
- FILE *fd;
- char *path;
- char *tp;
-
- /* try to open file relative to current directory */
- if ((fd = fopen(fname, type)) != NULL)
- return (fd);
- #ifndef MSDOS
- /* didn't work, try home directory */
- if ((path = getenv("HOME")) != NULL)
- {
- fpath = talloc(strlen(path) + strlen(fname) + 2);
-
- strcpy(fpath, path);
- len = strlen(fpath) - 1;
-
- /* make sure there is a separator between path and filename */
-
- if (!strchr(FILE_SEPARATOR, fpath[len]))
- fpath[++len] = '/';
-
- strcpy(&fpath[len + 1], fname); /* attach the filename */
- fd = fopen(fpath, type);
- tfree(fpath);
-
- if (fd != NULL)
- return (fd);
- }
- #endif
- /* didn't work, search along path */
-
- if ((path = getenv("PATH")) == NULL)
- return (NULL);
-
- path = tstrcpy(path); /* allocate string and copy */
- fpath = talloc(strlen(path) + strlen(fname) + 2);
-
- /* look for tokens separated by semi-colons (;) or colons (:) */
-
- tp = token(path, PATH_SEPARATOR, NULL);
- while (tp != NULL)
- {
- strcpy(fpath, tp);
- len = strlen(fpath) - 1;
-
- /* make sure there is a separator between path and filename */
-
- if (!strchr(FILE_SEPARATOR, fpath[len]))
- fpath[++len] = '/';
-
- strcpy(&fpath[len + 1], fname); /* attach the filename */
- if ((fd = fopen(fpath, type)) != NULL)
- break;
-
- tp = token(NULL, PATH_SEPARATOR, NULL);
- }
-
- tfree(path);
- tfree(fpath);
-
- return (fd);
- }
-
-
- /*
- * make - guts of the make command
- * - make all pre-requisites, and if necessary, build target
- *
- * returns -1 target was already up to date w.r.t. pre-requisites
- * 0 target has not been built
- * 1 target is now built (and up to date)
- */
- make(targname, worry)
- char *targname;
- int worry; /* if set, it is an error to NOT build this */
- {
- targptr targp;
- fileptr *preqp;
- int mk;
- fileptr filep;
- long targtime;
- long preqtime;
- char *dol_quest;
- char *dq;
-
- mk = 0;
-
- /* if recorded time of file is not default, we've already built it */
- filep = get_file(targname);
- if (filep && filep->ftime != MAXNEGTIME)
- return (1);
-
- targp = get_target(targname); /* find the target node */
- if (targp == NULL)
- return (default_rule(targname, NULL, worry, 0));
-
- /* keep actual time of current target */
- targtime = file_time(targname, 0);
-
- /* must build non-existant files, even with no pre-requisites */
- preqtime = MAXNEGTIME + 1;
-
- dol_quest = tstrcpy("");
-
- /* make all pre-requisites */
- preqp = targp->tpreq;
- while (preqp && *preqp)
- {
- mk |= make((*preqp)->fname, worry);
-
- /* keep track of newest pre-requisite */
- if (preqtime < (*preqp)->ftime)
- preqtime = (*preqp)->ftime;
-
- if (targtime < (*preqp)->ftime)
- {
- dq = tstrcat(dol_quest, (*preqp)->fname);
- tfree(dol_quest);
- dol_quest = tstrcat(dq, " ");
- tfree(dq);
- }
-
- /* display as necessary */
- if (opts.depend > 1 ||
- (opts.depend && (*preqp)->ftime > targtime))
- {
- display_prereq(targname, targtime, (*preqp)->fname,
- (*preqp)->ftime);
- }
-
- ++preqp;
- }
-
- add_symbol("?", dol_quest, 0);
- tfree(dol_quest);
-
- if (targp->tshell == NULL) /* try default rules anyway */
- {
- if (default_rule(targname, targp, 0, preqtime > targtime))
- return (1);
- return (mk);
- }
- else
- if (preqtime > targtime)
- {
- if (opts.touch) /* won't be set when `noexec' */
- touch_file(targname);
- else
- {
- add_metas("", "", targname);
- if (build(targp->tshell))
- return (0);
- }
-
- targp->tfile->ftime = (opts.noexec) ? now
- : file_time(targname, 1);
- return (1);
- }
-
- targp->tfile->ftime = targtime;
-
- return (mk);
- }
-
-
- /*
- * default_rule - try the .SUFFIXES when we don't have an explicit target
- * - if `worry' is set, it is an ERROR to NOT build this target
- * - `mustbuild' is set if make() has out-of-date prereq's
- * but no explicit shell rules
- */
- default_rule(targname, targetp, worry, mustbuild)
- char *targname;
- targptr targetp;
- int worry;
- int mustbuild;
- {
- targptr targp;
- fileptr *preqp;
- fileptr filep;
- char *ext;
- char *basename;
- char *preqname;
- long targtime;
- long preqtime;
- int built;
- char suffrule[80];
-
- ext = strrchr(targname, '.'); /* find the extension */
- if (ext == NULL)
- ext = targname + strlen(targname);
-
- basename = tstrncpy(targname, ext - targname); /* find the base name */
-
- targtime = file_time(targname, 0);
-
- /* suffix_targ is used to (slightly) speed up this function */
- preqp = suffix_targ ? suffix_targ->tpreq : NULL;
- built = 0;
-
- while (preqp && *preqp && !built)
- {
- /* look for a default rule from SUFFIX to `ext' */
- strcat(strcpy(suffrule, (*preqp)->fname), ext);
- targp = get_target(suffrule); /* e.g. `.c.o' */
-
- if (targp != NULL)
- {
- /* found a rule; see if file exists */
- preqname = get_preqname(targetp, (*preqp)->fname,
- basename);
- preqtime = file_time(preqname, 0);
-
- /*
- * don't bother recursive makes unless necessary e.g.
- * we have .c.o and .l.c, but also .l.o! we want to
- * use .l.o if a .c file does not exist
- */
- if (preqtime != MAXNEGTIME || mustbuild)
- built = make(preqname, 0);
-
- /* check if pre-req file exists and is newer */
- preqtime = file_time(preqname, 0);
- if (preqtime > targtime || (mustbuild && built))
- {
- if (opts.depend)
- {
- display_prereq(targname, targtime,
- preqname, preqtime);
- }
-
- if (opts.touch) /* won't be set when `noexec' */
- touch_file(targname);
- else
- {
- add_metas(basename, preqname, targname);
- if (build(targp->tshell))
- return (0);
- }
- built = 1;
- }
- else
- if (opts.depend > 1 && preqtime != MAXNEGTIME)
- {
- display_prereq(targname, targtime,
- preqname, preqtime);
- }
-
- tfree(preqname);
- }
-
- ++preqp; /* try next .SUFFIXES rule */
- }
-
- if (!built)
- {
- /* didn't find anything; try the default rule */
- targp = get_target(".DEFAULT");
- if (targp != NULL)
- {
- add_metas(basename, "", targname);
- if (build(targp->tshell))
- return (0);
- built = 1;
- }
- else
- if (targtime == MAXNEGTIME && worry)
- terror(1, tstrcat("Don't know how to make ", targname));
- }
-
- tfree(basename);
-
- /* record the current file time */
- if ((filep = get_file(targname)) != NULL)
- {
- filep->ftime = (built == 1 && opts.noexec) ? now
- : file_time(targname, 1);
- }
-
- return (built ? built : ((targtime == MAXNEGTIME) ? 0 : 1));
- }
-
-
- /*
- * get_preqname - find prerequisite name from target and prerequisite suffix
- */
- char *get_preqname(targp, suffix, basename)
- targptr targp;
- char *suffix;
- char *basename;
- {
- fileptr *preqp;
- char *preqf;
- char *basef;
- int i;
-
- if (targp != NULL)
- {
- /* strip the directory name from the basename */
- basef = tsplit(basename, FILE_SEPARATOR, NULL);
-
- /* look through prerequisite list for file with right name */
- for (preqp = targp->tpreq; preqp && *preqp; ++preqp)
- {
- /* split the pre-requisite into dir and filenames */
- preqf = tsplit((*preqp)->fname, FILE_SEPARATOR, NULL);
-
- /* see if the filename part matches the target */
- for (i = 0; preqf[i] != '\0'; i++)
- {
- if (preqf[i] != basef[i])
- break;
- }
-
- /* if we differed only on the suffix, we're okay */
- if (strcmp(preqf + i, suffix) == 0)
- return (tstrcpy((*preqp)->fname));
- }
- #ifdef ALL_PREQS
- /* didn't find a matching basename + suffix in the preq-list. */
- /* look through prerequisite list for file with right suffix. */
- for (preqp = targp->tpreq; preqp && *preqp; ++preqp)
- {
- preqf = strrchr((*preqp)->fname, '.');
- if (preqf == NULL)
- continue;
-
- /* take the first file which has right suffix */
- if (strcmp(suffix, preqf) == 0)
- return (tstrcpy((*preqp)->fname));
- }
- #endif /* ALL_PREQS */
- }
-
- /* didn't find one, so try forming one using basename + suffix */
-
- return (tstrcat(basename, suffix));
- }
-
-
- /*
- * add_metas - add symbols for $*, $< and $@
- */
- add_metas(basename, preqname, targname)
- char *basename;
- char *preqname;
- char *targname;
- {
- /* $* is the basename */
- add_symbol("*", basename, 0);
- split_meta("*", basename);
-
- add_symbol("<", preqname, 0);
- split_meta("<", preqname);
-
- add_symbol("@", targname, 0);
- split_meta("@", targname);
- }
-
-
- /*
- * split_meta - split a metasymbol into Directory and File parts
- */
- split_meta(sym, name)
- char *sym;
- char *name;
- {
- char *dname;
- char *dsym;
- char *fsym;
-
- /* construct the macro names (e.g. $(*D), $(@F)) */
- dsym = tstrcat(sym, "D");
- fsym = tstrcat(sym, "F");
-
- add_symbol(fsym, tsplit(name, FILE_SEPARATOR, &dname), 0);
-
- if (dname == NULL)
- add_symbol(dsym, ".", 0);
- else
- {
- add_symbol(dsym, dname, 0);
- tfree(dname);
- }
-
- tfree(dsym);
- tfree(fsym);
- }
-
-
- /*
- * touch_file - set the MODIFICATION time of the file to NOW
- */
- touch_file(targname)
- char *targname;
- {
- int handle;
- #ifndef MSDOS
- time_t timep[2];
-
- time(&timep[0]);
- timep[1] = timep[0];
- handle = utime(targname, timep);
- #else
- handle = utime(targname, NULL);
- #endif
- fputs("touch ", stdout);
- puts(targname);
-
- if (handle == 0)
- return;
-
- /* create the file, if it did not exist */
- if (errno == ENOENT)
- {
- handle = open(targname, O_CREAT | O_TRUNC, S_IWRITE);
- if (handle != -1)
- {
- close(handle);
- return;
- }
- }
-
- perror("touch");
- exit(1);
- }
-
- display_prereq(targname, targtime, preqname, preqtime)
- char *targname;
- long targtime;
- char *preqname;
- long preqtime;
- {
- #ifdef MSDOS
- char chtime[10];
-
- fputs(targname, stdout);
- fputs(" (", stdout);
- fputs(ltoa(targtime, chtime, 16), stdout);
- fputs((targtime <= preqtime) ? ") older than " : ") newer than ", stdout);
- fputs(preqname, stdout);
- fputs(" (", stdout);
- fputs(ltoa(preqtime, chtime, 16), stdout);
- puts(")");
- #else
- printf("%s (%08lx) %s than %s (%08lx)\n",
- targname, targtime,
- (targtime < preqtime) ? "older" : "newer",
- preqname, preqtime);
- #endif
- }
-
-
- long file_time(fname, built)
- char *fname;
- int built;
- {
- struct stat sbuf;
-
- /*
- * if the file is supposedly built, but still does not exists, just
- * fake it by returning the current time.
- */
- if (stat(fname, &sbuf) != 0)
- return (built ? now : MAXNEGTIME);
- return (sbuf.st_mtime);
- }
-
-
- usage()
- {
- puts("make [-f file] [-dDiknqrsStWb-] [target ...] [macro=value ...]");
- exit(1);
- }
-