home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power Programming
/
powerprogramming1994.iso
/
progtool
/
c
/
abmake14.arc
/
make.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-08-10
|
24KB
|
1,046 lines
/*
* 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-17 v1.2 AB added lots of options
* 89-04-30 v1.3 AB added environment flag MAKEFLAGS=
* 89-06-16 v1.4 AB lib response file not working
*
*/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <memory.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef MSDOS
#include <stdlib.h>
#include <process.h>
#include <dos.h>
#include <io.h>
#include <direct.h>
#else
#include <sys/errno.h>
#include <sys/wait.h>
#endif
#include "make.h"
#include "tstring.h"
#include "decl.h"
#ifdef MSDOS
#define PATH_SEPARATOR ";"
#define FILE_SEPARATOR "/\\"
#define RD_PERM 4 /* access() function */
#else
#define PATH_SEPARATOR ":"
#define FILE_SEPARATOR "/"
#define RD_PERM 4 /* access() function */
#endif
#define MAKEINI "default.mk"
char **resp_cmds; /* .RESPONSE commands */
char *shell_cmds[] = {
#ifdef MSDOS
"dir", "type", "rem", "pause", "date", "time", "ren", "rename",
"ver", "vol", "break", "verify", "mkdir", "md", "exit", "ctty",
"echo", "if", "cls", "chdir", "cd", "rmdir", "rd", "copy", "del",
"erase", "set", "for", "prompt", "path",
#endif
NULL,
};
char *makelist[] = /* List of possible makefile names */
{
"makefile",
"Makefile",
NULL
};
char *usage_str[] =
{
#ifdef MSDOS
"Make v1.4 (DOS version)\n",
#else
"Make v1.4\n",
#endif
"Usage : make [-f filename] [-CdeiknrstFV] [target ...] [macro = value ...]",
"",
"Options:",
"",
" -f the next argument is the name of a makefile",
" -C force forking shell for building target",
" -d show target dependencies",
"! -D show text of makefile",
" -e environment variables override macro assignments",
" -F force target updates",
" -i ignore error codes retunred by commands",
" -k abandon building current target, continue with other targets",
" -n don't execute, list actions",
"! -p print out macro definitions and target description",
"! -P print out macro definitions and target description",
"! -q question mode, make returns zero or nonzero status code",
" -r don't use builtin rules",
" -s run silently without listing actions",
" -S undo the effect of the -k option",
" -t touch the target files (bring them up to date)",
" -V list the current program version",
"",
"",
"Macro usage:",
".DEFAULT - define default rule to build target",
".DONE - target to build, after all other targets are build",
".IGNORE - ignore all error codes during building of targets",
".INIT - first target to build, before any target is build",
".SILENT - do not echo commands",
".RESPONSE - define a response file for long ( > 128) command lines",
".SUFFIXES - the suffixes list for selecting implicit rules",
NULL
};
static char **version = &usage_str[0];
targptr target_list; /* list of target nodes */
fileptr file_list; /* list of file nodes */
symptr symbol_list; /* list of symbol nodes */
shellptr shell_list; /* list of shell nodes */
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 */
short forceshell = 0; /* -C option */
short debug = 0; /* -d option */
short touchenv = 1; /* -e option */
short ignore = 0; /* -i option */
short single_ign = 0; /* -k option */
short noexec = 0; /* -n option */
short silent = 0; /* -s option */
short touch = 0; /* -t option */
short readdef = 1; /* -r option */
short depend = 0; /* -D option */
short force = 0; /* -F option */
long now; /* time at startup */
#ifndef MSDOS
static void _searchenv();
#endif
main(argc, argv)
int argc;
char **argv;
{
REGISTER int i;
REGISTER targptr targp;
targptr resp_targ; /* file list for long command lines>response file */
int done = 0;
char **makefile;
char *mk, *envargs, **eargv;
/* get current date & time */
now = curr_time();
/* initialize the various global lists */
target_list = NULL;
file_list = NULL;
symbol_list = NULL;
shell_list = NULL;
/* allocate space for command line targets, files and macros */
tlist = grow_list(NULL, &tmax);
flist = grow_list(NULL, &fmax);
mlist = grow_list(NULL, &mmax);
add_symbol("MAKE", "make"); /* Default $(MAKE) macro */
if ( (envargs = getenv("MAKEFLAGS")) )
{
eargv = tokenize(tstrcpy(envargs));
make_args(-1, eargv);
tfree(eargv);
}
make_args(--argc, ++argv); /* process command line options */
if (noexec)
silent = touch = 0; /* -n always displays; never touches */
first_targ = NULL; /* used in parse() */
if (readdef)
parse(fopenp(MAKEINI, "r"), 0); /* read in `default.mk' */
first_targ = NULL; /* get first target in `makefile' */
/* parse the makefiles given on command line */
for(i = 0; flist[i] != NULL; i++)
parse(fopen(flist[i], "r"), 0);
/* no makefiles specified, so use "makefile" */
if (i == 0)
{
for ( makefile = makelist; !done && *makefile; makefile++ )
{
if ( debug > 1 )
printf("*** Accessing makefile '%s'\n", *makefile);
if ( (done = !access(*makefile, RD_PERM)) )
{
if ( debug )
printf("*** Reading %s\n", *makefile);
parse(fopen(*makefile, "r"), 0);
}
}
}
tfree(flist); /* all done with makefile's */
for(i = 0; mlist[i] != NULL; i++)
add_macro(mlist[i]); /* add command line macros */
tfree(mlist); /* all done with macros */
if ( (resp_targ = get_target(".RESPONSE")) )
resp_cmds = tokenize(breakout((*resp_targ->tshell)->scmd));
if ((targp = get_target(".INIT")) != NULL)
build(targp->tshell); /* process the .INIT rule */
for (i = 0; tlist[i] != NULL; i++)
make(tlist[i], 1); /* process command line targets */
tfree(tlist); /* all done with targets */
/* if no targets specified, make the first one */
if (i == 0 && first_targ)
make(first_targ->tfile->fname, 1);
if ((targp = get_target(".DONE")) != NULL)
build(targp->tshell); /* process the .DONE rule */
return (0); /* not exit(); see new_make() */
}
/* decode all command line & environment options */
/* no -f option accepted from env, argc set neg */
make_args(argc, argv)
int argc;
char **argv;
{
static int tlen = 0;
static int flen = 0;
static int mlen = 0;
char no_k = 0;
for ( ; *argv && argc; *(++argv) && --argc )
{
/* look for macros and targets */
if ( **argv != '-' )
{
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;
}
while ( *argv && *++*argv )
{
switch (**argv)
{
case 'C': /* force shell forking for target */
forceshell = 1;
break;
case 'd': /* show dependencies */
if ( isdigit(*(*argv + 1)) )
debug = *++*argv;
else
debug++;
break;
case 'e': /* environment overrides assignments */
touchenv = 0;
break;
case 'f': /* new makefile name */
if ( argc > 0 )
{
if (argc < 2)
usage();
if (flen == fmax)
flist = grow_list(flist, &fmax);
flist[flen++] = *(++argv);
}
else
{
printf("Illegal -f option in environment, ignored\n");
++argv;
}
--argc;
*argv = NULL;
break;
case 'i': /* ignore errors */
ignore = 1;
break;
case 'k': /* ignore single target errors only */
single_ign = 1;
break;
case 'n': /* don't execute commands */
noexec = 1;
break;
case 'r': /* don't read default.mk */
readdef = 0;
break;
case 's': /* don't echo commands */
silent = 1;
break;
case 'S': /* undo the -k option */
no_k = 1;
break;
case 't': /* touch files, don't build */
touch = 1;
break;
case 'D': /* display makefiles */
depend = 1;
break;
case 'F': /* ignore target date, force make */
force = 1;
break;
case 'V': /* ignore target date, force make */
puts(*version);
break;
default:
usage();
}
}
}
if ( no_k )
single_ign = 0;
/* terminate all lists with a NULL pointer */
tlist[tlen] = NULL;
flist[flen] = NULL;
mlist[mlen] = NULL;
}
/*
* grow_list - expand the list of pointers by a factor of two
*/
char **grow_list(list, len)
char **list;
int *len;
{
REGISTER int l;
l = *len; /* get current length */
/* if list is NULL, start off with a default list */
if ( list == NULL )
list = (char **)talloc(((l=1)+1) * sizeof(char *));
else
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;
{
REGISTER int len;
REGISTER char *fpath;
FILE *fd;
/* if the filename is -, use stdin */
if (equal(fname, "-"))
return (stdin);
fpath = talloc(256 + strlen(fname) + 2);
_searchenv(fname, "PATH", fpath);
/* try to open file relative to current directory */
if ( (fd = fopen(fpath, type)) != NULL && debug )
printf("*** Reading default rules from %s\n", fpath);
tfree(fpath);
return (fd);
}
#ifndef MSDOS
void _searchenv(fname, env, fpath)
char *fname, *env, *fpath;
{
char *path;
char *tp;
*fpath = '\0';
if ((path = getenv(env)) == NULL)
return;
path = tstrcpy(path); /* allocate string and copy */
/* look for tokens separated by semi-colons (;) or colons (:) */
tp = token(path, PATH_SEPARATOR);
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);
if ( !access(fpath, RD_PERM)) )
break;
tp = token(NULL, PATH_SEPARATOR);
}
tfree(path);
}
#endif
/*
* 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 */
{
REGISTER targptr targp;
REGISTER fileptr *preqp;
fileptr filep;
long targtime;
long preqtime;
/* 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, worry, 0));
targtime = file_time(targname); /* keep actual time of current target */
/* must build non-existant files, even with no pre-requisites */
preqtime = MAXNEGTIME + 1;
/* make all pre-requisites */
preqp = targp->tpreq;
while (*preqp)
{
make((*preqp)->fname, worry);
/* keep track of newest pre-requisite */
if (preqtime < (*preqp)->ftime)
preqtime = (*preqp)->ftime;
/* display as necessary */
if (debug || (debug && (*preqp)->ftime > targtime))
{
display_prereq(targname, targtime, (*preqp)->fname,
(*preqp)->ftime);
}
++preqp;
}
if (targp->tshell == NULL) /* try default rules anyway */
return (default_rule(targname, 0, force || preqtime > targtime));
else if (force || preqtime > targtime)
{
if (touch) /* won't be set when `noexec' */
touch_file(targname);
else
{
add_metas("", "", targname);
build(targp->tshell);
}
targp->tfile->ftime = (noexec) ? now : file_time(targname);
return (1);
}
targp->tfile->ftime = targtime;
return (-1);
}
/*
* 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, worry, mustbuild)
char *targname;
int worry;
int mustbuild;
{
REGISTER targptr targp;
REGISTER fileptr *preqp;
fileptr filep;
char *ext;
char *basename;
char *preqname;
long targtime;
long preqtime;
int built;
char suffrule[80];
/* find the extension */
ext = strrchr(targname, '.');
if (ext == NULL)
ext = targname + strlen(targname);
/* find the base name */
basename = tstrncpy(targname, ext - targname);
targtime = file_time(targname);
/* 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 = tstrcat(basename, (*preqp)->fname);
preqtime = file_time(preqname);
/*
* 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);
if (preqtime > targtime || (mustbuild && built))
{
if (debug)
display_prereq(targname, targtime, preqname, preqtime);
if (touch) /* won't be set when `noexec' */
touch_file(targname);
else
{
add_metas(basename, preqname, targname);
build(targp->tshell);
}
built = 1;
}
else if (debug && 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);
build(targp->tshell);
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 && noexec) ? now
: file_time(targname);
}
return (built ? built : ((targtime == MAXNEGTIME) ? 0 : -1));
}
/*
* add_metas - add symbols for $*, $< and $@
*/
add_metas(basename, preqname, targname)
char *basename;
char *preqname;
char *targname;
{
add_symbol("*", basename);
add_symbol("<", preqname);
add_symbol("@", targname);
}
/*
* touch_file - set the MODIFICATION time of the file to NOW
*/
touch_file(targname)
char *targname;
{
REGISTER 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("*** touching : ", 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("make");
exit (1);
}
/*
* build - process the shell commands
*/
build(shellp)
REGISTER shellptr *shellp;
{
REGISTER char **argv; /* argified version of scmd */
int runst = 0; /* exec return status */
char *scmd; /* command with symbols broken out */
char *tcmd; /* copy of scmd for `tokenize' */
char *mk; /* builtin make macro */
char **shcp; /* pointer to shell command list */
symptr symp; /* points to macro definition */
#ifndef MSDOS
char *format; /* for displaying error msg's */
#else
int resp_file; /* response file possible */
char *cwd; /* current work directory */
FILE *fd; /* temporary file (if needed) */
char *fname, *comma; /* temporary name pointers (if needed) */
int i;
#endif
symp = get_symbol("MAKE");
mk = symp->svalue;
#ifdef MSDOS
cwd = getcwd(NULL, 1); /* where are we ? */
#endif
for (;*shellp != NULL && runst == 0;
tfree(scmd), tfree(tcmd), tfree(argv), ++shellp)
{
/* breakout runtime symbols (e.g. $* $@ $<) */
scmd = breakout((*shellp)->scmd);
if (!(*shellp)->s_silent && !silent)
puts(scmd);
/* make a copy because tokenize litters '\0's */
tcmd = tstrcpy(scmd);
argv = tokenize(tcmd);
#ifdef MSDOS
/* check for .RESPONSE command */
shcp = resp_cmds;
for (resp_file = 0; !resp_file && *shcp; ++shcp)
resp_file = equal(*shcp, argv[0]);
if ( !noexec && resp_file && strlen(scmd) > 128 )
{ /* Create temporary response file */
if ( (fname = tempnam("\TMP", "MAK")) )
{
fd = fopen(fname, "w");
for ( i = 1; argv[i]; i++ )
{
for ( comma = argv[i];
*comma && (comma = strchr(comma, ',')); argv[i] = comma )
{
*comma++ = '\0';
fprintf(fd, "%s,\n", argv[i]);
}
fprintf(fd, "%s +\n", argv[i]);
}
fputc(';', fd);
fputc('\n', fd);
fclose(fd);
argv[1] = tstrcat("@", fname);
argv[2] = NULL;
}
}
else
resp_file = 0;
#endif
if ( equal(argv[0], mk) )
{
/* call ourselves recursively */
new_make(argv);
continue;
}
if (noexec)
continue;
/* any indirection MUST be handled by the shell */
if (!(*shellp)->s_shell && strpbrk(scmd, "<|>"))
(*shellp)->s_shell = 1;
#ifdef MSDOS
/* likewise, check for COMMAND.COM builtin commands */
for (shcp = shell_cmds; !(*shellp)->s_shell && *shcp; ++shcp)
(*shellp)->s_shell = equal(*shcp, argv[0]);
#endif
/* run without COMMAND.COM if possible, 'cause it uses RAM */
if ( forceshell || (*shellp)->s_shell )
runst = system(scmd);
else
runst = spawnvp(P_WAIT, argv[0], argv);
#ifdef MSDOS
chdir(cwd); /* back to work directory */
if ( resp_file ) /* free temporary file argument */
{
remove(fname);
tfree(argv[2]);
free(fname);
}
#endif
if (runst == 0)
continue;
/* uh-oh, an error */
if (runst == -1)
perror("make");
#ifdef MSDOS
itoa(runst, scmd, 10);
if ( !single_ign && (ignore || (*shellp)->s_ignore) )
strcat(scmd, " (ignored)");
else if ( single_ign && !(ignore || (*shellp)->s_ignore) )
strcat(scmd, " (target aborted)");
terror(0, tstrcat("\n\n*** Error code ",scmd));
putchar('\n');
#else
if ( !single_ign && (ignore || (*shellp)->s_ignore) )
format = "(ignored)");
else if ( single_ign && !(ignore || (*shellp)->s_ignore) )
format = "(target aborted)");
else
format = "";
sprintf(scmd, "\n\n*** Error code %d %s\n", runst, format);
terror(0, scmd);
#endif
if (!single_ign && !ignore && !(*shellp)->s_ignore)
exit(1);
else if ( !single_ign )
runst = 0;
}
#ifdef MSDOS
free(cwd); /* free directory path space */
#endif
}
/*
* new_make - save current environment
* - call make() recursively (actually main())
* - clean up new environment
* - restore environment
*/
new_make(argv)
char **argv;
{
targptr thead, tnext;
fileptr fhead, fnext;
symptr shead, snext;
shellptr shhead, shnext;
char **ttlist;
long tnow;
int i;
/* save all the globals */
thead = target_list;
fhead = file_list;
shead = symbol_list;
shhead = shell_list;
ttlist = tlist;
tnow = now;
/* count the arguments */
for (i = 0; argv[i]; ++i);
/* call ourselves recursively; this inherits flags */
main(i, argv);
/* we're back, so gotta clean up and dispose of a few things */
while (target_list)
{
tnext = target_list->tnext;
if (target_list->tpreq)
tfree(target_list->tpreq);
if (target_list->tshell)
tfree(target_list->tshell);
tfree(target_list);
target_list = tnext;
}
while (file_list)
{
fnext = file_list->fnext;
tfree(file_list->fname);
tfree(file_list);
file_list = fnext;
}
while (symbol_list)
{
snext = symbol_list->snext;
tfree(symbol_list->sname);
tfree(symbol_list->svalue);
tfree(symbol_list);
symbol_list = snext;
}
while (shell_list)
{
shnext = shell_list->slink;
tfree(shell_list->scmd);
tfree(shell_list);
shell_list = shnext;
}
/* restore our original globals */
target_list = thead;
file_list = fhead;
symbol_list = shead;
shell_list = shhead;
tlist = ttlist;
now = tnow;
}
usage()
{
char **us_str;
int i;
for ( i = 1, us_str = usage_str; *us_str; puts(*us_str++), i++ )
if ( !(i % 24) )
{
printf("Press key to continue...");
getchar();
}
exit(1);
}
display_prereq(targname, targtime, preqname, preqtime)
char *targname;
long targtime;
char *preqname;
long preqtime;
{
printf("%s (%08lx) %s than %s (%08lx)\n",
targname, targtime,
(targtime < preqtime) ? "older" : "newer",
preqname, preqtime);
}
long file_time(fname)
char *fname;
{
REGISTER int handle;
#ifdef MSDOS
/*
union
{
long ltime;
struct
{
unsigned time;
unsigned date;
} s;
} ftime;
*/
struct stat sbuf;
#else
struct stat sbuf;
#endif
handle = open(fname, O_RDONLY);
if (handle == -1)
return (MAXNEGTIME);
#ifdef MSDOS
/*
if (_dos_getftime(handle, &ftime.s.date, &ftime.s.time))
ftime.ltime = MAXNEGTIME;
*/
fstat(handle, &sbuf);
#else
fstat(handle, &sbuf);
#endif
close(handle);
#ifdef MSDOS
/*
return (ftime.ltime);
*/
return (sbuf.st_mtime);
#else
return (sbuf.st_mtime);
#endif
}
/*
* curr_time - return current time in same format as file_time()
* - used with the `-n' option
*/
long
curr_time()
{
#ifndef MSDOS
return (time(NULL));
#else
struct dosdate_t date;
struct dostime_t time;
_dos_getdate(&date);
_dos_gettime(&time);
return ((((long) (date.year-1980)<<9 | date.month<<5 | date.day) << 15)
| (time.hour << 11 | time.minute << 5 | time.second >> 1) >> 1);
#endif
}
#ifndef MSDOS
int
spawnvp(mode, path, args)
int mode;
char *path;
char **args;
{
int pid = 0;
union wait waitword;
if (mode != P_OVERLAY)
pid = fork();
if (pid == 0)
execvp(path, args);
wait(&waitword);
return (waitword.w_retcode);
}
#endif