diff options
| author | cgd <cgd@NetBSD.org> | 1994-03-05 00:34:29 +0000 |
|---|---|---|
| committer | cgd <cgd@NetBSD.org> | 1994-03-05 00:34:29 +0000 |
| commit | 3db59563ee7dca190481dfee041222f12ed3a64f (patch) | |
| tree | ee00f5ed3bf2662f98775537e76f6ba74977a1f6 /usr.bin/make | |
| parent | 3e02952c52fd23241dc58d7a0165a865dd508bcd (diff) | |
fixes/improvements from Christos Zoulas <christos@deshaw.com>.
Diffstat (limited to 'usr.bin/make')
31 files changed, 5566 insertions, 688 deletions
diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile index e9af6d26a54..c2eb7d9b8b1 100644 --- a/usr.bin/make/Makefile +++ b/usr.bin/make/Makefile @@ -1,10 +1,10 @@ # from: @(#)Makefile 5.2 (Berkeley) 12/28/90 -# $Id: Makefile,v 1.3 1993/12/14 18:27:15 jtc Exp $ +# $Id: Makefile,v 1.4 1994/03/05 00:34:29 cgd Exp $ PROG= make -CFLAGS+=-I${.CURDIR} -DPOSIX -SRCS= arch.c buf.c compat.c cond.c dir.c hash.c job.c main.c \ - make.c parse.c str.c suff.c targ.c var.c +CFLAGS+= -I${.CURDIR} -DPOSIX +SRCS= arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \ + make.c parse.c str.c suff.c targ.c var.c util.c SRCS+= lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \ lstDatum.c lstDeQueue.c lstDestroy.c lstDupl.c lstEnQueue.c \ lstFind.c lstFindFrom.c lstFirst.c lstForEach.c lstForEachFrom.c \ diff --git a/usr.bin/make/Makefile.boot b/usr.bin/make/Makefile.boot new file mode 100644 index 00000000000..bbdf2b4e87d --- /dev/null +++ b/usr.bin/make/Makefile.boot @@ -0,0 +1,15 @@ +# a very simple makefile... +# $Id: Makefile.boot,v 1.1 1994/03/05 00:34:30 cgd Exp $ + +CC=gcc +CFLAGS=-Wall -I. -g -O -DMACHINE=\"sun4\" -Dnotyet + +OBJ=arch.o buf.o compat.o cond.o dir.o hash.o job.o main.o make.o \ + parse.o str.o suff.o targ.o var.o util.o + +pmake: ${OBJ} + @echo 'make of make and make.0 started.' + (cd lst.lib; make) + ${CC} *.o lst.lib/*.o -o pmake +# nroff -h -man make.1 > make.0 +# @echo 'make of make and make.0 completed.' diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c index 9cbae3042e4..3dbd3f7b3ed 100644 --- a/usr.bin/make/arch.c +++ b/usr.bin/make/arch.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)arch.c 5.7 (Berkeley) 12/28/90";*/ -static char rcsid[] = "$Id: arch.c,v 1.4 1994/01/13 21:01:40 jtc Exp $"; +/* from: static char sccsid[] = "@(#)arch.c 5.7 (Berkeley) 12/28/90"; */ +static char *rcsid = "$Id: arch.c,v 1.5 1994/03/05 00:34:32 cgd Exp $"; #endif /* not lint */ /*- @@ -94,13 +94,10 @@ static char rcsid[] = "$Id: arch.c,v 1.4 1994/01/13 21:01:40 jtc Exp $"; #include <ar.h> #include <ranlib.h> #include <stdio.h> -#include <stdlib.h> #include "make.h" #include "hash.h" - -#ifndef RANLIBMAG -#define RANLIBMAG "__.SYMDEF" -#endif +#include "dir.h" +#include "config.h" static Lst archives; /* Lst of archives we've already examined */ @@ -110,7 +107,9 @@ typedef struct Arch { * by <name, struct ar_hdr *> key/value pairs */ } Arch; -static FILE *ArchFindMember(); +static int ArchFindArchive __P((Arch *, char *)); +static struct ar_hdr *ArchStatMember __P((char *, char *, Boolean)); +static FILE *ArchFindMember __P((char *, char *, struct ar_hdr *, char *)); /*- *----------------------------------------------------------------------- @@ -139,7 +138,7 @@ Arch_ParseArchive (linePtr, nodeLst, ctxt) GNode *gn; /* New node */ char *libName; /* Library-part of specification */ char *memName; /* Member-part of specification */ - char nameBuf[BSIZE]; /* temporary place for node name */ + char nameBuf[MAKE_BSIZE]; /* temporary place for node name */ char saveChar; /* Ending delimiter of member-name */ Boolean subLibName; /* TRUE if libName should have/had * variable substitution performed on it */ @@ -174,11 +173,11 @@ Arch_ParseArchive (linePtr, nodeLst, ctxt) *cp++ = '\0'; if (subLibName) { - libName = Var_Subst(libName, ctxt, TRUE); + libName = Var_Subst(NULL, libName, ctxt, TRUE); } - while (1) { + for (;;) { /* * First skip to the start of the member's name, mark that * place and skip to the end of it (either white-space or @@ -253,7 +252,7 @@ Arch_ParseArchive (linePtr, nodeLst, ctxt) char *sacrifice; char *oldMemName = memName; - memName = Var_Subst(memName, ctxt, TRUE); + memName = Var_Subst(NULL, memName, ctxt, TRUE); /* * Now form an archive spec and recurse to deal with nested @@ -264,7 +263,7 @@ Arch_ParseArchive (linePtr, nodeLst, ctxt) sprintf(buf, "%s(%s)", libName, memName); - if (index(memName, '$') && strcmp(memName, oldMemName) == 0) { + if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { /* * Must contain dynamic sources, so we can't deal with it now. * Just create an ARCHV node for the thing and let @@ -433,7 +432,7 @@ ArchStatMember (archive, member, hash) * to point 'member' to the final component, if there is one, to make * the comparisons easier... */ - cp = rindex (member, '/'); + cp = strrchr (member, '/'); if (cp != (char *) NULL) { member = cp + 1; } @@ -522,7 +521,7 @@ ArchStatMember (archive, member, hash) he = Hash_CreateEntry (&ar->members, strdup (memName), (Boolean *)NULL); Hash_SetValue (he, (ClientData)emalloc (sizeof (struct ar_hdr))); - bcopy ((Address)&arh, (Address)Hash_GetValue (he), + memcpy ((Address)Hash_GetValue (he), (Address)&arh, sizeof (struct ar_hdr)); } /* @@ -605,7 +604,7 @@ ArchFindMember (archive, member, arhPtr, mode) * to point 'member' to the final component, if there is one, to make * the comparisons easier... */ - cp = rindex (member, '/'); + cp = strrchr (member, '/'); if (cp != (char *) NULL) { member = cp + 1; } @@ -690,7 +689,7 @@ Arch_Touch (gn) arch = ArchFindMember(Var_Value (ARCHIVE, gn), Var_Value (TARGET, gn), &arh, "r+"); - sprintf(arh.ar_date, "%-12d", now); + sprintf(arh.ar_date, "%-12ld", (long) now); if (arch != (FILE *) NULL) { (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch); @@ -722,7 +721,7 @@ Arch_TouchLib (gn) struct timeval times[2]; /* Times for utimes() call */ arch = ArchFindMember (gn->path, RANLIBMAG, &arh, "r+"); - sprintf(arh.ar_date, "%-12d", now); + sprintf(arh.ar_date, "%-12ld", (long) now); if (arch != (FILE *) NULL) { (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch); @@ -806,8 +805,8 @@ Arch_MemMTime (gn) * child. We keep searching its parents in case some other * parent requires this child to exist... */ - nameStart = index (pgn->name, '(') + 1; - nameEnd = index (nameStart, ')'); + nameStart = strchr (pgn->name, '(') + 1; + nameEnd = strchr (nameStart, ')'); if (pgn->make && strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) { diff --git a/usr.bin/make/bit.h b/usr.bin/make/bit.h index 76bad137b81..461761032f3 100644 --- a/usr.bin/make/bit.h +++ b/usr.bin/make/bit.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)bit.h 5.3 (Berkeley) 6/1/90 - * $Id: bit.h,v 1.2 1993/08/01 18:12:06 mycroft Exp $ + * $Id: bit.h,v 1.3 1994/03/05 00:34:33 cgd Exp $ */ /* @@ -98,4 +98,4 @@ extern Boolean Bit_Union(); extern Boolean Bit_AnySet(); extern int *Bit_Expand(); -#endif _BIT +#endif /* _BIT */ diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c index ea578cfdb9b..9a720acfd0a 100644 --- a/usr.bin/make/buf.c +++ b/usr.bin/make/buf.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)buf.c 5.5 (Berkeley) 12/28/90";*/ -static char rcsid[] = "$Id: buf.c,v 1.3 1994/01/13 21:01:42 jtc Exp $"; +/* from: static char sccsid[] = "@(#)buf.c 5.5 (Berkeley) 12/28/90"; */ +static char *rcsid = "$Id: buf.c,v 1.4 1994/03/05 00:34:34 cgd Exp $"; #endif /* not lint */ /*- @@ -46,9 +46,8 @@ static char rcsid[] = "$Id: buf.c,v 1.3 1994/01/13 21:01:42 jtc Exp $"; * Functions for automatically-expanded buffers. */ -#include <stdlib.h> -#include <string.h> #include "sprite.h" +#include "make.h" #include "buf.h" #ifndef max @@ -94,7 +93,7 @@ static char rcsid[] = "$Id: buf.c,v 1.3 1994/01/13 21:01:42 jtc Exp $"; void Buf_OvAddByte (bp, byte) register Buffer bp; - Byte byte; + int byte; { bp->left = 0; @@ -131,7 +130,7 @@ Buf_AddBytes (bp, numBytes, bytesPtr) BufExpand (bp, numBytes); - bcopy (bytesPtr, bp->inPtr, numBytes); + memcpy (bp->inPtr, bytesPtr, numBytes); bp->inPtr += numBytes; bp->left -= numBytes; @@ -157,7 +156,7 @@ Buf_AddBytes (bp, numBytes, bytesPtr) void Buf_UngetByte (bp, byte) register Buffer bp; - Byte byte; + int byte; { if (bp->outPtr != bp->buffer) { @@ -179,8 +178,7 @@ Buf_UngetByte (bp, byte) Byte *newBuf; newBuf = (Byte *)emalloc(bp->size + BUF_UNGET_INC); - bcopy ((char *)bp->outPtr, - (char *)(newBuf+BUF_UNGET_INC), numBytes+1); + memcpy ((char *)(newBuf+BUF_UNGET_INC), (char *)bp->outPtr, numBytes+1); bp->outPtr = newBuf + BUF_UNGET_INC; bp->inPtr = bp->outPtr + numBytes; free ((char *)bp->buffer); @@ -214,7 +212,7 @@ Buf_UngetBytes (bp, numBytes, bytesPtr) if (bp->outPtr - bp->buffer >= numBytes) { bp->outPtr -= numBytes; - bcopy (bytesPtr, bp->outPtr, numBytes); + memcpy (bp->outPtr, bytesPtr, numBytes); } else if (bp->outPtr == bp->inPtr) { Buf_AddBytes (bp, numBytes, bytesPtr); } else { @@ -223,7 +221,7 @@ Buf_UngetBytes (bp, numBytes, bytesPtr) int newBytes = max(numBytes,BUF_UNGET_INC); newBuf = (Byte *)emalloc (bp->size + newBytes); - bcopy((char *)bp->outPtr, (char *)(newBuf+newBytes), curNumBytes+1); + memcpy((char *)(newBuf+newBytes), (char *)bp->outPtr, curNumBytes+1); bp->outPtr = newBuf + newBytes; bp->inPtr = bp->outPtr + curNumBytes; free ((char *)bp->buffer); @@ -231,7 +229,7 @@ Buf_UngetBytes (bp, numBytes, bytesPtr) bp->size += newBytes; bp->left = bp->size - (bp->inPtr - bp->buffer); bp->outPtr -= numBytes; - bcopy ((char *)bytesPtr, (char *)bp->outPtr, numBytes); + memcpy ((char *)bp->outPtr, (char *)bytesPtr, numBytes); } } @@ -293,7 +291,7 @@ Buf_GetBytes (bp, numBytes, bytesPtr) if (bp->inPtr - bp->outPtr < numBytes) { numBytes = bp->inPtr - bp->outPtr; } - bcopy (bp->outPtr, bytesPtr, numBytes); + memcpy (bytesPtr, bp->outPtr, numBytes); bp->outPtr += numBytes; if (bp->outPtr == bp->inPtr) { diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h index c025f4f9d90..85e1ba8e830 100644 --- a/usr.bin/make/buf.h +++ b/usr.bin/make/buf.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)buf.h 5.4 (Berkeley) 12/28/90 - * $Id: buf.h,v 1.2 1993/08/01 18:12:06 mycroft Exp $ + * $Id: buf.h,v 1.3 1994/03/05 00:34:36 cgd Exp $ */ /*- @@ -59,24 +59,23 @@ typedef struct Buffer { Byte *outPtr; /* Place to read from */ } *Buffer; -Buffer Buf_Init(); /* Initialize a buffer */ -void Buf_Destroy(); /* Destroy a buffer */ -void Buf_AddBytes(); /* Add a range of bytes to a buffer */ -int Buf_GetByte(); /* Get a byte from a buffer */ -int Buf_GetBytes(); /* Get multiple bytes */ -void Buf_UngetByte(); /* Push a byte back into the buffer */ -void Buf_UngetBytes(); /* Push many bytes back into the buf */ -Byte *Buf_GetAll(); /* Get them all */ -void Buf_Discard(); /* Throw away some of the bytes */ -int Buf_Size(); /* See how many are there */ - /* Buf_AddByte adds a single byte to a buffer. */ #define Buf_AddByte(bp, byte) \ - (--(bp)->left <= 0 ? Buf_OvAddByte(bp, byte) : \ - (void)(*(bp)->inPtr++ = (byte), *(bp)->inPtr = 0)) - -void Buf_OvAddByte(); /* adds a byte when buffer overflows */ + (void) (--(bp)->left <= 0 ? Buf_OvAddByte(bp, byte), 1 : \ + (*(bp)->inPtr++ = (byte), *(bp)->inPtr = 0), 1) #define BUF_ERROR 256 -#endif _BUF_H +void Buf_OvAddByte __P((Buffer, int)); +void Buf_AddBytes __P((Buffer, int, Byte *)); +void Buf_UngetByte __P((Buffer, int)); +void Buf_UngetBytes __P((Buffer, int, Byte *)); +int Buf_GetByte __P((Buffer)); +int Buf_GetBytes __P((Buffer, int, Byte *)); +Byte *Buf_GetAll __P((Buffer, int *)); +void Buf_Discard __P((Buffer, int)); +int Buf_Size __P((Buffer)); +Buffer Buf_Init __P((int)); +void Buf_Destroy __P((Buffer, Boolean)); + +#endif /* _BUF_H */ diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c index bfe6fcff919..b2ffb4c3023 100644 --- a/usr.bin/make/compat.c +++ b/usr.bin/make/compat.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)compat.c 5.7 (Berkeley) 3/1/91";*/ -static char rcsid[] = "$Id: compat.c,v 1.4 1994/01/13 21:01:44 jtc Exp $"; +/* from: static char sccsid[] = "@(#)compat.c 5.7 (Berkeley) 3/1/91"; */ +static char *rcsid = "$Id: compat.c,v 1.5 1994/03/05 00:34:37 cgd Exp $"; #endif /* not lint */ /*- @@ -59,10 +59,12 @@ static char rcsid[] = "$Id: compat.c,v 1.4 1994/01/13 21:01:44 jtc Exp $"; #include <sys/signal.h> #include <sys/wait.h> #include <sys/errno.h> +#include <sys/stat.h> #include <ctype.h> -#include <sys/stat.h> /* 10 Aug 92*/ -#include <unistd.h> #include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" extern int errno; /* @@ -76,14 +78,15 @@ static char meta[256]; static GNode *curTarg = NILGNODE; static GNode *ENDNode; -static int CompatRunCommand(); +static void CompatInterrupt __P((int)); +static int CompatRunCommand __P((char *, GNode *)); +static int CompatMake __P((GNode *, GNode *)); /*- *----------------------------------------------------------------------- * CompatInterrupt -- * Interrupt the creation of the current target and remove it if * it ain't precious. - * Don't unlink it if it is a directory XXX 10 Aug 92 * * Results: * None. @@ -99,16 +102,14 @@ CompatInterrupt (signo) int signo; { GNode *gn; - struct stat sbuf; /* 10 Aug 92*/ if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) { char *file = Var_Value (TARGET, curTarg); + struct stat st; - stat (file, &sbuf); - if (!(sbuf.st_mode & S_IFDIR)) { - if (unlink (file) == SUCCESS) { - printf ("*** %s removed\n", file); - } + if (lstat(file, &st) != -1 && !S_ISDIR(st.st_mode) && + unlink(file) != -1) { + printf ("*** %s removed\n", file); } /* @@ -150,7 +151,6 @@ CompatRunCommand (cmd, gn) union wait reason; /* Reason for child's death */ int status; /* Description of child's death */ int cpid; /* Child actually found */ - int numWritten; /* Number of bytes written for error message */ ReturnStatus stat; /* Status of fork */ LstNode cmdNode; /* Node where current command is located */ char **av; /* Argument vector for thing to exec */ @@ -163,7 +163,7 @@ CompatRunCommand (cmd, gn) errCheck = !(gn->type & OP_IGNORE); cmdNode = Lst_Member (gn->commands, (ClientData)cmd); - cmdStart = Var_Subst (cmd, gn, FALSE); + cmdStart = Var_Subst (NULL, cmd, gn, FALSE); /* * brk_string will return an argv with a NULL in av[1], thus causing @@ -197,14 +197,15 @@ CompatRunCommand (cmd, gn) cmd++; } - while (isspace(*cmd)) cmd++; + while (isspace((unsigned char)*cmd)) + cmd++; /* * Search for meta characters in the command. If there are no meta * characters, there's no need to execute a shell to execute the * command. */ - for (cp = cmd; !meta[*cp]; cp++) { + for (cp = cmd; !meta[(unsigned char)*cp]; cp++) { continue; } @@ -262,8 +263,8 @@ CompatRunCommand (cmd, gn) if (cpid == 0) { if (local) { execvp(av[0], av); - numWritten = write (2, av[0], strlen (av[0])); - numWritten = write (2, ": not found\n", sizeof(": not found")); + (void) write (2, av[0], strlen (av[0])); + (void) write (2, ": not found\n", sizeof(": not found")); } else { (void)execv(av[0], av); } @@ -493,6 +494,8 @@ CompatMake (gn, pgn) if (noExecute || Dir_MTime(gn) == 0) { gn->mtime = now; } + if (gn->cmtime > gn->mtime) + gn->mtime = gn->cmtime; if (DEBUG(MAKE)) { printf("update time: %s\n", Targ_FmtTime(gn->mtime)); } @@ -534,6 +537,8 @@ CompatMake (gn, pgn) Make_TimeStamp(pgn, gn); } break; + default: + break; } } @@ -558,7 +563,7 @@ Compat_Run(targs) Lst targs; /* List of target nodes to re-create */ { char *cp; /* Pointer to string of shell meta-characters */ - GNode *gn; /* Current root target */ + GNode *gn = NULL;/* Current root target */ int errors; /* Number of targets not remade due to errors */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) { @@ -575,7 +580,7 @@ Compat_Run(targs) } for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) { - meta[*cp] = 1; + meta[(unsigned char) *cp] = 1; } /* * The null character serves as a sentinel in the string. diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c index d84b2e9a9ea..94ee378f61b 100644 --- a/usr.bin/make/cond.c +++ b/usr.bin/make/cond.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)cond.c 5.6 (Berkeley) 6/1/90";*/ -static char rcsid[] = "$Id: cond.c,v 1.3 1994/01/13 21:01:45 jtc Exp $"; +/* from: static char sccsid[] = "@(#)cond.c 5.6 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: cond.c,v 1.4 1994/03/05 00:34:39 cgd Exp $"; #endif /* not lint */ /*- @@ -50,11 +50,12 @@ static char rcsid[] = "$Id: cond.c,v 1.3 1994/01/13 21:01:45 jtc Exp $"; * */ -#include "make.h" -#include <buf.h> -#include <stdio.h> -#include <stdlib.h> #include <ctype.h> +#include <math.h> +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "buf.h" /* * The parsing of conditional expressions is based on this grammar: @@ -94,8 +95,17 @@ typedef enum { * Structures to handle elegantly the different forms of #if's. The * last two fields are stored in condInvert and condDefProc, respectively. */ -static Boolean CondDoDefined(), - CondDoMake(); +static int CondGetArg __P((char **, char **, char *, Boolean)); +static Boolean CondDoDefined __P((int, char *)); +static int CondStrMatch __P((char *, char *)); +static Boolean CondDoMake __P((int, char *)); +static Boolean CondDoExists __P((int, char *)); +static Boolean CondDoTarget __P((int, char *)); +static Boolean CondCvtArg __P((char *, double *)); +static Token CondToken __P((Boolean)); +static Token CondT __P((Boolean)); +static Token CondF __P((Boolean)); +static Token CondE __P((Boolean)); static struct If { char *form; /* Form of if */ @@ -103,12 +113,12 @@ static struct If { Boolean doNot; /* TRUE if default function should be negated */ Boolean (*defProc)(); /* Default function to apply */ } ifs[] = { - "ifdef", 5, FALSE, CondDoDefined, - "ifndef", 6, TRUE, CondDoDefined, - "ifmake", 6, FALSE, CondDoMake, - "ifnmake", 7, TRUE, CondDoMake, - "if", 2, FALSE, CondDoDefined, - (char *)0, 0, FALSE, (Boolean (*)())0, + { "ifdef", 5, FALSE, CondDoDefined }, + { "ifndef", 6, TRUE, CondDoDefined }, + { "ifmake", 6, FALSE, CondDoMake }, + { "ifnmake", 7, TRUE, CondDoMake }, + { "if", 2, FALSE, CondDoDefined }, + { (char *)0, 0, FALSE, (Boolean (*)())0 } }; static Boolean condInvert; /* Invert the default function */ @@ -125,8 +135,6 @@ static int skipIfLevel=0; /* Depth of skipped conditionals */ static Boolean skipLine = FALSE; /* Whether the parse module is skipping * lines */ -static Token CondT(), CondF(), CondE(); - /*- *----------------------------------------------------------------------- * CondPushBack -- @@ -204,7 +212,7 @@ CondGetArg (linePtr, argPtr, func, parens) */ buf = Buf_Init(16); - while ((index(" \t)&|", *cp) == (char *)NULL) && (*cp != '\0')) { + while ((strchr(" \t)&|", *cp) == (char *)NULL) && (*cp != '\0')) { if (*cp == '$') { /* * Parse the variable spec and install it as part of the argument @@ -407,60 +415,45 @@ CondDoTarget (argLen, arg) *----------------------------------------------------------------------- * CondCvtArg -- * Convert the given number into a double. If the number begins - * with 0x, or just x, it is interpreted as a hexadecimal integer + * with 0x, it is interpreted as a hexadecimal integer * and converted to a double from there. All other strings just have - * atof called on them. + * strtod called on them. * * Results: - * The double value of string. + * Sets 'value' to double value of string. + * Returns true if the string was a valid number, false o.w. * * Side Effects: + * Can change 'value' even if string is not a valid number. * * *----------------------------------------------------------------------- */ -static double -CondCvtArg(str) +static Boolean +CondCvtArg(str, value) register char *str; + double *value; { - int sign = 1; - double atof(); - - if (*str == '-') { - sign = -1; - str++; - } else if (*str == '+') { - str++; - } - if (((*str == '0') && (str[1] == 'x')) || - (*str == 'x')) - { - register int i; - - str += (*str == 'x') ? 1 : 2; + if ((*str == '0') && (str[1] == 'x')) { + register long i; - i = 0; - - while (isxdigit(*str)) { - i *= 16; - if (*str <= '9') { - i += *str - '0'; - } else if (*str <= 'F') { - i += *str - 'A' + 10; - } else { - i += *str - 'a' + 10; - } - str++; - } - if (sign < 0) { - return((double)(-i)); - } else { - return((double)i); + for (str += 2, i = 0; *str; str++) { + int x; + if (isdigit((unsigned char) *str)) + x = *str - '0'; + else if (isxdigit((unsigned char) *str)) + x = 10 + *str - isupper((unsigned char) *str) ? 'A' : 'a'; + else + return FALSE; + i = (i << 4) + x; } - } else if (sign < 0) { - return(- atof(str)); - } else { - return(atof(str)); + *value = (double) i; + return TRUE; + } + else { + char *eptr; + *value = strtod(str, &eptr); + return *eptr == '\0'; } } @@ -540,12 +533,34 @@ CondToken(doEval) } condExpr += varSpecLen; + if (!isspace(*condExpr) && strchr("!=><", *condExpr) == NULL) { + Buffer buf; + char *cp; + + buf = Buf_Init(0); + + for (cp = lhs; *cp; cp++) + Buf_AddByte(buf, (Byte)*cp); + + if (doFree) + free(lhs); + + for (;*condExpr && !isspace(*condExpr); condExpr++) + Buf_AddByte(buf, (Byte)*condExpr); + + Buf_AddByte(buf, (Byte)'\0'); + lhs = (char *)Buf_GetAll(buf, &varSpecLen); + Buf_Destroy(buf, FALSE); + + doFree = TRUE; + } + /* * Skip whitespace to get to the operator */ - while (isspace(*condExpr)) { + while (isspace(*condExpr)) condExpr++; - } + /* * Make sure the operator is a valid one. If it isn't a * known relational operator, pretend we got a @@ -586,8 +601,10 @@ do_compare: */ char *string; char *cp, *cp2; + int qt; Buffer buf; +do_string_compare: if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { Parse_Error(PARSE_WARNING, "String comparison operator should be either == or !="); @@ -595,8 +612,12 @@ do_compare: } buf = Buf_Init(0); + qt = *rhs == '"' ? 1 : 0; - for (cp = rhs+1; (*cp != '"') && (*cp != '\0'); cp++) { + for (cp = &rhs[qt]; + ((qt && (*cp != '"')) || + (!qt && strchr(" \t)", *cp) == NULL)) && + (*cp != '\0'); cp++) { if ((*cp == '\\') && (cp[1] != '\0')) { /* * Backslash escapes things -- skip over next @@ -643,7 +664,10 @@ do_compare: } free(string); if (rhs == condExpr) { - condExpr = cp + 1; + if (!qt && *cp == ')') + condExpr = cp; + else + condExpr = cp + 1; } } else { /* @@ -653,7 +677,8 @@ do_compare: double left, right; char *string; - left = CondCvtArg(lhs); + if (!CondCvtArg(lhs, &left)) + goto do_string_compare; if (*rhs == '$') { int len; Boolean freeIt; @@ -662,16 +687,19 @@ do_compare: if (string == var_Error) { right = 0.0; } else { - right = CondCvtArg(string); - if (freeIt) { - free(string); + if (!CondCvtArg(string, &right)) { + if (freeIt) + free(string); + goto do_string_compare; } - if (rhs == condExpr) { + if (freeIt) + free(string); + if (rhs == condExpr) condExpr += len; - } } } else { - right = CondCvtArg(rhs); + if (!CondCvtArg(rhs, &right)) + goto do_string_compare; if (rhs == condExpr) { /* * Skip over the right-hand side @@ -720,9 +748,8 @@ do_compare: } } error: - if (doFree) { + if (doFree) free(lhs); - } break; } default: { @@ -793,7 +820,14 @@ error: if (val == var_Error) { t = Err; } else { - t = (*val == '\0') ? True : False; + /* + * A variable is empty when it just contains + * spaces... 4/15/92, christos + */ + char *p; + for (p = val; *p && isspace(*p); p++) + continue; + t = (*p == '\0') ? True : False; } if (doFree) { free(val); @@ -1022,12 +1056,13 @@ CondE(doEval) * *----------------------------------------------------------------------- */ +int Cond_Eval (line) char *line; /* Line to parse */ { struct If *ifp; Boolean isElse; - Boolean value; + Boolean value = FALSE; int level; /* Level at which to report errors. */ level = PARSE_FATAL; @@ -1157,6 +1192,8 @@ Cond_Eval (line) Parse_Error (level, "Malformed conditional (%s)", line); return (COND_INVALID); + default: + break; } } if (!isElse) { diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h index 28949868927..a5baadac0d8 100644 --- a/usr.bin/make/config.h +++ b/usr.bin/make/config.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)config.h 5.9 (Berkeley) 6/1/90 - * $Id: config.h,v 1.2 1993/08/01 18:12:05 mycroft Exp $ + * $Id: config.h,v 1.3 1994/03/05 00:34:40 cgd Exp $ */ #define DEFSHELL 1 /* Bourne shell */ @@ -86,4 +86,8 @@ */ #define LIBSUFF ".a" #define RECHECK + +#ifndef RANLIBMAG +#define RANLIBMAG "__.SYMDEF" +#endif /*#define POSIX*/ diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c index b4e82ce6160..5dd748c819e 100644 --- a/usr.bin/make/dir.c +++ b/usr.bin/make/dir.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)dir.c 5.6 (Berkeley) 12/28/90";*/ -static char rcsid[] = "$Id: dir.c,v 1.4 1994/01/13 21:01:47 jtc Exp $"; +/* from: static char sccsid[] = "@(#)dir.c 5.6 (Berkeley) 12/28/90"; */ +static char *rcsid = "$Id: dir.c,v 1.5 1994/03/05 00:34:41 cgd Exp $"; #endif /* not lint */ /*- @@ -83,12 +83,12 @@ static char rcsid[] = "$Id: dir.c,v 1.4 1994/01/13 21:01:47 jtc Exp $"; */ #include <stdio.h> -#include <stdlib.h> #include <sys/types.h> -#include <sys/stat.h> #include <dirent.h> +#include <sys/stat.h> #include "make.h" #include "hash.h" +#include "dir.h" /* * A search path consists of a Lst of Path structures. A Path structure @@ -173,14 +173,6 @@ static int hits, /* Found in directory cache */ nearmisses, /* Found under search path */ bigmisses; /* Sought by itself */ -typedef struct Path { - char *name; /* Name of directory */ - int refCount; /* Number of paths with this directory */ - int hits; /* the number of times a file in this - * directory has been found */ - Hash_Table files; /* Hash table of files in directory */ -} Path; - static Path *dot; /* contents of current directory */ static Hash_Table mtimes; /* Results of doing a last-resort stat in * Dir_FindFile -- if we have to go to the @@ -193,6 +185,13 @@ static Hash_Table mtimes; /* Results of doing a last-resort stat in * should be ok, but... */ +static int DirFindName __P((Path *, char *)); +static int DirMatchFiles __P((char *, Path *, Lst)); +static void DirExpandCurly __P((char *, char *, Lst, Lst)); +static void DirExpandInt __P((char *, Lst, Lst)); +static int DirPrintWord __P((char *)); +static int DirPrintDir __P((Path *)); + /*- *----------------------------------------------------------------------- * Dir_Init -- @@ -286,7 +285,7 @@ Dir_HasWildcards (name) * Given a pattern and a Path structure, see if any files * match the pattern and add their names to the 'expansions' list if * any do. This is incomplete -- it doesn't take care of patterns like - * src/*src/*.c properly (just *.c on any of the directories), but it + * src / *src / *.c properly (just *.c on any of the directories), but it * will do for now. * * Results: @@ -305,7 +304,6 @@ DirMatchFiles (pattern, p, expansions) { Hash_Search search; /* Index into the directory's table */ Hash_Entry *entry; /* Current entry in the table */ - char *f; /* Current entry in the directory */ Boolean isDot; /* TRUE if the directory being searched is . */ isDot = (*p->name == '.' && p->name[1] == '\0'); @@ -528,11 +526,11 @@ Dir_Expand (word, path, expansions) printf("expanding \"%s\"...", word); } - cp = index(word, '{'); + cp = strchr(word, '{'); if (cp) { DirExpandCurly(word, cp, path, expansions); } else { - cp = index(word, '/'); + cp = strchr(word, '/'); if (cp) { /* * The thing has a directory component -- find the first wildcard @@ -559,13 +557,15 @@ Dir_Expand (word, path, expansions) cp--; } if (cp != word) { + char sc; /* * If the glob isn't in the first component, try and find * all the components up to the one with a wildcard. */ - *cp = '\0'; + sc = cp[1]; + cp[1] = '\0'; dirpath = Dir_FindFile(word, path); - *cp = '/'; + cp[1] = sc; /* * dirpath is null if can't find the leading component * XXX: Dir_FindFile won't find internal components. @@ -574,6 +574,9 @@ Dir_Expand (word, path, expansions) * Probably not important. */ if (dirpath != (char *)NULL) { + char *dp = &dirpath[strlen(dirpath) - 1]; + if (*dp == '/') + *dp = '\0'; path = Lst_Init(FALSE); Dir_AddDir(path, dirpath); DirExpandInt(cp+1, path, expansions); @@ -605,7 +608,7 @@ Dir_Expand (word, path, expansions) } if (DEBUG(DIR)) { Lst_ForEach(expansions, DirPrintWord, NULL); - putchar('\n'); + fputc('\n', stdout); } } @@ -646,7 +649,7 @@ Dir_FindFile (name, path) * Find the final component of the name and note whether it has a * slash in it (the name, I mean) */ - cp = rindex (name, '/'); + cp = strrchr (name, '/'); if (cp) { hasSlash = TRUE; cp += 1; @@ -807,7 +810,7 @@ Dir_FindFile (name, path) * again in such a manner, we will find it without having to do * numerous numbers of access calls. Hurrah! */ - cp = rindex (file, '/'); + cp = strrchr (file, '/'); *cp = '\0'; Dir_AddDir (path, file); *cp = '/'; @@ -820,7 +823,7 @@ Dir_FindFile (name, path) printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), file); } - entry = Hash_CreateEntry(&mtimes, (ClientData)file, + entry = Hash_CreateEntry(&mtimes, (char *) file, (Boolean *)NULL); Hash_SetValue(entry, stb.st_mtime); nearmisses += 1; @@ -956,7 +959,7 @@ Dir_MTime (gn) */ if (DEBUG(DIR)) { printf("Using cached time %s for %s\n", - Targ_FmtTime(Hash_GetValue(entry)), fullName); + Targ_FmtTime((time_t) Hash_GetValue(entry)), fullName); } stb.st_mtime = (time_t)Hash_GetValue(entry); Hash_DeleteEntry(&mtimes, entry); @@ -1000,8 +1003,6 @@ Dir_AddDir (path, name) register Path *p; /* pointer to new Path structure */ DIR *d; /* for reading directory */ register struct dirent *dp; /* entry in directory */ - Hash_Entry *he; - char *fName; ln = Lst_Find (openDirectories, (ClientData)name, DirFindName); if (ln != NILLNODE) { @@ -1135,9 +1136,6 @@ void Dir_Destroy (p) Path *p; /* The directory descriptor to nuke */ { - Hash_Search thing1; - Hash_Entry *thing2; - p->refCount -= 1; if (p->refCount == 0) { @@ -1210,6 +1208,7 @@ Dir_Concat(path1, path2) } /********** DEBUG INFO **********/ +void Dir_PrintDirectories() { LstNode ln; @@ -1232,6 +1231,7 @@ Dir_PrintDirectories() static int DirPrintDir (p) Path *p; { printf ("%s ", p->name); return (0); } +void Dir_PrintPath (path) Lst path; { diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h new file mode 100644 index 00000000000..4f3adc2970f --- /dev/null +++ b/usr.bin/make/dir.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)dir.h 5.4 (Berkeley) 12/28/90 + * $Id: dir.h,v 1.1 1994/03/05 00:34:43 cgd Exp $ + */ + +/* dir.h -- + */ + +#ifndef _DIR +#define _DIR + +typedef struct Path { + char *name; /* Name of directory */ + int refCount; /* Number of paths with this directory */ + int hits; /* the number of times a file in this + * directory has been found */ + Hash_Table files; /* Hash table of files in directory */ +} Path; + +void Dir_Init __P((void)); +Boolean Dir_HasWildcards __P((char *)); +void Dir_Expand __P((char *, Lst, Lst)); +char *Dir_FindFile __P((char *, Lst)); +int Dir_MTime __P((GNode *)); +void Dir_AddDir __P((Lst, char *)); +char *Dir_MakeFlags __P((char *, Lst)); +void Dir_ClearPath __P((Lst)); +void Dir_Concat __P((Lst, Lst)); +void Dir_PrintDirectories __P((void)); +void Dir_PrintPath __P((Lst)); +void Dir_Destroy __P((Path *)); +ClientData Dir_CopyDir __P((Path *)); + +#endif /* _DIR */ diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c new file mode 100644 index 00000000000..bd7436cf750 --- /dev/null +++ b/usr.bin/make/for.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 1992, The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +/* from: static char sccsid[] = "@(#)for.c 5.6 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: for.c,v 1.1 1994/03/05 00:34:44 cgd Exp $"; +#endif /* not lint */ + +/*- + * for.c -- + * Functions to handle loops in a makefile. + * + * Interface: + * For_Eval Evaluate the loop in the passed line. + * For_Run Run accumulated loop + * + */ + +#include <ctype.h> +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "buf.h" + +/* + * For statements are of the form: + * + * .for <variable> in <varlist> + * ... + * .endfor + * + * The trick is to look for the matching end inside for for loop + * To do that, we count the current nesting level of the for loops. + * and the .endfor statements, accumulating all the statements between + * the initial .for loop and the matching .endfor; + * then we evaluate the for loop for each variable in the varlist. + */ + +static int forLevel = 0; /* Nesting level */ +static char *forVar; /* Iteration variable */ +static Buffer forBuf; /* Commands in loop */ +static Lst forLst; /* List of items */ + +/* + * State of a for loop. + */ +struct For { + Buffer buf; /* Unexpanded buffer */ + char* var; /* Index name */ + Lst lst; /* List of variables */ +}; + +static int ForExec __P((char *, struct For *)); + + + + +/*- + *----------------------------------------------------------------------- + * For_Eval -- + * Evaluate the for loop in the passed line. The line + * looks like this: + * .for <variable> in <varlist> + * + * Results: + * TRUE: We found a for loop, or we are inside a for loop + * FALSE: We did not find a for loop, or we found the end of the for + * for loop. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +int +For_Eval (line) + char *line; /* Line to parse */ +{ + char *ptr = line, *sub, *wrd; + int level; /* Level at which to report errors. */ + + level = PARSE_FATAL; + + + if (forLevel == 0) { + Buffer buf; + int varlen; + + for (ptr++; *ptr && isspace(*ptr); ptr++) + continue; + /* + * If we are not in a for loop quickly determine if the statement is + * a for. + */ + if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || !isspace(ptr[3])) + return FALSE; + ptr += 3; + + /* + * we found a for loop, and now we are going to parse it. + */ + while (*ptr && isspace(*ptr)) + ptr++; + + /* + * Grab the variable + */ + buf = Buf_Init(0); + for (wrd = ptr; *ptr && !isspace(*ptr); ptr++) + continue; + Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd); + + forVar = (char *) Buf_GetAll(buf, &varlen); + if (varlen == 0) { + Parse_Error (level, "missing variable in for"); + return 0; + } + Buf_Destroy(buf, FALSE); + + while (*ptr && isspace(*ptr)) + ptr++; + + /* + * Grab the `in' + */ + if (ptr[0] != 'i' || ptr[1] != 'n' || !isspace(ptr[2])) { + Parse_Error (level, "missing `in' in for"); + printf("%s\n", ptr); + return 0; + } + ptr += 3; + + while (*ptr && isspace(*ptr)) + ptr++; + + /* + * Make a list with the remaining words + */ + forLst = Lst_Init(FALSE); + buf = Buf_Init(0); + sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE); + +#define ADDWORD() \ + Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \ + Buf_AddByte(buf, (Byte) '\0'), \ + Lst_AtEnd(forLst, (ClientData) Buf_GetAll(buf, &varlen)), \ + Buf_Destroy(buf, FALSE) + + for (ptr = sub; *ptr && isspace(*ptr); ptr++) + continue; + + for (wrd = ptr; *ptr; ptr++) + if (isspace(*ptr)) { + ADDWORD(); + buf = Buf_Init(0); + while (*ptr && isspace(*ptr)) + ptr++; + wrd = ptr--; + } + if (DEBUG(FOR)) + (void) fprintf(stderr, "For: Iterator %s List %s\n", forVar, sub); + if (ptr - wrd > 0) + ADDWORD(); + else + Buf_Destroy(buf, TRUE); + free((Address) sub); + + forBuf = Buf_Init(0); + forLevel++; + return 1; + } + else if (*ptr == '.') { + + for (ptr++; *ptr && isspace(*ptr); ptr++) + continue; + + if (strncmp(ptr, "endfor", 6) == 0 && (isspace(ptr[6]) || !ptr[6])) { + if (DEBUG(FOR)) + (void) fprintf(stderr, "For: end for %d\n", forLevel); + if (--forLevel < 0) { + Parse_Error (level, "for-less endfor"); + return 0; + } + } + else if (strncmp(ptr, "for", 3) == 0 && isspace(ptr[3])) { + forLevel++; + if (DEBUG(FOR)) + (void) fprintf(stderr, "For: new loop %d\n", forLevel); + } + } + + if (forLevel != 0) { + Buf_AddBytes(forBuf, strlen(line), (Byte *) line); + Buf_AddByte(forBuf, (Byte) '\n'); + return 1; + } + else { + return 0; + } +} + +/*- + *----------------------------------------------------------------------- + * ForExec -- + * Expand the for loop for this index and push it in the Makefile + * + * Results: + * None. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static int +ForExec(name, arg) + char *name; + struct For *arg; +{ + int len; + Var_Set(arg->var, name, VAR_GLOBAL); + if (DEBUG(FOR)) + (void) fprintf(stderr, "--- %s = %s\n", arg->var, name); + Parse_FromString(Var_Subst(arg->var, (char *) Buf_GetAll(arg->buf, &len), + VAR_GLOBAL, FALSE)); + Var_Delete(arg->var, VAR_GLOBAL); + + return 0; +} + + +/*- + *----------------------------------------------------------------------- + * For_Run -- + * Run the for loop, immitating the actions of an include file + * + * Results: + * None. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +void +For_Run() +{ + struct For arg; + + if (forVar == NULL || forBuf == NULL || forLst == NULL) + return; + arg.var = forVar; + arg.buf = forBuf; + arg.lst = forLst; + forVar = NULL; + forBuf = NULL; + forLst = NULL; + + Lst_ForEach(arg.lst, ForExec, (ClientData) &arg); + + free((Address)arg.var); + Lst_Destroy(arg.lst, free); + Buf_Destroy(arg.buf, TRUE); +} diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c index 2fa0b3d032a..202648a9bdf 100644 --- a/usr.bin/make/hash.c +++ b/usr.bin/make/hash.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)hash.c 5.5 (Berkeley) 12/28/90";*/ -static char rcsid[] = "$Id: hash.c,v 1.3 1994/01/13 21:01:49 jtc Exp $"; +/* from: static char sccsid[] = "@(#)hash.c 5.5 (Berkeley) 12/28/90"; */ +static char *rcsid = "$Id: hash.c,v 1.4 1994/03/05 00:34:45 cgd Exp $"; #endif /* not lint */ /* hash.c -- @@ -48,11 +48,8 @@ static char rcsid[] = "$Id: hash.c,v 1.3 1994/01/13 21:01:49 jtc Exp $"; * table. Hash tables grow automatically as the amount of * information increases. */ - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> #include "sprite.h" +#include "make.h" #include "hash.h" /* @@ -60,7 +57,7 @@ static char rcsid[] = "$Id: hash.c,v 1.3 1994/01/13 21:01:49 jtc Exp $"; * defined: */ -static void RebuildTable(); +static void RebuildTable __P((Hash_Table *)); /* * The following defines the ratio of # entries to # buckets @@ -104,7 +101,7 @@ Hash_InitTable(t, numBuckets) i = 16; else { for (i = 2; i < numBuckets; i <<= 1) - /* void */ ; + continue; } t->numEntries = 0; t->size = i; @@ -136,7 +133,7 @@ void Hash_DeleteTable(t) Hash_Table *t; { - register struct Hash_Entry **hp, *h, *nexth; + register struct Hash_Entry **hp, *h, *nexth = NULL; register int i; for (hp = t->bucketPtr, i = t->size; --i >= 0;) { @@ -397,7 +394,7 @@ static void RebuildTable(t) register Hash_Table *t; { - register Hash_Entry *e, *next, **hp, **xp; + register Hash_Entry *e, *next = NULL, **hp, **xp; register int i, mask; register Hash_Entry **oldhp; int oldsize; diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h index 23e9d982a3d..dca6c0956ab 100644 --- a/usr.bin/make/hash.h +++ b/usr.bin/make/hash.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)hash.h 5.4 (Berkeley) 12/28/90 - * $Id: hash.h,v 1.2 1993/08/01 18:12:04 mycroft Exp $ + * $Id: hash.h,v 1.3 1994/03/05 00:34:46 cgd Exp $ */ /* hash.h -- @@ -106,20 +106,12 @@ typedef struct Hash_Search { #define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int)) -/* - * The following procedure declarations and macros - * are the only things that should be needed outside - * the implementation code. - */ +void Hash_InitTable __P((Hash_Table *, int)); +void Hash_DeleteTable __P((Hash_Table *)); +Hash_Entry *Hash_FindEntry __P((Hash_Table *, char *)); +Hash_Entry *Hash_CreateEntry __P((Hash_Table *, char *, Boolean *)); +void Hash_DeleteEntry __P((Hash_Table *, Hash_Entry *)); +Hash_Entry *Hash_EnumFirst __P((Hash_Table *, Hash_Search *)); +Hash_Entry *Hash_EnumNext __P((Hash_Search *)); -extern Hash_Entry * Hash_CreateEntry(); -extern void Hash_DeleteTable(); -extern void Hash_DeleteEntry(); -extern void Hash_DeleteTable(); -extern Hash_Entry * Hash_EnumFirst(); -extern Hash_Entry * Hash_EnumNext(); -extern Hash_Entry * Hash_FindEntry(); -extern void Hash_InitTable(); -extern void Hash_PrintStats(); - -#endif _HASH +#endif /* _HASH */ diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c index 547c253816a..32f81b6ced2 100644 --- a/usr.bin/make/job.c +++ b/usr.bin/make/job.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)job.c 5.15 (Berkeley) 3/1/91";*/ -static char rcsid[] = "$Id: job.c,v 1.4 1994/01/13 21:01:51 jtc Exp $"; +/* from: static char sccsid[] = "@(#)job.c 5.15 (Berkeley) 3/1/91"; */ +static char *rcsid = "$Id: job.c,v 1.5 1994/03/05 00:34:48 cgd Exp $"; #endif /* not lint */ /*- @@ -97,7 +97,7 @@ static char rcsid[] = "$Id: job.c,v 1.4 1994/01/13 21:01:51 jtc Exp $"; * Job_Wait Wait for all currently-running jobs to finish. */ -#include "make.h" +#include <sys/types.h> #include <sys/signal.h> #include <sys/stat.h> #include <sys/file.h> @@ -106,10 +106,11 @@ static char rcsid[] = "$Id: job.c,v 1.4 1994/01/13 21:01:51 jtc Exp $"; #include <fcntl.h> #include <errno.h> #include <stdio.h> -#include <stdlib.h> #include <string.h> -#include <ctype.h> -#include <unistd.h> +#include <signal.h> +#include "make.h" +#include "hash.h" +#include "dir.h" #include "job.h" #include "pathnames.h" @@ -118,8 +119,8 @@ extern int errno; /* * error handling variables */ -int errors = 0; /* number of errors reported */ -int aborting = 0; /* why is the make aborting? */ +static int errors = 0; /* number of errors reported */ +static int aborting = 0; /* why is the make aborting? */ #define ABORT_ERROR 1 /* Because of an error */ #define ABORT_INTERRUPT 2 /* Because it was interrupted */ #define ABORT_WAIT 3 /* Waiting for jobs to finish */ @@ -189,22 +190,22 @@ static Shell shells[] = { (char *)0, (char *)0, } }; -Shell *commandShell = &shells[DEFSHELL]; /* this is the shell to +static Shell *commandShell = &shells[DEFSHELL];/* this is the shell to * which we pass all * commands in the Makefile. * It is set by the * Job_ParseShell function */ -char *shellPath = (char *) NULL, /* full pathname of +static char *shellPath = (char *) NULL, /* full pathname of * executable image */ *shellName; /* last component of shell */ static int maxJobs; /* The most children we can run at once */ static int maxLocal; /* The most local ones we can have */ -int nJobs; /* The number of children currently running */ -int nLocal; /* The number of local children */ -Lst jobs; /* The structures that describe them */ -Boolean jobFull; /* Flag to tell when the job table is full. It +int nJobs; /* The number of children currently running */ +int nLocal; /* The number of local children */ +Lst jobs; /* The structures that describe them */ +Boolean jobFull; /* Flag to tell when the job table is full. It * is set TRUE when (1) the total number of * running jobs equals the maximum allowed or * (2) a job can only be run locally, but @@ -214,9 +215,9 @@ static fd_set outputs; /* Set of descriptors of pipes connected to * the output channels of children */ #endif -GNode *lastNode; /* The node for which output was most recently +GNode *lastNode; /* The node for which output was most recently * produced. */ -char *targFmt; /* Format string to use to head output from a +char *targFmt; /* Format string to use to head output from a * job when it's not the most-recent job heard * from */ #define TARG_FMT "--- %s ---\n" /* Default format */ @@ -227,20 +228,34 @@ char *targFmt; /* Format string to use to head output from a * been migrated home, the job is placed on the stoppedJobs queue to be run * when the next job finishes. */ -Lst stoppedJobs; /* Lst of Job structures describing +Lst stoppedJobs; /* Lst of Job structures describing * jobs that were stopped due to concurrency * limits or migration home */ +#if defined(USE_PGRP) && defined(SYSV) +#define KILL(pid,sig) killpg (-(pid),(sig)) +#else # if defined(USE_PGRP) -#define KILL(pid,sig) killpg((pid),(sig)) +#define KILL(pid,sig) killpg ((pid),(sig)) # else -#define KILL(pid,sig) kill((pid),(sig)) +#define KILL(pid,sig) kill ((pid),(sig)) # endif +#endif -static void JobRestart(); -static int JobStart(); -static void JobInterrupt(); +static int JobCondPassSig __P((Job *, int)); +static void JobPassSig __P((int)); +static int JobCmpPid __P((Job *, int)); +static int JobPrintCommand __P((char *, Job *)); +static int JobSaveCommand __P((char *, GNode *)); +static void JobFinish __P((Job *, union wait)); +static void JobExec __P((Job *, char **)); +static void JobMakeArgv __P((Job *, char **)); +static void JobRestart __P((Job *)); +static int JobStart __P((GNode *, int, Job *)); +static void JobDoOutput __P((Job *, Boolean)); +static Shell *JobMatchShell __P((char *)); +static void JobInterrupt __P((int)); /*- *----------------------------------------------------------------------- @@ -314,7 +329,7 @@ JobPassSig(signo) * Leave gracefully if SIGQUIT, rather than core dumping. */ if (signo == SIGQUIT) { - Finish(); + Finish(0); } /* @@ -406,6 +421,7 @@ JobPrintCommand (cmd, job) noSpecials = (noExecute && ! (job->node->type & OP_MAKE)); if (strcmp (cmd, "...") == 0) { + job->node->type |= OP_SAVE_CMDS; if ((job->flags & JOB_IGNDOTS) == 0) { job->tailCmds = Lst_Succ (Lst_Member (job->node->commands, (ClientData)cmd)); @@ -423,7 +439,7 @@ JobPrintCommand (cmd, job) * the variables in the command. */ cmdNode = Lst_Member (job->node->commands, (ClientData)cmd); - cmdStart = cmd = Var_Subst (cmd, job->node, FALSE); + cmdStart = cmd = Var_Subst (NULL, cmd, job->node, FALSE); Lst_Replace (cmdNode, (ClientData)cmdStart); cmdTemplate = "%s\n"; @@ -440,7 +456,8 @@ JobPrintCommand (cmd, job) cmd++; } - while (isspace(*cmd)) cmd++; + while (isspace((unsigned char) *cmd)) + cmd++; if (shutUp) { if (! (job->flags & JOB_SILENT) && !noSpecials && @@ -542,7 +559,7 @@ JobSaveCommand (cmd, gn) char *cmd; GNode *gn; { - cmd = Var_Subst (cmd, gn, FALSE); + cmd = Var_Subst (NULL, cmd, gn, FALSE); (void)Lst_AtEnd (postCommands->commands, (ClientData)cmd); return (0); } @@ -571,7 +588,7 @@ JobSaveCommand (cmd, gn) *----------------------------------------------------------------------- */ /*ARGSUSED*/ -void +static void JobFinish (job, status) Job *job; /* job to finish */ union wait status; /* sub-why job went away */ @@ -822,7 +839,6 @@ Job_Touch (gn, silent) { int streamID; /* ID of stream opened to do the touch */ struct timeval times[2]; /* Times for utimes() call */ - struct stat attr; /* Attributes of the file */ if (gn->type & (OP_JOIN|OP_USE|OP_EXEC|OP_OPTIONAL)) { /* @@ -888,7 +904,8 @@ Boolean Job_CheckCommands (gn, abortProc) GNode *gn; /* The target whose commands need * verifying */ - void (*abortProc)(); /* Function to abort with message */ + void (*abortProc) __P((char *, ...)); + /* Function to abort with message */ { if (OP_NOP(gn->type) && Lst_IsEmpty (gn->commands) && (gn->type & OP_LIB) == 0) { @@ -1078,7 +1095,7 @@ JobExec(job, argv) } if (job->flags & JOB_REMOTE) { - job->rmtID = (char *)0; + job->rmtID = 0; } else { nLocal += 1; /* @@ -1091,7 +1108,9 @@ JobExec(job, argv) } } +#ifdef RMT_NO_EXEC jobExecFinish: +#endif /* * Now the job is actually running, add it to the table. */ @@ -1349,7 +1368,6 @@ JobStart (gn, flags, previous) { register Job *job; /* new job descriptor */ char *argv[4]; /* Argument vector to shell */ - char args[5]; /* arguments to shell */ static int jobno = 0; /* job number of catching output in a file */ Boolean cmdsOK; /* true if the nodes commands were all right */ Boolean local; /* Set true if the job was run locally */ @@ -1423,7 +1441,7 @@ JobStart (gn, flags, previous) * used to be backwards; replace when start doing multiple commands * per shell. */ - if (1) { + if (compatMake) { /* * Be compatible: If this is the first time for this node, * verify its commands are ok and open the commands list for @@ -1653,7 +1671,7 @@ JobStart (gn, flags, previous) * curPos may be shifted as may the contents of outBuf. *----------------------------------------------------------------------- */ -void +static void JobDoOutput (job, finish) register Job *job; /* the job whose output needs printing */ Boolean finish; /* TRUE if this is the last time we'll be @@ -1665,7 +1683,6 @@ JobDoOutput (job, finish) register int max; /* limit for i (end of current data) */ int nRead; /* (Temporary) number of bytes read */ - char c; /* character after noPrint string */ FILE *oFILE; /* Stream pointer to shell's output file */ char inLine[132]; @@ -1799,9 +1816,8 @@ end_loop: fflush (stdout); } if (i < max - 1) { - bcopy (&job->outBuf[i + 1], /* shift the remaining */ - job->outBuf, /* characters down */ - max - (i + 1)); + /* shift the remaining characters down */ + memcpy ( job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); } else { @@ -1987,7 +2003,9 @@ Job_CatchOutput () fd_set readfds; register LstNode ln; register Job *job; +#ifdef RMT_WILL_WATCH int pnJobs; /* Previous nJobs */ +#endif fflush(stdout); #ifdef RMT_WILL_WATCH @@ -2018,7 +2036,7 @@ Job_CatchOutput () timeout.tv_sec = SEL_SEC; timeout.tv_usec = SEL_USEC; - if ((nfds = select (FD_SETSIZE, &readfds, (int *) 0, (int *) 0, &timeout)) < 0) + if ((nfds = select (FD_SETSIZE, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout)) < 0) { return; } else { @@ -2336,7 +2354,7 @@ Job_ParseShell (line) } words = brk_string (line, &wordCount); - bzero ((Address)&newShell, sizeof(newShell)); + memset ((Address)&newShell, 0, sizeof(newShell)); /* * Parse the specification by keyword @@ -2400,7 +2418,7 @@ Job_ParseShell (line) * path the user gave for the shell. */ shellPath = path; - path = rindex (path, '/'); + path = strrchr (path, '/'); if (path == (char *)NULL) { path = shellPath; } else { @@ -2472,13 +2490,10 @@ JobInterrupt (runINTERRUPT) char *file = (job->node->path == (char *)NULL ? job->node->name : job->node->path); - /* Don't unlink directories */ /* 10 Aug 92*/ - struct stat sbuf; - stat (file, &sbuf); - if (!(sbuf.st_mode & S_IFDIR)) { - if (unlink (file) == 0) { - Error ("*** %s removed", file); - } + struct stat st; + if (lstat(file, &st) != -1 && !S_ISDIR(st.st_mode) && + unlink(file) != -1) { + Error ("*** %s removed", file); } } #ifdef RMT_WANTS_SIGNALS @@ -2640,8 +2655,7 @@ Job_AbortAll () /* * Catch as many children as want to report in at first, then give up */ - while (wait3(&foo, WNOHANG, (struct rusage *)0) > 0) { - ; - } + while (wait3(&foo, WNOHANG, (struct rusage *)0) > 0) + continue; (void) unlink (tfile); } diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h index 1442cab7b5b..d4ba49d93c6 100644 --- a/usr.bin/make/job.h +++ b/usr.bin/make/job.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)job.h 5.3 (Berkeley) 6/1/90 - * $Id: job.h,v 1.2 1993/08/01 18:12:03 mycroft Exp $ + * $Id: job.h,v 1.3 1994/03/05 00:34:49 cgd Exp $ */ /*- @@ -98,7 +98,7 @@ typedef struct Job { * saved when the job has been run */ FILE *cmdFILE; /* When creating the shell script, this is * where the commands go */ - char *rmtID; /* ID returned from Rmt module */ + int rmtID; /* ID returned from Rmt module */ short flags; /* Flags to control treatment of job */ #define JOB_IGNERR 0x001 /* Ignore non-zero exits */ #define JOB_SILENT 0x002 /* no output */ @@ -216,18 +216,19 @@ extern Lst stoppedJobs; /* List of jobs that are stopped or didn't * quite get started */ extern Boolean jobFull; /* Non-zero if no more jobs should/will start*/ -/* - * These functions should be used only by an intelligent Rmt module, hence - * their names do *not* include an underscore as they are not fully exported, - * if you see what I mean. - */ -extern void JobDoOutput(/* job, final? */); /* Funnel output from - * job->outPipe to the screen, - * filtering out echo-off - * strings etc. */ -extern void JobFinish(/* job, status */); /* Finish out a job. If - * status indicates job has - * just stopped, not finished, - * the descriptor is placed on - * the stoppedJobs list. */ + +void Job_Touch __P((GNode *, Boolean)); +Boolean Job_CheckCommands __P((GNode *, void (*abortProc )(char *, ...))); +void Job_CatchChildren __P((Boolean)); +void Job_CatchOutput __P((void)); +void Job_Make __P((GNode *)); +void Job_Init __P((int, int)); +Boolean Job_Full __P((void)); +Boolean Job_Empty __P((void)); +ReturnStatus Job_ParseShell __P((char *)); +int Job_End __P((void)); +void Job_Wait __P((void)); +void Job_AbortAll __P((void)); +void JobFlagForMigration __P((int)); + #endif /* _JOB_H_ */ diff --git a/usr.bin/make/list.h b/usr.bin/make/list.h index 520d2cba660..d89b9ed7748 100644 --- a/usr.bin/make/list.h +++ b/usr.bin/make/list.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)list.h 5.3 (Berkeley) 6/1/90 - * $Id: list.h,v 1.2 1993/08/01 18:12:02 mycroft Exp $ + * $Id: list.h,v 1.3 1994/03/05 00:34:51 cgd Exp $ */ /* @@ -296,4 +296,4 @@ void List_Move(); /* move an element elsewhere in a list */ #define LIST_ATREAR(headerPtr) (((List_Links *) headerPtr)->prevPtr) -#endif _LIST +#endif /* _LIST */ diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h index 1dace269679..d1bd1c37f32 100644 --- a/usr.bin/make/lst.h +++ b/usr.bin/make/lst.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)lst.h 5.3 (Berkeley) 6/1/90 - * $Id: lst.h,v 1.2 1993/08/01 18:12:01 mycroft Exp $ + * $Id: lst.h,v 1.3 1994/03/05 00:34:52 cgd Exp $ */ /*- @@ -47,6 +47,9 @@ #define _LST_H_ #include <sprite.h> +#if __STDC__ +#include <stdlib.h> +#endif /* * basic typedef. This is what the Lst_ functions handle diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c index 0499d3ddabb..dac66b7ddee 100644 --- a/usr.bin/make/main.c +++ b/usr.bin/make/main.c @@ -43,8 +43,8 @@ char copyright[] = #endif /* not lint */ #ifndef lint -/*static char sccsid[] = "from: @(#)main.c 5.25 (Berkeley) 4/1/91";*/ -static char rcsid[] = "$Id: main.c,v 1.12 1994/01/13 21:01:55 jtc Exp $"; +/* from: static char sccsid[] = "@(#)main.c 5.25 (Berkeley) 4/1/91"; */ +static char *rcsid = "$Id: main.c,v 1.13 1994/03/05 00:34:53 cgd Exp $"; #endif /* not lint */ /*- @@ -74,16 +74,24 @@ static char rcsid[] = "$Id: main.c,v 1.12 1994/01/13 21:01:55 jtc Exp $"; * exiting. */ +#include <sys/types.h> +#include <sys/time.h> #include <sys/param.h> +#include <sys/resource.h> #include <sys/signal.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> -#include <stdlib.h> +#if __STDC__ +#include <stdarg.h> +#else #include <varargs.h> -#include <unistd.h> +#endif #include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" #include "pathnames.h" #ifndef DEFMAXLOCAL @@ -99,8 +107,9 @@ Boolean allPrecious; /* .PRECIOUS given on line by itself */ static Boolean noBuiltins; /* -r flag */ static Lst makefiles; /* ordered list of makefiles to read */ -int maxJobs; /* -j argument */ +int maxJobs; /* -J argument */ static int maxLocal; /* -L argument */ +Boolean compatMake; /* -B argument */ Boolean debug; /* -d flag */ Boolean noExecute; /* -n flag */ Boolean keepgoing; /* -k flag */ @@ -114,9 +123,10 @@ Boolean checkEnvFirst; /* -e flag */ static Boolean jobsRunning; /* TRUE if the jobs might be running */ static Boolean ReadMakefile(); +static void usage(); -static char *curdir; /* pathname of dir where make ran */ -static int obj_is_elsewhere; /* if chdir'd for an architecture */ +static char *curdir; /* startup directory */ +static char *objdir; /* where we chdir'ed to */ /*- * MainParseArgs -- @@ -139,12 +149,15 @@ MainParseArgs(argc, argv) { extern int optind; extern char *optarg; - register int i; - register char *cp; char c; optind = 1; /* since we're called more than once */ -rearg: while((c = getopt(argc, argv, "D:I:Sd:ef:ij:knqrst")) != EOF) { +#ifdef notyet +# define OPTFLAGS "BD:I:L:PSd:ef:ij:knqrst" +#else +# define OPTFLAGS "D:I:d:ef:ij:knqrst" +#endif +rearg: while((c = getopt(argc, argv, OPTFLAGS)) != EOF) { switch(c) { case 'D': Var_Set(optarg, "1", VAR_GLOBAL); @@ -156,7 +169,10 @@ rearg: while((c = getopt(argc, argv, "D:I:Sd:ef:ij:knqrst")) != EOF) { Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); break; -#ifdef notdef +#ifdef notyet + case 'B': + compatMake = TRUE; + break; case 'L': maxLocal = atoi(optarg); Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL); @@ -166,11 +182,11 @@ rearg: while((c = getopt(argc, argv, "D:I:Sd:ef:ij:knqrst")) != EOF) { usePipes = FALSE; Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL); break; -#endif case 'S': keepgoing = FALSE; Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); break; +#endif case 'd': { char *modules = optarg; @@ -188,6 +204,9 @@ rearg: while((c = getopt(argc, argv, "D:I:Sd:ef:ij:knqrst")) != EOF) { case 'd': debug |= DEBUG_DIR; break; + case 'f': + debug |= DEBUG_FOR; + break; case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; @@ -284,10 +303,10 @@ rearg: while((c = getopt(argc, argv, "D:I:Sd:ef:ij:knqrst")) != EOF) { if (!**argv) Punt("illegal (null) argument."); if (**argv == '-') { -/* 17 Mar 92*/ if ((*argv)[1]) -/* 17 Mar 92*/ optind = 0; /* -flag... */ -/* 17 Mar 92*/ else -/* 17 Mar 92*/ optind = 1; /* - */ + if ((*argv)[1]) + optind = 0; /* -flag... */ + else + optind = 1; /* - */ goto rearg; } (void)Lst_AtEnd(create, (ClientData)*argv); @@ -318,7 +337,8 @@ Main_ParseArgLine(line) if (line == NULL) return; - for (; *line == ' '; ++line); + for (; *line == ' '; ++line) + continue; if (!*line) return; @@ -343,15 +363,42 @@ Main_ParseArgLine(line) * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ +int main(argc, argv) int argc; char **argv; { Lst targs; /* target nodes to create -- passed to Make_Init */ - Boolean outOfDate; /* FALSE if all targets up to date */ - struct stat sb; - char mdpath[MAXPATHLEN + 1], *p, *path, *getenv(); - char objpath[MAXPATHLEN + 1]; + Boolean outOfDate = TRUE; /* FALSE if all targets up to date */ + struct stat sb, sa; + char *p, *path, *pwd, *getenv(), *getwd(); + char mdpath[MAXPATHLEN + 1]; + char obpath[MAXPATHLEN + 1]; + char cdpath[MAXPATHLEN + 1]; + + /* + * Find where we are and take care of PWD for the automounter... + * All this code is so that we know where we are when we start up + * on a different machine with pmake. + */ + curdir = cdpath; + if (getwd(curdir) == NULL) { + (void)fprintf(stderr, "make: %s.\n", curdir); + exit(2); + } + + if (stat(curdir, &sa) == -1) { + (void)fprintf(stderr, "make: %s: %s.\n", + curdir, strerror(errno)); + exit(2); + } + + if ((pwd = getenv("PWD")) != NULL) { + if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino && + sa.st_dev == sb.st_dev) + (void) strcpy(curdir, pwd); + } + /* * if the MAKEOBJDIR (or by default, the _PATH_OBJDIR) directory @@ -362,34 +409,51 @@ main(argc, argv) */ if (!(path = getenv("MAKEOBJDIR"))) { path = _PATH_OBJDIR; - snprintf(mdpath, MAXPATHLEN + 1, "%s.%s", path, MACHINE); - } else { - strncpy(mdpath, path, MAXPATHLEN + 1); + (void) sprintf(mdpath, "%s.%s", path, MACHINE); } + else + (void) strncpy(mdpath, path, MAXPATHLEN + 1); + + if (stat(mdpath, &sb) == 0 && S_ISDIR(sb.st_mode)) { - curdir = emalloc((u_int)MAXPATHLEN + 1); - if (!getwd(curdir)) { - (void)fprintf(stderr, "make: %s.\n", curdir); - exit(2); - } - if (!lstat(mdpath, &sb)) { - snprintf(objpath, MAXPATHLEN + 1, "%s/%s", curdir, mdpath); - if (chdir(mdpath)) + if (chdir(mdpath)) { (void)fprintf(stderr, "make warning: %s: %s.\n", - mdpath, strerror(errno)); - else - obj_is_elsewhere = 1; - } else { - if (!lstat(path, &sb)) { - snprintf(objpath, MAXPATHLEN + 1, "%s/%s", curdir, path); - if (chdir(path)) - (void)fprintf(stderr, "make warning: %s: %s.\n", - path, strerror(errno)); + mdpath, strerror(errno)); + objdir = curdir; + } + else { + if (mdpath[0] != '/') { + (void) sprintf(obpath, "%s/%s", curdir, mdpath); + objdir = obpath; + } else - obj_is_elsewhere = 1; + objdir = mdpath; + } + } + else { + if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + + if (chdir(path)) { + (void)fprintf(stderr, "make warning: %s: %s.\n", + path, strerror(errno)); + objdir = curdir; + } + else { + if (path[0] != '/') { + (void) sprintf(obpath, "%s/%s", curdir, + path); + objdir = obpath; + } + else + objdir = obpath; + } } + else + objdir = curdir; } + setenv("PWD", objdir, 1); + create = Lst_Init(FALSE); makefiles = Lst_Init(FALSE); beSilent = FALSE; /* Print commands as executed */ @@ -406,7 +470,13 @@ main(argc, argv) maxJobs = DEFMAXJOBS; /* Set default max concurrency */ maxLocal = DEFMAXLOCAL; /* Set default local max concurrency */ +#ifdef notyet + compatMake = FALSE; /* No compat mode */ +#else + compatMake = TRUE; /* No compat mode */ +#endif + /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the @@ -418,11 +488,10 @@ main(argc, argv) * directories */ Var_Init(); /* As well as the lists of variables for * parsing arguments */ - - if (obj_is_elsewhere) + if (objdir != curdir) Dir_AddDir(dirSearchPath, curdir); Var_Set(".CURDIR", curdir, VAR_GLOBAL); - Var_Set(".OBJDIR", obj_is_elsewhere?objpath:curdir, VAR_GLOBAL); + Var_Set(".OBJDIR", objdir, VAR_GLOBAL); /* * Initialize various variables. @@ -433,8 +502,12 @@ main(argc, argv) Var_Set("MAKE", argv[0], VAR_GLOBAL); Var_Set(MAKEFLAGS, "", VAR_GLOBAL); Var_Set("MFLAGS", "", VAR_GLOBAL); +#ifdef MACHINE Var_Set("MACHINE", MACHINE, VAR_GLOBAL); +#endif +#ifdef MACHINE_ARCH Var_Set("MACHINE_ARCH", MACHINE_ARCH, VAR_GLOBAL); +#endif /* * First snag any flags out of the MAKE environment variable. @@ -499,7 +572,7 @@ main(argc, argv) Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL), VAR_GLOBAL); /* Install all the flags into the MAKE envariable. */ - if ((p = Var_Value(MAKEFLAGS, VAR_GLOBAL)) && *p) + if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL)) != NULL) && *p) #ifdef POSIX setenv("MAKEFLAGS", p, 1); #else @@ -521,11 +594,12 @@ main(argc, argv) */ static char VPATH[] = "${VPATH}"; - vpath = Var_Subst(VPATH, VAR_CMD, FALSE); + vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ - for (cp = path; *cp != ':' && *cp != '\0'; cp++); + for (cp = path; *cp != ':' && *cp != '\0'; cp++) + continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; @@ -561,7 +635,7 @@ main(argc, argv) * this was original amMake -- want to allow parallelism, so put this * back in, eventually. */ - if (0) { + if (!compatMake) { /* * Initialize job module before traversing the graph, now that * any .BEGIN and .END targets have been read. This is done @@ -589,9 +663,9 @@ main(argc, argv) Targ_PrintGraph(2); if (queryFlag && outOfDate) - exit(1); + return(1); else - exit(0); + return(0); } /*- @@ -616,12 +690,12 @@ ReadMakefile(fname) Parse_File("(stdin)", stdin); Var_Set("MAKEFILE", "", VAR_GLOBAL); } else { - if (stream = fopen(fname, "r")) + if ((stream = fopen(fname, "r")) != NULL) goto found; /* if we've chdir'd, rebuild the path name */ - if (obj_is_elsewhere && *fname != '/') { + if (curdir != objdir && *fname != '/') { (void)sprintf(path, "%s/%s", curdir, fname); - if (stream = fopen(path, "r")) { + if ((stream = fopen(path, "r")) != NULL) { fname = path; goto found; } @@ -657,14 +731,22 @@ found: Var_Set("MAKEFILE", fname, VAR_GLOBAL); */ /* VARARGS */ void +#if __STDC__ +Error(char *fmt, ...) +#else Error(va_alist) va_dcl +#endif { va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else char *fmt; va_start(ap); fmt = va_arg(ap, char *); +#endif (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -684,17 +766,25 @@ Error(va_alist) */ /* VARARGS */ void +#if __STDC__ +Fatal(char *fmt, ...) +#else Fatal(va_alist) va_dcl +#endif { va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else char *fmt; + va_start(ap); + fmt = va_arg(ap, char *); +#endif if (jobsRunning) Job_Wait(); - va_start(ap); - fmt = va_arg(ap, char *); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -718,15 +808,24 @@ Fatal(va_alist) */ /* VARARGS */ void +#if __STDC__ +Punt(char *fmt, ...) +#else Punt(va_alist) va_dcl +#endif { va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else char *fmt; - (void)fprintf(stderr, "make: "); va_start(ap); fmt = va_arg(ap, char *); +#endif + + (void)fprintf(stderr, "make: "); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -792,6 +891,7 @@ emalloc(len) * enomem -- * die when out of memory. */ +void enomem() { (void)fprintf(stderr, "make: %s.\n", strerror(errno)); @@ -802,6 +902,7 @@ enomem() * usage -- * exit with usage message */ +static void usage() { (void)fprintf(stderr, diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 index ea0521674d9..e2722c15cb6 100644 --- a/usr.bin/make/make.1 +++ b/usr.bin/make/make.1 @@ -30,7 +30,7 @@ .\" SUCH DAMAGE. .\" .\" from: @(#)make.1 5.7 (Berkeley) 7/24/91 -.\" $Id: make.1,v 1.5 1994/02/10 18:25:01 jtc Exp $ +.\" $Id: make.1,v 1.6 1994/03/05 00:34:56 cgd Exp $ .\" .Dd July 24, 1991 .Dt MAKE 1 @@ -75,8 +75,7 @@ and makefiles, please refer to The options are as follows: .Bl -tag -width Ds .It Fl D Ar variable -Define -.Ar variable +Define Ar variable to be 1, in the global context. .It Fl d Ar flags Turn on debugging, and specify which portions of @@ -164,9 +163,9 @@ to .Ar value . .El .Pp -There are six different types of lines in a makefile: file dependency +There are seven different types of lines in a makefile: file dependency specifications, shell commands, variable assignments, include statements, -conditional directives, and comments. +conditional directives, for loops, and comments. .Pp In general, lines may be continued from one line to the next by ending them with a backslash @@ -488,13 +487,24 @@ This is the .At V style variable substitution. It must be the last modifier specified. -.Ar Old_string -is anchored at the end of each word, so only suffixes or entire -words may be replaced. +If +.Ar old_string +or +.Ar new_string +do not contain the pattern matching character +.Ar % +then it is assumed that they are +anchored at the end of each word, so only suffixes or entire +words may be replaced. Otherwise +.Ar % +is the substring of +.Ar old_string +to be replaced in +.Ar new_string .El -.Sh INCLUDE STATEMENTS AND CONDITIONALS -Makefile inclusion and conditional structures reminiscent of the C -programming language are provided in +.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS +Makefile inclusion, conditional structures and for loops reminiscent +of the C programming language are provided in .Nm make . All such structures are identified by a line beginning with a single dot @@ -655,20 +665,18 @@ has been defined. .El .Pp .Ar Expression -may also be an arithmetic or string comparison, with the left-hand side -being a variable expansion. -The standard C relational operators are all supported, and the usual -number/base conversion is performed. -Note, octal numbers are not supported. -If the righthand value of a +may also be an arithmetic or string comparison. Variable expansion is +performed on both sides of the comparison, after which the integral +values are compared. A value is interpreted as hexadecimal if it is +preceded by 0x, otherwise it is decimal; octal numbers are not supported. +The standard C relational operators are all supported. If after +variable expansion, either the left or right hand side of a .Ql Ic == or .Ql Ic "!=" -operator begins with a -quotation mark -.Pq Ql \*q -a string comparison is done between the expanded -variable and the text between the quotation marks. +operator is not an integral value, then +string comparison is performed between the expanded +variables. If no relational operator is given, it is assumed that the expanded variable is being compared against 0. .Pp @@ -697,6 +705,31 @@ In both cases this continues until a or .Ql Ic .endif is found. +.Pp +For loops are typically used to apply a set of rules to a list of files. +The syntax of a for loop is: +.Bl -tag -width Ds +.It Xo +.Ic \&.for +.Ar variable +.Ic in +.Ar expression +.Xc +.It Xo +<make-rules> +.Xc +.It Xo +.Ic \&.endfor +.Xc +.El +After the for +.Ic expression +is evaluated, it is split into words. The +iteration +.Ic variable +is successively set to each word, and substituted in the +.Ic make-rules +inside the body of the for loop. .Sh COMMENTS Comments begin with a hash .Pq Ql \&# diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c index 56125122387..0f57e69038d 100644 --- a/usr.bin/make/make.c +++ b/usr.bin/make/make.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)make.c 5.3 (Berkeley) 6/1/90";*/ -static char rcsid[] = "$Id: make.c,v 1.3 1994/01/13 21:01:57 jtc Exp $"; +/* from: static char sccsid[] = "@(#)make.c 5.3 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: make.c,v 1.4 1994/03/05 00:34:58 cgd Exp $"; #endif /* not lint */ /*- @@ -73,8 +73,10 @@ static char rcsid[] = "$Id: make.c,v 1.3 1994/01/13 21:01:57 jtc Exp $"; * and perform the .USE actions if so. */ -#include <stdio.h> #include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" static Lst toBeMade; /* The current fringe of the graph. These * are nodes which await examination by @@ -85,6 +87,10 @@ static int numNodes; /* Number of nodes to be processed. If this * is non-zero when Job_Empty() returns * TRUE, there's a cycle in the graph */ +static int MakeAddChild __P((GNode *, Lst)); +static int MakeAddAllSrc __P((GNode *, GNode *)); +static Boolean MakeStartJobs __P((void)); +static int MakePrintStatus __P((GNode *, Boolean)); /*- *----------------------------------------------------------------------- * Make_TimeStamp -- @@ -427,7 +433,12 @@ Make_Update (cgn) * little, so this stuff is commented out unless you're sure it's ok. * -- ardeb 1/12/88 */ - if (noExecute || Dir_MTime(cgn) == 0) { + /* + * Christos, 4/9/92: If we are saving commands pretend that + * the target is made now. Otherwise archives with ... rules + * don't work! + */ + if (noExecute || (cgn->type & OP_SAVE_CMDS) || Dir_MTime(cgn) == 0) { cgn->mtime = now; } if (DEBUG(MAKE)) { diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h index e1a8cdcd521..28e2d070325 100644 --- a/usr.bin/make/make.h +++ b/usr.bin/make/make.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)make.h 5.13 (Berkeley) 3/1/91 - * $Id: make.h,v 1.2 1993/08/01 18:12:00 mycroft Exp $ + * $Id: make.h,v 1.3 1994/03/05 00:35:00 cgd Exp $ */ /*- @@ -48,11 +48,18 @@ #define _MAKE_H_ #include <sys/types.h> +#include <stdio.h> #include <string.h> #include <ctype.h> +#include <sys/cdefs.h> +#if __STDC__ +#include <stdlib.h> +#include <unistd.h> +#endif #include "sprite.h" #include "lst.h" #include "config.h" +#include "buf.h" /*- * The structure for an individual graph node. Each node has several @@ -215,7 +222,7 @@ typedef struct GNode { * case, it ought to be a power of two simply because most storage allocation * schemes allocate in powers of two. */ -#define BSIZE 256 /* starting size for expandable buffers */ +#define MAKE_BSIZE 256 /* starting size for expandable buffers */ /* * These constants are all used by the Str_Concat function to decide how the @@ -272,6 +279,7 @@ extern Lst create; /* The list of target names specified on the extern Lst dirSearchPath; /* The list of directories to search when * looking for targets */ +extern Boolean compatMake; /* True if we are make compatible */ extern Boolean ignoreErrors; /* True if should ignore all errors */ extern Boolean beSilent; /* True if should print no commands */ extern Boolean noExecute; /* True if should execute nothing */ @@ -323,6 +331,7 @@ extern int debug; #define DEBUG_SUFF 0x0080 #define DEBUG_TARG 0x0100 #define DEBUG_VAR 0x0200 +#define DEBUG_FOR 0x0400 #ifdef __STDC__ #define CONCAT(a,b) a##b @@ -339,4 +348,11 @@ extern int debug; */ #include "nonints.h" -#endif _MAKE_H_ +int Make_TimeStamp __P((GNode *, GNode *)); +Boolean Make_OODate __P((GNode *)); +int Make_HandleUse __P((GNode *, GNode *)); +void Make_Update __P((GNode *)); +void Make_DoAllVar __P((GNode *)); +Boolean Make_Run __P((Lst)); + +#endif /* _MAKE_H_ */ diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h index 91a6a5a31ca..d1b77a9a44d 100644 --- a/usr.bin/make/nonints.h +++ b/usr.bin/make/nonints.h @@ -36,94 +36,98 @@ * SUCH DAMAGE. * * from: @(#)nonints.h 5.6 (Berkeley) 4/18/91 - * $Id: nonints.h,v 1.2 1993/08/01 18:11:59 mycroft Exp $ + * $Id: nonints.h,v 1.3 1994/03/05 00:35:02 cgd Exp $ */ -char **brk_string(), *emalloc(), *str_concat(); +/* arch.c */ +ReturnStatus Arch_ParseArchive __P((char **, Lst, GNode *)); +void Arch_Touch __P((GNode *)); +void Arch_TouchLib __P((GNode *)); +int Arch_MTime __P((GNode *)); +int Arch_MemMTime __P((GNode *)); +void Arch_FindLib __P((GNode *, Lst)); +Boolean Arch_LibOODate __P((GNode *)); +void Arch_Init __P((void)); -ReturnStatus Arch_ParseArchive (); -void Arch_Touch (); -void Arch_TouchLib (); -int Arch_MTime (); -int Arch_MemMTime (); -void Arch_FindLib (); -Boolean Arch_LibOODate (); -void Arch_Init (); -void Compat_Run(); -void Dir_Init (); -Boolean Dir_HasWildcards (); -void Dir_Expand (); -char * Dir_FindFile (); -int Dir_MTime (); -void Dir_AddDir (); -ClientData Dir_CopyDir (); -char * Dir_MakeFlags (); -void Dir_Destroy (); -void Dir_ClearPath (); -void Dir_Concat (); -int Make_TimeStamp (); -Boolean Make_OODate (); -int Make_HandleUse (); -void Make_Update (); -void Make_DoAllVar (); -Boolean Make_Run (); -void Job_Touch (); -Boolean Job_CheckCommands (); -void Job_CatchChildren (); -void Job_CatchOutput (); -void Job_Make (); -void Job_Init (); -Boolean Job_Full (); -Boolean Job_Empty (); -ReturnStatus Job_ParseShell (); -int Job_End (); -void Job_Wait(); -void Job_AbortAll (); -void Main_ParseArgLine (); -void Error (); -void Fatal (); -void Punt (); -void DieHorribly (); -void Finish (); -void Parse_Error (); -Boolean Parse_IsVar (); -void Parse_DoVar (); -void Parse_AddIncludeDir (); -void Parse_File(); -Lst Parse_MainName(); -void Suff_ClearSuffixes (); -Boolean Suff_IsTransform (); -GNode * Suff_AddTransform (); -void Suff_AddSuffix (); -int Suff_EndTransform (); -Lst Suff_GetPath (); -void Suff_DoPaths(); -void Suff_AddInclude (); -void Suff_AddLib (); -void Suff_FindDeps (); -void Suff_SetNull(); -void Suff_Init (); -void Targ_Init (); -GNode * Targ_NewGN (); -GNode * Targ_FindNode (); -Lst Targ_FindList (); -Boolean Targ_Ignore (); -Boolean Targ_Silent (); -Boolean Targ_Precious (); -void Targ_SetMain (); -int Targ_PrintCmd (); -char * Targ_FmtTime (); -void Targ_PrintType (); -char * Str_Concat (); -int Str_Match(); -void Var_Delete(); -void Var_Set (); -void Var_Append (); -Boolean Var_Exists(); -char * Var_Value (); -char * Var_Parse (); -char * Var_Subst (); -char * Var_GetTail(); -char * Var_GetHead(); -void Var_Init (); -char * Str_FindSubstring(); +/* compat.c */ +void Compat_Run __P((Lst)); + +/* cond.c */ +int Cond_Eval __P((char *)); +void Cond_End __P((void)); + +/* for.c */ +int For_Eval __P((char *)); +void For_Run __P((void)); + +/* main.c */ +void Main_ParseArgLine __P((char *)); +int main __P((int, char **)); +void Error __P((char *, ...)); +void Fatal __P((char *, ...)); +void Punt __P((char *, ...)); +void DieHorribly __P((void)); +void Finish __P((int)); +char *emalloc __P((u_int)); +void enomem __P((void)); + +/* parse.c */ +void Parse_Error __P((int, char *, ...)); +Boolean Parse_AnyExport __P((void)); +Boolean Parse_IsVar __P((char *)); +void Parse_DoVar __P((char *, GNode *)); +void Parse_AddIncludeDir __P((char *)); +void Parse_File __P((char *, FILE *)); +void Parse_Init __P((void)); +void Parse_FromString __P((char *)); +Lst Parse_MainName __P((void)); + +/* str.c */ +char *str_concat __P((char *, char *, int)); +char **brk_string __P((char *, int *)); +char *Str_FindSubstring __P((char *, char *)); +int Str_Match __P((char *, char *)); +char *Str_SYSVMatch __P((char *, char *, int *len)); +void Str_SYSVSubst __P((Buffer, char *, char *, int)); + +/* suff.c */ +void Suff_ClearSuffixes __P((void)); +Boolean Suff_IsTransform __P((char *)); +GNode *Suff_AddTransform __P((char *)); +int Suff_EndTransform __P((GNode *)); +void Suff_AddSuffix __P((char *)); +Lst Suff_GetPath __P((char *)); +void Suff_DoPaths __P((void)); +void Suff_AddInclude __P((char *)); +void Suff_AddLib __P((char *)); +void Suff_FindDeps __P((GNode *)); +void Suff_SetNull __P((char *)); +void Suff_Init __P((void)); +void Suff_PrintAll __P((void)); + +/* targ.c */ +void Targ_Init __P((void)); +GNode *Targ_NewGN __P((char *)); +GNode *Targ_FindNode __P((char *, int)); +Lst Targ_FindList __P((Lst, int)); +Boolean Targ_Ignore __P((GNode *)); +Boolean Targ_Silent __P((GNode *)); +Boolean Targ_Precious __P((GNode *)); +void Targ_SetMain __P((GNode *)); +int Targ_PrintCmd __P((char *)); +char *Targ_FmtTime __P((time_t)); +void Targ_PrintType __P((int)); +void Targ_PrintGraph __P((int)); + +/* var.c */ +void Var_Delete __P((char *, GNode *)); +void Var_Set __P((char *, char *, GNode *)); +void Var_Append __P((char *, char *, GNode *)); +Boolean Var_Exists __P((char *, GNode *)); +char *Var_Value __P((char *, GNode *)); +char *Var_Parse __P((char *, GNode *, Boolean, int *, Boolean *)); +char *Var_Subst __P((char *, char *, GNode *, Boolean)); +char *Var_GetTail __P((char *)); +char *Var_GetHead __P((char *)); +void Var_Init __P((void)); +void Var_Dump __P((GNode *)); diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c index 2aff1048d40..a933a6262f9 100644 --- a/usr.bin/make/parse.c +++ b/usr.bin/make/parse.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)parse.c 5.18 (Berkeley) 2/19/91";*/ -static char rcsid[] = "$Id: parse.c,v 1.4 1994/01/13 21:01:59 jtc Exp $"; +/* from: static char sccsid[] = "@(#)parse.c 5.18 (Berkeley) 2/19/91"; */ +static char *rcsid = "$Id: parse.c,v 1.5 1994/03/05 00:35:04 cgd Exp $"; #endif /* not lint */ /*- @@ -81,13 +81,19 @@ static char rcsid[] = "$Id: parse.c,v 1.4 1994/01/13 21:01:59 jtc Exp $"; * Parse_MainName Returns a Lst of the main target to create. */ +#if __STDC__ +#include <stdarg.h> +#else #include <varargs.h> -#include <ctype.h> +#endif #include <stdio.h> -#include <stdlib.h> -#include <unistd.h> +#include <ctype.h> +#include <errno.h> #include <sys/wait.h> #include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" #include "buf.h" #include "pathnames.h" @@ -98,15 +104,19 @@ static char rcsid[] = "$Id: parse.c,v 1.4 1994/01/13 21:01:59 jtc Exp $"; */ #define CONTINUE 1 #define DONE 0 -static int ParseEOF(); - static Lst targets; /* targets we're working on */ static Boolean inLine; /* true if currently in a dependency * line or its commands */ +typedef struct { + char *str; + char *ptr; +} PTR; static char *fname; /* name of current file (for errors) */ static int lineno; /* line number in current file */ -static FILE *curFILE; /* current makefile */ +static FILE *curFILE = NULL; /* current makefile */ + +static PTR *curPTR = NULL; /* current makefile */ static int fatals = 0; @@ -119,8 +129,9 @@ static GNode *mainNode; /* The main target to create. This is the typedef struct IFile { char *fname; /* name of previous file */ int lineno; /* saved line number */ - FILE * F; /* the open stream */ -} IFile; + FILE * F; /* the open stream */ + PTR * p; /* the char pointer */ +} IFile; static Lst includes; /* stack of IFiles generated by * #includes */ @@ -144,20 +155,21 @@ typedef enum { MFlags, /* .MFLAGS or .MAKEFLAGS */ Main, /* .MAIN and we don't have anything user-specified to * make */ + NoExport, /* .NOEXPORT */ Not, /* Not special */ NotParallel, /* .NOTPARALELL */ Null, /* .NULL */ Order, /* .ORDER */ - Path, /* .PATH */ + ExPath, /* .PATH */ Precious, /* .PRECIOUS */ - Shell, /* .SHELL */ + ExShell, /* .SHELL */ Silent, /* .SILENT */ SingleShell, /* .SINGLESHELL */ Suffixes, /* .SUFFIXES */ - Attribute, /* Generic attribute */ + Attribute /* Generic attribute */ } ParseSpecial; -ParseSpecial specType; +static ParseSpecial specType; /* * Predecessor node for handling .ORDER. Initialized to NILGNODE when .ORDER @@ -196,16 +208,37 @@ static struct { { ".NOTPARALLEL", NotParallel, 0 }, { ".NULL", Null, 0 }, { ".ORDER", Order, 0 }, -{ ".PATH", Path, 0 }, +{ ".PATH", ExPath, 0 }, { ".PRECIOUS", Precious, OP_PRECIOUS }, { ".RECURSIVE", Attribute, OP_MAKE }, -{ ".SHELL", Shell, 0 }, +{ ".SHELL", ExShell, 0 }, { ".SILENT", Silent, OP_SILENT }, { ".SINGLESHELL", SingleShell, 0 }, { ".SUFFIXES", Suffixes, 0 }, { ".USE", Attribute, OP_USE }, }; +static int ParseFindKeyword __P((char *)); +static int ParseLinkSrc __P((GNode *, GNode *)); +static int ParseDoOp __P((GNode *, int)); +static void ParseDoSrc __P((int, char *)); +static int ParseFindMain __P((GNode *)); +static int ParseAddDir __P((Lst, char *)); +static int ParseClearPath __P((Lst)); +static void ParseDoDependency __P((char *)); +static int ParseAddCmd __P((GNode *, char *)); +static int ParseReadc __P((void)); +static void ParseUnreadc __P((int)); +static int ParseHasCommands __P((GNode *)); +static void ParseDoInclude __P((char *)); +#ifdef SYSVINCLUDE +static void ParseTraditionalInclude __P((char *)); +#endif +static int ParseEOF __P((int)); +static char *ParseReadLine __P((void)); +static char *ParseSkipLine __P((int)); +static void ParseFinishLine __P((void)); + /*- *---------------------------------------------------------------------- * ParseFindKeyword -- @@ -259,18 +292,28 @@ ParseFindKeyword (str) */ /* VARARGS */ void -Parse_Error(type, va_alist) - int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */ +#if __STDC__ +Parse_Error(int type, char *fmt, ...) +#else +Parse_Error(va_alist) va_dcl +#endif { va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */ char *fmt; + va_start(ap); + type = va_arg(ap, int); + fmt = va_arg(ap, char *); +#endif + (void)fprintf(stderr, "\"%s\", line %d: ", fname, lineno); if (type == PARSE_WARNING) (void)fprintf(stderr, "warning: "); - va_start(ap); - fmt = va_arg(ap, char *); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -678,7 +721,7 @@ ParseDoDependency (line) */ int keywd = ParseFindKeyword(line); if (keywd != -1) { - if (specType == Path && parseKeywords[keywd].spec != Path) { + if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return; } @@ -714,7 +757,7 @@ ParseDoDependency (line) * .ORDER Must set initial predecessor to NIL */ switch (specType) { - case Path: + case ExPath: if (paths == NULL) { paths = Lst_Init(FALSE); } @@ -746,11 +789,13 @@ ParseDoDependency (line) break; } case SingleShell: - /* backwards = 1; */ + compatMake = 1; break; case Order: predecessor = NILGNODE; break; + default: + break; } } else if (strncmp (line, ".PATH", 5) == 0) { /* @@ -760,7 +805,7 @@ ParseDoDependency (line) */ Lst path; - specType = Path; + specType = ExPath; path = Suff_GetPath (&line[5]); if (path == NILLST) { Parse_Error (PARSE_FATAL, @@ -812,7 +857,7 @@ ParseDoDependency (line) (void)Lst_AtEnd (targets, (ClientData)gn); } - } else if (specType == Path && *line != '.' && *line != '\0') { + } else if (specType == ExPath && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } @@ -821,7 +866,7 @@ ParseDoDependency (line) * If it is a special type and not .PATH, it's the only target we * allow on this line... */ - if (specType != Not && specType != Path) { + if (specType != Not && specType != ExPath) { Boolean warn = FALSE; while ((*cp != '!') && (*cp != ':') && *cp) { @@ -920,9 +965,11 @@ ParseDoDependency (line) case Silent: beSilent = TRUE; break; - case Path: + case ExPath: Lst_ForEach(paths, ParseClearPath, (ClientData)NULL); break; + default: + break; } } else if (specType == MFlags) { /* @@ -932,7 +979,7 @@ ParseDoDependency (line) */ Main_ParseArgLine (line); *line = '\0'; - } else if (specType == Shell) { + } else if (specType == ExShell) { if (Job_ParseShell (line) != SUCCESS) { Parse_Error (PARSE_FATAL, "improper shell specification"); return; @@ -945,7 +992,7 @@ ParseDoDependency (line) /* * NOW GO FOR THE SOURCES */ - if ((specType == Suffixes) || (specType == Path) || + if ((specType == Suffixes) || (specType == ExPath) || (specType == Includes) || (specType == Libs) || (specType == Null)) { @@ -984,7 +1031,7 @@ ParseDoDependency (line) case Suffixes: Suff_AddSuffix (line); break; - case Path: + case ExPath: Lst_ForEach(paths, ParseAddDir, (ClientData)line); break; case Includes: @@ -996,6 +1043,8 @@ ParseDoDependency (line) case Null: Suff_SetNull (line); break; + default: + break; } *cp = savec; if (savec != '\0') { @@ -1259,13 +1308,12 @@ Parse_DoVar (line, ctxt) Boolean oldOldVars = oldVars; oldVars = FALSE; - cp = Var_Subst(cp, ctxt, FALSE); + cp = Var_Subst(NULL, cp, ctxt, FALSE); oldVars = oldOldVars; Var_Set(line, cp, ctxt); free(cp); } else if (type == VAR_SHELL) { - char result[BUFSIZ]; /* Result of command */ char *args[4]; /* Args for invoking the shell */ int fds[2]; /* Pipe streams */ int cpid; /* Child PID */ @@ -1273,18 +1321,19 @@ Parse_DoVar (line, ctxt) Boolean freeCmd; /* TRUE if the command needs to be freed, i.e. * if any variable expansion was performed */ + /* * Set up arguments for shell */ args[0] = "sh"; args[1] = "-c"; - if (index(cp, '$') != (char *)NULL) { + if (strchr(cp, '$') != (char *)NULL) { /* * There's a dollar sign in the command, so perform variable * expansion on the whole thing. The resulting string will need * freeing when we're done, so set freeCmd to TRUE. */ - args[2] = Var_Subst(cp, VAR_CMD, TRUE); + args[2] = Var_Subst(NULL, cp, VAR_CMD, TRUE); freeCmd = TRUE; } else { args[2] = cp; @@ -1326,34 +1375,44 @@ Parse_DoVar (line, ctxt) } else { int status; int cc; + Buffer buf; + char *res; /* * No need for the writing half */ close(fds[1]); + buf = Buf_Init (MAKE_BSIZE); + + do { + char result[BUFSIZ]; + cc = read(fds[0], result, sizeof(result)); + if (cc > 0) + Buf_AddBytes(buf, cc, (unsigned char *) result); + } + while (cc > 0 || (cc == -1 && errno == EINTR)); + /* - * Wait for the process to exit. - * - * XXX: If the child writes more than a pipe's worth, we will - * deadlock. + * Close the input side of the pipe. */ - while(((pid = wait(&status)) != cpid) && (pid >= 0)) { - ; - } + close(fds[0]); /* - * Read all the characters the child wrote. + * Wait for the process to exit. */ - cc = read(fds[0], result, sizeof(result)); + while(((pid = wait(&status)) != cpid) && (pid >= 0)) + continue; + + res = (char *)Buf_GetAll (buf, &cc); + Buf_Destroy (buf, FALSE); - if (cc < 0) { + if (cc == 0) { /* * Couldn't read the child's output -- tell the user and * set the variable to null */ Parse_Error(PARSE_WARNING, "Couldn't read shell's output"); - cc = 0; } if (status) { @@ -1363,12 +1422,13 @@ Parse_DoVar (line, ctxt) */ Parse_Error(PARSE_WARNING, "\"%s\" returned non-zero", cp); } + /* * Null-terminate the result, convert newlines to spaces and * install it in the variable. */ - result[cc] = '\0'; - cp = &result[cc] - 1; + res[cc] = '\0'; + cp = &res[cc] - 1; if (*cp == '\n') { /* @@ -1376,18 +1436,15 @@ Parse_DoVar (line, ctxt) */ *cp-- = '\0'; } - while (cp >= result) { + while (cp >= res) { if (*cp == '\n') { *cp = ' '; } cp--; } - Var_Set(line, result, ctxt); + Var_Set(line, res, ctxt); + free(res); - /* - * Close the input side of the pipe. - */ - close(fds[0]); } if (freeCmd) { free(args[2]); @@ -1410,7 +1467,7 @@ Parse_DoVar (line, ctxt) * Side Effects: * A new element is added to the commands list of the node. */ -static +static int ParseAddCmd(gn, cmd) GNode *gn; /* the node to which the command is to be added */ char *cmd; /* the command to add */ @@ -1492,7 +1549,6 @@ ParseDoInclude (file) { char *fullname; /* full pathname of file */ IFile *oldFile; /* state associated with current file */ - Lst path; /* the path to use to find the file */ char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ Boolean isSystem; /* TRUE if makefile is a system makefile */ @@ -1532,7 +1588,8 @@ ParseDoInclude (file) if (*cp != endc) { Parse_Error (PARSE_FATAL, - "Unclosed .include filename. '%c' expected", endc); + "Unclosed %cinclude filename. '%c' expected", + '.', endc); return; } *cp = '\0'; @@ -1541,7 +1598,7 @@ ParseDoInclude (file) * Substitute for any variables in the file name before trying to * find the thing. */ - file = Var_Subst (file, VAR_CMD, FALSE); + file = Var_Subst (NULL, file, VAR_CMD, FALSE); /* * Now we know the file's name and its search path, we attempt to @@ -1558,7 +1615,7 @@ ParseDoInclude (file) */ char *prefEnd; - prefEnd = rindex (fname, '/'); + prefEnd = strrchr (fname, '/'); if (prefEnd != (char *)NULL) { char *newName; @@ -1615,6 +1672,7 @@ ParseDoInclude (file) oldFile->fname = fname; oldFile->F = curFILE; + oldFile->p = curPTR; oldFile->lineno = lineno; (void) Lst_AtFront (includes, (ClientData)oldFile); @@ -1629,6 +1687,7 @@ ParseDoInclude (file) lineno = 0; curFILE = fopen (fullname, "r"); + curPTR = NULL; if (curFILE == (FILE * ) NULL) { Parse_Error (PARSE_FATAL, "Cannot open %s", fullname); /* @@ -1638,6 +1697,189 @@ ParseDoInclude (file) } } + +/*- + *--------------------------------------------------------------------- + * Parse_FromString -- + * Start Parsing from the given string + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +void +Parse_FromString(str) + char *str; +{ + IFile *oldFile; /* state associated with this file */ + + if (DEBUG(FOR)) + (void) fprintf(stderr, "%s\n----\n", str); + + oldFile = (IFile *) emalloc (sizeof (IFile)); + oldFile->lineno = lineno; + oldFile->fname = fname; + oldFile->F = curFILE; + oldFile->p = curPTR; + + (void) Lst_AtFront (includes, (ClientData)oldFile); + + curFILE = NULL; + curPTR = (PTR *) emalloc (sizeof (PTR)); + curPTR->str = curPTR->ptr = str; + lineno = 0; + fname = strdup(fname); +} + + +#ifdef SYSVINCLUDE +/*- + *--------------------------------------------------------------------- + * ParseTraditionalInclude -- + * Push to another file. + * + * The input is the line minus the "include". The file name is + * the string following the "include". + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +static void +ParseTraditionalInclude (file) + char *file; /* file specification */ +{ + char *fullname; /* full pathname of file */ + IFile *oldFile; /* state associated with current file */ + char *cp; /* current position in file spec */ + char *prefEnd; + + /* + * Skip over whitespace + */ + while ((*file == ' ') || (*file == '\t')) { + file++; + } + + if (*file == '\0') { + Parse_Error (PARSE_FATAL, + "Filename missing from \"include\""); + return; + } + + /* + * Skip to end of line or next whitespace + */ + for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) { + continue; + } + + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Var_Subst (NULL, file, VAR_CMD, FALSE); + + /* + * Now we know the file's name, we attempt to find the durn thing. + * A return of NULL indicates the file don't exist. + * + * Include files are first searched for relative to the including + * file's location. We don't want to cd there, of course, so we + * just tack on the old file's leading path components and call + * Dir_FindFile to see if we can locate the beast. + * XXX - this *does* search in the current directory, right? + */ + + prefEnd = strrchr (fname, '/'); + if (prefEnd != (char *)NULL) { + char *newName; + + *prefEnd = '\0'; + newName = str_concat (fname, file, STR_ADDSLASH); + fullname = Dir_FindFile (newName, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(newName, dirSearchPath); + } + free (newName); + *prefEnd = '/'; + } else { + fullname = (char *)NULL; + } + + if (fullname == (char *)NULL) { + /* + * System makefile or makefile wasn't found in same directory as + * included makefile. Search for it first on the -I search path, + * then on the .PATH search path, if not found in a -I directory. + * XXX: Suffix specific? + */ + fullname = Dir_FindFile (file, parseIncPath); + if (fullname == (char *)NULL) { + fullname = Dir_FindFile(file, dirSearchPath); + } + } + + if (fullname == (char *)NULL) { + /* + * Still haven't found the makefile. Look for it on the system + * path as a last resort. + */ + fullname = Dir_FindFile(file, sysIncPath); + } + + if (fullname == (char *) NULL) { + Parse_Error (PARSE_FATAL, "Could not find %s", file); + return; + } + + /* + * Once we find the absolute path to the file, we get to save all the + * state from the current file before we can start reading this + * include file. The state is stored in an IFile structure which + * is placed on a list with other IFile structures. The list makes + * a very nice stack to track how we got here... + */ + oldFile = (IFile *) emalloc (sizeof (IFile)); + oldFile->fname = fname; + + oldFile->F = curFILE; + oldFile->p = curPTR; + oldFile->lineno = lineno; + + (void) Lst_AtFront (includes, (ClientData)oldFile); + + /* + * Once the previous state has been saved, we can get down to reading + * the new file. We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. Naturally enough, we start reading at line number 0. + */ + fname = fullname; + lineno = 0; + + curFILE = fopen (fullname, "r"); + curPTR = NULL; + if (curFILE == (FILE * ) NULL) { + Parse_Error (PARSE_FATAL, "Cannot open %s", fullname); + /* + * Pop to previous file + */ + (void) ParseEOF(1); + } +} +#endif + /*- *--------------------------------------------------------------------- * ParseEOF -- @@ -1664,12 +1906,17 @@ ParseEOF (opened) } ifile = (IFile *) Lst_DeQueue (includes); - free (fname); + free ((Address) fname); fname = ifile->fname; lineno = ifile->lineno; - if (opened) + if (opened && curFILE) (void) fclose (curFILE); + if (curPTR) { + free((Address) curPTR->str); + free((Address) curPTR); + } curFILE = ifile->F; + curPTR = ifile->p; free ((Address)ifile); return (CONTINUE); } @@ -1677,24 +1924,110 @@ ParseEOF (opened) /*- *--------------------------------------------------------------------- * ParseReadc -- - * Read a character from the current file and update the line number - * counter as necessary + * Read a character from the current file * * Results: * The character that was read * * Side Effects: - * The lineno counter is incremented if the character is a newline *--------------------------------------------------------------------- */ -#ifdef notdef -static int parseReadChar; +static int +ParseReadc() +{ + if (curFILE) + return fgetc(curFILE); + + if (curPTR && *curPTR->ptr) + return *curPTR->ptr++; + return EOF; +} -#define ParseReadc() (((parseReadChar = getc(curFILE)) == '\n') ? \ - (lineno++, '\n') : parseReadChar) -#else -#define ParseReadc() (getc(curFILE)) -#endif /* notdef */ + +/*- + *--------------------------------------------------------------------- + * ParseUnreadc -- + * Put back a character to the current file + * + * Results: + * None. + * + * Side Effects: + *--------------------------------------------------------------------- + */ +static void +ParseUnreadc(c) + int c; +{ + if (curFILE) { + ungetc(c, curFILE); + return; + } + if (curPTR) { + *--(curPTR->ptr) = c; + return; + } +} + + +/* ParseSkipLine(): + * Grab the next line + */ +static char * +ParseSkipLine(skip) + int skip; /* Skip lines that don't start with . */ +{ + char *line; + int c, lastc = '\0', lineLength; + Buffer buf; + + c = ParseReadc(); + + if (skip) { + /* + * Skip lines until get to one that begins with a + * special char. + */ + while ((c != '.') && (c != EOF)) { + while (((c != '\n') || (lastc == '\\')) && (c != EOF)) + { + /* + * Advance to next unescaped newline + */ + if ((lastc = c) == '\n') { + lineno++; + } + c = ParseReadc(); + } + lineno++; + + lastc = c; + c = ParseReadc (); + } + } + + if (c == EOF) { + Parse_Error (PARSE_FATAL, "Unclosed conditional/for loop"); + return ((char *)NULL); + } + + /* + * Read the entire line into buf + */ + buf = Buf_Init (MAKE_BSIZE); + if (c != '\n') { + do { + Buf_AddByte (buf, (Byte)c); + c = ParseReadc(); + } while ((c != '\n') && (c != EOF)); + } + lineno++; + + Buf_AddByte (buf, (Byte)'\0'); + line = (char *)Buf_GetAll (buf, &lineLength); + Buf_Destroy (buf, FALSE); + return line; +} /*- @@ -1739,20 +2072,17 @@ ParseReadLine () * semi-colons as semi-colons (by leaving semiNL FALSE). This also * discards completely blank lines. */ - while(1) { + for (;;) { c = ParseReadc(); if (c == '\t') { ignComment = ignDepOp = TRUE; break; - } else if (c == '.') { - ignComment = TRUE; - break; } else if (c == '\n') { lineno++; } else if (c == '#') { - ungetc(c, curFILE); - break; + ParseUnreadc(c); + break; } else { /* * Anything else breaks out without doing anything @@ -1763,7 +2093,7 @@ ParseReadLine () if (c != EOF) { lastc = c; - buf = Buf_Init(BSIZE); + buf = Buf_Init(MAKE_BSIZE); while (((c = ParseReadc ()) != '\n' || (lastc == '\\')) && (c != EOF)) @@ -1789,11 +2119,13 @@ test_char: } else { /* * Check for comments, semiNL's, etc. -- easier than - * ungetc(c, curFILE); continue; + * ParseUnreadc(c); continue; */ goto test_char; } + /*NOTREACHED*/ break; + case ';': /* * Semi-colon: Need to see if it should be interpreted as a @@ -1808,7 +2140,7 @@ test_char: * harm, since the newline remains in the buffer and the * whole line is ignored. */ - ungetc('\t', curFILE); + ParseUnreadc('\t'); goto line_read; } break; @@ -1836,6 +2168,7 @@ test_char: break; case '#': if (!ignComment) { + if (compatMake || (lastc != '\\')) { /* * If the character is a hash mark and it isn't escaped * (or we're being compatible), the thing is a comment. @@ -1845,6 +2178,14 @@ test_char: c = ParseReadc(); } while ((c != '\n') && (c != EOF)); goto line_read; + } else { + /* + * Don't add the backslash. Just let the # get copied + * over. + */ + lastc = c; + continue; + } } break; case ':': @@ -1883,62 +2224,47 @@ test_char: */ switch (Cond_Eval (line)) { case COND_SKIP: + /* + * Skip to next conditional that evaluates to COND_PARSE. + */ do { - /* - * Skip to next conditional that evaluates to COND_PARSE. - */ free (line); - c = ParseReadc(); - /* - * Skip lines until get to one that begins with a - * special char. - */ - while ((c != '.') && (c != EOF)) { - while (((c != '\n') || (lastc == '\\')) && - (c != EOF)) - { - /* - * Advance to next unescaped newline - */ - if ((lastc = c) == '\n') { - lineno++; - } - c = ParseReadc(); - } - lineno++; - - lastc = c; - c = ParseReadc (); - } - - if (c == EOF) { - Parse_Error (PARSE_FATAL, "Unclosed conditional"); - return ((char *)NULL); - } - - /* - * Read the entire line into buf - */ - buf = Buf_Init (BSIZE); - do { - Buf_AddByte (buf, (Byte)c); - c = ParseReadc(); - } while ((c != '\n') && (c != EOF)); - lineno++; - - Buf_AddByte (buf, (Byte)'\0'); - line = (char *)Buf_GetAll (buf, &lineLength); - Buf_Destroy (buf, FALSE); - } while (Cond_Eval(line) != COND_PARSE); + line = ParseSkipLine(1); + } while (line && Cond_Eval(line) != COND_PARSE); + if (line == NULL) + break; /*FALLTHRU*/ case COND_PARSE: - free (line); + free ((Address) line); line = ParseReadLine(); break; + case COND_INVALID: + if (For_Eval(line)) { + int ok; + free(line); + do { + /* + * Skip after the matching end + */ + line = ParseSkipLine(0); + if (line == NULL) { + Parse_Error (PARSE_FATAL, + "Unexpected end of file in for loop.\n"); + break; + } + ok = For_Eval(line); + free(line); + } + while (ok); + if (line != NULL) + For_Run(); + line = ParseReadLine(); + } + break; } } - return (line); + } else { /* * Hit end-of-file, so return a NULL line to indicate this. @@ -2003,7 +2329,7 @@ Parse_File(name, stream) fatals = 0; do { - while (line = ParseReadLine ()) { + while ((line = ParseReadLine ()) != NULL) { if (*line == '.') { /* * Lines that begin with the special character are either @@ -2036,10 +2362,15 @@ Parse_File(name, stream) goto nextLine; } - if (*line == '\t') { + if (*line == '\t' +#ifdef POSIX + || *line == ' ' +#endif + ) + { /* - * If a line starts with a tab, it can only hope to be - * a creation command. + * If a line starts with a tab (or space in POSIX-land), it + * can only hope to be a creation command. */ shellCommand: for (cp = line + 1; isspace (*cp); cp++) { @@ -2060,6 +2391,15 @@ Parse_File(name, stream) cp); } } +#ifdef SYSVINCLUDE + } else if (strncmp (line, "include", 7) == 0 && + strchr(line, ':') == NULL) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude (line + 7); + goto nextLine; +#endif } else if (Parse_IsVar (line)) { ParseFinishLine(); Parse_DoVar (line, VAR_GLOBAL); @@ -2103,7 +2443,7 @@ Parse_File(name, stream) #endif ParseFinishLine(); - cp = Var_Subst (line, VAR_CMD, TRUE); + cp = Var_Subst (NULL, line, VAR_CMD, TRUE); free (line); line = cp; @@ -2151,9 +2491,10 @@ Parse_File(name, stream) * the parseIncPath list is initialized... *--------------------------------------------------------------------- */ +void Parse_Init () { - char *cp, *start; + char *cp = NULL, *start; /* avoid faults on read-only strings */ static char syspath[] = _PATH_DEFSYSPATH; @@ -2167,9 +2508,8 @@ Parse_Init () * as dir1:...:dirn) to the system include path. */ for (start = syspath; *start != '\0'; start = cp) { - for (cp = start; *cp != '\0' && *cp != ':'; cp++) { - ; - } + for (cp = start; *cp != '\0' && *cp != ':'; cp++) + continue; if (*cp == '\0') { Dir_AddDir(sysIncPath, start); } else { @@ -2204,8 +2544,10 @@ Parse_MainName() Punt ("make: no target to make.\n"); /*NOTREACHED*/ } else if (mainNode->type & OP_DOUBLEDEP) { + (void) Lst_AtEnd (main, (ClientData)mainNode); Lst_Concat(main, mainNode->cohorts, LST_CONCNEW); } - (void) Lst_AtEnd (main, (ClientData)mainNode); + else + (void) Lst_AtEnd (main, (ClientData)mainNode); return (main); } diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h index 5ae51adac58..d9bf882a569 100644 --- a/usr.bin/make/sprite.h +++ b/usr.bin/make/sprite.h @@ -36,7 +36,7 @@ * SUCH DAMAGE. * * from: @(#)sprite.h 5.3 (Berkeley) 6/1/90 - * $Id: sprite.h,v 1.2 1993/08/01 18:11:57 mycroft Exp $ + * $Id: sprite.h,v 1.3 1994/03/05 00:35:07 cgd Exp $ */ /* @@ -87,7 +87,7 @@ typedef int ReturnStatus; * by user processes. */ -#define NIL 0xFFFFFFFF +#define NIL ~0 #define USER_NIL 0 #ifndef NULL #define NULL 0 diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c index c88a60cf647..5021365f092 100644 --- a/usr.bin/make/str.c +++ b/usr.bin/make/str.c @@ -37,11 +37,10 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)str.c 5.8 (Berkeley) 6/1/90";*/ -static char rcsid[] = "$Id: str.c,v 1.3 1994/01/13 21:02:03 jtc Exp $"; +/* from: static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: str.c,v 1.4 1994/03/05 00:35:08 cgd Exp $"; #endif /* not lint */ -#include <stdlib.h> #include "make.h" /*- @@ -68,7 +67,7 @@ str_concat(s1, s2, flags) result = emalloc((u_int)(len1 + len2 + 2)); /* copy first string into place */ - bcopy(s1, result, len1); + memcpy(result, s1, len1); /* add separator character */ if (flags & STR_ADDSPACE) { @@ -80,7 +79,7 @@ str_concat(s1, s2, flags) } /* copy second string plus EOS into place */ - bcopy(s2, result + len1, len2 + 1); + memcpy(result + len1, s2, len2 + 1); /* free original strings */ if (flags & STR_DOFREE) { @@ -117,8 +116,9 @@ brk_string(str, store_argc) argv[0] = Var_Value(".MAKE", VAR_GLOBAL); } - /* skip leading space chars. - for (; *str == ' ' || *str == '\t'; ++str); + /* skip leading space chars. */ + for (; *str == ' ' || *str == '\t'; ++str) + continue; /* allocate room for a copy of the string */ if ((len = strlen(str) + 1) > curlen) @@ -136,11 +136,11 @@ brk_string(str, store_argc) case '\'': if (inquote) if (inquote == ch) - inquote = NULL; + inquote = '\0'; else break; else - inquote = ch; + inquote = (char) ch; continue; case ' ': case '\t': @@ -195,7 +195,7 @@ brk_string(str, store_argc) } if (!start) start = t; - *t++ = ch; + *t++ = (char) ch; } done: argv[argc] = (char *)NULL; *store_argc = argc; @@ -251,6 +251,7 @@ Str_FindSubstring(string, substring) * * Side effects: None. */ +int Str_Match(string, pattern) register char *string; /* String */ register char *pattern; /* Pattern */ @@ -339,3 +340,98 @@ thisCharOK: ++pattern; ++string; } } + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVMatch -- + * Check word against pattern for a match (% is wild), + * + * Results: + * Returns the beginning position of a match or null. The number + * of characters matched is returned in len. + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +char * +Str_SYSVMatch(word, pattern, len) + char *word; /* Word to examine */ + char *pattern; /* Pattern to examine against */ + int *len; /* Number of characters to substitute */ +{ + char *p = pattern; + char *w = word; + char *m; + + if (*p == '\0') + return NULL; + + if ((m = strchr(p, '%')) != NULL) { + /* check that the prefix matches */ + for (; p != m && *w && *w == *p; w++, p++) + continue; + + if (p != m) + return NULL; /* No match */ + + if (*++p == '\0') { + /* No more pattern, return the rest of the string */ + *len = strlen(w); + return w; + } + } + + m = w; + + /* Find a matching tail */ + do + if (strcmp(p, w) == 0) { + *len = w - m; + return m; + } + while (*w++ != '\0'); + + return NULL; +} + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVSubst -- + * Substitute '%' on the pattern with len characters from src. + * If the pattern does not contain a '%' prepend len characters + * from src. + * + * Results: + * None + * + * Side Effects: + * Places result on buf + * + *----------------------------------------------------------------------- + */ +void +Str_SYSVSubst(buf, pat, src, len) + Buffer buf; + char *pat; + char *src; + int len; +{ + char *m; + + if ((m = strchr(pat, '%')) != NULL) { + /* Copy the prefix */ + Buf_AddBytes(buf, m - pat, (Byte *) pat); + /* skip the % */ + pat = m + 1; + } + + /* Copy the pattern */ + Buf_AddBytes(buf, len, (Byte *) src); + + /* append the rest */ + Buf_AddBytes(buf, strlen(pat), (Byte *) pat); +} diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c index 652a45c3620..8ac46c6eea2 100644 --- a/usr.bin/make/suff.c +++ b/usr.bin/make/suff.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)suff.c 5.6 (Berkeley) 6/1/90";*/ -static char rcsid[] = "$Id: suff.c,v 1.3 1994/01/13 21:02:06 jtc Exp $"; +/* from: static char sccsid[] = "@(#)suff.c 5.6 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: suff.c,v 1.4 1994/03/05 00:35:11 cgd Exp $"; #endif /* not lint */ /*- @@ -91,8 +91,9 @@ static char rcsid[] = "$Id: suff.c,v 1.3 1994/01/13 21:02:06 jtc Exp $"; */ #include <stdio.h> -#include <stdlib.h> #include "make.h" +#include "hash.h" +#include "dir.h" #include "bit.h" static Lst sufflist; /* Lst of suffixes */ @@ -130,10 +131,43 @@ typedef struct _Src { * this thing too early or never nuke it) */ } Src; +/* + * A structure for passing more than one argument to the Lst-library-invoked + * function... + */ +typedef struct { + Lst l; + Src *s; +} LstSrc; + static Suff *suffNull; /* The NULL suffix for this run */ static Suff *emptySuff; /* The empty suffix required for POSIX * single-suffix transformation rules */ + +static char *SuffStrIsPrefix __P((char *, char *)); +static char *SuffSuffIsSuffix __P((Suff *, char *)); +static int SuffSuffIsSuffixP __P((Suff *, char *)); +static int SuffSuffHasNameP __P((Suff *, char *)); +static int SuffSuffIsPrefix __P((Suff *, char *)); +static int SuffGNHasNameP __P((GNode *, char *)); +static void SuffFree __P((Suff *)); +static void SuffInsert __P((Lst, Suff *)); +static Boolean SuffParseTransform __P((char *, Suff **, Suff **)); +static int SuffRebuildGraph __P((GNode *, Suff *)); +static int SuffAddSrc __P((Suff *, LstSrc *)); +static void SuffAddLevel __P((Lst, Src *)); +static void SuffFreeSrc __P((Src *)); +static Src *SuffFindThem __P((Lst)); +static Src *SuffFindCmds __P((Src *)); +static int SuffExpandChildren __P((GNode *, GNode *)); +static Boolean SuffApplyTransform __P((GNode *, GNode *, Suff *, Suff *)); +static void SuffFindArchiveDeps __P((GNode *)); +static void SuffFindNormalDeps __P((GNode *)); +static int SuffPrintName __P((Suff *)); +static int SuffPrintSuff __P((Suff *)); +static int SuffPrintTrans __P((GNode *)); + /*************** Lst Predicates ****************/ /*- *----------------------------------------------------------------------- @@ -207,6 +241,7 @@ SuffSuffIsSuffix (s, str) * *----------------------------------------------------------------------- */ +static int SuffSuffIsSuffixP(s, str) Suff *s; char *str; @@ -321,7 +356,7 @@ SuffInsert (l, s) Suff *s; /* the suffix to insert */ { LstNode ln; /* current element in l we're examining */ - Suff *s2; /* the suffix descriptor in this element */ + Suff *s2 = NULL; /* the suffix descriptor in this element */ if (Lst_Open (l) == FAILURE) { return; @@ -404,7 +439,7 @@ SuffParseTransform(str, srcPtr, targPtr) register char *str2; /* Extra pointer (maybe target suffix) */ LstNode singleLn; /* element in suffix list of any suffix * that exactly matches str */ - Suff *single; /* Source of possible transformation to + Suff *single = NULL;/* Source of possible transformation to * null suffix */ srcLn = NILLNODE; @@ -416,7 +451,7 @@ SuffParseTransform(str, srcPtr, targPtr) * we can find two that meet these criteria, we've successfully * parsed the string. */ - while (1) { + for (;;) { if (srcLn == NILLNODE) { srcLn = Lst_Find(sufflist, (ClientData)str, SuffSuffIsPrefix); } else { @@ -870,14 +905,6 @@ Suff_AddLib (sname) } /********** Implicit Source Search Functions *********/ -/* - * A structure for passing more than one argument to the Lst-library-invoked - * function... - */ -typedef struct { - Lst l; - Src *s; -} LstSrc; /*- *----------------------------------------------------------------------- @@ -1069,7 +1096,7 @@ SuffFindCmds (targ) while ((ln = Lst_Next (t->children)) != NILLNODE) { s = (GNode *)Lst_Datum (ln); - cp = rindex (s->name, '/'); + cp = strrchr (s->name, '/'); if (cp == (char *)NULL) { cp = s->name; } else { @@ -1158,11 +1185,11 @@ SuffExpandChildren(cgn, pgn) * to later since the resulting words are tacked on to the end of * the children list. */ - if (index(cgn->name, '$') != (char *)NULL) { + if (strchr(cgn->name, '$') != (char *)NULL) { if (DEBUG(SUFF)) { printf("Expanding \"%s\"...", cgn->name); } - cp = Var_Subst(cgn->name, pgn, TRUE); + cp = Var_Subst(NULL, cgn->name, pgn, TRUE); if (cp != (char *)NULL) { Lst members = Lst_Init(FALSE); @@ -1187,9 +1214,8 @@ SuffExpandChildren(cgn, pgn) char *start; char *initcp = cp; /* For freeing... */ - for (start = cp; *start == ' ' || *start == '\t'; start++) { - ; - } + for (start = cp; *start == ' ' || *start == '\t'; start++) + continue; for (cp = start; *cp != '\0'; cp++) { if (*cp == ' ' || *cp == '\t') { /* @@ -1496,7 +1522,6 @@ SuffFindArchiveDeps(gn) }; char *vals[sizeof(copy)/sizeof(copy[0])]; int i; /* Index into copy and vals */ - char *cp; /* Suffix for member */ Suff *ms; /* Suffix descriptor for member */ char *name; /* Start of member's name */ @@ -1504,8 +1529,8 @@ SuffFindArchiveDeps(gn) * The node is an archive(member) pair. so we must find a * suffix for both of them. */ - eoarch = index (gn->name, '('); - eoname = index (eoarch, ')'); + eoarch = strchr (gn->name, '('); + eoname = strchr (eoarch, ')'); *eoname = '\0'; /* Nuke parentheses during suffix search */ *eoarch = '\0'; /* So a suffix can be found */ @@ -1625,7 +1650,6 @@ SuffFindNormalDeps(gn) { char *eoname; /* End of name */ char *sopref; /* Start of prefix */ - Suff *s; /* Current suffix */ LstNode ln; /* Next suffix node to check */ Lst srcs; /* List of sources at which to look */ Lst targs; /* List of targets to which things can be @@ -1684,6 +1708,7 @@ SuffFindNormalDeps(gn) targ->suff = (Suff *)Lst_Datum(ln); targ->node = gn; targ->parent = (Src *)NULL; + targ->children = 0; /* * Allocate room for the prefix, whose end is found by subtracting @@ -1691,7 +1716,7 @@ SuffFindNormalDeps(gn) */ prefLen = (eoname - targ->suff->nameLen) - sopref; targ->pref = emalloc(prefLen + 1); - bcopy(sopref, targ->pref, prefLen); + memcpy(targ->pref, sopref, prefLen); targ->pref[prefLen] = '\0'; /* @@ -1724,6 +1749,7 @@ SuffFindNormalDeps(gn) targ->suff = suffNull; targ->node = gn; targ->parent = (Src *)NULL; + targ->children = 0; targ->pref = strdup(sopref); SuffAddLevel(srcs, targ); @@ -1751,9 +1777,8 @@ SuffFindNormalDeps(gn) * Work up the transformation path to find the suffix of the * target to which the transformation was made. */ - for (targ = bottom; targ->parent != NULL; targ = targ->parent) { - ; - } + for (targ = bottom; targ->parent != NULL; targ = targ->parent) + continue; } /* @@ -2083,6 +2108,7 @@ Suff_Init () suffNull->name = strdup (""); suffNull->nameLen = 0; suffNull->searchPath = Lst_Init (FALSE); + Dir_Concat(suffNull->searchPath, dirSearchPath); suffNull->children = Lst_Init (FALSE); suffNull->parents = Lst_Init (FALSE); suffNull->sNum = sNum++; @@ -2120,19 +2146,19 @@ SuffPrintSuff (s) printf ("LIBRARY"); break; } - putc(flags ? '|' : ')', stdout); + fputc(flags ? '|' : ')', stdout); } } - putc ('\n', stdout); + fputc ('\n', stdout); printf ("#\tTo: "); Lst_ForEach (s->parents, SuffPrintName, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); printf ("#\tFrom: "); Lst_ForEach (s->children, SuffPrintName, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); printf ("#\tSearch Path: "); Dir_PrintPath (s->searchPath); - putc ('\n', stdout); + fputc ('\n', stdout); return (0); } @@ -2144,12 +2170,13 @@ SuffPrintTrans (t) printf ("%-16s: ", t->name); Targ_PrintType (t->type); - putc ('\n', stdout); + fputc ('\n', stdout); Lst_ForEach (t->commands, Targ_PrintCmd, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); return(0); } +void Suff_PrintAll() { printf ("#*** Suffixes:\n"); diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c index 8501ca6aaf0..e880ecc0c9b 100644 --- a/usr.bin/make/targ.c +++ b/usr.bin/make/targ.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)targ.c 5.9 (Berkeley) 3/1/91";*/ -static char rcsid[] = "$Id: targ.c,v 1.2 1993/08/01 18:11:39 mycroft Exp $"; +/* from: static char sccsid[] = "@(#)targ.c 5.9 (Berkeley) 3/1/91"; */ +static char *rcsid = "$Id: targ.c,v 1.3 1994/03/05 00:35:13 cgd Exp $"; #endif /* not lint */ /*- @@ -84,6 +84,7 @@ static char rcsid[] = "$Id: targ.c,v 1.2 1993/08/01 18:11:39 mycroft Exp $"; #include <time.h> #include "make.h" #include "hash.h" +#include "dir.h" static Lst allTargets; /* the list of all targets found so far */ static Hash_Table targets; /* a hash table of same */ @@ -149,6 +150,7 @@ Targ_NewGN (name) gn->preds = Lst_Init(FALSE); gn->context = Lst_Init (FALSE); gn->commands = Lst_Init (FALSE); + gn->suffix = NULL; return (gn); } @@ -342,6 +344,7 @@ Targ_SetMain (gn) } static int +/*ARGSUSED*/ TargPrintName (gn, ppath) GNode *gn; int ppath; @@ -356,7 +359,7 @@ TargPrintName (gn, ppath) printf ("(MAIN NAME) "); } } -#endif notdef +#endif /* notdef */ return (0); } @@ -496,13 +499,13 @@ TargPrintNode (gn, pass) if (!Lst_IsEmpty (gn->iParents)) { printf("# implicit parents: "); Lst_ForEach (gn->iParents, TargPrintName, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); } } if (!Lst_IsEmpty (gn->parents)) { printf("# parents: "); Lst_ForEach (gn->parents, TargPrintName, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); } printf("%-16s", gn->name); @@ -516,7 +519,7 @@ TargPrintNode (gn, pass) } Targ_PrintType (gn->type); Lst_ForEach (gn->children, TargPrintName, (ClientData)0); - putc ('\n', stdout); + fputc ('\n', stdout); Lst_ForEach (gn->commands, Targ_PrintCmd, (ClientData)0); printf("\n\n"); if (gn->type & OP_DOUBLEDEP) { @@ -562,6 +565,7 @@ TargPrintOnlySrc(gn) * lots o' output *----------------------------------------------------------------------- */ +void Targ_PrintGraph (pass) int pass; /* Which pass this is. 1 => no processing * 2 => processing done */ diff --git a/usr.bin/make/tutorial.ms b/usr.bin/make/tutorial.ms new file mode 100644 index 00000000000..55c9371c581 --- /dev/null +++ b/usr.bin/make/tutorial.ms @@ -0,0 +1,3385 @@ +'\" +'\" This file contains a tutorial for pmake. +'\" +'\" $Id: tutorial.ms,v 1.1 1994/03/05 00:35:14 cgd Exp $ +'\" +'\" xH is a macro to provide numbered headers that are automatically stuffed +'\" into a table-of-contents, properly indented, etc. If the first argument +'\" is numeric, it is taken as the depth for numbering (as for .NH), else +'\" the default (1) is assumed. +'\" +.de xH +.ds @Q +.ie \\$1 \{\ +.ds @Q \\$1 +.ds @R \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 +.\} +.el .ds @R \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 +.nr @S \\*(@Q-1*5 +.if \\*(@S==-5 .nr @S 0 +.NH \\*(@Q +\\*(@R +.XS \\n(PN \\n(@S +\\*(SN \\*(@R +.XE +.. +'\" CW is used to place a string in fixed-width or switch to a +'\" fixed-width font. +'\" C is a typewriter font for a laserwriter. Use something else if +'\" you don't have one... +.de CW +.ie !\\n(.$ .ft C +.el \&\\$3\fC\\$1\fP\\$2 +.. +'\" Anything I put in a display I want to be in fixed-width +.am DS +.CW +.. +'\" The stuff in .No produces a little stop sign in the left margin +'\" that says NOTE in it. Unfortunately, it does cause a break, but +'\" hey. Can't have everything. In case you're wondering how I came +'\" up with such weird commands, they came from running grn on a +'\" gremlin file... +.de No +.br +.ne 0.5i +.po -0.5i +.br +.mk +.nr g3 \\n(.f +.nr g4 \\n(.s +.sp -1 +.st cf +\D's -1u'\D't 5u' +.sp -1 +\h'50u'\D'l 71u 0u'\D'l 50u 50u'\D'l 0u 71u'\D'l -50u 50u'\D'l -71u 0u'\D'l -50u -50u'\D'l 0u -71u'\D'l 50u -50u' +.sp -1 +\D't 3u' +.sp -1 +.sp 7u +\h'53u'\D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u' +.sp -1 +.ft R +.ps 6 +.nr g8 \\n(.d +.ds g9 "NOTE +.sp 74u +\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9 +.sp |\\n(g8u +.sp 166u +\D't 3u'\D's -1u' +.br +.po +.rt +.ft \\n(g3 +.ps \\n(g4 +.. +.de Bp +.ie !\\n(.$ .IP \(bu 2 +.el .IP "\&" 2 +.. +.po +.3i +.RP +.TL +PMake \*- A Tutorial +.AU +Adam de Boor +.AI +University of California, Berkeley +Computer Science Department +Berkeley, CA 94720 +(415) 642-8282 +.LP +.xH Introduction +.PP +PMake is a program for creating other programs, or anything else you +can think of for it to do. The basic idea behind PMake is that, for +any given system, be it a program or a document or whatever, there +will be some files that depend on the state of other files (on when +they were last modified). PMake takes these dependencies, which you +must specify, and uses them to build whatever it is you want it to +build. +.PP +PMake is almost fully-compatible with Make, with which you may already +be familiar. PMake's most important feature is its ability to run +several different jobs at once, making the creation of systems +considerably faster. It also has a great deal more functionality than +Make. Throughout the text, whenever something is mentioned that is an +important difference between PMake and Make (i.e. something that will +cause a makefile to fail if you don't do something about it), or is +simply important, it will be flagged with a little sign in the left +margin, like this: +.No +.PP +This tutorial is divided into three main sections corresponding to basic, +intermediate and advanced PMake usage. If you already know Make well, +you will only need to skim chapter 2 (there are some aspects of +PMake that I consider basic to its use that didn't exist in Make). +Things in chapter 3 make life much easier, while those in chapter 4 +are strictly for those who know what they are doing. Chapter 5 has +definitions for the jargon I use and chapter 6 contains possible +solutions to the problems presented throughout the tutorial. +.xH The Basics of PMake +.PP +PMake takes as input a file that tells a) which files depend on which +other files to be complete and b) what to do about files that are +``out-of-date.'' This file is known as a ``makefile'' and is usually +.Ix 0 def makefile +kept in the top-most directory of the system to be built. While you +can call the makefile anything you want, PMake will look for +.CW Makefile +and +.CW makefile +(in that order) in the current directory if you don't tell it +otherwise. +.Ix 0 def makefile default +To specify a different makefile, use the +.B \-f +flag (e.g. +.CW "pmake -f program.mk" ''). `` +.Ix 0 ref flags -f +.Ix 0 ref makefile other +.PP +A makefile has four different types of lines in it: +.RS +.IP \(bu 2 +File dependency specifications +.IP \(bu 2 +Creation commands +.IP \(bu 2 +Variable assignments +.IP \(bu 2 +Comments, include statements and conditional directives +.RE +.LP +Any line may be continued over multiple lines by ending it with a +backslash. +.Ix 0 def "continuation line" +The backslash, following newline and any initial whitespace +on the following line are compressed into a single space before the +input line is examined by PMake. +.xH 2 Dependency Lines +.PP +As mentioned in the introduction, in any system, there are +dependencies between the files that make up the system. For instance, +in a program made up of several C source files and one header file, +the C files will need to be re-compiled should the header file be +changed. For a document of several chapters and one macro file, the +chapters will need to be reprocessed if any of the macros changes. +.Ix 0 def "dependency" +These are dependencies and are specified by means of dependency lines in +the makefile. +.PP +.Ix 0 def "dependency line" +On a dependency line, there are targets and sources, separated by a +one- or two-character operator. +The targets ``depend'' on the sources and are usually created from +them. +.Ix 0 def target +.Ix 0 def source +.Ix 0 ref operator +Any number of targets and sources may be specified on a dependency line. +All the targets in the line are made to depend on all the sources. +Targets and sources need not be actual files, but every source must be +either an actual file or another target in the makefile. +If you run out of room, use a backslash at the end of the line to continue onto +the next one. +.PP +Any file may be a target and any file may be a source, but the +relationship between the two (or however many) is determined by the +``operator'' that separates them. +.Ix 0 def operator +Three types of operators exist: one specifies that the datedness of a +target is determined by the state of its sources, while another +specifies other files (the sources) that need to be dealt with before +the target can be re-created. The third operator is very similar to +the first, with the additional condition that the target is +out-of-date if it has no sources. These operations are represented by +the colon, the exclamation point and the double-colon, respectively, and are +mutually exclusive. Their exact semantics are as follows: +.IP ":" +.Ix 0 def operator colon +.Ix 0 def : +If a colon is used, a target on the line is considered to be +``out-of-date'' (and in need of creation) if +.RS +.IP \(bu 2 +any of the sources is out-of-date, or +.IP \(bu 2 +any of the sources has been modified more recently than the target, or +.IP \(bu 2 +the target doesn't exist. +.RE +.Ix 0 def out-of-date +.IP "\&" +Under this operation, steps will be taken to re-create the target only +if it is found to be out-of-date by using these three rules. +.IP "!" +.Ix 0 def operator force +.Ix 0 def ! +If an exclamation point is used, the target will always be re-created, +but this will not happen until all of its sources have been examined +and re-created, if necessary. +.IP "::" +.Ix 0 def operator double-colon +.Ix 0 def :: +If a double-colon is used, a target is out-of-date if: +.RS +.IP \(bu 2 +any of the sources is out-of-date, or +.IP \(bu 2 +any of the sources has been modified more recently than the target, or +.IP \(bu 2 +the target doesn't exist, or +.IP \(bu 2 +the target has no sources. +.RE +.IP "\&" +If the target is out-of-date according to these rules, it will be re-created. +This operator also does something else to the targets, but I'll go +into that in the next section (``Shell Commands''). +.PP +Enough words, now for an example. Take that C program I mentioned +earlier. Say there are three C files +.CW a.c , ( +.CW b.c +and +.CW c.c ) +each of which +includes the file +.CW defs.h . +The dependencies between the files could then be expressed as follows: +.DS +program : a.o b.o c.o +a.o b.o c.o : defs.h +a.o : a.c +b.o : b.c +c.o : c.c +.DE +.LP +You may be wondering at this point, where +.CW a.o , +.CW b.o +and +.CW c.o +came in and why +.I they +depend on +.CW defs.h +and the C files don't. The reason is quite simple: +.CW program +cannot be made by linking together .c files \*- it must be +made from .o files. Likewise, if you change +.CW defs.h , +it isn't the .c files that need to be re-created, it's the .o files. +If you think of dependencies in these terms \*- which files (targets) +need to be created from which files (sources) \*- you should have no problems. +.PP +An important thing to notice about the above example, is that all the +\&.o files appear as targets on more than one line. This is perfectly +all right: the target is made to depend on all the sources mentioned +on all the dependency lines. E.g. +.CW a.o +depends on both +.CW defs.h +and +.CW a.c . +.Ix 0 ref dependency +.No +.PP +The order of the dependency lines in the makefile is +important: the first target on the first dependency line in the +makefile will be the one that gets made if you don't say otherwise. +That's why +.CW program +comes first in the example makefile, above. +.xH 2 Shell Commands +.PP +``Isn't that nice,'' you say to yourself, ``but how are files +actually `re-created,' as he likes to spell it?'' +The re-creation is accomplished by commands you place in the makefile. +These commands are passed to the Bourne shell (better known as +``/bin/sh'') to be executed and are +.Ix 0 ref shell +.Ix 0 ref re-creation +.Ix 0 ref update +expected to do what's necessary to update the target file (PMake +doesn't actually check to see if the target was created. It just +assumes it's there). +.Ix 0 ref target +.PP +Shell commands in a makefile look a lot like shell commands you would +type at a terminal, with one important exception: each command in a +makefile +.I must +be preceeded by at least one tab. +.PP +Each target has associated with it a shell script made up of +one or more of these shell commands. The creation script for a target +should immediately follow the dependency line for that target. While +any given target may appear on more than one dependency line, only one +of these dependency lines may be followed by a creation script, unless +the `::' operator was used on the dependency line. +.Ix 0 ref operator double-colon +.Ix 0 ref :: +.No +.PP +If the double-colon was used, then each dependency line for the target +may be followed by a shell script. That script will only be executed +if the target on the associated dependency line is out-of-date with +respect to the sources on that line, according to the rules I gave +earlier. +I'll give you a good example of this later on. +.PP +To expand on the earlier makefile, you might add commands as follows: +.DS +program : a.o b.o c.o + cc a.o b.o c.o \-o program +a.o b.o c.o : defs.h +a.o : a.c + cc \-c a.c +b.o : b.c + cc \-c b.c +c.o : c.c + cc \-c c.c +.DE +.LP +Something you should remember when writing a makefile is the +commands will be executed if the +.I target +on the dependency line is out-of-date, not the sources. +.Ix 0 ref target +.Ix 0 ref source +.Ix 0 ref out-of-date +In this example, the command +.CW "cc \-c a.c" '' `` +will be executed if +.CW a.o +is out-of-date. Because of the `:' operator, +.Ix 0 ref : +.Ix 0 ref operator colon +this means that should +.CW a.c +.I or +.CW defs.h +have been modified more recently than +.CW a.o , +the command will be executed +.CW a.o "\&" ( +will be considered out-of-date). +.Ix 0 ref out-of-date +.PP +Remember how I said the only difference between a makefile shell +command and a regular shell command was the leading tab? I lied. There +is another way in which makefile commands differ from regular ones. +The first two characters after the initial whitespace are treated +specially. +If they are any combination of `@' and `\-', they cause PMake to do +different things. +.PP +In most cases, shell commands are printed before they're +actually executed. This is to keep you informed of what's going on. If +an `@' appears, however, this echoing is suppressed. In the case of an +.CW echo +command, say +.CW "echo Linking index" ,'' `` +it would be +rather silly to see +.DS +echo Linking index +Linking index +.DE +.LP +so PMake allows you to place an `@' before the command +.CW "@echo Linking index" '') (`` +to prevent the command from being printed. +.PP +The other special character is the `\-'. In case you didn't know, +shell commands finish with a certain ``exit status.'' This status is +made available by the operating system to whatever program invoked the +command. Normally this status will be 0 if everything went ok and +non-zero if something went wrong. For this reason, PMake will consider +an error to have occurred if one of the shells it invokes returns a non-zero +status. When it detects an error, PMake's usual action is to abort +whatever it's doing and exit with a non-zero status itself (any other +targets that were being created will continue being made, but nothing +new will be started. PMake will exit after the last job finishes). +This behavior can be altered, however, by placing a `\-' at the front +of a command +.CW "\-mv index index.old" ''), (`` +certain command-line arguments, +or doing other things, to be detailed later. In such +a case, the non-zero status is simply ignored and PMake keeps chugging +along. +.No +.PP +Because all the commands are given to a single shell to execute, such +things as setting shell variables, changing directories, etc., last +beyond the command in which they are found. This also allows shell +compound commands (like +.CW for +loops) to be entered in a natural manner. +Since this could cause problems for some makefiles that depend on +each command being executed by a single shell, PMake has a +.B \-B +.Ix 0 ref compatibility +.Ix 0 ref flags -B +flag (it stands for backwards-compatible) that forces each command to +be given to a separate shell. It also does several other things, all +of which I discourage since they are now old-fashioned.\|.\|.\|. +.No +.PP +A target's shell script is fed to the shell on its (the shell's) input stream. +This means that any commands, such as +.CW ci +that need to get input from the terminal won't work right \*- they'll +get the shell's input, something they probably won't find to their +liking. A simple way around this is to give a command like this: +.DS +ci $(SRCS) < /dev/tty +.DE +This would force the program's input to come from the terminal. If you +can't do this for some reason, your only other alternative is to use +PMake in its fullest compatibility mode. See +.B Compatibility +in chapter 4. +.Ix 0 ref compatibility +.PP +.xH 2 Variables +.PP +PMake, like Make before it, has the ability to save text in variables +to be recalled later at your convenience. Variables in PMake are used +much like variables in the shell and, by tradition, consist of +all upper-case letters (you don't +.I have +to use all upper-case letters. +In fact there's nothing to stop you from calling a variable +.CW @^&$%$ . +Just tradition). Variables are assigned-to using lines of the form +.Ix 0 def variable assignment +.DS +VARIABLE = value +.DE +.Ix 0 def variable assignment +appended-to by +.DS +VARIABLE += value +.DE +.Ix 0 def variable appending +.Ix 0 def variable assignment appended +.Ix 0 def += +conditionally assigned-to (if the variable isn't already defined) by +.DS +VARIABLE ?= value +.DE +.Ix 0 def variable assignment conditional +.Ix 0 def ?= +and assigned-to with expansion (i.e. the value is expanded (see below) +before being assigned to the variable\*-useful for placing a value at +the beginning of a variable, or other things) by +.DS +VARIABLE := value +.DE +.Ix 0 def variable assignment expanded +.Ix 0 def := +.LP +Any whitespace before +.I value +is stripped off. When appending, a space is placed between the old +value and the stuff being appended. +The value of a variable may be retrieved by enclosing the variable +name in parentheses or curly braces and preceeding the whole thing +with a dollar sign. +.PP +For example, to set the variable CFLAGS to the string +.CW "\-I/sprite/src/lib/libc \-O" ,'' `` +you would place a line +.DS +CFLAGS = \-I/sprite/src/lib/libc \-O +.DE +in the makefile and use the word +.CW "$(CFLAGS)" +wherever you would like the string +.CW "\-I/sprite/src/lib/libc \-O" +to appear. This is called variable expansion. +.Ix 0 def variable expansion +.No +.PP +Unlike Make, PMake will not expand a variable unless it knows +the variable exists. E.g. if you have a +.CW "${i}" +in a shell command and you have not assigned a value to the variable +.CW i +(the empty string is considered a value, by the way), where Make would have +substituted the empty string, PMake will leave the +.CW "${i}" +alone. +To keep PMake from substituting for a variable it knows, precede the +dollar sign with another dollar sign. +(e.g. to pass +.CW "${HOME}" +to the shell, use +.CW "$${HOME}" ). +This causes PMake, in effect, to expand the +.CW $ +macro, which expands to a single +.CW $ . +For compatibility, Make's style of variable expansion will be used +if you invoke PMake with any of the compatibility flags (\c +.B \-V , +.B \-B +or +.B \-M . +The +.B \-V +flag alters just the variable expansion). +.Ix 0 ref flags -V +.Ix 0 ref flags -B +.Ix 0 ref compatibility +.PP +.Ix 0 ref variable expansion +There are two different times at which variable expansion occurs: +When parsing a dependency line, the expansion occurs immediately +upon reading the line. If any variable used on a dependency line is +undefined, PMake will print a message and exit. +Variables in shell commands are expanded when the command is +executed. +Variables used inside another variable are expanded whenever the outer +variable is expanded (the expansion of an inner variable has no effect +on the outer variable. I.e. if the outer variable is used on a dependency +line and in a shell command, and the inner variable changes value +between when the dependency line is read and the shell command is +executed, two different values will be substituted for the outer +variable). +.Ix 0 def variable types +.PP +Variables come in four flavors, though they are all expanded the same +and all look about the same. They are (in order of expanding scope): +.RS +.IP \(bu 2 +Local variables. +.Ix 0 ref variable local +.IP \(bu 2 +Command-line variables. +.Ix 0 ref variable command-line +.IP \(bu 2 +Global variables. +.Ix 0 ref variable global +.IP \(bu 2 +Environment variables. +.Ix 0 ref variable environment +.RE +.LP +The classification of variables doesn't matter much, except that the +classes are searched from the top (local) to the bottom (environment) +when looking up a variable. The first one found wins. +.xH 3 Local Variables +.PP +.Ix 0 def variable local +Each target can have as many as seven local variables. These are +variables that are only ``visible'' within that target's shell script +and contain such things as the target's name, all of its sources (from +all its dependency lines), those sources that were out-of-date, etc. +Four local variables are defined for all targets. They are: +.RS +.IP ".TARGET" +.Ix 0 def variable local .TARGET +.Ix 0 def .TARGET +The name of the target. +.IP ".OODATE" +.Ix 0 def variable local .OODATE +.Ix 0 def .OODATE +The list of the sources for the target that were considered out-of-date. +The order in the list is not guaranteed to be the same as the order in +which the dependencies were given. +.IP ".ALLSRC" +.Ix 0 def variable local .ALLSRC +.Ix 0 def .ALLSRC +The list of all sources for this target in the order in which they +were given. +.IP ".PREFIX" +.Ix 0 def variable local .PREFIX +.Ix 0 def .PREFIX +The target without its suffix and without any leading path. E.g. for +the target +.CW ../../lib/compat/fsRead.c , +this variable would contain +.CW fsRead . +.RE +.LP +Three other local variables are set only for certain targets under +special circumstances. These are the ``.IMPSRC,'' +.Ix 0 ref variable local .IMPSRC +.Ix 0 ref .IMPSRC +``.ARCHIVE,'' +.Ix 0 ref variable local .ARCHIVE +.Ix 0 ref .ARCHIVE +and ``.MEMBER'' +.Ix 0 ref variable local .MEMBER +.ix 0 ref .MEMBER +variables. When they are set and how they are used is described later. +.xH 3 Command-line Variables +.PP +.Ix 0 def variable command-line +Command-line variables are set when PMake is first invoked by giving a +variable assignment as one of the arguments. For example, +.DS +pmake "CFLAGS = -I/sprite/src/lib/libc -O" +.DE +would make +.CW CFLAGS +be a command-line variable with the given value. Any assignments to +.CW CFLAGS +in the makefile will have no effect, because once it +is set, there is (almost) nothing you can do to change a command-line +variable (the search order, you see). Command-line variables may be +set using any of the four assignment operators, though only +.CW = +and +.CW ?= +behave as you would expect them to, mostly because assignments to +command-line variables are performed before the makefile is read, thus +the values set in the makefile are unavailable at the time. +.CW += +.Ix 0 ref += +.Ix 0 ref variable assignment appended +is the same as +.CW = , +because the old value of the variable is sought only on the scope in +which the assignment is taking place (for reasons of efficiency that I +won't get into here). +.CW := +and +.CW ?= +.Ix 0 ref := +.Ix 0 ref ?= +.Ix 0 ref variable assignment expanded +.Ix 0 ref variable assignment conditional +will work if the only variables used are in the environment. +.xH 3 Global Variables +.PP +.Ix 0 def variable global +Global variables are those set or appended-to in the makefile. +There are two classes of global variables: those you set and those PMake sets. +As I said before, the ones you set can have any name you want them to have, +except they may not contain a colon or an exclamation point. +The variables PMake sets (almost) always begin with a +period and always contain upper-case letters, only. The variables are +as follows: +.RS +.IP .PMAKE +.Ix 0 def variable global .PMAKE +.Ix 0 def .PMAKE +.Ix 0 def variable global MAKE +.Ix 0 def MAKE +The name by which PMake was invoked is stored in this variable. For +compatibility, the name is also stored in the MAKE variable. +.IP .MAKEFLAGS +.Ix 0 def variable global .MAKEFLAGS +.Ix 0 def .MAKEFLAGS variable +.Ix 0 def variable global MFLAGS +.Ix 0 def MFLAGS +All the relevant flags with which PMake was invoked. This does not +include such things as +.B \-f +or variable assignments. Again for compatibility, this value is stored +in the MFLAGS variable as well. +.RE +.LP +Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the +section on special targets in chapter 3. +.Ix 0 ref variable global .INCLUDES +.Ix 0 ref variable global .LIBS +.PP +Global variables may be deleted using lines of the form: +.Ix 0 def #undef +.Ix 0 def variable deletion +.DS +#undef \fIvariable\fP +.DE +The +.CW # ' ` +must be the first character on the line. Note that this may only be +done on global variables. +.xH 3 Environment Variables +.PP +.Ix 0 def variable environment +Environment variables are passed by the shell that invoked PMake and +are given by PMake to each shell it invokes. They are expanded like +any other variable, but they cannot be altered in any way. +.PP +One special environment variable, +.CW PMAKE , +.Ix 0 def variable environment PMAKE +is examined by PMake for command-line flags, variable assignments, +etc., it should always use. This variable is examined before the +actual arguments to PMake are. In addition, all flags given to PMake, +either through the +.CW PMAKE +variable or on the command line, are placed in this environment +variable and exported to each shell PMake executes. Thus recursive +invocations of PMake automatically receive the same flags as the +top-most one. +.PP +Using all these variables, you can compress the sample makefile even more: +.DS +OBJS = a.o b.o c.o +program : $(OBJS) + cc $(.ALLSRC) \-o $(.TARGET) +$(OBJS) : defs.h +a.o : a.c + cc \-c a.c +b.o : b.c + cc \-c b.c +c.o : c.c + cc \-c c.c +.DE +.Ix 0 ref variable local .ALLSRC +.Ix 0 ref .ALLSRC +.Ix 0 ref variable local .TARGET +.Ix 0 ref .TARGET +.xH 2 Comments +.PP +.Ix 0 def comments +Comments in a makefile start with a `#' character and extend to the +end of the line. They may appear +anywhere you want them, except in a shell command (though the shell +will treat it as a comment, too). If, for some reason, you need to use the `#' +in a variable or on a dependency line, put a backslash in front of it. +PMake will compress the two into a single `#'. +.xH 2 Parallelism +.No +.PP +PMake was specifically designed to re-create several targets at once, +when possible. You do not have to do anything special to cause this to +happen (unless PMake was configured to not act in parallel, in which +case you will have to make use of the +.B \-L +and +.B \-J +flags (see below).), +.Ix 0 ref flags -L +.Ix 0 ref flags -J +but you do have to be careful at times. +.PP +There are several problems you are likely to encounter. One is +that some makefiles (and programs) are written in such a way that it is +impossible for two targets to be made at once. The program +.CW xstr , +for example, +always modifies the files +.CW strings +and +.CW x.c . +There is no way to change it. Thus you cannot run two of them at once +without something being trashed. Similarly, if you have commands +in the makefile that always send output to the same file, you will not +be able to make more than one target at once unless you change the +file you use. You can, for instance, add a +.CW $$$$ +to the end of the file name to tack on the process ID of the shell +executing the command (each +.CW $$ +expands to a single +.CW $ , +thus giving you the shell variable +.CW $$ ). +Since only one shell is used for all the +commands, you'll get the same file name for each command in the +script. +.PP +The other problem comes from improperly-specified dependencies that +worked in Make because of its sequential, depth-first way of examining +them. While I don't want to go into depth on how PMake +works (look in chapter 4 if you're interested), I will warn you that +files in two different ``levels'' of the dependency tree may be +examined in a different order in PMake than they were in Make. For +example, given the makefile +.DS +a : b c +b : d +.DE +PMake will examine the targets in the order +.CW c , +.CW d , +.CW b , +.CW a . +If the makefile's author expected PMake to abort before making +.CW c +if an error occurred while making +.CW b , +or if +.CW b +needed to exist before +.CW c +was made, +s/he will be sorely disappointed. The dependencies are +incomplete, since in both these cases, +.CW c +would depend on +.CW b . +So watch out. +.PP +Another problem you may face is that, while PMake is set up to handle the +output from multiple jobs in a graceful fashion, the same is not so for input. +It has no way to regulate input to different jobs, +so if you use the redirection from +.CW /dev/tty +I mentioned earlier, you must be careful not to run two of the jobs at once. +.xH 2 Writing and Debugging a Makefile +.PP +Now you know most of what's in a makefile, what do you do next? There +are two choices: (1) use one of the uncommonly-available makefile +generators or (2) write your own makefile (I leave out the third choice of +ignoring PMake and doing everything by hand as being beyond the bounds +of common sense). +.PP +When faced with the writing of a makefile, it is usually best to start +from first principles: just what +.I are +you trying to do? What do you want to makefile finally to produce? +.PP +To begin with a somewhat traditional example, let's say you need to +write a makefile to create a program, +.CW expr , +that takes standard infix expressions and converts them to prefix form (for +no readily apparent reason). You've got three source files, in C, that +make up the program: +.CW main.c , +.CW parse.c , +and +.CW output.c . +Harking back to my pithy advice about dependency lines, you write the +first line of the file: +.DS +expr : main.o parse.o output.o +.DE +because you remember +.CW expr +is made from +.CW .o +files, not +.CW .c +files. Similarly for the +.CW .o +files you produce the lines: +.DS +main.o : main.c +parse.o : parse.c +output.o : output.c +main.o parse.o output.o : defs.h +.DE +.PP +Great. You've now got the dependencies specified. What you need now is +commands. These commands, remember, must produce the target on the +dependency line, usually by using the sources you've listed. +You remember about local variables? Good, so it should come +to you as no surprise when you write +.DS +expr : main.o parse.o output.o + cc -o $(.TARGET) $(.ALLSRC) +.DE +Why use the variables? If your program grows to produce postfix +expressions too (which, of course, requires a name change or two), it +is one fewer place you have to change the file. You cannot do this for +the object files, however, because they depend on their corresponding +source files +.I and +.CW defs.h , +thus if you said +.DS + cc -c $(.ALLSRC) +.DE +you'd get (for +.CW main.o ): +.DS + cc -c main.c defs.h +.DE +which is wrong. So you round out the makefile with these lines: +.DS +main.o : main.c + cc -c main.c +parse.o : parse.c + cc -c parse.c +output.o : output.c + cc -c output.c +.DE +.PP +The makefile is now complete and will, in fact, create the program you +want it to without unnecessary compilations or excessive typing on +your part. There are two things wrong with it, however (aside from it +being altogether too long, something I'll address in chapter 3): +.IP 1) +The string +.CW "main.o parse.o output.o" '' `` +is repeated twice, necessitating two changes when you add postfix +(you were planning on that, weren't you?). This is in direct violation +of de Boor's first rule of writing makefiles: +.QP +.I +Anything that needs to be written more than once +should be placed in a variable. +.IP "\&" +I cannot emphasize this enough as being very important to the +maintenance of a makefile and its program. +.IP 2) +There is no way to alter the way compilations are performed short of +editing the makefile and making the change in all places. This is evil +and violates de Boor's second rule, which follows directly from the +first: +.QP +.I +Any flags or programs used inside a makefile should be placed in a variable so +they may be changed, temporarily or permanently, with the greatest ease. +.PP +The makefile should more properly read: +.DS +OBJS = main.o parse.o output.o +expr : $(OBJS) + $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) +main.o : main.c + $(CC) $(CFLAGS) -c main.c +parse.o : parse.c + $(CC) $(CFLAGS) -c parse.c +output.o : output.c + $(CC) $(CFLAGS) -c output.c +$(OBJS) : defs.h +.DE +These two rules lead to de Boor's first corrolary: +.QP +.I +Variables are your friends. +.PP +Once you've written the makefile comes the sometimes-difficult task of +.Ix 0 ref debugging +making sure the darn thing works. Your most helpful tool to make sure +the makefile is at least syntactically correct is the +.B \-n +.Ix 0 ref flags -n +flag, which allows you to see if PMake will choke on the makefile. The +second thing the +.B \-n +flag lets you do is see what PMake would do without it actually doing +it, thus you can make sure the right commands would be executed were +you to give PMake its head. +.PP +When you find your makefile isn't behaving as you hoped, the first +question that comes to mind (after ``What time is it, anyway?'') is +``Why not?'' In answering this, two flags will serve you well: +.CW "-d m" '' `` +.Ix 0 ref flags -d +and +.CW "-p 2" .'' `` +.Ix 0 ref flags -p +The first causes PMake to tell you as it examines each target in the +makefile and indicate why it is deciding whatever it is deciding. You +can then use the information printed for other targets to see where +you went wrong. The +.CW "-p 2" '' `` +flag makes PMake print out its internal state when it is done, +allowing you to see that you forgot to make that one chapter depend on +that file of macros you just got a new version of. The output from +.CW "-p 2" '' `` +is intended to resemble closely a real makefile, but with additional +information provided and with variables expanded in those commands +PMake actually printed or executed. +.PP +Something to be especially careful about is circular dependencies. +.Ix 0 def dependency circular +E.g. +.DS +a : b +b : c d +d : a +.DE +In this case, because of how PMake works, +.CW c +is the only thing PMake will examine, because +.CW d +and +.CW a +will effectively fall off the edge of the universe, making it +impossible to examine +.CW b +(or them, for that matter). +The sad thing is, this all happens quite +silently. PMake will complain about the circularity (and tell you +where it is) if you run it as +.CW make '' `` +or give it the +.B \-M +.Ix 0 ref flags -M +flag. Otherwise, you will suffer in silence. It's the nature of the +beast (see chapter 4 for why). +.xH 2 Invoking PMake +.PP +.Ix 0 ref flags +.Ix 0 ref arguments +.Ix 0 ref usage +PMake comes with a wide variety of flags to choose from. +They may appear in any order, interspersed with command-line variable +assignments and targets to create. +The flags are as follows: +.IP "\fB\-d\fP \fIwhat\fP" +.Ix 0 def flags -d +.Ix 0 ref debugging +This causes PMake to spew out debugging information that +may prove useful to you. If you can't +figure out why PMake is doing what it's doing, you might try using +this flag. The +.I what +parameter is a string of single characters that tell PMake what +aspects you are interested in. Most of what I describe will make +little sense to you, unless you've dealt with Make before. Just +remember where this table is and come back to it as you read on. +The characters and the information they produce are as follows: +.RS +.IP a +Archive searching and caching. +.IP c +Conditional evaluation. +.IP d +The searching and caching of directories. +.IP j +Various snippets of information related to the running of the multiple +shells. Not particularly interesting. +.IP m +The making of each target: what target is being examined; when it was +last modified; whether it is out-of-date; etc. +.IP p +Makefile parsing. +.IP r +Remote execution. +.IP s +The application of suffix-transformation rules. (See chapter 3) +.IP t +The maintenance of the list of targets. +.IP v +Variable assignment. +.RE +.IP "\&" +Of these all, the +.CW m +and +.CW s +letters will be most useful to you. +If the +.B \-d +is the final argument or the argument from which it would get these +key letters (see below) begins with a +.B \- , +all of these debugging flags will be set, resulting in massive amounts +of output. +.IP "\fB\-f\fP \fImakefile\fP" +.Ix 0 def flags -f +Specify a makefile to read different from the standard makefiles +.CW Makefile "\&" ( +or +.CW makefile ). +.Ix 0 ref makefile default +.Ix 0 ref makefile other +If +.I makefile +is ``\-'', PMake uses the standard input. This is useful for making +quick and dirty makefiles.\|.\|. +.Ix 0 ref makefile "quick and dirty" +.IP \fB\-h\fP +.Ix 0 def flags -h +Prints out a summary of the various flags PMake accepts. It can also +be used to find out what level of concurrency was compiled into the +version of PMake you are using (look at +.B \-J +and +.B \-L ) +and various other information on how PMake was configured. +.Ix 0 ref configuration +.Ix 0 ref makefile system +.IP \fB\-i\fP +.Ix 0 def flags -i +If you give this flag, PMake will ignore non-zero status returned +by any of its shells. It's like placing a `\-' before all the commands +in the makefile. +.IP \fB\-k\fP +.Ix 0 def flags -k +This is similar to +.B \-i +in that it allows PMake to continue when it sees an error, but unlike +.B \-i , +where PMake continues blithely as if nothing went wrong, +.B \-k +causes it to recognize the error and only continue work on those +things that don't depend on the target, either directly or indirectly (through +depending on something that depends on it), whose creation returned the error. +The `k' is for ``keep going''.\|.\|. +.Ix 0 ref target +.IP \fB\-l\fP +.Ix 0 def flags -l +This turns off directory locking. Normally when PMake is invoked, it +checks for the existence of a certain file (``LOCK.make,'' if you must +know) that indicates that someone else is executing PMake in that +directory. Because two people doing the same thing in the same place +can be disastrous for the final product, PMake will refuse to do +anything until the lockfile is removed (unless you own the file. If +you're doing the same thing twice, it's you're fault if neither turns +out right). If you give the +.B \-l +flag, however, none of this checking occurs. +.IP \fB\-n\fP +.Ix 0 def flags -n +This flag tells PMake not to execute the commands needed to update the +out-of-date targets in the makefile. Rather, PMake will simply print +the commands it would have executed and exit. This is particularly +useful for checking the correctness of a makefile. If PMake doesn't do +what you expect it to, it's a good chance the makefile is wrong. +.IP "\fB\-p\fP \fInumber\fP" +.Ix 0 def flags -p +.Ix 0 ref debugging +This causes PMake to print its input in a reasonable form, though +not necessarily one that would make immediate sense to anyone but me. The +.I number +is a bitwise-or of 1 and 2 where 1 means it should print the input +before doing any processing and 2 says it should print it after +everything has been re-created. Thus +.CW "\-p 3" +would print it twice\*-once before processing and once after (you +might find the difference between the two interesting). This is mostly +useful to me, but you may find it informative in some bizarre circumstances. +.IP \fB\-q\fP +.Ix 0 def flags -q +If you give PMake this flag, it will not try to re-create anything. It +will just see if anything is out-of-date and exit non-zero if so. +.IP \fB\-r\fP +.Ix 0 def flags -r +When PMake starts up, it reads a default makefile that tells it what +sort of system it's on and gives it some idea of what to do if you +don't tell it anything. I'll tell you about it in chapter 3. If you +give this flag, PMake won't read the default makefile. +.IP \fB\-s\fP +.Ix 0 def flags -s +This causes PMake to not print commands before they're executed. It +is the equivalent of putting an `@' before every command in the +makefile. +.IP \fB\-t\fP +.Ix 0 def flags -t +Rather than try to re-create a target, PMake will simply ``touch'' it +so as to make it appear up-to-date. If the target didn't exist before, +it will when PMake finishes, but if the target did exist, it will +appear to have been updated. +.IP \fB\-B\fP +.Ix 0 ref compatibility +.Ix 0 def flags -B +Forces PMake to be as backwards-compatible with Make as possible while +still being itself. +This includes: +.RS +.IP \(bu 2 +Executing one shell per shell command +.IP \(bu 2 +Expanding anything that looks even vaguely like a variable, with the +empty string replacing any variable PMake doesn't know. +.IP \(bu 2 +Refusing to allow you to escape a `#' with a backslash. +.IP \(bu 2 +Permitting undefined variables on dependency lines and conditionals +(see below). Normally this causes PMake to abort. +.RE +.IP \fB\-C\fP +.Ix 0 def flags -C +This nullifies any and all compatibility mode flags you may have given +or implied. It is useful mostly in a makefile that you wrote for PMake +to avoid bad things happening when someone runs PMake as +.CW make '' `` +or has things set in the environment that tell it to be compatible. +.B \-C +is +.I not +placed in the +.CW PMAKE +environment variable or the +.CW .MAKEFLAGS +or +.CW MFLAGS +global variables. +.Ix 0 ref variable environment PMAKE +.Ix 0 ref variable global .MAKEFLAGS +.Ix 0 ref variable global MFLAGS +.Ix 0 ref .MAKEFLAGS variable +.Ix 0 ref MFLAGS +.IP "\fB\-D\fP \fIvariable\fP" +.Ix 0 def flags -D +Allows you to define a variable to have the empty string as its value. +The variable is a global variable, not a command-line variable. This +is useful mostly for people who are used to the C compiler arguments +and those using conditionals, which I'll get into in chapter 4. +.IP "\fB\-I\fP \fIdirectory\fP" +.Ix 0 def flags -I +Tells PMake another place to search for included makefiles. Yet +another thing to be explained in chapter 3. +.IP "\fB\-J\fP \fInumber\fP" +.Ix 0 def flags -J +Gives the absolute maximum number of targets to create at once on both +local and remote machines. +.IP "\fB\-L\fP \fInumber\fP" +.Ix 0 def flags -L +This specifies the maximum number of targets to create on the local +machine at once. This may be 0, though you should be wary of doing +this, as PMake will hang until a remote machine becomes available, if +one is not available when it is started. +.IP \fB\-M\fP +.Ix 0 ref compatibility +.Ix 0 def flags -M +This is the flag that provides absolute, complete, full compatibility +with Make. It still allows you to use all but a few of the features of +PMake, but it is non-parallel. This is the mode PMake enters if you +call it +.CW make .'' `` +.IP \fB\-P\fP +.Ix 0 def flags -P +.Ix 0 ref "output control" +When creating targets in parallel, several shells are executing at +once, each wanting to write its own two cent's-worth to the screen. +This output must be captured by PMake in some way in order to prevent +the screen from being filled with garbage even more indecipherable +than you usually see. PMake has two ways of doing this, one of which +provides for much cleaner output and a clear separation between the +output of different jobs, the other of which provides a more immediate +response so one can tell what is really happpening. The former is done +by notifying you when the creation of a target starts, capturing the +output and transferring it to the screen all at once when the job +finishes. The latter is done by catching the output of the shell (and +its children) and buffering it until an entire line is received, then +printing that line preceeded by an indication of which job produced +the output. Since I prefer this second method, it is the one used by +default. The first method will be used if you give the +.B \-P +flag to PMake. +.IP \fB\-V\fP +.Ix 0 def flags -V +As mentioned before, the +.B \-V +flag tells PMake to use Make's style of expanding variables, +substituting the empty string for any variable it doesn't know. +.IP \fB\-W\fP +.Ix 0 def flags -W +There are several times when PMake will print a message at you that is +only a warning, i.e. it can continue to work in spite of your having +done something silly (such as forgotten a leading tab for a shell +command). Sometimes you are well aware of silly things you have done +and would like PMake to stop bothering you. This flag tells it to shut +up about anything non-fatal. +.IP \fB\-X\fP +.Ix 0 def flags -X +This flag causes PMake to not attempt to export any jobs to another +machine (unless the +.B \-M +flag is in effect, or PMake was invoked as +.CW make ,'' `` +in which case it means +.I do +attempt to export jobs to another machine. +Why? Because exportation slows the process down substantially when +only a single target is made at a time). +.PP +Several flags may follow a single `\-'. Those flags that require +arguments take them from successive parameters. E.g. +.DS +pmake -fDnI server.mk DEBUG /chip2/X/server/include +.DE +will cause PMake to read +.CW server.mk +as the input makefile, define the variable +.CW DEBUG +as a global variable and look for included makefiles in the directory +.CW /chip2/X/server/include . +.xH 2 Summary +.PP +A makefile is made of four types of lines: +.RS +.IP \(bu 2 +Dependency lines +.IP \(bu 2 +Creation commands +.IP \(bu 2 +Variable assignments +.IP \(bu 2 +Comments, include statements and conditional directives +.RE +.PP +A dependency line is a list of one or more targets, an operator +.CW : ', (` +.CW :: ', ` +or +.CW ! '), ` +and a list of zero or more sources. +.PP +A creation command is a regular shell command preceeded by a tab. In +addition, if the first two characters after the tab (and other +whitespace) are a combination of +.CW @ ' ` +or +.CW - ', ` +PMake will cause the command to not be printed (if the character is +.CW @ ') ` +or errors from it to be ignored (if +.CW - '). ` +A blank line, dependency line or variable assignment terminates a +creation script. There may be only one creation script for each target +with a +.CW : ' ` +or +.CW ! ' ` +operator. +.PP +Variables are places to store text. They may be unconditionally +assigned-to using the +.CW = ' ` +operator, appended-to using the +.CW += ' ` +operator, conditionally (if the variable is undefined) assigned-to +with the +.CW ?= ' ` +operator, and assigned-to with variable expansion with the +.CW := ' ` +operator. They may be expanded (their value inserted) by enclosing +their name in parentheses or curly braces, prceeded by a dollar sign. +A dollar sign may be escaped with another dollar sign. Variables are +not expanded if PMake doesn't know about them. There are seven local +variables: +.CW .TARGET , +.CW .ALLSRC , +.CW .OODATE , +.CW .PREFIX , +.CW .IMPSRC , +.CW .ARCHIVE , +and +.CW .MEMBER . +Variables are good. Know them. Love them. Live them. +.PP +Debugging of makefiles is best accomplished using the +.B \-n , +.B "\-d m" , +and +.B "\-p 2" +flags. +.xH 2 Exercises +.ce +\s+4\fBTBA\fP\s0 +.xH Short-cuts and Other Nice Things +.PP +Based on what I've told you so far, you may have gotten the impression +that PMake is just a way of storing away commands and making sure you +don't forget to compile something. Good. That's just what it is. +However, the ways I've described have been inelegant, at best, and +painful, at worst. +This chapter contains things that make the +writing of makefiles easier and the makefiles themselves shorter and +easier to modify (and, occasionally, simpler). In this chapter, I +assume you are somewhat more +familiar with Sprite (or UNIX, if that's what you're using) than I did +in chapter 2, just so you're on your toes. +So without further ado... +.xH 2 Transformation Rules +.PP +As you know, a file's name consists of two parts: a base name, which +gives some hint as to the contents of the file, and a suffix, which +usually indicates the format of the file. +Over the years, as +.UX +has developed, +naming conventions, with regards to suffixes, have also developed that have +become almost as incontrovertible as Law. E.g. a file ending in +.CW .c +is assumed to contain C source code; one with a +.CW .o +suffix is assumed to be a compiled, relocatable object file that may +be linked into any program; a file with a +.CW .ms +suffix is usually a text file to be processed by Troff with the \-ms +macro package, and so on. +One of the best aspects of both Make and PMake comes from their +understanding of how the suffix of a file pertains to its contents and +their ability to do things with a file based soley on its suffix. This +ability comes from something known as a transformation rule. A +transformation rule specifies how to change a file with one suffix +into a file with another suffix. +.PP +A transformation rule looks much like a dependency line, except the +target is made of two known suffixes stuck together. Suffixes are made +known to PMake by placing them as sources on a dependency line whose +target is the special target +.CW .SUFFIXES . +E.g. +.DS +\&.SUFFIXES : .o .c +\&.c.o : + $(CC) $(CFLAGS) -c $(.IMPSRC) +.DE +The creation script attached to the target is used to transform a file with +the first suffix (in this case, +.CW .c ) +into a file with the second suffix (here, +.CW .o ). +In addition, the target inherits whatever attributes have been applied +to the transformation rule. +The simple rule given above says that to transform a C source file +into an object file, you compile it using +.CW cc +with the +.CW \-c +flag. +This rule is taken straight from the system makefile. Many +transformation rules (and suffixes) are defined there, and I refer you +to it for more examples (type +.CW "pmake -h" '' `` +to find out where it is). +.PP +There are several things to note about the transformation rule given +above: +.RS +.IP 1) +The +.CW .IMPSRC +variable. +.Ix 0 def variable local .IMPSRC +.Ix 0 def .IMPSRC +This variable is set to the ``implied source'' (the file from which +the target is being created; the one with the first suffix) which, in this +case, is the .c file. +.IP 2) +The +.CW CFLAGS +variable. Almost all of the transformation rules in the system +makefile are set up using variables that you can alter in your +makefile to tailor the rule to your needs. In this case, if you want +all your C files to be compiled with the +.B \-g +flag, to provide information for +.CW dbx , +you would set the +.CW CFLAGS +variable to contain +.CW -g +.CW "CFLAGS = -g" '') (`` +and PMake would take care of the rest. +.RE +.PP +To give you a quick example, the first makefile in chapter 2 could be +changed to this: +.DS +OBJS = a.o b.o c.o +program : $(OBJS) + $(CC) -o $(.TARGET) $(.ALLSRC) +$(OBJS) : defs.h +.DE +The transformation rule I gave above takes the place of the 6 lines +.DS +a.o : a.c + cc -c a.c +b.o : b.c + cc -c b.c +c.o : c.c + cc -c c.c +.DE +.PP +Now you may be wondering about the dependency between the +.CW .o +and +.CW .c +files \*- it's not mentioned anywhere in the new makefile. This is +because it isn't needed: one of the effects of applying a +transformation rule is the target comes to depend on the implied +source. That's why it's called the implied +.I source . +.PP +For a more detailed example. Say you have a makefile like this: +.DS +a.out : a.o b.o + $(CC) $(.ALLSRC) +.DE +and a directory set up like this: +.DS +total 4 +-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile +-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c +-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o +-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c +.DE +While just typing +.CW pmake '' `` +will do the right thing, it's much more informative to type +.CW "pmake -d s" ''. `` +This will show you what PMake is up to as it processes the files. In +this case, PMake prints the following: +.DS +Suff_FindDeps (a.out) + using existing source a.o + applying .o -> .out to "a.o" +Suff_FindDeps (a.o) + trying a.c...got it + applying .c -> .o to "a.c" +Suff_FindDeps (b.o) + trying b.c...got it + applying .c -> .o to "b.c" +Suff_FindDeps (a.c) + trying a.y...not there + trying a.l...not there + trying a.c,v...not there + trying a.y,v...not there + trying a.l,v...not there +Suff_FindDeps (b.c) + trying b.y...not there + trying b.l...not there + trying b.c,v...not there + trying b.y,v...not there + trying b.l,v...not there +--- a.o --- +cc -c a.c +--- b.o --- +cc -c b.c +--- a.out --- +cc a.o b.o +.DE +.PP +.CW Suff_FindDeps +is the name of a function in PMake that is called to check for implied +sources for a target using transformation rules. +The transformations it tries are, naturally +enough, limited to the ones that have been defined (a transformation +may be defined multiple times, by the way, but only the most recent +one will be used). You will notice, however, that there is a definite +order to the suffixes that are tried. This order is set by the +relative positions of the suffixes on the +.CW .SUFFIXES +line \*- the earlier a suffix appears, the earlier it is checked as +the source of a transformation. Once a suffix has been defined, the +only way to change its position in the pecking order is to remove all +the suffixes (by having a +.CW .SUFFIXES +dependency line with no sources) and redefine them in the order you +want. (Previously-defined transformation rules will be automatically +redefined as the suffixes they involve are re-entered.) +.PP +Another way to affect the search order is to make the dependency +explicit. In the above example, +.CW a.out +depends on +.CW a.o +and +.CW b.o . +Since a transformation exists from +.CW .o +to +.CW .out , +PMake uses that, as indicated by the +.CW "using existing source a.o" '' `` +message. +.PP +The search for a transformation starts from the suffix of the target +and continues through all the defined transformations, in the order +dictated by the suffix ranking, until an existing file with the same +base (the target name minus the suffix and any leading directories) is +found. At that point, one or more transformation rules will have been +found to change the one existing file into the target. +.PP +For example, ignoring what's in the system makefile for now, say you +have a makefile like this: +.DS +\&.SUFFIXES : .out .o .c .y .l +\&.l.c : + lex $(.IMPSRC) + mv lex.yy.c $(.TARGET) +\&.y.c : + yacc $(.IMPSRC) + mv y.tab.c $(.TARGET) +\&.c.o : + cc -c $(.IMPSRC) +\&.o.out : + cc -o $(.TARGET) $(.IMPSRC) +.DE +and the single file +.CW jive.l . +If you were to type +.CW "pmake -rd jive.out ms" ,'' `` +you would get the following output for +.CW jive.out : +.DS +Suff_FindDeps (jive.out) + trying jive.o...not there + trying jive.c...not there + trying jive.y...not there + trying jive.l...got it + applying .l -> .c to "jive.l" + applying .c -> .o to "jive.c" + applying .o -> .out to "jive.o" +.DE +and this is why: PMake starts with the target +.CW jive.out , +figures out its suffix +.CW .out ) ( +and looks for things it can transform to a +.CW .out +file. In this case, it only finds +.CW .o , +so it looks for the file +.CW jive.o . +It fails to find it, so it looks for transformations into a +.CW .o +file. Again it has only one choice: +.CW .c . +So it looks for +.CW jive.c +and, as you know, fails to find it. At this point it has two choices: +it can create the +.CW .c +file from either a +.CW .y +file or a +.CW .l +file. Since +.CW .y +came first on the +.CW .SUFFIXES +line, it checks for +.CW jive.y +first, but can't find it, so it looks for +.CW jive.l +and, lo and behold, there it is. +At this point, it has defined a transformation path as follows: +.CW .l +\(-> +.CW .c +\(-> +.CW .o +\(-> +.CW .out +and applies the transformation rules accordingly. For completeness, +and to give you a better idea of what PMake actually did with this +three-step transformation, this is what PMake printed for the rest of +the process: +.DS +Suff_FindDeps (jive.o) + using existing source jive.c + applying .c -> .o to "jive.c" +Suff_FindDeps (jive.c) + using existing source jive.l + applying .l -> .c to "jive.l" +Suff_FindDeps (jive.l) +Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date +Examining jive.c...non-existent...out-of-date +--- jive.c --- +lex jive.l +.\|.\|. meaningless lex output deleted .\|.\|. +mv lex.yy.c jive.c +Examining jive.o...non-existent...out-of-date +--- jive.o --- +cc -c jive.c +Examining jive.out...non-existent...out-of-date +--- jive.out --- +cc -o jive.out jive.o +.DE +.PP +One final question remains: what does PMake do with targets that have +no known suffix? PMake simply pretends it actually has a known suffix +and searches for transformations accordingly. +The suffix it chooses is the source for the +.CW .NULL +.Ix 0 ref .NULL +target mentioned later. In the system makefile, +.CW .out +is chosen as the ``null suffix'' +.Ix 0 def suffix null +.Ix 0 def "null suffix" +because most people use PMake to create programs. You are, however, +free and welcome to change it to a suffix of your own choosing. +The null suffix is ignored, however, when PMake is in compatibility +mode (see chapter 4). +.xH 2 Including Other Makefiles +.Ix 0 def makefile inclusion +.PP +Just as for programs, it is often useful to extract certain parts of a +makefile into another file and just include it in other makefiles +somehow. Many compilers allow you say something like +.DS +#include "defs.h" +.DE +to include the contents of +.CW defs.h +in the source file. PMake allows you to do the same thing for +makefiles, with the added ability to use variables in the filenames. +An include directive in a makefile looks either like this: +.DS +#include <file> +.DE +or this +.DS +#include "file" +.DE +The difference between the two is where PMake searches for the file: +the first way, PMake will look for +the file only in the system makefile directory (to find out what that +directory is, give PMake the +.B \-h +flag). +.Ix 0 ref flags -h +For files in double-quotes, the search is more complex: +.RS +.IP 1) +The directory of the makefile that's including the file. +.IP 2) +The current directory (the one in which you invoked PMake). +.IP 3) +The directories given by you using +.B \-I +flags, in the order in which you gave them. +.IP 4) +Directories given by +.CW .PATH +dependency lines (see chapter 4). +.IP 5) +The system makefile directory. +.RE +.LP +in that order. +.PP +You are free to use PMake variables in the filename\*-PMake will +expand them before searching for the file. You must specify the +searching method with either angle brackets or double-quotes +.I outside +of a variable expansion. I.e. the following +.DS +SYSTEM = <command.mk> + +#include $(SYSTEM) +.DE +won't work. +.xH 2 Saving Commands +.PP +.Ix 0 def ... +There may come a time when you will want to save certain commands to +be executed when everything else is done. For instance: you're +making several different libraries at one time and you want to create the +members in parallel. Problem is, +.CW ranlib +is another one of those programs that can't be run more than once in +the same directory at the same time (each one creates a file called +.CW __.SYMDEF +into which it stuffs information for the linker to use. Two of them +running at once will overwrite each other's file and the result will +be garbage for both parties). You might want a way to save the ranlib +commands til the end so they can be run one after the other, thus +keeping them from trashing each other's file. PMake allows you to do +this by inserting an ellipsis (``.\|.\|.'') as a command between +commands to be run at once and those to be run later. +.PP +So for the +.CW ranlib +case above, you might do this: +.DS +lib1.a : $(LIB1OBJS) + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) + +lib2.a : $(LIB2OBJS) + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +This would save both +.DS +ranlib $(.TARGET) +.DE +commands until the end, when they would run one after the other +(using the correct value for the +.CW .TARGET +variable, of course). +.PP +Commands saved in this manner are only executed if PMake manages to +re-create everything without an error. In addition, any `@' or `\-' +characters that precede the command are ignored. +.xH 2 Target Attributes +.PP +PMake allows you to give attributes to targets by means of special +sources. Like everything else PMake uses, these sources begin with a +period and are made up of all upper-case letters. There are various +reasons for using them, and I will try to give examples for most of +them. Others you'll have to find uses for yourself. Think of it as ``an +exercise for the reader.'' By placing one (or more) of these as a source on a +dependency line, you are ``marking the target(s) with that +attribute.'' That's just the way I phrase it, so you know. +.PP +Any attributes given as sources for a transformation rule are applied +to the target of the transformation rule when the rule is applied. +.Ix 0 def attributes +.Ix 0 ref source +.Ix 0 ref target +.nr pw \w'.EXPORTSAME 'u +.IP .DONTCARE \n(pwu +.Ix 0 def attributes .DONTCARE +.Ix 0 def .DONTCARE +If a target is marked with this attribute and PMake can't figure out +how to create it, it will ignore this fact and assume the file isn't +really needed or actually exists and PMake just can't find it. This may prove +wrong, but the error will be noted later on, not when PMake tries to create +the target so marked. +.IP .EXEC \n(pwu +.Ix 0 def attributes .EXEC +.Ix 0 def .EXEC +This attribute causes its shell script to be executed while having no +effect on targets that depend on it. This makes the target into a sort +of subroutine. An example. Say you have some LISP files that need to +be compiled and loaded into a LISP process. To do this, you echo LISP +commands into a file and execute a LISP with this file as its input +when everything's done. Say also that you have to load other files +from another system before you can compile your files and further, +that you don't want to go through the loading and dumping unless one +of .I your files has changed. Your makefile might look a little bit +like this (remember this is an educational example): +.DS +system : init a.fasl b.fasl c.fasl + for i in $(.ALLSRC); + do + echo -n '(load "' >> input + echo -n ${i} >> input + echo '")' >> input + done + echo '(dump "system")' >> input + lisp < input + +a.fasl : a.l init COMPILE +b.fasl : b.l init COMPILE +c.fasl : c.l init COMPILE +COMPILE : .USE + echo '(compile "$(.ALLSRC)")' >> input +init : .EXEC + echo '(load-system)' > input +.DE +.Ix 0 ref .USE +.Ix 0 ref attributes .USE +.Ix 0 ref variable local .ALLSRC +.IP "\&" +.CW .EXEC +sources, don't appear in the local variables of targets that depend on +them. Note that all the rules, not just that for +.CW system , +include +.CW init +as a source. This is because none of the other targets can be made +until +.CW init +has been made, thus they depend on it. +.IP .EXPORT \n(pwu +.Ix 0 def attributes .EXPORT +.Ix 0 def .EXPORT +This is used to mark those targets whose creation should be sent to +another machine if at all possible. This is may be used by some +exportation schemes if the exportation is expensive. You should ask +your administrator if it is necessary. +.IP .EXPORTSAME \n(pwu +.Ix 0 def attributes .EXPORTSAME +.Ix 0 def .EXPORTSAME +Tells the export system that the job should be exported to a machine +of the same architecture as the current one. Certain operations (e.g. +running text through +.CW nroff ) +can be performed the same on any architecture (CPU and +operating system type), while others (e.g. compiling a program with +.CW cc ) +must be performed on a machine with the same architecture. Not all +export systems will support this attribute. +.IP .IGNORE \n(pwu +.Ix 0 def attributes .IGNORE +.Ix 0 def .IGNORE attribute +Giving a target the +.CW .IGNORE +attribute causes PMake to ignore errors from any of the target's commands, as +if they all had `\-' before them. +.IP .INVISIBLE \n(pwu +.Ix 0 def attributes .INVISIBLE +.Ix 0 def .INVISIBLE +This allows you to specify one target as a source for another without +the one affecting the other's local variables. Useful if, say, you +have a makefile that creates two programs, one of which is used to +create the other, so it must exist before the other is created. You +could say +.DS +prog1 : $(PROG1OBJS) prog2 MAKEINSTALL +prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL +.DE +where +.CW MAKEINSTALL +is some complex .USE rule (see below) that depends on the +.Ix 0 ref .USE +.CW .ALLSRC +variable containing the right things. Without the +.CW .INVISIBLE +attribute for +.CW prog2 , +the +.CW MAKEINSTALL +rule couldn't be applied. This is not as useful as it should be, and +the semantics may change (or the whole thing go away) in the +not-too-distant future. +.IP .JOIN \n(pwu +.Ix 0 def attributes .JOIN +.Ix 0 def .JOIN +This is another way to avoid performing some operations in parallel +while permitting everything else to be done so. Specifically it +forces the target's shell script to be executed only if one or more of the +sources was out-of-date. In addition, the target's name, +in both its +.CW .TARGET +variable and all the local variables of any target that depends on it, +is replaced by the value of its +.CW .ALLSRC +variable. +As an example, suppose you have a program that has four libraries that +compile in the same directory along with, and at the same time as, the +program. You again have the problem with +.CW ranlib +that I mentioned earlier, only this time it's more severe: you +can't just sort of put the ranlib off to the end since the program +will need those libraries before it can be re-created. You can do +something like this: +.DS +program : $(OBJS) libraries + cc -o $(.TARGET) $(.ALLSRC) + +libraries : lib1.a lib2.a lib3.a lib4.a .JOIN + ranlib $(.OODATE) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +.Ix 0 ref variable local .OODATE +.Ix 0 ref .TARGET +.Ix 0 ref .ALLSRC +.Ix 0 ref .OODATE +In this case, PMake will re-create the +.CW $(OBJS) +as necessary, along with +.CW lib1.a , +.CW lib2.a , +.CW lib3.a +and +.CW lib4.a . +It will then execute +.CW ranlib +on any library that was changed and set +.CW program 's +.CW .ALLSRC +variable to contain what's in +.CW $(OBJS) +followed by +.CW "lib1.a lib2.a lib3.a lib4.a" .'' `` +In case you're wondering, it's called +.CW .JOIN +because it joins together different threads of the ``input graph'' at +the target marked with the attribute. +Another aspect of the .JOIN attribute is it keeps the target from +being created if the +.B \-t +flag was given. +.Ix 0 ref flags -t +.IP .MAKE \n(pwu +.Ix 0 def attributes .MAKE +.Ix 0 def .MAKE +The +.CW .MAKE +attribute marks its target as being a recursive invocation of PMake. +What this does is force PMake to execute the script associated with +the target (if it's out-of-date) even if you gave the +.B \-n +or +.B \-t +flag. By doing this, you can start at the top of a system and type +.DS +pmake -n +.DE +and have it descend the directory tree (if your makefiles are set up +correctly), printing what it would have executed if you hadn't +included the +.B \-n +flag. +.IP .NOEXPORT \n(pwu +.Ix 0 def attributes .NOEXPORT +.Ix 0 def .NOEXPORT attribute +If possible, PMake will attempt to export the creation of all targets to +another machine (this depends on how PMake was configured). Sometimes, +the creation is so simple, it is pointless to send it to another +machine. If you give the target the +.CW .NOEXPORT +attribute, it will be run locally, even if you've given PMake the +.B "\-L 0" +flag. +.IP .NOTMAIN \n(pwu +.Ix 0 def attributes .NOTMAIN +.Ix 0 def .NOTMAIN +Normally, if you do not specify a target to make in any other way, +PMake will take the first target on the first dependency line of a +makefile as the target to create. That target is known as the ``Main +Target'' and is labelled as such if you print the dependencies out +using the +.B \-p +flag. +.Ix 0 ref flags -p +Giving a target this attribute tells PMake that the target is +definitely +.I not +the Main Target. +This allows you to place targets in an included makefile and +have PMake create something else by default. +.IP .PRECIOUS \n(pwu +.Ix 0 def attributes .PRECIOUS +.Ix 0 def .PRECIOUS attribute +When PMake is interrupted (you type control-C at the keyboard), it +will attempt to clean up after itself by removing any half-made +targets. If a target has the +.CW .PRECIOUS +attribute, however, PMake will leave it alone. An additional side +effect of the `::' operator is to mark the targets as +.CW .PRECIOUS . +.Ix 0 ref operator double-colon +.Ix 0 ref :: +.IP .SILENT \n(pwu +.Ix 0 def attributes .SILENT +.Ix 0 def .SILENT attribute +Marking a target with this attribute keeps its commands from being +printed when they're executed, just as if they had an `@' in front of them. +.IP .USE \n(pwu +.Ix 0 def attributes .USE +.Ix 0 def .USE +By giving a target this attribute, you turn the target into PMake's equivalent +of a macro. When the target is used as a source for another target, +the other target acquires the commands, sources and attributes (except +.CW .USE ) +of the source. +If the target already has commands, the +.CW .USE +target's commands are added to the end. If more than one .USE-marked +source is given to a target, the rules are applied sequentially. +.IP "\&" \n(pwu +The typical .USE rule (as I call them) will use the sources of the +target to which it is applied (as stored in the +.CW .ALLSRC +variable for the target) as its ``arguments,'' if you will. +For example, you probably noticed that the commands for creating +.CW lib1.a +and +.CW lib2.a +in the example above were exactly the same. You can use the +.CW .USE +attribute to eliminate the repetition, like so: +.DS +lib1.a : $(LIB1OBJS) MAKELIB +lib2.a : $(LIB2OBJS) MAKELIB + +MAKELIB : .USE + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +.IP "\&" \n(pwu +Several system makefiles (not to be confused with The System Makefile) +exist that make use of these .USE rules to make your +life easier. (They're in the default, system makefile directory. Take a look.) +Note that the .USE rule source itself +.CW MAKELIB ) ( +does not appear in any of the targets's local variables. +There is no limit to the number of times I could use the +.CW MAKELIB +rule. If there were more libraries, I could continue with +.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' `` +and so on and so forth. +.xH 2 Special Targets +.PP +As there were in Make, so there are certain targets that have special +meaning to PMake. When you use one on a dependency line, it is the +only target that may appear on the left-hand-side of the operator. +.Ix 0 ref target +.Ix 0 ref operator +As for the attributes and variables, all the special targets +begin with a period and consist of upper-case letters only. +I won't describe them all in detail because some of them are rather +complex and I'll describe them in more detail than you'll want in +chapter 4. +The targets are as follows: +.nr pw \w'.MAKEFLAGS 'u +.IP .BEGIN \n(pwu +.Ix 0 def .BEGIN +Any commands attached to this target are executed before anything else +is done. You can use it for any initialization that needs doing. +Errors are checked as for any other target, but the only way to turn +off error checking is to use the +.CW .IGNORE +.Ix 0 ref .IGNORE attribute +.Ix 0 ref attributes .IGNORE +attribute. This may change. +.IP .DEFAULT \n(pwu +.Ix 0 def .DEFAULT +This is sort of a .USE rule for any target (that was used only as a +source) that PMake can't figure out any other way to create. It's only +``sort of'' a .USE rule because only the shell script attached to the +.CW .DEFAULT +target is used. The +.CW .IMPSRC +variable of a target that inherits +.CW .DEFAULT 's +commands is set to the target's own name. +.Ix 0 ref .IMPSRC +.Ix 0 ref variable local .IMPSRC +.IP .END \n(pwu +.Ix 0 def .END +This serves a function similar to +.CW .BEGIN , +in that commands attached to it are executed once everything has been +re-created (so long as no errors occurred). It also serves the extra +function of being a place on which PMake can hang commands you put off +to the end. Thus the script for this target will be executed before +any of the commands you save with the ``.\|.\|.''. There is no echo +control or error ignoring allowed, save with +.CW .SILENT +and +.CW .IGNORE +attributes. +.Ix 0 ref ... +.IP .EXPORT \n(pwu +The sources for this target are passed to the exportation system compiled +into PMake. Some systems will use these sources to configure +themselves. You should ask your system administrator about this. +.IP .IGNORE \n(pwu +.Ix 0 def .IGNORE target +.Ix 0 ref .IGNORE attribute +.Ix 0 ref attributes .IGNORE +This target marks each of its sources with the +.CW .IGNORE +attribute. If you don't give it any sources, then it is like +giving the +.B \-i +flag when you invoke PMake \*- errors are ignored for all commands. +.Ix 0 ref flags -i +.IP .INCLUDES \n(pwu +.Ix 0 def .INCLUDES target +.Ix 0 def variable global .INCLUDES +.Ix 0 def .INCLUDES variable +The sources for this target are taken to be suffixes that indicate a +file that can be included in a program source file. +The suffix must have already been declared with +.CW .SUFFIXES +(see below). +Any suffix so marked will have the directories on its search path +(see +.CW .PATH , +below) placed in the +.CW .INCLUDES +variable, each preceeded by a +.B \-I +flag. This variable can then be used as an argument for the compiler +in the normal fashion. The +.CW .h +suffix is already marked in this way in the system makefile. +.Ix 0 ref makefile system +E.g. if you have +.DS +.SUFFIXES : .bitmap +.PATH.bitmap : /usr/local/X/lib/bitmaps +.INCLUDES : .bitmap +.DE +PMake will place +.CW "-I/usr/local/X/lib/bitmaps" '' `` +in the +.CW .INCLUDES +variable and you can then say +.DS +cc $(.INCLUDES) -c xprogram.c +.DE +(Note: the +.CW .INCLUDES +variable is not actually filled in until the entire makefile has been read.) +.IP .INTERRUPT \n(pwu +.Ix 0 def .INTERRUPT +When PMake is interrupted, +it will execute the commands in the script for this target, if it +exists. There is neither error nor echo control for the commands +attached to the +.CW .INTERRUPT +target. +.IP .LIBS \n(pwu +.Ix 0 def .LIBS target +.Ix 0 def .LIBS variable +.Ix 0 def variable global .LIBS +This does for libraries what +.CW .INCLUDES +does for include files, except the flag used is +.B \-L , +as required by those linkers that allow you to tell them where to find +libraries. The variable used is +.CW .LIBS . +Be forewarned that PMake may not have been compiled to do this if the +linker on your system doesn't accept the +.B \-L +flag, though the +.CW .LIBS +variable will always be defined once the makefile has been read. +.IP .MAIN \n(pwu +.Ix 0 def .MAIN +If you didn't give a target (or targets) to create when you invoked +PMake, it will take the sources of this target as the targets to +create. +.IP .MAKEFLAGS \n(pwu +.Ix 0 def .MAKEFLAGS target +This target provides a way for you to always specify flags for PMake +when the makefile is used. The flags are just as they would be typed +to the shell (except you can't use shell variables unless they're in +the environment), +though the +.B \-f +and +.B \-r +flags have no effect. +.IP .NULL \n(pwu +.Ix 0 def .NULL +.Ix 0 ref suffix null +.Ix 0 ref "null suffix" +This allows you to specify what suffix PMake should pretend a file has +if, in fact, it has no known suffix. Only one suffix may be so +designated. The last source on the dependency line is the suffix that +is used (you should, however, only give one suffix.\|.\|.). +.IP .PATH \n(pwu +.Ix 0 def .PATH +If you give sources for this target, PMake will take them as +directories to search for files it cannot find in the current +directory. If you give no sources, it will clear out any directories +added to the search path before. Since the effects of this all get +very complex, I'll leave it til chapter four to give you a complete +explanation. +.IP .PATH\fIsuffix\fP \n(pwu +.Ix 0 ref .PATH +This does a similar thing to +.CW .PATH , +but it does it only for files with the given suffix. The suffix must +have been defined already. Look at +.B "Search Paths" +in chapter 4 for more information. +.IP .PRECIOUS \n(pwu +.Ix 0 def .PRECIOUS target +.Ix 0 ref .PRECIOUS attribute +.Ix 0 ref attributes .PRECIOUS +Similar to +.CW .IGNORE , +this gives the +.CW .PRECIOUS +attribute to each source on the dependency line, unless there are no +sources, in which case the +.CW .PRECIOUS +attribute is given to every target in the file. +.IP .RECURSIVE \n(pwu +.Ix 0 def .RECURSIVE +.Ix 0 ref attributes .MAKE +.Ix 0 ref .MAKE +This target applies the +.CW .MAKE +attribute to all its sources. It does nothing if you don't give it any sources. +.IP .SHELL \n(pwu +.Ix 0 def .SHELL +PMake is not constrained to only using the Bourne shell to execute +the commands you put in the makefile. You can tell it some other shell +to use with this target. Check out +.B "A Shell is a Shell is a Shell" +in chapter 4 for more information. +.IP .SILENT \n(pwu +.Ix 0 def .SILENT target +.Ix 0 ref .SILENT attribute +.Ix 0 ref attributes .SILENT +When you use +.CW .SILENT +as a target, it applies the +.CW .SILENT +attribute to each of its sources. If there are no sources on the +dependency line, then it is as if you gave PMake the +.B \-s +flag and no commands will be echoed. +.IP .SUFFIXES \n(pwu +.Ix 0 def .SUFFIXES +This is used to give new file suffixes for PMake to handle. Each +source is a suffix PMake should recognize. If you give a +.CW .SUFFIXES +dependency line with no sources, PMake will forget about all the +suffixes it knew (this also nukes the null suffix). +For those targets that need to have suffixes defined, this is how you do it. +.PP +In addition to these targets, a line of the form +.DS +\fIattribute\fP : \fIsources\fP +.DE +applies the +.I attribute +to all the targets listed as +.I sources . +.xH 2 Modifying Variable Expansion +.PP +.Ix 0 def variable expansion modified +.Ix 0 ref variable expansion +.Ix 0 def variable modifiers +Variables need not always be expanded verbatim. PMake defines several +modifiers that may be applied to a variable's value before it is +expanded. You apply a modifier by placing it after the variable name +with a colon between the two, like so: +.DS +${\fIVARIABLE\fP:\fImodifier\fP} +.DE +Each modifier is a single character followed by something specific to +the modifier itself. +You may apply as many modifiers as you want \*- each one is applied to +the result of the previous and is separated from the previous by +another colon. +.PP +There are seven ways to modify a variable's expansion, most of which +come from the C shell variable modification characters: +.RS +.IP "M\fIpattern\fP" +.Ix 0 def :M +.Ix 0 def modifier match +This is used to select only those words (a word is a series of +characters that are neither spaces nor tabs) that match the given +.I pattern . +The pattern is a wildcard pattern like that used by the shell, where +.CW * +means 0 or more characters of any sort; +.CW ? +is any single character; +.CW [abcd] +matches any single character that is either `a', `b', `c' or `d' +(there may be any number of characters between the brackets); +.CW [0-9] +matches any single character that is between `0' and `9' (i.e. any +digit. This form may be freely mixed with the other bracket form), and +`\\' is used to escape any of the characters `*', `?', `[' or `:', +leaving them as regular characters to match themselves in a word. +For example, the system makefile +.CW <makedepend.mk> +uses +.CW "$(CFLAGS:M-[ID]*)" '' `` +to extract all the +.CW \-I +and +.CW \-D +flags that would be passed to the C compiler. This allows it to +properly locate include files and generate the correct dependencies. +.IP "N\fIpattern\fP" +.Ix 0 def :N +.Ix 0 def modifier nomatch +This is identical to +.CW :M +except it substitutes all words that don't match the given pattern. +.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]" +.Ix 0 def :S +.Ix 0 def modifier substitute +Causes the first occurrence of +.I search-string +in the variable to be replaced by +.I replacement-string , +unless the +.CW g +flag is given at the end, in which case all occurences of the string +are replaced. The substitution is performed on each word in the +variable in turn. If +.I search-string +begins with a +.CW ^ , +the string must match starting at the beginning of the word. If +.I search-string +ends with a +.CW $ , +the string must match to the end of the word (these two may be +combined to force an exact match). If a backslash preceeds these two +characters, however, they lose their special meaning. Variable +expansion also occurs in the normal fashion inside both the +.I search-string +and the +.I replacement-string , +.B except +that a backslash is used to prevent the expansion of a +.CW $ , +not another dollar sign, as is usual. +Note that +.I search-string +is just a string, not a pattern, so none of the usual +regular-expression/wildcard characters have any special meaning save +.CW ^ +and +.CW $ . +In the replacement string, +the +.CW & +character is replaced by the +.I search-string +unless it is preceeded by a backslash. +You are allowed to use any character except +colon or exclamation point to separate the two strings. This so-called +delimiter character may be placed in either string by preceeding it +with a backslash. +.IP T +.Ix 0 def :T +.Ix 0 def modifier tail +Replaces each word in the variable expansion by its last +component (its ``tail''). For example, given +.DS +OBJS = ../lib/a.o b /usr/lib/libm.a +TAILS = $(OBJS:T) +.DE +the variable +.CW TAILS +would expand to +.CW "a.o b libm.a" .'' `` +.IP H +.Ix 0 def :H +.Ix 0 def modifier head +This is similar to +.CW :T , +except that every word is replaced by everything but the tail (the +``head''). Using the same definition of +.CW OBJS , +the string +.CW "$(OBJS:H)" '' `` +would expand to +.CW "../lib /usr/lib" .'' `` +Note that the final slash on the heads is removed and +anything without a head is replaced by the empty string. +.IP E +.Ix 0 def :E +.Ix 0 def modifier extension +.Ix 0 def modifier suffix +.CW :E +replaces each word by its suffix (``extension''). So +.CW "$(OBJS:E)" '' `` +would give you +.CW ".o .a" .'' `` +.IP R +.Ix 0 def :R +.Ix 0 def modifier root +.Ix 0 def modifier base +This replaces each word by everything but the suffix (the ``root'' of +the word). +.CW "$(OBJS:R)" '' `` +expands to `` +.CW "../lib/a b /usr/lib/libm" .'' +.RE +.xH 2 More on Debugging +.xH 2 More Exercises +.xH PMake for Gods +.PP +This chapter is devoted to those facilities in PMake that allow you to +do a great deal in a makefile with very little work, as well as do +some things you couldn't do in Make without a great deal of work (and +perhaps the use of other programs). The problem with these features, +is they must be handled with care, or you will end up with a mess. +.PP +Once more, I assume a greater familiarity with +.UX +or Sprite than I did in the previous two chapters. +.xH 2 Search Paths +.PP +PMake supports the dispersal of files into multiple directories by +allowing you to specify places to look for sources with +.CW .PATH +targets in the makefile. The directories you give as sources for these +targets make up a ``search path.'' There are two types of search paths +in PMake: one is used for all types of files (including included +makefiles) and is specified with a plain +.CW .PATH +target (e.g. +.CW ".PATH : RCS" ''), `` +while the other is specific to a certain type of file, as indicated by +the file's suffix. A specific search path is indicated by immediately following +the +.CW .PATH +with the suffix of the file. For instance +.DS +\&.PATH.h : /sprite/lib/include /sprite/att/lib/include +.DE +would tell PMake to look in the directories +.CW /sprite/lib/include +and +.CW /sprite/att/lib/include +for any files whose suffix is +.CW .h . +.PP +The current directory is always consulted first to see if a file +exists. Only if it cannot be found there are the directories in the +specific search path, followed by those in the general search path, +consulted. This searching is only performed for those targets in the +makefile that are used exclusively as sources, that is only for those +files that aren't created by the makefile. +.PP +When a file is found in some directory other than the current one, all +local variables that would have contained the target's name +.CW .ALLSRC , ( +and +.CW .IMPSRC ) +will instead contain the path to the file, as found by PMake. +Thus if you have a file +.CW ../lib/mumble.c +and a makefile +.DS +\&.PATH.c : ../lib +mumble : mumble.c + $(CC) -o $(.TARGET) $(.ALLSRC) +.DE +the command executed to create +.CW mumble +would be +.CW "cc -o mumble ../lib/mumble.c" .'' `` +(As an aside, the command in this case isn't strictly necessary, since +it will be found using transformation rules if it isn't given. This is because +.CW .out +is the null suffix by default and a transformation exists from +.CW .c +to +.CW .out . +Just thought I'd throw that in.) +.PP +If a file exists in two directories on the same search path, the file +in the first directory on the path will be the one PMake uses. So if +you have a large system spread over many directories, it would behoove +you to follow a naming convention that avoids such conflicts. +.PP +Something you should know about the way search paths are implemented +is that each directory is read, and its contents cached, exactly once +\&\*- when it is first encountered \*- so any changes to the +directories while PMake is running will not be noted when searching +for implicit sources, nor will they be found when PMake attempts to +discover when the file was last modified, unless the file was created in the +current directory. While people have suggested that PMake should read +the directories each time, my experience suggests that the caching seldom +causes problems. +.xH 2 Archives and Libraries +.PP +.UX +and Sprite allow you to merge files into an archive using the +.CW ar +command. Further, if the files are relocatable object files, you can +run +.CW ranlib +on the archive and get yourself a library that you can link into any +program you want. The main problem with archives is they double the +space you need to store the archived files, since there's one copy in +the archive and one copy out by itself. The problem with libraries is +you usually think of them as +.CW -lm +rather than +.CW /usr/lib/libm.a +and the linker thinks they're out-of-date if you so much as look at +them. +.PP +PMake solves the problem with archives by allowing you to tell it to +examine the files in the archives (so you can remove the individual +files without having to regenerate them later). To handle the problem +with libraries, PMake adds an additional way of deciding if a library +is out-of-date: +.IP \(bu 2 +If the table of contents is older than the library, or is missing, the +library is out-of-date. +.LP +A library is any target that looks like +.CW \-l name'' `` +or that ends in a suffix that was marked as a library using the +.CW .LIBS +target. +.CW .a +is so marked in the system makefile. +.PP +Members of an archive are specified as +``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''. +Thus +.CW libdix.a(window.o) '' ``' +specifies the file +.CW window.o +in the archive +.CW libdix.a . +.PP +A file that is a member of an archive is treated specially. If the +file doesn't exist, but it is in the archive, the modification time +recorded in the archive is used for the file when determining if the +file is out-of-date. When figuring out how to make an archived member target +(not the file itself, but the file in the archive \*- the +\fIarchive\fP(\fImember\fP) target), special care is +taken with the transformation rules, as follows: +.IP \(bu 2 +\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP. +.IP \(bu 2 +The transformation from the \fImember\fP's suffix to the +\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target. +.IP \(bu 2 +The \fIarchive\fP(\fImember\fP)'s +.CW .TARGET +variable is set to the name of the \fImember\fP if \fImember\fP is +actually a target or the path to the member file if \fImember\fP is +only a source. +.IP \(bu 2 +The +.CW .ARCHIVE +variable for the \fIarchive\fP(\fImember\fP) target is set to the path +of the \fIarchive\fP. +.Ix 0 def variable local .ARCHIVE +.Ix 0 def .ARCHIVE +.IP \(bu 2 +The +.CW .MEMBER +variable is set to the actual string inside the parentheses. In most +cases, this will be the same as the +.CW .TARGET +variable. +.Ix 0 def variable local .MEMBER +.Ix 0 def .MEMBER +.IP \(bu 2 +The \fIarchive\fP(\fImember\fP)'s place in the local variables of the +targets that depend on it is taken by the value of its +.CW .TARGET +variable. +.LP +Thus, a program library could be created with the following makefile: +.DS +\&.o.a : + ... + rm -f $(.TARGET:T) +OBJS = obj1.o obj2.o obj3.o +libprog.a : libprog.a($(OBJS)) + ar cru $(.TARGET) $(.OODATE) + ranlib $(.TARGET) +.DE +This will cause the three object files to be compiled (if the +corresponding source files were modified after the object file or, if +that doesn't exist, the archived object file), the out-of-date ones +archived in +.CW libprog.a , +a table of contents placed in the archive and the newly-archived +object files to be removed. +.PP +All this is used in the +.CW makelib.mk +system makefile to create a single library with ease. This makefile +looks like this: +.DS +# +# Rules for making libraries. The object files which make up the library are +# removed once they are archived. +# +# To make several libararies in parallel, you should define the variable +# "many_libraries". This will serialize the invocations of ranlib. +# +# To use, do something like this: +# +# OBJECTS = <files in the library> +# +# fish.a: fish.a($(OBJECTS)) MAKELIB +# +# $Header: /cvsroot/src/usr.bin/make/Attic/tutorial.ms,v 1.1 1994/03/05 00:35:14 cgd Exp $ SPRITE (Berkeley) +# + +#ifndef _MAKELIB_MK +_MAKELIB_MK = + +#include <po.mk> + +.po.a .o.a : + ... + rm -f $(.MEMBER) + +ARFLAGS ?= crl + +# +# Re-archive the out-of-date members and recreate the library's table of +# contents using ranlib. If many_libraries is defined, put the ranlib off +# til the end so many libraries can be made at once. +# +MAKELIB : .USE .PRECIOUS + ar $(ARFLAGS) $(.TARGET) $(.OODATE) +#ifndef no_ranlib +# ifdef many_libraries + ... +# endif many_libraries + ranlib $(.TARGET) +#endif no_ranlib + +#endif _MAKELIB_MK +.DE +.xH 2 On the Condition... +.PP +Like the C compiler before it, PMake allows you to configure the makefile, +based on the current environment, using conditional statements. A +conditional looks like this: +.DS +#if \fIboolean expression\fP +\fIlines\fP +#elif \fIanother boolean expression\fP +\fImore lines\fP +#else +\fIstill more lines\fP +#endif +.DE +They may be nested to a maximum depth of 30 and may occur anywhere +(except in a comment, of course). The +.CW # '' `` +must the very first character on the line. +.PP +Each +.I "boolean expression" +is made up of terms that look like function calls, the standard C +boolean operators +.CW && , +.CW || , +and +.CW ! , +and the standard arithmetic operators +.CW == , +.CW != , +.CW > , +.CW >= , +.CW < , +and +.CW <= , +with +.CW == +and +.CW != +being overloaded to allow string comparisons as well. +.CW && +represents logical AND; +.CW || +is logical OR and +.CW ! +is logical NOT. The arithmetic and string operators take precedence +over all three of these operators, while NOT takes precedence over +AND, which takes precedence over OR. This precedence may be +overridden with parentheses, and an expression may be parenthesized to +your heart's content. Each term looks like a call on one of four +functions: +.nr pw \w'defined 'u +.Ix 0 def make +.Ix 0 def conditional make +.Ix 0 def if make +.IP make \n(pwu +The syntax is +.CW make( \fItarget\fP\c +.CW ) +where +.I target +is a target in the makefile. This is true if the given target was +specified on the command line, or as the source for a +.CW .MAIN +target (note that the sources for +.CW .MAIN +are only used if no targets were given on the command line). +.IP defined \n(pwu +.Ix 0 def defined +.Ix 0 def conditional defined +.Ix 0 def if defined +The syntax is +.CW defined( \fIvariable\fP\c +.CW ) +and is true if +.I variable +is defined. Certain variables are defined in the system makefile that +identify the system on which PMake is being run. +.IP exists \n(pwu +.Ix 0 def exists +.Ix 0 def conditional exists +.Ix 0 def if exists +The syntax is +.CW exists( \fIfile\fP\c +.CW ) +and is true if the file can be found on the global search path (i.e. +that defined by +.CW .PATH +targets, not by +.CW .PATH \fIsuffix\fP +targets). +.IP empty \n(pwu +.Ix 0 def empty +.Ix 0 def conditional empty +.Ix 0 def if empty +This syntax is much like the others, except the string inside the +parentheses is of the same form as you would put between parentheses +when expanding a variable, complete with modifiers and everything. The +function returns true if the resulting string is empty (NOTE: an undefined +variable in this context will cause at the very least a warning +message about a malformed conditional, and at the worst will cause the +process to stop once it has read the makefile. If you want to check +for a variable being defined or empty, use the expression +.CW !defined( \fIvar\fP\c `` +.CW ") || empty(" \fIvar\fP\c +.CW ) '' +as the definition of +.CW || +will prevent the +.CW empty() +from being evaluated and causing an error, if the variable is +undefined). This can be used to see if a variable contains a given +word, for example: +.DS +#if !empty(\fIvar\fP:M\fIword\fP) +.DE +.PP +The arithmetic and string operators may only be used to test the value +of a variable. The lefthand side must contain the variable expansion, +while the righthand side contains either a string, enclosed in +double-quotes, or a number. The standard C numeric conventions (except +for specifying an octal number) apply to both sides. E.g. +.DS +#if $(OS) == 4.3 + +#if $(MACHINE) == "sun3" + +#if $(LOAD_ADDR) < 0xc000 +.DE +are all valid conditionals. In addition, the numeric value of a +variable can be tested as a boolean as follows: +.DS +#if $(LOAD) +.DE +would see if +.CW LOAD +contains a non-zero value and +.DS +#if !$(LOAD) +.DE +would test if +.CW LOAD +contains a zero value. +.PP +In addition to the bare +.CW #if ,'' `` +there are other forms that apply one of the first two functions to each +term. They are as follows: +.DS + ifdef \fRdefined\fP + ifndef \fR!defined\fP + ifmake \fRmake\fP + ifnmake \fR!make\fP +.DE +There are also the ``else if'' forms: +.CW elif , +.CW elifdef , +.CW elifndef , +.CW elifmake , +and +.CW elifnmake . +.PP +For instance, if you wish to create two versions of a program, one of which +is optimized (the production version) and the other of which is for debugging +(has symbols for dbx), you have two choices: you can create two +makefiles, one of which uses the +.CW \-g +flag for the compilation, while the other uses the +.CW \-O +flag, or you can use another target (call it +.CW debug ) +to create the debug version. The construct below will take care of +this for you. I have also made it so defining the variable +.CW DEBUG +(say with +.CW "pmake -D DEBUG" ) +will also cause the debug version to be made. +.DS +#if defined(DEBUG) || make(debug) +CFLAGS += -g +#else +CFLAGS += -O +#endif +.DE +There are, of course, problems with this approach. The most glaring +annoyance is that if you want to go from making a debug version to +making a production version, you have to remove all the object files, +or you will get some optimized and some debug versions in the same +program. Another annoyance is you have to be careful not to make two +targets that ``conflict'' because of some conditionals in the +makefile. For instance +.DS +#if make(print) +FORMATTER = ditroff -Plaser_printer +#endif +#if make(draft) +FORMATTER = nroff -Pdot_matrix_printer +#endif +.DE +would wreak havok if you tried +.CW "pmake draft print" '' `` +since you would use the same formatter for each target. As I said, +this all gets somewhat complicated. +.xH 2 A Shell is a Shell is a Shell +.PP +In normal operation, the Bourne Shell (better known as +.CW sh '') `` +is used to execute the commands to re-create targets. PMake also allows you +to specify a different shell for it to use when executing these +commands. There are several things PMake must know about the shell you +wish to use. These things are specified as the sources for the +.CW .SHELL +.Ix 0 ref .SHELL +.Ix 0 ref target .SHELL +target by keyword, as follows: +.IP "\fBpath=\fP\fIpath\fP" +PMake needs to know where the shell actually resides, so it can +execute it. If you specify this and nothing else, PMake will use the +last component of the path and look in its table of the shells it +knows and use the specification it finds, if any. Use this if you just +want to use a different version of the Bourne or C Shell (PMake knows +how to use the C Shell too). +.IP "\fBname=\fP\fIname\fP" +This is the name by which the shell is to be known. It is a single +word and, if no other keywords are specified (other than +.B path ), +it is the name by which PMake attempts to find a specification for the +it (as mentioned above). You can use this if you would just rather use +the C Shell than the Bourne Shell +.CW ".SHELL: name=csh" '' (`` +will do it). +.IP "\fBquiet=\fP\fIecho-off command\fP" +As mentioned before, PMake actually controls whether commands are +printed by introducing commands into the shell's input stream. This +keyword, and the next two, control what those commands are. The +.B quiet +keyword is the command used to turn echoing off. Once it is turned +off, echoing is expected to remain off until the echo-on command is given. +.IP "\fBecho=\fP\fIecho-on command\fP" +The command PMake should give to turn echoing back on again. +.IP "\fBfilter=\fP\fIprinted echo-off command\fP" +Many shells will echo the echo-off command when it is given. This +keyword tells PMake in what format the shell actually prints the +echo-off command. Where ever PMake sees this string in the shell's +output, it will delete it and any following whitespace, up to and +including the next newline. See the example at the end of this section +for more details. +.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP" +Unless a target has been marked +.CW .SILENT , +PMake wants to start the shell running with echoing on. To do this, it +passes this flag to the shell as one of its arguments. If either this +or the next flag begins with a `\-', the flags will be passed to the +shell as separate arguments. Otherwise, the two will be concatenated +(if they are used at the same time, of course). +.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP" +Likewise, unless a target is marked +.CW .IGNORE , +PMake wishes error-checking to be on from the very start. To this end, +it will pass this flag to the shell as an argument. The same rules for +an initial `\-' apply as for the +.B echoFlag . +.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP" +Just as for echo-control, error-control is achieved by inserting +commands into the shell's input stream. This is the command to make +the shell check for errors. It also serves another purpose if the +shell doesn't have error-control as commands, but I'll get into that +in a minute. Again, once error checking has been turned on, it is +expected to remain on until it is turned off again. +.IP "\fBignore=\fP\fIcommand to turn error checking off\fP" +This is the command PMake uses to turn error checking off. It has +another use if the shell doesn't do error-control, but I'll tell you +about that.\|.\|.\|now. +.IP "\fBhasErrCtl=\fP\fIyes or no\fP" +This takes a value that is either +.B yes +or +.B no . +Now you might think that the existence of the +.B check +and +.B ignore +keywords would be enough to tell PMake if the shell can do +error-control, but you'd be wrong. If +.B hasErrCtl +is +.B yes , +PMake uses the check and ignore commands in a straight-forward manner. +If this is +.B no , +however, their use is rather different. In this case, the check +command is used as a template, in which the string +.B %s +is replaced by the command that's about to be executed, to produce a +command for the shell that will echo the command to be executed. The +ignore command is also used as a template, again with +.B %s +replaced by the command to be executed, to produce a command that will +execute the command to be executed and ignore any error it returns. +When these strings are used as templates, you must provide newline(s) +.CW \en '') (`` +in the appropriate place(s). +.PP +The strings that follow these keywords may be enclosed in single or +double quotes (the quotes will be stripped off) and may contain the +usual C backslash-characters (\en is newline, \er is return, \eb is +backspace, \e' escapes a single-quote inside single-quotes, \e" +escapes a double-quote inside double-quotes). Now for an example. +.PP +This is actually the contents of the +.CW <shx.mk> +system makefile, and causes PMake to use the Bourne Shell in such a +way that each command is printed as it is executed. That is, if more +than one command is given on a line, each will be printed separately. +Similarly, each time the body of a loop is executed, the commands +within that loop will be printed, etc. The specification runs like +this: +.DS +# +# This is a shell specification to have the bourne shell echo +# the commands just before executing them, rather than when it reads +# them. Useful if you want to see how variables are being expanded, etc. +# +\&.SHELL : path=/bin/sh \e + quiet="set -" \e + echo="set -x" \e + filter="+ set - " \e + echoFlag=x \e + errFlag=e \e + hasErrCtl=yes \e + check="set -e" \e + ignore="set +e" +.DE +.LP +It tells PMake the following: +.Bp +The shell is located in the file +.CW /bin/sh . +It need not tell PMake that the name of the shell is +.CW sh +as PMake can figure that out for itself (it's the last component of +the path). +.Bp +The command to stop echoing is +.CW "set -" . +.Bp +The command to start echoing is +.CW "set -x" . +.Bp +When the echo off command is executed, the shell will print +.CW "+ set - " +(The `+' comes from using the +.CW \-x +flag (rather than the +.CW \-v +flag PMake usually uses)). PMake will remove all occurences of this +string from the output, so you don't notice extra commands you didn't +put there. +.Bp +The flag the Bourne Shell will take to start echoing in this way is +the +.CW \-x +flag. The Bourne Shell will only take its flag arguments concatenated +as its first argument, so neither this nor the +.B errFlag +specification begins with a \-. +.Bp +The flag to use to turn error-checking on from the start is +.CW \-e . +.Bp +The shell can turn error-checking on and off, and the commands to do +so are +.CW "set +e" +and +.CW "set -e" , +respectively. +.PP +I should note that this specification is for Bourne Shells that are +not part of Berkeley +.UX , +as shells from Berkeley don't do error control. You can get a similar +effect, however, by changing the last three lines to be: +.DS + hasErrCtl=no \e + check="echo \e"+ %s\e"\en" \e + ignore="sh -c '%s || exit 0\en" +.DE +.LP +This will cause PMake to execute the two commands +.DS +echo "+ \fIcmd\fP" +sh -c '\fIcmd\fP || true' +.DE +for each command for which errors are to be ignored. (In case you are +wondering, the thing for +.CW ignore +tells the shell to execute another shell without error checking on and +always exit 0, since the +.B || +causes the +.CW "exit 0" +to be executed only if the first command exited non-zero, and if the +first command exited zero, the shell will also exit zero, since that's +the last command it executed). +.xH 2 Compatibility +.Ix 0 ref compatibility +.PP +There are three levels of backwards-compatibility built into PMake. +Most makefiles will need none at all. Some may need a little bit of +work to operate correctly when run in parallel. Each level encompasses +the previous levels (e.g. +.B \-B +(one shell per command) implies +.B \-V ) +The three levels are described in the following three sections. +.xH 3 DEFCON 3 \*- Variable Expansion +.Ix 0 ref compatibility +.PP +As noted before, PMake will not expand a variable unless it knows of a +value for it. This can cause problems for makefiles that expect to +leave variables undefined except in special circumstances (e.g. if +more flags need to be passed to the C compiler or the output from a +text processor should be sent to a different printer). If the +variables are enclosed in curly braces +.CW ${PRINTER} ''), (`` +the shell will let them pass. If they are enclosed in parentheses, +however, the shell will declare a syntax error and the make will come +to a grinding halt. +.PP +You have two choices: change the makefile to define the variables +(their values can be overridden on the command line, since that's +where they would have been set if you used Make, anyway) or always give the +.B \-V +flag (this can be done with the +.CW .MAKEFLAGS +target, if you want). +.xH 3 DEFCON 2 \*- The Number of the Beast +.Ix 0 ref compatibility +.PP +Then there are the makefiles that expect certain commands, such as +changing to a different directory, to not affect other commands in a +target's creation script. You can solve this is either by going +back to executing one shell per command (which is what the +.B \-B +flag forces PMake to do), which slows the process down a good bit and +requires you to use semicolons and escaped newlines for shell constructs, or +by changing the makefile to execute the offending command(s) in a subshell +(by placing the line inside parentheses), like so: +.DS +install :: .MAKE + (cd src; $(.PMAKE) install) + (cd lib; $(.PMAKE) install) + (cd man; $(.PMAKE) install) +.DE +.Ix 0 ref operator double-colon +.Ix 0 ref variable global .PMAKE +.Ix 0 ref .PMAKE +.Ix 0 ref .MAKE +.Ix 0 ref attribute .MAKE +This will always execute the three makes (even if the +.B \-n +flag was given) because of the combination of the ``::'' operator and +the +.CW .MAKE +attribute. Each command will change to the proper directory to perform +the install, leaving the main shell in the directory in which it started. +.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery" +.Ix 0 ref compatibility +.PP +The final category of makefile is the one where every command requires +input, the dependencies are incompletely specified, or you simply +cannot create more than one target at a time, as mentioned earlier. In +addition, you may not have the time or desire to upgrade the makefile +to run smoothly with PMake. If you are the conservative sort, this is +the compatibility mode for you. It is entered either by giving PMake +the +.B \-M +flag (for Make), or by executing PMake as +.CW make .'' `` +In either case, PMake performs things exactly like Make, while still +supporting most of the nice new features PMake provides. This +includes: +.IP \(bu 2 +No parallel execution. +.IP \(bu 2 +Targets are made in the exact order specified by the makefile. The +sources for each target are made in strict left-to-right order, etc. +.IP \(bu 2 +A single Bourne shell is used to execute each command, thus the +shell's +.CW $$ +variable is useless, changing directories doesn't work across command +lines, etc. +.IP \(bu 2 +If no special characters exist in a command line, PMake will break the +command into words itself and execute the command directly, without +executing a shell first. The characters that cause PMake to execute a +shell are: +.CW # , +.CW = , +.CW | , +.CW ^ , +.CW ( , +.CW ) , +.CW { , +.CW } , +.CW ; , +.CW & , +.CW < , +.CW > , +.CW * , +.CW ? , +.CW [ , +.CW ] , +.CW : , +.CW $ , +.CW ` , +and +.CW \e . +You should notice that these are all the characters that are given +special meaning by the shell. +.IP \(bu 2 +The use of the null suffix is turned off. +.Ix 0 ref "null suffix" +.Ix 0 ref suffix null +.xH 2 The Way Things Work +.PP +When PMake reads the makefile, it parses sources and targets into +nodes in a graph. The graph is directed only in the sense that PMake +knows which way is up. Each node contains not only links to all its +parents and children (the nodes that depend on it and those on which +it depends, respectively), but also a count of the number of its +children that have already been processed. +.PP +The most important thing to know about how PMake uses this graph is +that the traversal is breadth-first and occurs in two passes. +.PP +After PMake has parsed the makefile, it begins with the nodes the user +has told it to make (either on the command line, or via a +.CW .MAIN +target, or by the target being the first in the file not labelled with +the +.CW .NOTMAIN +attribute) placed in a queue. It continues to take the node of the +front of the queue, mark it as something that needs to be made, pass +the node to +.CW Suff_FindDeps +(mentioned earlier) to find any implicit sources for the node, and +place all the node's children that have yet to be marked at the end of +the queue. If any of the children is a +.CW .USE +rule, its children are linked to its parent and the parent's unmade +children counter is decremented (since the +.CW .USE +node has been processed). You will note that this allows a +.CW .USE +node to have children that are +.CW .USE +nodes and the rules will be applied in sequence. +If the node has no children, it is placed at the end of +another queue to be examined in the second pass. This process +continues until the queue is empty. +.PP +At this point, all the leaves of the graph are in the examination +queue. PMake removes the node at the head of the queue and sees if it +is out-of-date. If it is, it is passed to a function that will execute +the commands for the node asynchronously. When the commands have +completed, all the node's parents have their unmade children counter +decremented and, if the counter is then 0, they are placed on the +examination queue. Likewise, if the node is up-to-date. Only those +parents that were marked on the downward pass are processed in this +way. Thus PMake traverses the graph back up to the nodes the user +instructed it to create. When the examination queue is empty and no +shells are running to create a target, PMake is finished. +.xH Answers to Exercises +.xH Glossary of Jargon +.de Gp +.XP +\&\fB\\$1:\fP +.. +.Gp "attribute" +A property given to a target that causes PMake to treat it differently. +.Gp "command script" +The lines immediately following a dependency line that specify +commands to execute to create each of the targets on the dependency +line. Each line in the command script must begin with a tab. +.Gp "command-line variable" +A variable defined in an argument when PMake is first executed. +Overrides all assignments to the same variable name in the makefile. +.Gp "conditional" +A construct much like that used in C that allows a makefile to be +configured on the fly based on the local environment, or on what is being +made by that invocation of PMake. +.Gp "creation script" +Commands used to create a target. See ``command script.'' +.Gp "dependency" +The relationship between a source and a target. +'\" Elaborate on this +.Gp "global variable" +Any variable defined in a makefile. Takes precedence over variables +defined in the environment, but not over command-line or local variables. +.Gp "input graph" +What PMake constructs from a makefile. Consists of nodes made of the +targets in the makefile, and the links between them (the +dependencies). The links are directed (from source to target) and +there may not be any cycles (loops) in the graph. +.Gp "local variable" +A variable defined by PMake visible only in a target's shell script. +There are seven local variables, not all of which are defined for +every target: +.CW .TARGET , +.CW .ALLSRC , +.CW .OODATE , +.CW .PREFIX , +.CW .IMPSRC , +.CW .ARCHIVE , +and +.CW .MEMBER . +.Gp "makefile" +A file that describes how a system is built. If you don't know what it +is after reading this tutorial.\|.\|.\|. +.Gp "modifier" +A letter, following a colon, used to alter how a variable is expanded. +It has no effect on the variable itself. +.Gp "operator" +What separates a source from a target (on a dependency line) and specifies +the relationship between the two. There are three: +.CW : ', ` +.CW :: ', ` +and +.CW ! '. ` +.Gp "search path" +A list of directories in which a file should be sought. PMake's view +of the contents of directories in a search path does not change once +the makefile has been read. A file is sought on a search path only if +it is exclusively a source. +.Gp "shell" +A program to which commands are passed in order to create targets. +.Gp "source" +Anything to the right of an operator on a dependency line. Targets on +the dependency line are usually created from the sources. +.Gp "special target" +A target that causes PMake to do special things when it's encountered. +.Gp "suffix" +The tail end of a file name. Usually begins with a period, +.CW .c +or +.CW .ms , +e.g. +.Gp "target" +A word to the left of the operator on a dependency line. More +generally, any file that PMake might create. A file may be (and often +is) both a target and a source (what it is depends on how PMake is +looking at it at the time). +.Gp "transformation rule" +A special construct in a makefile that specifies how to create a file +of one type from a file of another, as indicated by their suffixes. +.Gp "variable expansion" +The process of substituting the value of a variable for a reference to +it. Expansion may be altered by means of modifiers. +.Gp "variable" +A place in which to store text that may be retrieved later. Also used +to define the local environment. Conditionals exist that test whether +a variable is defined or not. +'\" Output table of contents last, with an entry for the index, making +'\" sure to save and restore the last real page number for the index... +.nr @n \n(PN+1 +.XS \n(@n 0 +Index +.XE +.nr %% \n% +.TC +.nr % \n(%% diff --git a/usr.bin/make/util.c b/usr.bin/make/util.c new file mode 100644 index 00000000000..6e2f777dba8 --- /dev/null +++ b/usr.bin/make/util.c @@ -0,0 +1,325 @@ +/* + * Missing stuff from OS's + * + * $Id: util.c,v 1.1 1994/03/05 00:35:16 cgd Exp $ + */ +#include <stdio.h> +#include <sys/cdefs.h> + +#if !__STDC__ +# ifndef const +# define const +# endif +#endif + +#ifdef sun + + + +extern int errno, sys_nerr; +extern char *sys_errlist[]; + +char * +strerror(e) + int e; +{ + static char buf[100]; + if (e < 0 || e >= sys_nerr) { + sprintf(buf, "Unknown error %d", e); + return buf; + } + else + return sys_errlist[e]; +} +#endif + +#if defined(sun) || defined(__hpux) + +int +setenv(name, value, dum) + const char *name; + const char *value; + int dum; +{ + register char *p; + int len = strlen(name) + strlen(value) + 2; /* = \0 */ + char *ptr = (char*) malloc(len); + + (void) dum; + + if (ptr == NULL) + return -1; + + p = ptr; + + while (*name) + *p++ = *name++; + + *p++ = '='; + + while (*value) + *p++ = *value++; + + *p = '\0'; + + len = putenv(ptr); +/* free(ptr); */ + return len; +} +#endif + +#ifdef __hpux +#include <sys/types.h> +#include <sys/param.h> +#include <sys/syscall.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <stdio.h> +#include <dirent.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + + +int +killpg(pid, sig) + int pid, sig; +{ + return kill(-pid, sig); +} + +void +srandom(seed) + long seed; +{ + srand48(seed); +} + +long +random() +{ + return lrand48(); +} + +int +setpriority(which, who, niceval) + int which, who, niceval; +{ +#ifdef SYS_setpriority + return syscall(SYS_setpriority, which, who, niceval); +#else + extern int errno; + errno = ENOSYS; + return -1; +#endif +} + +int +setreuid(euid, ruid) + int euid, ruid; +{ + return setresuid(euid, ruid, -1); +} + +int +setregid(egid, rgid) + int egid, rgid; +{ + return setresgid(egid, rgid, -1); +} + +/* turn into bsd signals */ +void (* +signal(s, a)) () + int s; + void (*a)(); +{ + struct sigvec osv, sv; + + (void) sigvector(s, (struct sigvec *) 0, &osv); + sv = osv; + sv.sv_handler = a; +#ifdef SV_BSDSIG + sv.sv_flags = SV_BSDSIG; +#endif + + if (sigvector(s, &sv, (struct sigvec *) 0) == -1) + return (BADSIG); + return (osv.sv_handler); +} + +#if !defined(BSD) && !defined(d_fileno) +# define d_fileno d_ino +#endif + +#ifndef DEV_DEV_COMPARE +# define DEV_DEV_COMPARE(a, b) ((a) == (b)) +#endif +#define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/'))) +#define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1]))) + + +/* strrcpy(): + * Like strcpy, going backwards and returning the new pointer + */ +static char * +strrcpy(ptr, str) + register char *ptr, *str; +{ + register int len = strlen(str); + + while (len) + *--ptr = str[--len]; + + return (ptr); +} /* end strrcpy */ + + +char * +getwd(pathname) + char *pathname; +{ + DIR *dp; + struct dirent *d; + extern int errno; + + struct stat st_root, st_cur, st_next, st_dotdot; + char pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2]; + char *pathptr, *nextpathptr, *cur_name_add; + + /* find the inode of root */ + if (stat("/", &st_root) == -1) { + (void) sprintf(pathname, + "getwd: Cannot stat \"/\" (%s)", strerror(errno)); + return (NULL); + } + pathbuf[MAXPATHLEN - 1] = '\0'; + pathptr = &pathbuf[MAXPATHLEN - 1]; + nextpathbuf[MAXPATHLEN - 1] = '\0'; + cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1]; + + /* find the inode of the current directory */ + if (lstat(".", &st_cur) == -1) { + (void) sprintf(pathname, + "getwd: Cannot stat \".\" (%s)", strerror(errno)); + return (NULL); + } + nextpathptr = strrcpy(nextpathptr, "../"); + + /* Descend to root */ + for (;;) { + + /* look if we found root yet */ + if (st_cur.st_ino == st_root.st_ino && + DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) { + (void) strcpy(pathname, *pathptr != '/' ? "/" : pathptr); + return (pathname); + } + + /* open the parent directory */ + if (stat(nextpathptr, &st_dotdot) == -1) { + (void) sprintf(pathname, + "getwd: Cannot stat directory \"%s\" (%s)", + nextpathptr, strerror(errno)); + return (NULL); + } + if ((dp = opendir(nextpathptr)) == NULL) { + (void) sprintf(pathname, + "getwd: Cannot open directory \"%s\" (%s)", + nextpathptr, strerror(errno)); + return (NULL); + } + + /* look in the parent for the entry with the same inode */ + if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) { + /* Parent has same device. No need to stat every member */ + for (d = readdir(dp); d != NULL; d = readdir(dp)) + if (d->d_fileno == st_cur.st_ino) + break; + } + else { + /* + * Parent has a different device. This is a mount point so we + * need to stat every member + */ + for (d = readdir(dp); d != NULL; d = readdir(dp)) { + if (ISDOT(d->d_name) || ISDOTDOT(d->d_name)) + continue; + (void) strcpy(cur_name_add, d->d_name); + if (lstat(nextpathptr, &st_next) == -1) { + (void) sprintf(pathname, "getwd: Cannot stat \"%s\" (%s)", + d->d_name, strerror(errno)); + (void) closedir(dp); + return (NULL); + } + /* check if we found it yet */ + if (st_next.st_ino == st_cur.st_ino && + DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev)) + break; + } + } + if (d == NULL) { + (void) sprintf(pathname, "getwd: Cannot find \".\" in \"..\""); + (void) closedir(dp); + return (NULL); + } + st_cur = st_dotdot; + pathptr = strrcpy(pathptr, d->d_name); + pathptr = strrcpy(pathptr, "/"); + nextpathptr = strrcpy(nextpathptr, "../"); + (void) closedir(dp); + *cur_name_add = '\0'; + } +} /* end getwd */ + + +char *sys_siglist[] = { + "Signal 0", + "Hangup", /* SIGHUP */ + "Interrupt", /* SIGINT */ + "Quit", /* SIGQUIT */ + "Illegal instruction", /* SIGILL */ + "Trace/BPT trap", /* SIGTRAP */ + "IOT trap", /* SIGIOT */ + "EMT trap", /* SIGEMT */ + "Floating point exception", /* SIGFPE */ + "Killed", /* SIGKILL */ + "Bus error", /* SIGBUS */ + "Segmentation fault", /* SIGSEGV */ + "Bad system call", /* SIGSYS */ + "Broken pipe", /* SIGPIPE */ + "Alarm clock", /* SIGALRM */ + "Terminated", /* SIGTERM */ + "User defined signal 1", /* SIGUSR1 */ + "User defined signal 2", /* SIGUSR2 */ + "Child exited", /* SIGCLD */ + "Power-fail restart", /* SIGPWR */ + "Virtual timer expired", /* SIGVTALRM */ + "Profiling timer expired", /* SIGPROF */ + "I/O possible", /* SIGIO */ + "Window size changes", /* SIGWINDOW */ + "Stopped (signal)", /* SIGSTOP */ + "Stopped", /* SIGTSTP */ + "Continued", /* SIGCONT */ + "Stopped (tty input)", /* SIGTTIN */ + "Stopped (tty output)", /* SIGTTOU */ + "Urgent I/O condition", /* SIGURG */ + "Remote lock lost (NFS)", /* SIGLOST */ + "Signal 31", /* reserved */ + "DIL signal" /* SIGDIL */ +}; + +int +utimes(file, tvp) + char *file; + struct timeval tvp[2]; +{ + struct utimbuf t; + + t.actime = tvp[0].tv_sec; + t.modtime = tvp[1].tv_sec; + return(utime(file, &t)); +} + + +#endif /* __hpux */ diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c index 3212cba3111..8a16b300136 100644 --- a/usr.bin/make/var.c +++ b/usr.bin/make/var.c @@ -37,8 +37,8 @@ */ #ifndef lint -/*static char sccsid[] = "from: @(#)var.c 5.7 (Berkeley) 6/1/90";*/ -static char rcsid[] = "$Id: var.c,v 1.4 1994/01/13 21:02:09 jtc Exp $"; +/* from: static char sccsid[] = "@(#)var.c 5.7 (Berkeley) 6/1/90"; */ +static char *rcsid = "$Id: var.c,v 1.5 1994/03/05 00:35:17 cgd Exp $"; #endif /* not lint */ /*- @@ -62,7 +62,8 @@ static char rcsid[] = "$Id: var.c,v 1.4 1994/01/13 21:02:09 jtc Exp $"; * Var_Value Return the value of a variable in a context or * NULL if the variable is undefined. * - * Var_Subst Substitute for all variables in a string using + * Var_Subst Substitute named variable, or all variables if + * NULL in a string using * the given context as the top-most one. If the * third argument is non-zero, Parse_Error is * called if any variables are undefined. @@ -82,8 +83,6 @@ static char rcsid[] = "$Id: var.c,v 1.4 1994/01/13 21:02:09 jtc Exp $"; * XXX: There's a lot of duplication in these functions. */ -#include <stdio.h> -#include <stdlib.h> #include <ctype.h> #include "make.h" #include "buf.h" @@ -100,7 +99,7 @@ char var_Error[] = ""; * set false. Why not just use a constant? Well, gcc likes to condense * identical string instances... */ -char varNoError[] = ""; +static char varNoError[] = ""; /* * Internally, variables are contained in four different contexts. @@ -138,6 +137,32 @@ typedef struct Var { * modified variables */ } Var; +typedef struct { + char *lhs; /* String to match */ + int leftLen; /* Length of string */ + char *rhs; /* Replacement string (w/ &'s removed) */ + int rightLen; /* Length of replacement */ + int flags; +#define VAR_SUB_GLOBAL 1 /* Apply substitution globally */ +#define VAR_MATCH_START 2 /* Match at start of word */ +#define VAR_MATCH_END 4 /* Match at end of word */ +#define VAR_NO_SUB 8 /* Substitution is non-global and already done */ +} VarPattern; + +static int VarCmp __P((Var *, char *)); +static Var *VarFind __P((char *, GNode *, int)); +static void VarAdd __P((char *, char *, GNode *)); +static Boolean VarHead __P((char *, Boolean, Buffer)); +static Boolean VarTail __P((char *, Boolean, Buffer)); +static Boolean VarSuffix __P((char *, Boolean, Buffer)); +static Boolean VarRoot __P((char *, Boolean, Buffer)); +static Boolean VarMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSYSVMatch __P((char *, Boolean, Buffer, VarPattern *)); +static Boolean VarNoMatch __P((char *, Boolean, Buffer, char *)); +static Boolean VarSubstitute __P((char *, Boolean, Buffer, VarPattern *)); +static char *VarModify __P((char *, Boolean (*modProc )(), ClientData)); +static int VarPrintVar __P((Var *)); + /*- *----------------------------------------------------------------------- * VarCmp -- @@ -291,7 +316,7 @@ VarFind (name, ctxt, flags) * safely be freed. *----------------------------------------------------------------------- */ -static +static void VarAdd (name, val, ctxt) char *name; /* name of variable to add */ char *val; /* value to set it to */ @@ -304,7 +329,7 @@ VarAdd (name, val, ctxt) v->name = strdup (name); - len = strlen(val); + len = val ? strlen(val) : 0; v->val = Buf_Init(len+1); Buf_AddBytes(v->val, len, (Byte *)val); @@ -434,7 +459,6 @@ Var_Append (name, val, ctxt) GNode *ctxt; /* Context in which this should occur */ { register Var *v; - register char *cp; v = VarFind (name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); @@ -446,7 +470,7 @@ Var_Append (name, val, ctxt) if (DEBUG(VAR)) { printf("%s:%s = %s\n", ctxt->name, name, - Buf_GetAll(v->val, (int *)NULL)); + (char *) Buf_GetAll(v->val, (int *)NULL)); } if (v->flags & VAR_FROM_ENV) { @@ -544,7 +568,7 @@ VarHead (word, addSpace, buf) { register char *slash; - slash = rindex (word, '/'); + slash = strrchr (word, '/'); if (slash != (char *)NULL) { if (addSpace) { Buf_AddByte (buf, (Byte)' '); @@ -594,7 +618,7 @@ VarTail (word, addSpace, buf) Buf_AddByte (buf, (Byte)' '); } - slash = rindex (word, '/'); + slash = strrchr (word, '/'); if (slash != (char *)NULL) { *slash++ = '\0'; Buf_AddBytes (buf, strlen(slash), (Byte *)slash); @@ -628,7 +652,7 @@ VarSuffix (word, addSpace, buf) { register char *dot; - dot = rindex (word, '.'); + dot = strrchr (word, '.'); if (dot != (char *)NULL) { if (addSpace) { Buf_AddByte (buf, (Byte)' '); @@ -670,7 +694,7 @@ VarRoot (word, addSpace, buf) Buf_AddByte (buf, (Byte)' '); } - dot = rindex (word, '.'); + dot = strrchr (word, '.'); if (dot != (char *)NULL) { *dot = '\0'; Buf_AddBytes (buf, strlen (word), (Byte *)word); @@ -715,6 +739,50 @@ VarMatch (word, addSpace, buf, pattern) return(addSpace); } + + +/*- + *----------------------------------------------------------------------- + * VarSYSVMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the System V % + * modifiers. + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSYSVMatch (word, addSpace, buf, pat) + char *word; /* Word to examine */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the word, if it + * matches */ + Buffer buf; /* Buffer in which to store it */ + VarPattern *pat; /* Pattern the word must match */ +{ + int len; + char *ptr; + + if (addSpace) + Buf_AddByte(buf, (Byte)' '); + + addSpace = TRUE; + + if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) + Str_SYSVSubst(buf, pat->rhs, ptr, len); + else + Buf_AddBytes(buf, strlen(word), (Byte *) word); + + return(addSpace); +} + + /*- *----------------------------------------------------------------------- * VarNoMatch -- @@ -749,17 +817,6 @@ VarNoMatch (word, addSpace, buf, pattern) return(addSpace); } -typedef struct { - char *lhs; /* String to match */ - int leftLen; /* Length of string */ - char *rhs; /* Replacement string (w/ &'s removed) */ - int rightLen; /* Length of replacement */ - int flags; -#define VAR_SUB_GLOBAL 1 /* Apply substitution globally */ -#define VAR_MATCH_START 2 /* Match at start of word */ -#define VAR_MATCH_END 4 /* Match at end of word */ -#define VAR_NO_SUB 8 /* Substitution is non-global and already done */ -} VarPattern; /*- *----------------------------------------------------------------------- @@ -968,16 +1025,15 @@ VarModify (str, modProc, datum) cp = str; addSpace = FALSE; - while (1) { + for (;;) { /* * Skip to next word and place cp at its end. */ while (isspace (*str)) { str++; } - for (cp = str; *cp != '\0' && !isspace (*cp); cp++) { - /* void */ ; - } + for (cp = str; *cp != '\0' && !isspace (*cp); cp++) + continue; if (cp == str) { /* * If we didn't go anywhere, we must be done! @@ -1258,8 +1314,8 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) * return. */ str = (char *)Buf_GetAll(v->val, (int *)NULL); - if (index (str, '$') != (char *)NULL) { - str = Var_Subst(str, ctxt, err); + if (strchr (str, '$') != (char *)NULL) { + str = Var_Subst(NULL, str, ctxt, err); *freePtr = TRUE; } @@ -1353,8 +1409,6 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) VarPattern pattern; register char delim; Buffer buf; /* Buffer for patterns */ - register char *cp2; - int lefts; pattern.flags = 0; delim = tstr[1]; @@ -1580,9 +1634,8 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) * Now we break this sucker into the lhs and * rhs. We must null terminate them of course. */ - for (cp = tstr; *cp != '='; cp++) { - ; - } + for (cp = tstr; *cp != '='; cp++) + continue; pattern.lhs = tstr; pattern.leftLen = cp - tstr; *cp++ = '\0'; @@ -1598,9 +1651,7 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) * SYSV modifications happen through the whole * string. Note the pattern is anchored at the end. */ - pattern.flags |= VAR_SUB_GLOBAL|VAR_MATCH_END; - - newStr = VarModify(str, VarSubstitute, + newStr = VarModify(str, VarSYSVMatch, (ClientData)&pattern); /* @@ -1613,9 +1664,8 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) Error ("Unknown modifier '%c'\n", *tstr); for (cp = tstr+1; *cp != ':' && *cp != endc && *cp != '\0'; - cp++) { - ; - } + cp++) + continue; termc = *cp; newStr = var_Error; } @@ -1700,8 +1750,9 @@ Var_Parse (str, ctxt, err, lengthPtr, freePtr) *----------------------------------------------------------------------- */ char * -Var_Subst (str, ctxt, undefErr) - register char *str; /* the string in which to substitute */ +Var_Subst (var, str, ctxt, undefErr) + char *var; /* Named variable || NULL for all */ + char *str; /* the string in which to substitute */ GNode *ctxt; /* the context wherein to find variables */ Boolean undefErr; /* TRUE if undefineds are an error */ { @@ -1713,11 +1764,11 @@ Var_Subst (str, ctxt, undefErr) * been reported to prevent a plethora * of messages when recursing */ - buf = Buf_Init (BSIZE); + buf = Buf_Init (MAKE_BSIZE); errorReported = FALSE; while (*str) { - if ((*str == '$') && (str[1] == '$')) { + if (var == NULL && (*str == '$') && (str[1] == '$')) { /* * A dollar sign may be escaped either with another dollar sign. * In such a case, we skip over the escape character and store the @@ -1733,11 +1784,65 @@ Var_Subst (str, ctxt, undefErr) */ char *cp; - for (cp = str++; *str != '$' && *str != '\0'; str++) { - ; - } + for (cp = str++; *str != '$' && *str != '\0'; str++) + continue; Buf_AddBytes(buf, str - cp, (Byte *)cp); } else { + if (var != NULL) { + int expand; + for (;;) { + if (str[1] != '(' && str[1] != '{') { + if (str[1] != *var) { + Buf_AddBytes(buf, 2, (Byte *) str); + str += 2; + expand = FALSE; + } + else + expand = TRUE; + break; + } + else { + char *p; + + /* + * Scan up to the end of the variable name. + */ + for (p = &str[2]; *p && + *p != ':' && *p != ')' && *p != '}'; p++) + if (*p == '$') + break; + /* + * A variable inside the variable. We cannot expand + * the external variable yet, so we try again with + * the nested one + */ + if (*p == '$') { + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + continue; + } + + if (strncmp(var, str + 2, p - str - 2) != 0 || + var[p - str - 2] != '\0') { + /* + * Not the variable we want to expand, scan + * until the next variable + */ + for (;*p != '$' && *p != '\0'; p++) + continue; + Buf_AddBytes(buf, p - str, (Byte *) str); + str = p; + expand = FALSE; + } + else + expand = TRUE; + break; + } + } + if (!expand) + continue; + } + val = Var_Parse (str, ctxt, undefErr, &length, &doFree); /* @@ -1860,11 +1965,11 @@ Var_Init () } /****************** PRINT DEBUGGING INFO *****************/ -static +static int VarPrintVar (v) Var *v; { - printf ("%-16s = %s\n", v->name, Buf_GetAll(v->val, (int *)NULL)); + printf ("%-16s = %s\n", v->name, (char *) Buf_GetAll(v->val, (int *)NULL)); return (0); } @@ -1874,6 +1979,7 @@ VarPrintVar (v) * print all variables in a context *----------------------------------------------------------------------- */ +void Var_Dump (ctxt) GNode *ctxt; { |
