diff options
Diffstat (limited to 'usr.bin/make')
55 files changed, 21324 insertions, 0 deletions
diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile new file mode 100644 index 000000000000..b3e1b8237952 --- /dev/null +++ b/usr.bin/make/Makefile @@ -0,0 +1,14 @@ +# @(#)Makefile 5.2 (Berkeley) 12/28/90 + +PROG= make +CFLAGS+=-I${.CURDIR} +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 +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 \ + lstInit.c lstInsert.c lstIsAtEnd.c lstIsEmpty.c lstLast.c \ + lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c +.PATH: ${.CURDIR}/lst.lib + +.include <bsd.prog.mk> diff --git a/usr.bin/make/Makefile.dist b/usr.bin/make/Makefile.dist new file mode 100644 index 000000000000..1149e60facc0 --- /dev/null +++ b/usr.bin/make/Makefile.dist @@ -0,0 +1,21 @@ +# a very simple makefile... + +CFLAGS = -I. -DMACHINE=\"sun3\" + +MAKESRC = 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 +MAKEOBJS = 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 + + +all: make + +lst.lib/liblst.a: + cd lst.lib ; make -f Makefile.dist + +make: $(MAKEOBJS) lst.lib/liblst.a + $(CC) -o make $(MAKEOBJS) lst.lib/liblst.a + +clean: + rm -f $(MAKEOBJS) make + cd lst.lib ; make -f Makefile.dist clean diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c new file mode 100644 index 000000000000..ad5a60374d49 --- /dev/null +++ b/usr.bin/make/arch.c @@ -0,0 +1,951 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)arch.c 5.7 (Berkeley) 12/28/90"; +#endif /* not lint */ + +/*- + * arch.c -- + * Functions to manipulate libraries, archives and their members. + * + * Once again, cacheing/hashing comes into play in the manipulation + * of archives. The first time an archive is referenced, all of its members' + * headers are read and hashed and the archive closed again. All hashed + * archives are kept on a list which is searched each time an archive member + * is referenced. + * + * The interface to this module is: + * Arch_ParseArchive Given an archive specification, return a list + * of GNode's, one for each member in the spec. + * FAILURE is returned if the specification is + * invalid for some reason. + * + * Arch_Touch Alter the modification time of the archive + * member described by the given node to be + * the current time. + * + * Arch_TouchLib Update the modification time of the library + * described by the given node. This is special + * because it also updates the modification time + * of the library's table of contents. + * + * Arch_MTime Find the modification time of a member of + * an archive *in the archive*. The time is also + * placed in the member's GNode. Returns the + * modification time. + * + * Arch_MemTime Find the modification time of a member of + * an archive. Called when the member doesn't + * already exist. Looks in the archive for the + * modification time. Returns the modification + * time. + * + * Arch_FindLib Search for a library along a path. The + * library name in the GNode should be in + * -l<name> format. + * + * Arch_LibOODate Special function to decide if a library node + * is out-of-date. + * + * Arch_Init Initialize this module. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <ctype.h> +#include <ar.h> +#include <ranlib.h> +#include <stdio.h> +#include "make.h" +#include "hash.h" + +static Lst archives; /* Lst of archives we've already examined */ + +typedef struct Arch { + char *name; /* Name of archive */ + Hash_Table members; /* All the members of the archive described + * by <name, struct ar_hdr *> key/value pairs */ +} Arch; + +static FILE *ArchFindMember(); + +/*- + *----------------------------------------------------------------------- + * Arch_ParseArchive -- + * Parse the archive specification in the given line and find/create + * the nodes for the specified archive members, placing their nodes + * on the given list. + * + * Results: + * SUCCESS if it was a valid specification. The linePtr is updated + * to point to the first non-space after the archive spec. The + * nodes for the members are placed on the given list. + * + * Side Effects: + * Some nodes may be created. The given list is extended. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Arch_ParseArchive (linePtr, nodeLst, ctxt) + char **linePtr; /* Pointer to start of specification */ + Lst nodeLst; /* Lst on which to place the nodes */ + GNode *ctxt; /* Context in which to expand variables */ +{ + register char *cp; /* Pointer into line */ + 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 saveChar; /* Ending delimiter of member-name */ + Boolean subLibName; /* TRUE if libName should have/had + * variable substitution performed on it */ + + libName = *linePtr; + + subLibName = FALSE; + + for (cp = libName; *cp != '(' && *cp != '\0'; cp++) { + if (*cp == '$') { + /* + * Variable spec, so call the Var module to parse the puppy + * so we can safely advance beyond it... + */ + int length; + Boolean freeIt; + char *result; + + result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt); + if (result == var_Error) { + return(FAILURE); + } else { + subLibName = TRUE; + } + + if (freeIt) { + free(result); + } + cp += length-1; + } + } + + *cp++ = '\0'; + if (subLibName) { + libName = Var_Subst(libName, ctxt, TRUE); + } + + + while (1) { + /* + * First skip to the start of the member's name, mark that + * place and skip to the end of it (either white-space or + * a close paren). + */ + Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ + + while (*cp != '\0' && *cp != ')' && isspace (*cp)) { + cp++; + } + memName = cp; + while (*cp != '\0' && *cp != ')' && !isspace (*cp)) { + if (*cp == '$') { + /* + * Variable spec, so call the Var module to parse the puppy + * so we can safely advance beyond it... + */ + int length; + Boolean freeIt; + char *result; + + result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt); + if (result == var_Error) { + return(FAILURE); + } else { + doSubst = TRUE; + } + + if (freeIt) { + free(result); + } + cp += length; + } else { + cp++; + } + } + + /* + * If the specification ends without a closing parenthesis, + * chances are there's something wrong (like a missing backslash), + * so it's better to return failure than allow such things to happen + */ + if (*cp == '\0') { + printf("No closing parenthesis in archive specification\n"); + return (FAILURE); + } + + /* + * If we didn't move anywhere, we must be done + */ + if (cp == memName) { + break; + } + + saveChar = *cp; + *cp = '\0'; + + /* + * XXX: This should be taken care of intelligently by + * SuffExpandChildren, both for the archive and the member portions. + */ + /* + * If member contains variables, try and substitute for them. + * This will slow down archive specs with dynamic sources, of course, + * since we'll be (non-)substituting them three times, but them's + * the breaks -- we need to do this since SuffExpandChildren calls + * us, otherwise we could assume the thing would be taken care of + * later. + */ + if (doSubst) { + char *buf; + char *sacrifice; + char *oldMemName = memName; + + memName = Var_Subst(memName, ctxt, TRUE); + + /* + * Now form an archive spec and recurse to deal with nested + * variables and multi-word variable values.... The results + * are just placed at the end of the nodeLst we're returning. + */ + buf = sacrifice = emalloc(strlen(memName)+strlen(libName)+3); + + sprintf(buf, "%s(%s)", libName, memName); + + if (index(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 + * SuffExpandChildren handle it... + */ + gn = Targ_FindNode(buf, TARG_CREATE); + + if (gn == NILGNODE) { + free(buf); + return(FAILURE); + } else { + gn->type |= OP_ARCHV; + (void)Lst_AtEnd(nodeLst, (ClientData)gn); + } + } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) { + /* + * Error in nested call -- free buffer and return FAILURE + * ourselves. + */ + free(buf); + return(FAILURE); + } + /* + * Free buffer and continue with our work. + */ + free(buf); + } else if (Dir_HasWildcards(memName)) { + Lst members = Lst_Init(FALSE); + char *member; + + Dir_Expand(memName, dirSearchPath, members); + while (!Lst_IsEmpty(members)) { + member = (char *)Lst_DeQueue(members); + + sprintf(nameBuf, "%s(%s)", libName, member); + free(member); + gn = Targ_FindNode (nameBuf, TARG_CREATE); + if (gn == NILGNODE) { + return (FAILURE); + } else { + /* + * We've found the node, but have to make sure the rest of + * the world knows it's an archive member, without having + * to constantly check for parentheses, so we type the + * thing with the OP_ARCHV bit before we place it on the + * end of the provided list. + */ + gn->type |= OP_ARCHV; + (void) Lst_AtEnd (nodeLst, (ClientData)gn); + } + } + Lst_Destroy(members, NOFREE); + } else { + sprintf(nameBuf, "%s(%s)", libName, memName); + gn = Targ_FindNode (nameBuf, TARG_CREATE); + if (gn == NILGNODE) { + return (FAILURE); + } else { + /* + * We've found the node, but have to make sure the rest of the + * world knows it's an archive member, without having to + * constantly check for parentheses, so we type the thing with + * the OP_ARCHV bit before we place it on the end of the + * provided list. + */ + gn->type |= OP_ARCHV; + (void) Lst_AtEnd (nodeLst, (ClientData)gn); + } + } + if (doSubst) { + free(memName); + } + + *cp = saveChar; + } + + /* + * If substituted libName, free it now, since we need it no longer. + */ + if (subLibName) { + free(libName); + } + + /* + * We promised the pointer would be set up at the next non-space, so + * we must advance cp there before setting *linePtr... (note that on + * entrance to the loop, cp is guaranteed to point at a ')') + */ + do { + cp++; + } while (*cp != '\0' && isspace (*cp)); + + *linePtr = cp; + return (SUCCESS); +} + +/*- + *----------------------------------------------------------------------- + * ArchFindArchive -- + * See if the given archive is the one we are looking for. Called + * From ArchStatMember and ArchFindMember via Lst_Find. + * + * Results: + * 0 if it is, non-zero if it isn't. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static int +ArchFindArchive (ar, archName) + Arch *ar; /* Current list element */ + char *archName; /* Name we want */ +{ + return (strcmp (archName, ar->name)); +} + +/*- + *----------------------------------------------------------------------- + * ArchStatMember -- + * Locate a member of an archive, given the path of the archive and + * the path of the desired member. + * + * Results: + * A pointer to the current struct ar_hdr structure for the member. Note + * That no position is returned, so this is not useful for touching + * archive members. This is mostly because we have no assurances that + * The archive will remain constant after we read all the headers, so + * there's not much point in remembering the position... + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +static struct ar_hdr * +ArchStatMember (archive, member, hash) + char *archive; /* Path to the archive */ + char *member; /* Name of member. If it is a path, only the + * last component is used. */ + Boolean hash; /* TRUE if archive should be hashed if not + * already so. */ +{ +#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1) + FILE * arch; /* Stream to archive */ + int size; /* Size of archive member */ + char *cp; /* Useful character pointer */ + char magic[SARMAG]; + int len; + LstNode ln; /* Lst member containing archive descriptor */ + Arch *ar; /* Archive descriptor */ + Hash_Entry *he; /* Entry containing member's description */ + struct ar_hdr arh; /* archive-member header for reading archive */ + char memName[AR_MAX_NAME_LEN+1]; + /* Current member name while hashing. The name is + * truncated to AR_MAX_NAME_LEN bytes, but we need + * room for the null byte... */ + char copy[AR_MAX_NAME_LEN+1]; + /* Holds copy of last path element from member, if + * it has to be truncated, so we don't have to + * figure it out again once the table is hashed. */ + + /* + * Because of space constraints and similar things, files are archived + * using their final path components, not the entire thing, so we need + * to point 'member' to the final component, if there is one, to make + * the comparisons easier... + */ + cp = rindex (member, '/'); + if (cp != (char *) NULL) { + member = cp + 1; + } + len = strlen (member); + if (len > AR_MAX_NAME_LEN) { + len = AR_MAX_NAME_LEN; + strncpy(copy, member, AR_MAX_NAME_LEN); + copy[AR_MAX_NAME_LEN] = '\0'; + member = copy; + } + + ln = Lst_Find (archives, (ClientData) archive, ArchFindArchive); + if (ln != NILLNODE) { + ar = (Arch *) Lst_Datum (ln); + + he = Hash_FindEntry (&ar->members, member); + + if (he != (Hash_Entry *) NULL) { + return ((struct ar_hdr *) Hash_GetValue (he)); + } else { + return ((struct ar_hdr *) NULL); + } + } + + if (!hash) { + /* + * Caller doesn't want the thing hashed, just use ArchFindMember + * to read the header for the member out and close down the stream + * again. Since the archive is not to be hashed, we assume there's + * no need to allocate extra room for the header we're returning, + * so just declare it static. + */ + static struct ar_hdr sarh; + + arch = ArchFindMember(archive, member, &sarh, "r"); + + if (arch == (FILE *)NULL) { + return ((struct ar_hdr *)NULL); + } else { + fclose(arch); + return (&sarh); + } + } + + /* + * We don't have this archive on the list yet, so we want to find out + * everything that's in it and cache it so we can get at it quickly. + */ + arch = fopen (archive, "r"); + if (arch == (FILE *) NULL) { + return ((struct ar_hdr *) NULL); + } + + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if ((fread (magic, SARMAG, 1, arch) != 1) || + (strncmp (magic, ARMAG, SARMAG) != 0)) { + fclose (arch); + return ((struct ar_hdr *) NULL); + } + + ar = (Arch *)emalloc (sizeof (Arch)); + ar->name = strdup (archive); + Hash_InitTable (&ar->members, -1); + memName[AR_MAX_NAME_LEN] = '\0'; + + while (fread ((char *)&arh, sizeof (struct ar_hdr), 1, arch) == 1) { + if (strncmp ( arh.ar_fmag, ARFMAG, sizeof (arh.ar_fmag)) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + fclose (arch); + Hash_DeleteTable (&ar->members); + free ((Address)ar); + return ((struct ar_hdr *) NULL); + } else { + (void) strncpy (memName, arh.ar_name, sizeof(arh.ar_name)); + for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) { + continue; + } + cp[1] = '\0'; + + 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), + sizeof (struct ar_hdr)); + } + /* + * We need to advance the stream's pointer to the start of the + * next header. Files are padded with newlines to an even-byte + * boundary, so we need to extract the size of the file from the + * 'size' field of the header and round it up during the seek. + */ + arh.ar_size[sizeof(arh.ar_size)-1] = '\0'; + (void) sscanf (arh.ar_size, "%10d", &size); + fseek (arch, (size + 1) & ~1, 1); + } + + fclose (arch); + + (void) Lst_AtEnd (archives, (ClientData) ar); + + /* + * Now that the archive has been read and cached, we can look into + * the hash table to find the desired member's header. + */ + he = Hash_FindEntry (&ar->members, member); + + if (he != (Hash_Entry *) NULL) { + return ((struct ar_hdr *) Hash_GetValue (he)); + } else { + return ((struct ar_hdr *) NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * ArchFindMember -- + * Locate a member of an archive, given the path of the archive and + * the path of the desired member. If the archive is to be modified, + * the mode should be "r+", if not, it should be "r". + * + * Results: + * An FILE *, opened for reading and writing, positioned at the + * start of the member's struct ar_hdr, or NULL if the member was + * nonexistent. The current struct ar_hdr for member. + * + * Side Effects: + * The passed struct ar_hdr structure is filled in. + * + *----------------------------------------------------------------------- + */ +static FILE * +ArchFindMember (archive, member, arhPtr, mode) + char *archive; /* Path to the archive */ + char *member; /* Name of member. If it is a path, only the + * last component is used. */ + struct ar_hdr *arhPtr; /* Pointer to header structure to be filled in */ + char *mode; /* The mode for opening the stream */ +{ + FILE * arch; /* Stream to archive */ + int size; /* Size of archive member */ + char *cp; /* Useful character pointer */ + char magic[SARMAG]; + int len; + + arch = fopen (archive, mode); + if (arch == (FILE *) NULL) { + return ((FILE *) NULL); + } + + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if ((fread (magic, SARMAG, 1, arch) != 1) || + (strncmp (magic, ARMAG, SARMAG) != 0)) { + fclose (arch); + return ((FILE *) NULL); + } + + /* + * Because of space constraints and similar things, files are archived + * using their final path components, not the entire thing, so we need + * to point 'member' to the final component, if there is one, to make + * the comparisons easier... + */ + cp = rindex (member, '/'); + if (cp != (char *) NULL) { + member = cp + 1; + } + len = strlen (member); + if (len > sizeof (arhPtr->ar_name)) { + len = sizeof (arhPtr->ar_name); + } + + while (fread ((char *)arhPtr, sizeof (struct ar_hdr), 1, arch) == 1) { + if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof (arhPtr->ar_fmag) ) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + fclose (arch); + return ((FILE *) NULL); + } else if (strncmp (member, arhPtr->ar_name, len) == 0) { + /* + * If the member's name doesn't take up the entire 'name' field, + * we have to be careful of matching prefixes. Names are space- + * padded to the right, so if the character in 'name' at the end + * of the matched string is anything but a space, this isn't the + * member we sought. + */ + if (len != sizeof(arhPtr->ar_name) && arhPtr->ar_name[len] != ' '){ + continue; + } else { + /* + * To make life easier, we reposition the file at the start + * of the header we just read before we return the stream. + * In a more general situation, it might be better to leave + * the file at the actual member, rather than its header, but + * not here... + */ + fseek (arch, -sizeof(struct ar_hdr), 1); + return (arch); + } + } else { + /* + * This isn't the member we're after, so we need to advance the + * stream's pointer to the start of the next header. Files are + * padded with newlines to an even-byte boundary, so we need to + * extract the size of the file from the 'size' field of the + * header and round it up during the seek. + */ + arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0'; + (void)sscanf (arhPtr->ar_size, "%10d", &size); + fseek (arch, (size + 1) & ~1, 1); + } + } + + /* + * We've looked everywhere, but the member is not to be found. Close the + * archive and return NULL -- an error. + */ + fclose (arch); + return ((FILE *) NULL); +} + +/*- + *----------------------------------------------------------------------- + * Arch_Touch -- + * Touch a member of an archive. + * + * Results: + * The 'time' field of the member's header is updated. + * + * Side Effects: + * The modification time of the entire archive is also changed. + * For a library, this could necessitate the re-ranlib'ing of the + * whole thing. + * + *----------------------------------------------------------------------- + */ +void +Arch_Touch (gn) + GNode *gn; /* Node of member to touch */ +{ + FILE * arch; /* Stream open to archive, positioned properly */ + struct ar_hdr arh; /* Current header describing member */ + + arch = ArchFindMember(Var_Value (ARCHIVE, gn), + Var_Value (TARGET, gn), + &arh, "r+"); + sprintf(arh.ar_date, "%-12d", now); + + if (arch != (FILE *) NULL) { + (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch); + fclose (arch); + } +} + +/*- + *----------------------------------------------------------------------- + * Arch_TouchLib -- + * Given a node which represents a library, touch the thing, making + * sure that the table of contents also is touched. + * + * Results: + * None. + * + * Side Effects: + * Both the modification time of the library and of the RANLIBMAG + * member are set to 'now'. + * + *----------------------------------------------------------------------- + */ +void +Arch_TouchLib (gn) + GNode *gn; /* The node of the library to touch */ +{ + FILE * arch; /* Stream open to archive */ + struct ar_hdr arh; /* Header describing table of contents */ + struct timeval times[2]; /* Times for utimes() call */ + + arch = ArchFindMember (gn->path, RANLIBMAG, &arh, "r+"); + sprintf(arh.ar_date, "%-12d", now); + + if (arch != (FILE *) NULL) { + (void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch); + fclose (arch); + + times[0].tv_sec = times[1].tv_sec = now; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(gn->path, times); + } +} + +/*- + *----------------------------------------------------------------------- + * Arch_MTime -- + * Return the modification time of a member of an archive. + * + * Results: + * The modification time (seconds). + * + * Side Effects: + * The mtime field of the given node is filled in with the value + * returned by the function. + * + *----------------------------------------------------------------------- + */ +int +Arch_MTime (gn) + GNode *gn; /* Node describing archive member */ +{ + struct ar_hdr *arhPtr; /* Header of desired member */ + int modTime; /* Modification time as an integer */ + + arhPtr = ArchStatMember (Var_Value (ARCHIVE, gn), + Var_Value (TARGET, gn), + TRUE); + if (arhPtr != (struct ar_hdr *) NULL) { + (void)sscanf (arhPtr->ar_date, "%12d", &modTime); + } else { + modTime = 0; + } + + gn->mtime = modTime; + return (modTime); +} + +/*- + *----------------------------------------------------------------------- + * Arch_MemMTime -- + * Given a non-existent archive member's node, get its modification + * time from its archived form, if it exists. + * + * Results: + * The modification time. + * + * Side Effects: + * The mtime field is filled in. + * + *----------------------------------------------------------------------- + */ +int +Arch_MemMTime (gn) + GNode *gn; +{ + LstNode ln; + GNode *pgn; + char *nameStart, + *nameEnd; + + if (Lst_Open (gn->parents) != SUCCESS) { + gn->mtime = 0; + return (0); + } + while ((ln = Lst_Next (gn->parents)) != NILLNODE) { + pgn = (GNode *) Lst_Datum (ln); + + if (pgn->type & OP_ARCHV) { + /* + * If the parent is an archive specification and is being made + * and its member's name matches the name of the node we were + * given, record the modification time of the parent in the + * child. We keep searching its parents in case some other + * parent requires this child to exist... + */ + nameStart = index (pgn->name, '(') + 1; + nameEnd = index (nameStart, ')'); + + if (pgn->make && + strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) { + gn->mtime = Arch_MTime(pgn); + } + } else if (pgn->make) { + /* + * Something which isn't a library depends on the existence of + * this target, so it needs to exist. + */ + gn->mtime = 0; + break; + } + } + + Lst_Close (gn->parents); + + return (gn->mtime); +} + +/*- + *----------------------------------------------------------------------- + * Arch_FindLib -- + * Search for a library along the given search path. + * + * Results: + * None. + * + * Side Effects: + * The node's 'path' field is set to the found path (including the + * actual file name, not -l...). If the system can handle the -L + * flag when linking (or we cannot find the library), we assume that + * the user has placed the .LIBRARIES variable in the final linking + * command (or the linker will know where to find it) and set the + * TARGET variable for this node to be the node's name. Otherwise, + * we set the TARGET variable to be the full path of the library, + * as returned by Dir_FindFile. + * + *----------------------------------------------------------------------- + */ +void +Arch_FindLib (gn, path) + GNode *gn; /* Node of library to find */ + Lst path; /* Search path */ +{ + char *libName; /* file name for archive */ + + libName = (char *)emalloc (strlen (gn->name) + 6 - 2); + sprintf(libName, "lib%s.a", &gn->name[2]); + + gn->path = Dir_FindFile (libName, path); + + free (libName); + +#ifdef LIBRARIES + Var_Set (TARGET, gn->name, gn); +#else + Var_Set (TARGET, gn->path == (char *) NULL ? gn->name : gn->path, gn); +#endif LIBRARIES +} + +/*- + *----------------------------------------------------------------------- + * Arch_LibOODate -- + * Decide if a node with the OP_LIB attribute is out-of-date. Called + * from Make_OODate to make its life easier. + * + * There are several ways for a library to be out-of-date that are + * not available to ordinary files. In addition, there are ways + * that are open to regular files that are not available to + * libraries. A library that is only used as a source is never + * considered out-of-date by itself. This does not preclude the + * library's modification time from making its parent be out-of-date. + * A library will be considered out-of-date for any of these reasons, + * given that it is a target on a dependency line somewhere: + * Its modification time is less than that of one of its + * sources (gn->mtime < gn->cmtime). + * Its modification time is greater than the time at which the + * make began (i.e. it's been modified in the course + * of the make, probably by archiving). + * Its modification time doesn't agree with the modification + * time of its RANLIBMAG member (i.e. its table of contents + * is out-of-date). + * + * + * Results: + * TRUE if the library is out-of-date. FALSE otherwise. + * + * Side Effects: + * The library will be hashed if it hasn't been already. + * + *----------------------------------------------------------------------- + */ +Boolean +Arch_LibOODate (gn) + GNode *gn; /* The library's graph node */ +{ + Boolean oodate; + + if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) { + oodate = FALSE; + } else if ((gn->mtime > now) || (gn->mtime < gn->cmtime)) { + oodate = TRUE; + } else { + struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ + int modTimeTOC; /* The table-of-contents's mod time */ + + arhPtr = ArchStatMember (gn->path, RANLIBMAG, FALSE); + + if (arhPtr != (struct ar_hdr *)NULL) { + (void)sscanf (arhPtr->ar_date, "%12d", &modTimeTOC); + + if (DEBUG(ARCH) || DEBUG(MAKE)) { + printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); + } + oodate = (gn->mtime > modTimeTOC); + } else { + /* + * A library w/o a table of contents is out-of-date + */ + if (DEBUG(ARCH) || DEBUG(MAKE)) { + printf("No t.o.c...."); + } + oodate = TRUE; + } + } + return (oodate); +} + +/*- + *----------------------------------------------------------------------- + * Arch_Init -- + * Initialize things for this module. + * + * Results: + * None. + * + * Side Effects: + * The 'archives' list is initialized. + * + *----------------------------------------------------------------------- + */ +void +Arch_Init () +{ + archives = Lst_Init (FALSE); +} diff --git a/usr.bin/make/bit.h b/usr.bin/make/bit.h new file mode 100644 index 000000000000..710c1f6e424d --- /dev/null +++ b/usr.bin/make/bit.h @@ -0,0 +1,100 @@ +/* + * 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. + * + * @(#)bit.h 5.3 (Berkeley) 6/1/90 + */ + +/* + * bit.h -- + * + * Definition of macros for setting and clearing bits in an array + * of integers. + * + * It is assumed that "int" is 32 bits wide. + */ + +#ifndef _BIT +#define _BIT + +#include "sprite.h" + +#define BIT_NUM_BITS_PER_INT 32 +#define BIT_NUM_BITS_PER_BYTE 8 + +#define Bit_NumInts(numBits) \ + (((numBits)+BIT_NUM_BITS_PER_INT -1)/BIT_NUM_BITS_PER_INT) + +#define Bit_NumBytes(numBits) \ + (Bit_NumInts(numBits) * sizeof(int)) + +#define Bit_Alloc(numBits, bitArrayPtr) \ + bitArrayPtr = (int *)malloc((unsigned)Bit_NumBytes(numBits)); \ + Bit_Zero((numBits), (bitArrayPtr)) + +#define Bit_Free(bitArrayPtr) \ + free((char *)bitArrayPtr) + +#define Bit_Set(numBits, bitArrayPtr) \ + ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] |= \ + (1 << ((numBits) % BIT_NUM_BITS_PER_INT))) + +#define Bit_IsSet(numBits, bitArrayPtr) \ + ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] & \ + (1 << ((numBits) % BIT_NUM_BITS_PER_INT))) + +#define Bit_Clear(numBits, bitArrayPtr) \ + ((bitArrayPtr)[(numBits)/BIT_NUM_BITS_PER_INT] &= \ + ~(1 << ((numBits) % BIT_NUM_BITS_PER_INT))) + +#define Bit_IsClear(numBits, bitArrayPtr) \ + (!(Bit_IsSet((numBits), (bitArrayPtr)))) + +#define Bit_Copy(numBits, srcArrayPtr, destArrayPtr) \ + bcopy((char *)(srcArrayPtr), (char *)(destArrayPtr), \ + Bit_NumBytes(numBits)) + +#define Bit_Zero(numBits, bitArrayPtr) \ + bzero((char *)(bitArrayPtr), Bit_NumBytes(numBits)) + +extern int Bit_FindFirstSet(); +extern int Bit_FindFirstClear(); +extern Boolean Bit_Intersect(); +extern Boolean Bit_Union(); +extern Boolean Bit_AnySet(); +extern int *Bit_Expand(); + +#endif _BIT diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c new file mode 100644 index 000000000000..fd0b7071a013 --- /dev/null +++ b/usr.bin/make/buf.c @@ -0,0 +1,436 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)buf.c 5.5 (Berkeley) 12/28/90"; +#endif /* not lint */ + +/*- + * buf.c -- + * Functions for automatically-expanded buffers. + */ + +#include "sprite.h" +#include "buf.h" + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/* + * BufExpand -- + * Expand the given buffer to hold the given number of additional + * bytes. + * Makes sure there's room for an extra NULL byte at the end of the + * buffer in case it holds a string. + */ +#define BufExpand(bp,nb) \ + if (bp->left < (nb)+1) {\ + int newSize = (bp)->size + max((nb)+1,BUF_ADD_INC); \ + Byte *newBuf = (Byte *) realloc((bp)->buffer, newSize); \ + \ + (bp)->inPtr = newBuf + ((bp)->inPtr - (bp)->buffer); \ + (bp)->outPtr = newBuf + ((bp)->outPtr - (bp)->buffer);\ + (bp)->buffer = newBuf;\ + (bp)->size = newSize;\ + (bp)->left = newSize - ((bp)->inPtr - (bp)->buffer);\ + } + +#define BUF_DEF_SIZE 256 /* Default buffer size */ +#define BUF_ADD_INC 256 /* Expansion increment when Adding */ +#define BUF_UNGET_INC 16 /* Expansion increment when Ungetting */ + +/*- + *----------------------------------------------------------------------- + * Buf_OvAddByte -- + * Add a single byte to the buffer. left is zero or negative. + * + * Results: + * None. + * + * Side Effects: + * The buffer may be expanded. + * + *----------------------------------------------------------------------- + */ +void +Buf_OvAddByte (bp, byte) + register Buffer bp; + Byte byte; +{ + + bp->left = 0; + BufExpand (bp, 1); + + *bp->inPtr++ = byte; + bp->left--; + + /* + * Null-terminate + */ + *bp->inPtr = 0; +} + +/*- + *----------------------------------------------------------------------- + * Buf_AddBytes -- + * Add a number of bytes to the buffer. + * + * Results: + * None. + * + * Side Effects: + * Guess what? + * + *----------------------------------------------------------------------- + */ +void +Buf_AddBytes (bp, numBytes, bytesPtr) + register Buffer bp; + int numBytes; + Byte *bytesPtr; +{ + + BufExpand (bp, numBytes); + + bcopy (bytesPtr, bp->inPtr, numBytes); + bp->inPtr += numBytes; + bp->left -= numBytes; + + /* + * Null-terminate + */ + *bp->inPtr = 0; +} + +/*- + *----------------------------------------------------------------------- + * Buf_UngetByte -- + * Place the byte back at the beginning of the buffer. + * + * Results: + * SUCCESS if the byte was added ok. FAILURE if not. + * + * Side Effects: + * The byte is stuffed in the buffer and outPtr is decremented. + * + *----------------------------------------------------------------------- + */ +void +Buf_UngetByte (bp, byte) + register Buffer bp; + Byte byte; +{ + + if (bp->outPtr != bp->buffer) { + bp->outPtr--; + *bp->outPtr = byte; + } else if (bp->outPtr == bp->inPtr) { + *bp->inPtr = byte; + bp->inPtr++; + bp->left--; + *bp->inPtr = 0; + } else { + /* + * Yech. have to expand the buffer to stuff this thing in. + * We use a different expansion constant because people don't + * usually push back many bytes when they're doing it a byte at + * a time... + */ + int numBytes = bp->inPtr - bp->outPtr; + Byte *newBuf; + + newBuf = (Byte *)emalloc(bp->size + BUF_UNGET_INC); + bcopy ((char *)bp->outPtr, + (char *)(newBuf+BUF_UNGET_INC), numBytes+1); + bp->outPtr = newBuf + BUF_UNGET_INC; + bp->inPtr = bp->outPtr + numBytes; + free ((char *)bp->buffer); + bp->buffer = newBuf; + bp->size += BUF_UNGET_INC; + bp->left = bp->size - (bp->inPtr - bp->buffer); + bp->outPtr -= 1; + *bp->outPtr = byte; + } +} + +/*- + *----------------------------------------------------------------------- + * Buf_UngetBytes -- + * Push back a series of bytes at the beginning of the buffer. + * + * Results: + * None. + * + * Side Effects: + * outPtr is decremented and the bytes copied into the buffer. + * + *----------------------------------------------------------------------- + */ +void +Buf_UngetBytes (bp, numBytes, bytesPtr) + register Buffer bp; + int numBytes; + Byte *bytesPtr; +{ + + if (bp->outPtr - bp->buffer >= numBytes) { + bp->outPtr -= numBytes; + bcopy (bytesPtr, bp->outPtr, numBytes); + } else if (bp->outPtr == bp->inPtr) { + Buf_AddBytes (bp, numBytes, bytesPtr); + } else { + int curNumBytes = bp->inPtr - bp->outPtr; + Byte *newBuf; + int newBytes = max(numBytes,BUF_UNGET_INC); + + newBuf = (Byte *)emalloc (bp->size + newBytes); + bcopy((char *)bp->outPtr, (char *)(newBuf+newBytes), curNumBytes+1); + bp->outPtr = newBuf + newBytes; + bp->inPtr = bp->outPtr + curNumBytes; + free ((char *)bp->buffer); + bp->buffer = newBuf; + bp->size += newBytes; + bp->left = bp->size - (bp->inPtr - bp->buffer); + bp->outPtr -= numBytes; + bcopy ((char *)bytesPtr, (char *)bp->outPtr, numBytes); + } +} + +/*- + *----------------------------------------------------------------------- + * Buf_GetByte -- + * Return the next byte from the buffer. Actually returns an integer. + * + * Results: + * Returns BUF_ERROR if there's no byte in the buffer, or the byte + * itself if there is one. + * + * Side Effects: + * outPtr is incremented and both outPtr and inPtr will be reset if + * the buffer is emptied. + * + *----------------------------------------------------------------------- + */ +int +Buf_GetByte (bp) + register Buffer bp; +{ + int res; + + if (bp->inPtr == bp->outPtr) { + return (BUF_ERROR); + } else { + res = (int) *bp->outPtr; + bp->outPtr += 1; + if (bp->outPtr == bp->inPtr) { + bp->outPtr = bp->inPtr = bp->buffer; + bp->left = bp->size; + *bp->inPtr = 0; + } + return (res); + } +} + +/*- + *----------------------------------------------------------------------- + * Buf_GetBytes -- + * Extract a number of bytes from the buffer. + * + * Results: + * The number of bytes gotten. + * + * Side Effects: + * The passed array is overwritten. + * + *----------------------------------------------------------------------- + */ +int +Buf_GetBytes (bp, numBytes, bytesPtr) + register Buffer bp; + int numBytes; + Byte *bytesPtr; +{ + + if (bp->inPtr - bp->outPtr < numBytes) { + numBytes = bp->inPtr - bp->outPtr; + } + bcopy (bp->outPtr, bytesPtr, numBytes); + bp->outPtr += numBytes; + + if (bp->outPtr == bp->inPtr) { + bp->outPtr = bp->inPtr = bp->buffer; + bp->left = bp->size; + *bp->inPtr = 0; + } + return (numBytes); +} + +/*- + *----------------------------------------------------------------------- + * Buf_GetAll -- + * Get all the available data at once. + * + * Results: + * A pointer to the data and the number of bytes available. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Byte * +Buf_GetAll (bp, numBytesPtr) + register Buffer bp; + int *numBytesPtr; +{ + + if (numBytesPtr != (int *)NULL) { + *numBytesPtr = bp->inPtr - bp->outPtr; + } + + return (bp->outPtr); +} + +/*- + *----------------------------------------------------------------------- + * Buf_Discard -- + * Throw away bytes in a buffer. + * + * Results: + * None. + * + * Side Effects: + * The bytes are discarded. + * + *----------------------------------------------------------------------- + */ +void +Buf_Discard (bp, numBytes) + register Buffer bp; + int numBytes; +{ + + if (bp->inPtr - bp->outPtr <= numBytes) { + bp->inPtr = bp->outPtr = bp->buffer; + bp->left = bp->size; + *bp->inPtr = 0; + } else { + bp->outPtr += numBytes; + } +} + +/*- + *----------------------------------------------------------------------- + * Buf_Size -- + * Returns the number of bytes in the given buffer. Doesn't include + * the null-terminating byte. + * + * Results: + * The number of bytes. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +int +Buf_Size (buf) + Buffer buf; +{ + return (buf->inPtr - buf->outPtr); +} + +/*- + *----------------------------------------------------------------------- + * Buf_Init -- + * Initialize a buffer. If no initial size is given, a reasonable + * default is used. + * + * Results: + * A buffer to be given to other functions in this library. + * + * Side Effects: + * The buffer is created, the space allocated and pointers + * initialized. + * + *----------------------------------------------------------------------- + */ +Buffer +Buf_Init (size) + int size; /* Initial size for the buffer */ +{ + Buffer bp; /* New Buffer */ + + bp = (Buffer)emalloc(sizeof(*bp)); + + if (size <= 0) { + size = BUF_DEF_SIZE; + } + bp->left = bp->size = size; + bp->buffer = (Byte *)emalloc(size); + bp->inPtr = bp->outPtr = bp->buffer; + *bp->inPtr = 0; + + return (bp); +} + +/*- + *----------------------------------------------------------------------- + * Buf_Destroy -- + * Nuke a buffer and all its resources. + * + * Results: + * None. + * + * Side Effects: + * The buffer is freed. + * + *----------------------------------------------------------------------- + */ +void +Buf_Destroy (buf, freeData) + Buffer buf; /* Buffer to destroy */ + Boolean freeData; /* TRUE if the data should be destroyed as well */ +{ + + if (freeData) { + free ((char *)buf->buffer); + } + free ((char *)buf); +} diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h new file mode 100644 index 000000000000..4b7d5ac76b3c --- /dev/null +++ b/usr.bin/make/buf.h @@ -0,0 +1,81 @@ +/* + * 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. + * + * @(#)buf.h 5.4 (Berkeley) 12/28/90 + */ + +/*- + * buf.h -- + * Header for users of the buf library. + */ + +#ifndef _BUF_H +#define _BUF_H + +#include "sprite.h" + +typedef unsigned char Byte; + +typedef struct Buffer { + int size; /* Current size of the buffer */ + int left; /* Space left (== size - (inPtr - buffer)) */ + Byte *buffer; /* The buffer itself */ + Byte *inPtr; /* Place to write to */ + 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 */ + +#define BUF_ERROR 256 + +#endif _BUF_H diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c new file mode 100644 index 000000000000..6450f6dc59d7 --- /dev/null +++ b/usr.bin/make/compat.c @@ -0,0 +1,631 @@ +/* + * 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. + * + * PATCHES MAGIC LEVEL PATCH THAT GOT US HERE + * -------------------- ----- ---------------------- + * CURRENT PATCH LEVEL: 1 00039 + * -------------------- ----- ---------------------- + * + * 10 Aug 92 David Dawes Fixed "remove directory" bug + */ + +#ifndef lint +static char sccsid[] = "@(#)compat.c 5.7 (Berkeley) 3/1/91"; +#endif /* not lint */ + +/*- + * compat.c -- + * The routines in this file implement the full-compatibility + * mode of PMake. Most of the special functionality of PMake + * is available in this mode. Things not supported: + * - different shells. + * - friendly variable substitution. + * + * Interface: + * Compat_Run Initialize things for this module and recreate + * thems as need creatin' + */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/signal.h> +#include <sys/wait.h> +#include <sys/errno.h> +#include <ctype.h> +#include <sys/stat.h> /* 10 Aug 92*/ +#include "make.h" +extern int errno; + +/* + * The following array is used to make a fast determination of which + * characters are interpreted specially by the shell. If a command + * contains any of these characters, it is executed by the shell, not + * directly by us. + */ + +static char meta[256]; + +static GNode *curTarg = NILGNODE; +static GNode *ENDNode; +static int CompatRunCommand(); + +/*- + *----------------------------------------------------------------------- + * 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. + * + * Side Effects: + * The target is removed and the process exits. If .INTERRUPT exists, + * its commands are run first WITH INTERRUPTS IGNORED.. + * + *----------------------------------------------------------------------- + */ +static void +CompatInterrupt (signo) + int signo; +{ + GNode *gn; + struct stat sbuf; /* 10 Aug 92*/ + + if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) { + char *file = Var_Value (TARGET, curTarg); + + stat (file, &sbuf); + if (!(sbuf.st_mode & S_IFDIR)) { + if (unlink (file) == SUCCESS) { + printf ("*** %s removed\n", file); + } + } + + /* + * Run .INTERRUPT only if hit with interrupt signal + */ + if (signo == SIGINT) { + gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); + if (gn != NILGNODE) { + Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn); + } + } + } + exit (0); +} + +/*- + *----------------------------------------------------------------------- + * CompatRunCommand -- + * Execute the next command for a target. If the command returns an + * error, the node's made field is set to ERROR and creation stops. + * + * Results: + * 0 if the command succeeded, 1 if an error occurred. + * + * Side Effects: + * The node's 'made' field may be set to ERROR. + * + *----------------------------------------------------------------------- + */ +static int +CompatRunCommand (cmd, gn) + char *cmd; /* Command to execute */ + GNode *gn; /* Node from which the command came */ +{ + char *cmdStart; /* Start of expanded command */ + register char *cp; + Boolean silent, /* Don't print command */ + errCheck; /* Check errors */ + 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 */ + int argc; /* Number of arguments in av or 0 if not + * dynamically allocated */ + Boolean local; /* TRUE if command should be executed + * locally */ + + silent = gn->type & OP_SILENT; + errCheck = !(gn->type & OP_IGNORE); + + cmdNode = Lst_Member (gn->commands, (ClientData)cmd); + cmdStart = Var_Subst (cmd, gn, FALSE); + + /* + * brk_string will return an argv with a NULL in av[1], thus causing + * execvp to choke and die horribly. Besides, how can we execute a null + * command? In any case, we warn the user that the command expanded to + * nothing (is this the right thing to do?). + */ + + if (*cmdStart == '\0') { + Error("%s expands to empty string", cmd); + return(0); + } else { + cmd = cmdStart; + } + Lst_Replace (cmdNode, (ClientData)cmdStart); + + if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { + (void)Lst_AtEnd(ENDNode->commands, (ClientData)cmdStart); + return(0); + } else if (strcmp(cmdStart, "...") == 0) { + gn->type |= OP_SAVE_CMDS; + return(0); + } + + while ((*cmd == '@') || (*cmd == '-')) { + if (*cmd == '@') { + silent = TRUE; + } else { + errCheck = FALSE; + } + cmd++; + } + + while (isspace(*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++) { + continue; + } + + /* + * Print the command before echoing if we're not supposed to be quiet for + * this one. We also print the command if -n given. + */ + if (!silent || noExecute) { + printf ("%s\n", cmd); + fflush(stdout); + } + + /* + * If we're not supposed to execute any commands, this is as far as + * we go... + */ + if (noExecute) { + return (0); + } + + if (*cp != '\0') { + /* + * If *cp isn't the null character, we hit a "meta" character and + * need to pass the command off to the shell. We give the shell the + * -e flag as well as -c if it's supposed to exit when it hits an + * error. + */ + static char *shargv[4] = { "/bin/sh" }; + + shargv[1] = (errCheck ? "-ec" : "-c"); + shargv[2] = cmd; + shargv[3] = (char *)NULL; + av = shargv; + argc = 0; + } else { + /* + * No meta-characters, so no need to exec a shell. Break the command + * into words to form an argument vector we can execute. + * brk_string sticks our name in av[0], so we have to + * skip over it... + */ + av = brk_string(cmd, &argc); + av += 1; + } + + local = TRUE; + + /* + * Fork and execute the single command. If the fork fails, we abort. + */ + cpid = vfork(); + if (cpid < 0) { + Fatal("Could not fork"); + } + 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")); + } else { + (void)execv(av[0], av); + } + exit(1); + } + + /* + * The child is off and running. Now all we can do is wait... + */ + while (1) { + int id; + + if (!local) { + id = 0; + } + + while ((stat = wait((int *)&reason)) != cpid) { + if (stat == -1 && errno != EINTR) { + break; + } + } + + if (stat > -1) { + if (WIFSTOPPED(reason)) { + status = reason.w_stopval; /* stopped */ + } else if (WIFEXITED(reason)) { + status = reason.w_retcode; /* exited */ + if (status != 0) { + printf ("*** Error code %d", status); + } + } else { + status = reason.w_termsig; /* signaled */ + printf ("*** Signal %d", status); + } + + + if (!WIFEXITED(reason) || (status != 0)) { + if (errCheck) { + gn->made = ERROR; + if (keepgoing) { + /* + * Abort the current target, but let others + * continue. + */ + printf (" (continuing)\n"); + } + } else { + /* + * Continue executing commands for this target. + * If we return 0, this will happen... + */ + printf (" (ignored)\n"); + status = 0; + } + } + break; + } else { + Fatal ("error in wait: %d", stat); + /*NOTREACHED*/ + } + } + + return (status); +} + +/*- + *----------------------------------------------------------------------- + * CompatMake -- + * Make a target. + * + * Results: + * 0 + * + * Side Effects: + * If an error is detected and not being ignored, the process exits. + * + *----------------------------------------------------------------------- + */ +static int +CompatMake (gn, pgn) + GNode *gn; /* The node to make */ + GNode *pgn; /* Parent to abort if necessary */ +{ + if (gn->type & OP_USE) { + Make_HandleUse(gn, pgn); + } else if (gn->made == UNMADE) { + /* + * First mark ourselves to be made, then apply whatever transformations + * the suffix module thinks are necessary. Once that's done, we can + * descend and make all our children. If any of them has an error + * but the -k flag was given, our 'make' field will be set FALSE again. + * This is our signal to not attempt to do anything but abort our + * parent as well. + */ + gn->make = TRUE; + gn->made = BEINGMADE; + Suff_FindDeps (gn); + Lst_ForEach (gn->children, CompatMake, (ClientData)gn); + if (!gn->make) { + gn->made = ABORTED; + pgn->make = FALSE; + return (0); + } + + if (Lst_Member (gn->iParents, pgn) != NILLNODE) { + Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn); + } + + /* + * All the children were made ok. Now cmtime contains the modification + * time of the newest child, we need to find out if we exist and when + * we were modified last. The criteria for datedness are defined by the + * Make_OODate function. + */ + if (DEBUG(MAKE)) { + printf("Examining %s...", gn->name); + } + if (! Make_OODate(gn)) { + gn->made = UPTODATE; + if (DEBUG(MAKE)) { + printf("up-to-date.\n"); + } + return (0); + } else if (DEBUG(MAKE)) { + printf("out-of-date.\n"); + } + + /* + * If the user is just seeing if something is out-of-date, exit now + * to tell him/her "yes". + */ + if (queryFlag) { + exit (-1); + } + + /* + * We need to be re-made. We also have to make sure we've got a $? + * variable. To be nice, we also define the $> variable using + * Make_DoAllVar(). + */ + Make_DoAllVar(gn); + + /* + * Alter our type to tell if errors should be ignored or things + * should not be printed so CompatRunCommand knows what to do. + */ + if (Targ_Ignore (gn)) { + gn->type |= OP_IGNORE; + } + if (Targ_Silent (gn)) { + gn->type |= OP_SILENT; + } + + if (Job_CheckCommands (gn, Fatal)) { + /* + * Our commands are ok, but we still have to worry about the -t + * flag... + */ + if (!touchFlag) { + curTarg = gn; + Lst_ForEach (gn->commands, CompatRunCommand, (ClientData)gn); + curTarg = NILGNODE; + } else { + Job_Touch (gn, gn->type & OP_SILENT); + } + } else { + gn->made = ERROR; + } + + if (gn->made != ERROR) { + /* + * If the node was made successfully, mark it so, update + * its modification time and timestamp all its parents. Note + * that for .ZEROTIME targets, the timestamping isn't done. + * This is to keep its state from affecting that of its parent. + */ + gn->made = MADE; +#ifndef RECHECK + /* + * We can't re-stat the thing, but we can at least take care of + * rules where a target depends on a source that actually creates + * the target, but only if it has changed, e.g. + * + * parse.h : parse.o + * + * parse.o : parse.y + * yacc -d parse.y + * cc -c y.tab.c + * mv y.tab.o parse.o + * cmp -s y.tab.h parse.h || mv y.tab.h parse.h + * + * In this case, if the definitions produced by yacc haven't + * changed from before, parse.h won't have been updated and + * gn->mtime will reflect the current modification time for + * parse.h. This is something of a kludge, I admit, but it's a + * useful one.. + * + * XXX: People like to use a rule like + * + * FRC: + * + * To force things that depend on FRC to be made, so we have to + * check for gn->children being empty as well... + */ + if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { + gn->mtime = now; + } +#else + /* + * This is what Make does and it's actually a good thing, as it + * allows rules like + * + * cmp -s y.tab.h parse.h || cp y.tab.h parse.h + * + * to function as intended. Unfortunately, thanks to the stateless + * nature of NFS (and the speed of this program), there are times + * when the modification time of a file created on a remote + * machine will not be modified before the stat() implied by + * the Dir_MTime occurs, thus leading us to believe that the file + * is unchanged, wreaking havoc with files that depend on this one. + * + * I have decided it is better to make too much than to make too + * little, so this stuff is commented out unless you're sure it's + * ok. + * -- ardeb 1/12/88 + */ + if (noExecute || Dir_MTime(gn) == 0) { + gn->mtime = now; + } + if (DEBUG(MAKE)) { + printf("update time: %s\n", Targ_FmtTime(gn->mtime)); + } +#endif + if (!(gn->type & OP_EXEC)) { + pgn->childMade = TRUE; + Make_TimeStamp(pgn, gn); + } + } else if (keepgoing) { + pgn->make = FALSE; + } else { + printf ("\n\nStop.\n"); + exit (1); + } + } else if (gn->made == ERROR) { + /* + * Already had an error when making this beastie. Tell the parent + * to abort. + */ + pgn->make = FALSE; + } else { + if (Lst_Member (gn->iParents, pgn) != NILLNODE) { + Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn); + } + switch(gn->made) { + case BEINGMADE: + Error("Graph cycles through %s\n", gn->name); + gn->made = ERROR; + pgn->make = FALSE; + break; + case MADE: + if ((gn->type & OP_EXEC) == 0) { + pgn->childMade = TRUE; + Make_TimeStamp(pgn, gn); + } + break; + case UPTODATE: + if ((gn->type & OP_EXEC) == 0) { + Make_TimeStamp(pgn, gn); + } + break; + } + } + + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Compat_Run -- + * Initialize this mode and start making. + * + * Results: + * None. + * + * Side Effects: + * Guess what? + * + *----------------------------------------------------------------------- + */ +void +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 */ + int errors; /* Number of targets not remade due to errors */ + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) { + signal(SIGINT, CompatInterrupt); + } + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { + signal(SIGTERM, CompatInterrupt); + } + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { + signal(SIGHUP, CompatInterrupt); + } + if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) { + signal(SIGQUIT, CompatInterrupt); + } + + for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) { + meta[*cp] = 1; + } + /* + * The null character serves as a sentinel in the string. + */ + meta[0] = 1; + + ENDNode = Targ_FindNode(".END", TARG_CREATE); + /* + * If the user has defined a .BEGIN target, execute the commands attached + * to it. + */ + if (!queryFlag) { + gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); + if (gn != NILGNODE) { + Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn); + } + } + + /* + * For each entry in the list of targets to create, call CompatMake on + * it to create the thing. CompatMake will leave the 'made' field of gn + * in one of several states: + * UPTODATE gn was already up-to-date + * MADE gn was recreated successfully + * ERROR An error occurred while gn was being created + * ABORTED gn was not remade because one of its inferiors + * could not be made due to errors. + */ + errors = 0; + while (!Lst_IsEmpty (targs)) { + gn = (GNode *) Lst_DeQueue (targs); + CompatMake (gn, gn); + + if (gn->made == UPTODATE) { + printf ("`%s' is up to date.\n", gn->name); + } else if (gn->made == ABORTED) { + printf ("`%s' not remade because of errors.\n", gn->name); + errors += 1; + } + } + + /* + * If the user has defined a .END target, run its commands. + */ + if (errors == 0) { + Lst_ForEach(ENDNode->commands, CompatRunCommand, (ClientData)gn); + } +} diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c new file mode 100644 index 000000000000..d9b376ef9446 --- /dev/null +++ b/usr.bin/make/cond.c @@ -0,0 +1,1208 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)cond.c 5.6 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * cond.c -- + * Functions to handle conditionals in a makefile. + * + * Interface: + * Cond_Eval Evaluate the conditional in the passed line. + * + */ + +#include "make.h" +#include <buf.h> +#include <ctype.h> + +/* + * The parsing of conditional expressions is based on this grammar: + * E -> F || E + * E -> F + * F -> T && F + * F -> T + * T -> defined(variable) + * T -> make(target) + * T -> exists(file) + * T -> empty(varspec) + * T -> target(name) + * T -> symbol + * T -> $(varspec) op value + * T -> $(varspec) == "string" + * T -> $(varspec) != "string" + * T -> ( E ) + * T -> ! T + * op -> == | != | > | < | >= | <= + * + * 'symbol' is some other symbol to which the default function (condDefProc) + * is applied. + * + * Tokens are scanned from the 'condExpr' string. The scanner (CondToken) + * will return And for '&' and '&&', Or for '|' and '||', Not for '!', + * LParen for '(', RParen for ')' and will evaluate the other terminal + * symbols, using either the default function or the function given in the + * terminal, and return the result as either True or False. + * + * All Non-Terminal functions (CondE, CondF and CondT) return Err on error. + */ +typedef enum { + And, Or, Not, True, False, LParen, RParen, EndOfFile, None, Err +} Token; + +/*- + * 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 struct If { + char *form; /* Form of if */ + int formlen; /* Length of form */ + 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, +}; + +static Boolean condInvert; /* Invert the default function */ +static Boolean (*condDefProc)(); /* Default function to apply */ +static char *condExpr; /* The expression to parse */ +static Token condPushBack=None; /* Single push-back token used in + * parsing */ + +#define MAXIF 30 /* greatest depth of #if'ing */ + +static Boolean condStack[MAXIF]; /* Stack of conditionals's values */ +static int condTop = MAXIF; /* Top-most conditional */ +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 -- + * Push back the most recent token read. We only need one level of + * this, so the thing is just stored in 'condPushback'. + * + * Results: + * None. + * + * Side Effects: + * condPushback is overwritten. + * + *----------------------------------------------------------------------- + */ +static void +CondPushBack (t) + Token t; /* Token to push back into the "stream" */ +{ + condPushBack = t; +} + +/*- + *----------------------------------------------------------------------- + * CondGetArg -- + * Find the argument of a built-in function. + * + * Results: + * The length of the argument and the address of the argument. + * + * Side Effects: + * The pointer is set to point to the closing parenthesis of the + * function call. + * + *----------------------------------------------------------------------- + */ +static int +CondGetArg (linePtr, argPtr, func, parens) + char **linePtr; + char **argPtr; + char *func; + Boolean parens; /* TRUE if arg should be bounded by parens */ +{ + register char *cp; + int argLen; + register Buffer buf; + + cp = *linePtr; + if (parens) { + while (*cp != '(' && *cp != '\0') { + cp++; + } + if (*cp == '(') { + cp++; + } + } + + if (*cp == '\0') { + /* + * No arguments whatsoever. Because 'make' and 'defined' aren't really + * "reserved words", we don't print a message. I think this is better + * than hitting the user with a warning message every time s/he uses + * the word 'make' or 'defined' at the beginning of a symbol... + */ + *argPtr = cp; + return (0); + } + + while (*cp == ' ' || *cp == '\t') { + cp++; + } + + /* + * Create a buffer for the argument and start it out at 16 characters + * long. Why 16? Why not? + */ + buf = Buf_Init(16); + + while ((index(" \t)&|", *cp) == (char *)NULL) && (*cp != '\0')) { + if (*cp == '$') { + /* + * Parse the variable spec and install it as part of the argument + * if it's valid. We tell Var_Parse to complain on an undefined + * variable, so we don't do it too. Nor do we return an error, + * though perhaps we should... + */ + char *cp2; + int len; + Boolean doFree; + + cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &doFree); + + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (doFree) { + free(cp2); + } + cp += len; + } else { + Buf_AddByte(buf, (Byte)*cp); + cp++; + } + } + + Buf_AddByte(buf, (Byte)'\0'); + *argPtr = (char *)Buf_GetAll(buf, &argLen); + Buf_Destroy(buf, FALSE); + + while (*cp == ' ' || *cp == '\t') { + cp++; + } + if (parens && *cp != ')') { + Parse_Error (PARSE_WARNING, "Missing closing parenthesis for %s()", + func); + return (0); + } else if (parens) { + /* + * Advance pointer past close parenthesis. + */ + cp++; + } + + *linePtr = cp; + return (argLen); +} + +/*- + *----------------------------------------------------------------------- + * CondDoDefined -- + * Handle the 'defined' function for conditionals. + * + * Results: + * TRUE if the given variable is defined. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoDefined (argLen, arg) + int argLen; + char *arg; +{ + char savec = arg[argLen]; + Boolean result; + + arg[argLen] = '\0'; + if (Var_Value (arg, VAR_CMD) != (char *)NULL) { + result = TRUE; + } else { + result = FALSE; + } + arg[argLen] = savec; + return (result); +} + +/*- + *----------------------------------------------------------------------- + * CondStrMatch -- + * Front-end for Str_Match so it returns 0 on match and non-zero + * on mismatch. Callback function for CondDoMake via Lst_Find + * + * Results: + * 0 if string matches pattern + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +static int +CondStrMatch(string, pattern) + char *string; + char *pattern; +{ + return(!Str_Match(string,pattern)); +} + +/*- + *----------------------------------------------------------------------- + * CondDoMake -- + * Handle the 'make' function for conditionals. + * + * Results: + * TRUE if the given target is being made. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoMake (argLen, arg) + int argLen; + char *arg; +{ + char savec = arg[argLen]; + Boolean result; + + arg[argLen] = '\0'; + if (Lst_Find (create, (ClientData)arg, CondStrMatch) == NILLNODE) { + result = FALSE; + } else { + result = TRUE; + } + arg[argLen] = savec; + return (result); +} + +/*- + *----------------------------------------------------------------------- + * CondDoExists -- + * See if the given file exists. + * + * Results: + * TRUE if the file exists and FALSE if it does not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoExists (argLen, arg) + int argLen; + char *arg; +{ + char savec = arg[argLen]; + Boolean result; + char *path; + + arg[argLen] = '\0'; + path = Dir_FindFile(arg, dirSearchPath); + if (path != (char *)NULL) { + result = TRUE; + free(path); + } else { + result = FALSE; + } + arg[argLen] = savec; + return (result); +} + +/*- + *----------------------------------------------------------------------- + * CondDoTarget -- + * See if the given node exists and is an actual target. + * + * Results: + * TRUE if the node exists as a target and FALSE if it does not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoTarget (argLen, arg) + int argLen; + char *arg; +{ + char savec = arg[argLen]; + Boolean result; + GNode *gn; + + arg[argLen] = '\0'; + gn = Targ_FindNode(arg, TARG_NOCREATE); + if ((gn != NILGNODE) && !OP_NOP(gn->type)) { + result = TRUE; + } else { + result = FALSE; + } + arg[argLen] = savec; + return (result); +} + + +/*- + *----------------------------------------------------------------------- + * CondCvtArg -- + * Convert the given number into a double. If the number begins + * with 0x, or just x, it is interpreted as a hexadecimal integer + * and converted to a double from there. All other strings just have + * atof called on them. + * + * Results: + * The double value of string. + * + * Side Effects: + * + * + *----------------------------------------------------------------------- + */ +static double +CondCvtArg(str) + register char *str; +{ + 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; + + 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); + } + } else if (sign < 0) { + return(- atof(str)); + } else { + return(atof(str)); + } +} + +/*- + *----------------------------------------------------------------------- + * CondToken -- + * Return the next token from the input. + * + * Results: + * A Token for the next lexical token in the stream. + * + * Side Effects: + * condPushback will be set back to None if it is used. + * + *----------------------------------------------------------------------- + */ +static Token +CondToken(doEval) + Boolean doEval; +{ + Token t; + + if (condPushBack == None) { + while (*condExpr == ' ' || *condExpr == '\t') { + condExpr++; + } + switch (*condExpr) { + case '(': + t = LParen; + condExpr++; + break; + case ')': + t = RParen; + condExpr++; + break; + case '|': + if (condExpr[1] == '|') { + condExpr++; + } + condExpr++; + t = Or; + break; + case '&': + if (condExpr[1] == '&') { + condExpr++; + } + condExpr++; + t = And; + break; + case '!': + t = Not; + condExpr++; + break; + case '\n': + case '\0': + t = EndOfFile; + break; + case '$': { + char *lhs; + char *rhs; + char *op; + int varSpecLen; + Boolean doFree; + + /* + * Parse the variable spec and skip over it, saving its + * value in lhs. + */ + t = Err; + lhs = Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree); + if (lhs == var_Error) { + /* + * Even if !doEval, we still report syntax errors, which + * is what getting var_Error back with !doEval means. + */ + return(Err); + } + condExpr += varSpecLen; + + /* + * Skip whitespace to get to the operator + */ + while (isspace(*condExpr)) { + condExpr++; + } + /* + * Make sure the operator is a valid one. If it isn't a + * known relational operator, pretend we got a + * != 0 comparison. + */ + op = condExpr; + switch (*condExpr) { + case '!': + case '=': + case '<': + case '>': + if (condExpr[1] == '=') { + condExpr += 2; + } else { + condExpr += 1; + } + break; + default: + op = "!="; + rhs = "0"; + + goto do_compare; + } + while (isspace(*condExpr)) { + condExpr++; + } + if (*condExpr == '\0') { + Parse_Error(PARSE_WARNING, + "Missing right-hand-side of operator"); + goto error; + } + rhs = condExpr; +do_compare: + if (*rhs == '"') { + /* + * Doing a string comparison. Only allow == and != for + * operators. + */ + char *string; + char *cp, *cp2; + Buffer buf; + + if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { + Parse_Error(PARSE_WARNING, + "String comparison operator should be either == or !="); + goto error; + } + + buf = Buf_Init(0); + + for (cp = rhs+1; (*cp != '"') && (*cp != '\0'); cp++) { + if ((*cp == '\\') && (cp[1] != '\0')) { + /* + * Backslash escapes things -- skip over next + * character, if it exists. + */ + cp++; + Buf_AddByte(buf, (Byte)*cp); + } else if (*cp == '$') { + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, VAR_CMD, doEval,&len, &freeIt); + if (cp2 != var_Error) { + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (freeIt) { + free(cp2); + } + cp += len - 1; + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)0); + + string = (char *)Buf_GetAll(buf, (int *)0); + Buf_Destroy(buf, FALSE); + + if (DEBUG(COND)) { + printf("lhs = \"%s\", rhs = \"%s\", op = %.2s\n", + lhs, string, op); + } + /* + * Null-terminate rhs and perform the comparison. + * t is set to the result. + */ + if (*op == '=') { + t = strcmp(lhs, string) ? False : True; + } else { + t = strcmp(lhs, string) ? True : False; + } + free(string); + if (rhs == condExpr) { + condExpr = cp + 1; + } + } else { + /* + * rhs is either a float or an integer. Convert both the + * lhs and the rhs to a double and compare the two. + */ + double left, right; + char *string; + + left = CondCvtArg(lhs); + if (*rhs == '$') { + int len; + Boolean freeIt; + + string = Var_Parse(rhs, VAR_CMD, doEval,&len,&freeIt); + if (string == var_Error) { + right = 0.0; + } else { + right = CondCvtArg(string); + if (freeIt) { + free(string); + } + if (rhs == condExpr) { + condExpr += len; + } + } + } else { + right = CondCvtArg(rhs); + if (rhs == condExpr) { + /* + * Skip over the right-hand side + */ + while(!isspace(*condExpr) && (*condExpr != '\0')) { + condExpr++; + } + } + } + + if (DEBUG(COND)) { + printf("left = %f, right = %f, op = %.2s\n", left, + right, op); + } + switch(op[0]) { + case '!': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, + "Unknown operator"); + goto error; + } + t = (left != right ? True : False); + break; + case '=': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, + "Unknown operator"); + goto error; + } + t = (left == right ? True : False); + break; + case '<': + if (op[1] == '=') { + t = (left <= right ? True : False); + } else { + t = (left < right ? True : False); + } + break; + case '>': + if (op[1] == '=') { + t = (left >= right ? True : False); + } else { + t = (left > right ? True : False); + } + break; + } + } +error: + if (doFree) { + free(lhs); + } + break; + } + default: { + Boolean (*evalProc)(); + Boolean invert = FALSE; + char *arg; + int arglen; + + if (strncmp (condExpr, "defined", 7) == 0) { + /* + * Use CondDoDefined to evaluate the argument and + * CondGetArg to extract the argument from the 'function + * call'. + */ + evalProc = CondDoDefined; + condExpr += 7; + arglen = CondGetArg (&condExpr, &arg, "defined", TRUE); + if (arglen == 0) { + condExpr -= 7; + goto use_default; + } + } else if (strncmp (condExpr, "make", 4) == 0) { + /* + * Use CondDoMake to evaluate the argument and + * CondGetArg to extract the argument from the 'function + * call'. + */ + evalProc = CondDoMake; + condExpr += 4; + arglen = CondGetArg (&condExpr, &arg, "make", TRUE); + if (arglen == 0) { + condExpr -= 4; + goto use_default; + } + } else if (strncmp (condExpr, "exists", 6) == 0) { + /* + * Use CondDoExists to evaluate the argument and + * CondGetArg to extract the argument from the + * 'function call'. + */ + evalProc = CondDoExists; + condExpr += 6; + arglen = CondGetArg(&condExpr, &arg, "exists", TRUE); + if (arglen == 0) { + condExpr -= 6; + goto use_default; + } + } else if (strncmp(condExpr, "empty", 5) == 0) { + /* + * Use Var_Parse to parse the spec in parens and return + * True if the resulting string is empty. + */ + int length; + Boolean doFree; + char *val; + + condExpr += 5; + + for (arglen = 0; + condExpr[arglen] != '(' && condExpr[arglen] != '\0'; + arglen += 1) + { + /* void */ ; + } + if (condExpr[arglen] != '\0') { + val = Var_Parse(&condExpr[arglen - 1], VAR_CMD, + doEval, &length, &doFree); + if (val == var_Error) { + t = Err; + } else { + t = (*val == '\0') ? True : False; + } + if (doFree) { + free(val); + } + /* + * Advance condExpr to beyond the closing ). Note that + * we subtract one from arglen + length b/c length + * is calculated from condExpr[arglen - 1]. + */ + condExpr += arglen + length - 1; + } else { + condExpr -= 5; + goto use_default; + } + break; + } else if (strncmp (condExpr, "target", 6) == 0) { + /* + * Use CondDoTarget to evaluate the argument and + * CondGetArg to extract the argument from the + * 'function call'. + */ + evalProc = CondDoTarget; + condExpr += 6; + arglen = CondGetArg(&condExpr, &arg, "target", TRUE); + if (arglen == 0) { + condExpr -= 6; + goto use_default; + } + } else { + /* + * The symbol is itself the argument to the default + * function. We advance condExpr to the end of the symbol + * by hand (the next whitespace, closing paren or + * binary operator) and set to invert the evaluation + * function if condInvert is TRUE. + */ + use_default: + invert = condInvert; + evalProc = condDefProc; + arglen = CondGetArg(&condExpr, &arg, "", FALSE); + } + + /* + * Evaluate the argument using the set function. If invert + * is TRUE, we invert the sense of the function. + */ + t = (!doEval || (* evalProc) (arglen, arg) ? + (invert ? False : True) : + (invert ? True : False)); + free(arg); + break; + } + } + } else { + t = condPushBack; + condPushBack = None; + } + return (t); +} + +/*- + *----------------------------------------------------------------------- + * CondT -- + * Parse a single term in the expression. This consists of a terminal + * symbol or Not and a terminal symbol (not including the binary + * operators): + * T -> defined(variable) | make(target) | exists(file) | symbol + * T -> ! T | ( E ) + * + * Results: + * True, False or Err. + * + * Side Effects: + * Tokens are consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondT(doEval) + Boolean doEval; +{ + Token t; + + t = CondToken(doEval); + + if (t == EndOfFile) { + /* + * If we reached the end of the expression, the expression + * is malformed... + */ + t = Err; + } else if (t == LParen) { + /* + * T -> ( E ) + */ + t = CondE(doEval); + if (t != Err) { + if (CondToken(doEval) != RParen) { + t = Err; + } + } + } else if (t == Not) { + t = CondT(doEval); + if (t == True) { + t = False; + } else if (t == False) { + t = True; + } + } + return (t); +} + +/*- + *----------------------------------------------------------------------- + * CondF -- + * Parse a conjunctive factor (nice name, wot?) + * F -> T && F | T + * + * Results: + * True, False or Err + * + * Side Effects: + * Tokens are consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondF(doEval) + Boolean doEval; +{ + Token l, o; + + l = CondT(doEval); + if (l != Err) { + o = CondToken(doEval); + + if (o == And) { + /* + * F -> T && F + * + * If T is False, the whole thing will be False, but we have to + * parse the r.h.s. anyway (to throw it away). + * If T is True, the result is the r.h.s., be it an Err or no. + */ + if (l == True) { + l = CondF(doEval); + } else { + (void) CondF(FALSE); + } + } else { + /* + * F -> T + */ + CondPushBack (o); + } + } + return (l); +} + +/*- + *----------------------------------------------------------------------- + * CondE -- + * Main expression production. + * E -> F || E | F + * + * Results: + * True, False or Err. + * + * Side Effects: + * Tokens are, of course, consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondE(doEval) + Boolean doEval; +{ + Token l, o; + + l = CondF(doEval); + if (l != Err) { + o = CondToken(doEval); + + if (o == Or) { + /* + * E -> F || E + * + * A similar thing occurs for ||, except that here we make sure + * the l.h.s. is False before we bother to evaluate the r.h.s. + * Once again, if l is False, the result is the r.h.s. and once + * again if l is True, we parse the r.h.s. to throw it away. + */ + if (l == False) { + l = CondE(doEval); + } else { + (void) CondE(FALSE); + } + } else { + /* + * E -> F + */ + CondPushBack (o); + } + } + return (l); +} + +/*- + *----------------------------------------------------------------------- + * Cond_Eval -- + * Evaluate the conditional in the passed line. The line + * looks like this: + * #<cond-type> <expr> + * where <cond-type> is any of if, ifmake, ifnmake, ifdef, + * ifndef, elif, elifmake, elifnmake, elifdef, elifndef + * and <expr> consists of &&, ||, !, make(target), defined(variable) + * and parenthetical groupings thereof. + * + * Results: + * COND_PARSE if should parse lines after the conditional + * COND_SKIP if should skip lines after the conditional + * COND_INVALID if not a valid conditional. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Cond_Eval (line) + char *line; /* Line to parse */ +{ + struct If *ifp; + Boolean isElse; + Boolean value; + int level; /* Level at which to report errors. */ + + level = PARSE_FATAL; + + for (line++; *line == ' ' || *line == '\t'; line++) { + continue; + } + + /* + * Find what type of if we're dealing with. The result is left + * in ifp and isElse is set TRUE if it's an elif line. + */ + if (line[0] == 'e' && line[1] == 'l') { + line += 2; + isElse = TRUE; + } else if (strncmp (line, "endif", 5) == 0) { + /* + * End of a conditional section. If skipIfLevel is non-zero, that + * conditional was skipped, so lines following it should also be + * skipped. Hence, we return COND_SKIP. Otherwise, the conditional + * was read so succeeding lines should be parsed (think about it...) + * so we return COND_PARSE, unless this endif isn't paired with + * a decent if. + */ + if (skipIfLevel != 0) { + skipIfLevel -= 1; + return (COND_SKIP); + } else { + if (condTop == MAXIF) { + Parse_Error (level, "if-less endif"); + return (COND_INVALID); + } else { + skipLine = FALSE; + condTop += 1; + return (COND_PARSE); + } + } + } else { + isElse = FALSE; + } + + /* + * Figure out what sort of conditional it is -- what its default + * function is, etc. -- by looking in the table of valid "ifs" + */ + for (ifp = ifs; ifp->form != (char *)0; ifp++) { + if (strncmp (ifp->form, line, ifp->formlen) == 0) { + break; + } + } + + if (ifp->form == (char *) 0) { + /* + * Nothing fit. If the first word on the line is actually + * "else", it's a valid conditional whose value is the inverse + * of the previous if we parsed. + */ + if (isElse && (line[0] == 's') && (line[1] == 'e')) { + if (condTop == MAXIF) { + Parse_Error (level, "if-less else"); + return (COND_INVALID); + } else if (skipIfLevel == 0) { + value = !condStack[condTop]; + } else { + return (COND_SKIP); + } + } else { + /* + * Not a valid conditional type. No error... + */ + return (COND_INVALID); + } + } else { + if (isElse) { + if (condTop == MAXIF) { + Parse_Error (level, "if-less elif"); + return (COND_INVALID); + } else if (skipIfLevel != 0) { + /* + * If skipping this conditional, just ignore the whole thing. + * If we don't, the user might be employing a variable that's + * undefined, for which there's an enclosing ifdef that + * we're skipping... + */ + return(COND_SKIP); + } + } else if (skipLine) { + /* + * Don't even try to evaluate a conditional that's not an else if + * we're skipping things... + */ + skipIfLevel += 1; + return(COND_SKIP); + } + + /* + * Initialize file-global variables for parsing + */ + condDefProc = ifp->defProc; + condInvert = ifp->doNot; + + line += ifp->formlen; + + while (*line == ' ' || *line == '\t') { + line++; + } + + condExpr = line; + condPushBack = None; + + switch (CondE(TRUE)) { + case True: + if (CondToken(TRUE) == EndOfFile) { + value = TRUE; + break; + } + goto err; + /*FALLTHRU*/ + case False: + if (CondToken(TRUE) == EndOfFile) { + value = FALSE; + break; + } + /*FALLTHRU*/ + case Err: + err: + Parse_Error (level, "Malformed conditional (%s)", + line); + return (COND_INVALID); + } + } + if (!isElse) { + condTop -= 1; + } else if ((skipIfLevel != 0) || condStack[condTop]) { + /* + * If this is an else-type conditional, it should only take effect + * if its corresponding if was evaluated and FALSE. If its if was + * TRUE or skipped, we return COND_SKIP (and start skipping in case + * we weren't already), leaving the stack unmolested so later elif's + * don't screw up... + */ + skipLine = TRUE; + return (COND_SKIP); + } + + if (condTop < 0) { + /* + * This is the one case where we can definitely proclaim a fatal + * error. If we don't, we're hosed. + */ + Parse_Error (PARSE_FATAL, "Too many nested if's. %d max.", MAXIF); + return (COND_INVALID); + } else { + condStack[condTop] = value; + skipLine = !value; + return (value ? COND_PARSE : COND_SKIP); + } +} + +/*- + *----------------------------------------------------------------------- + * Cond_End -- + * Make sure everything's clean at the end of a makefile. + * + * Results: + * None. + * + * Side Effects: + * Parse_Error will be called if open conditionals are around. + * + *----------------------------------------------------------------------- + */ +void +Cond_End() +{ + if (condTop != MAXIF) { + Parse_Error(PARSE_FATAL, "%d open conditional%s", MAXIF-condTop, + MAXIF-condTop == 1 ? "" : "s"); + } + condTop = MAXIF; +} diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h new file mode 100644 index 000000000000..450d16b1803f --- /dev/null +++ b/usr.bin/make/config.h @@ -0,0 +1,88 @@ +/* + * 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. + * + * @(#)config.h 5.9 (Berkeley) 6/1/90 + */ + +#define DEFSHELL 1 /* Bourne shell */ + +/* + * DEFMAXJOBS + * DEFMAXLOCAL + * These control the default concurrency. On no occasion will more + * than DEFMAXJOBS targets be created at once (locally or remotely) + * DEFMAXLOCAL is the highest number of targets which will be + * created on the local machine at once. Note that if you set this + * to 0, nothing will ever happen... + */ +#define DEFMAXJOBS 4 +#define DEFMAXLOCAL 1 + +/* + * INCLUDES + * LIBRARIES + * These control the handling of the .INCLUDES and .LIBS variables. + * If INCLUDES is defined, the .INCLUDES variable will be filled + * from the search paths of those suffixes which are marked by + * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS + * See suff.c for more details. + */ +#define INCLUDES +#define LIBRARIES + +/* + * LIBSUFF + * Is the suffix used to denote libraries and is used by the Suff module + * to find the search path on which to seek any -l<xx> targets. + * + * RECHECK + * If defined, Make_Update will check a target for its current + * modification time after it has been re-made, setting it to the + * starting time of the make only if the target still doesn't exist. + * Unfortunately, under NFS the modification time often doesn't + * get updated in time, so a target will appear to not have been + * re-made, causing later targets to appear up-to-date. On systems + * that don't have this problem, you should defined this. Under + * NFS you probably should not, unless you aren't exporting jobs. + * + * POSIX + * If the POSIX standard for Make is to be followed. There are + * several areas that I dislike, hence this constant. + */ +#define LIBSUFF ".a" +#define RECHECK +/*#define POSIX*/ diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c new file mode 100644 index 000000000000..be200cba2b55 --- /dev/null +++ b/usr.bin/make/dir.c @@ -0,0 +1,1237 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)dir.c 5.6 (Berkeley) 12/28/90"; +#endif /* not lint */ + +/*- + * dir.c -- + * Directory searching using wildcards and/or normal names... + * Used both for source wildcarding in the Makefile and for finding + * implicit sources. + * + * The interface for this module is: + * Dir_Init Initialize the module. + * + * Dir_HasWildcards Returns TRUE if the name given it needs to + * be wildcard-expanded. + * + * Dir_Expand Given a pattern and a path, return a Lst of names + * which match the pattern on the search path. + * + * Dir_FindFile Searches for a file on a given search path. + * If it exists, the entire path is returned. + * Otherwise NULL is returned. + * + * Dir_MTime Return the modification time of a node. The file + * is searched for along the default search path. + * The path and mtime fields of the node are filled + * in. + * + * Dir_AddDir Add a directory to a search path. + * + * Dir_MakeFlags Given a search path and a command flag, create + * a string with each of the directories in the path + * preceded by the command flag and all of them + * separated by a space. + * + * Dir_Destroy Destroy an element of a search path. Frees up all + * things that can be freed for the element as long + * as the element is no longer referenced by any other + * search path. + * Dir_ClearPath Resets a search path to the empty list. + * + * For debugging: + * Dir_PrintDirectories Print stats about the directory cache. + */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/dir.h> +#include <sys/stat.h> +#include "make.h" +#include "hash.h" + +/* + * A search path consists of a Lst of Path structures. A Path structure + * has in it the name of the directory and a hash table of all the files + * in the directory. This is used to cut down on the number of system + * calls necessary to find implicit dependents and their like. Since + * these searches are made before any actions are taken, we need not + * worry about the directory changing due to creation commands. If this + * hampers the style of some makefiles, they must be changed. + * + * A list of all previously-read directories is kept in the + * openDirectories Lst. This list is checked first before a directory + * is opened. + * + * The need for the caching of whole directories is brought about by + * the multi-level transformation code in suff.c, which tends to search + * for far more files than regular make does. In the initial + * implementation, the amount of time spent performing "stat" calls was + * truly astronomical. The problem with hashing at the start is, + * of course, that pmake doesn't then detect changes to these directories + * during the course of the make. Three possibilities suggest themselves: + * + * 1) just use stat to test for a file's existence. As mentioned + * above, this is very inefficient due to the number of checks + * engendered by the multi-level transformation code. + * 2) use readdir() and company to search the directories, keeping + * them open between checks. I have tried this and while it + * didn't slow down the process too much, it could severely + * affect the amount of parallelism available as each directory + * open would take another file descriptor out of play for + * handling I/O for another job. Given that it is only recently + * that UNIX OS's have taken to allowing more than 20 or 32 + * file descriptors for a process, this doesn't seem acceptable + * to me. + * 3) record the mtime of the directory in the Path structure and + * verify the directory hasn't changed since the contents were + * hashed. This will catch the creation or deletion of files, + * but not the updating of files. However, since it is the + * creation and deletion that is the problem, this could be + * a good thing to do. Unfortunately, if the directory (say ".") + * were fairly large and changed fairly frequently, the constant + * rehashing could seriously degrade performance. It might be + * good in such cases to keep track of the number of rehashes + * and if the number goes over a (small) limit, resort to using + * stat in its place. + * + * An additional thing to consider is that pmake is used primarily + * to create C programs and until recently pcc-based compilers refused + * to allow you to specify where the resulting object file should be + * placed. This forced all objects to be created in the current + * directory. This isn't meant as a full excuse, just an explanation of + * some of the reasons for the caching used here. + * + * One more note: the location of a target's file is only performed + * on the downward traversal of the graph and then only for terminal + * nodes in the graph. This could be construed as wrong in some cases, + * but prevents inadvertent modification of files when the "installed" + * directory for a file is provided in the search path. + * + * Another data structure maintained by this module is an mtime + * cache used when the searching of cached directories fails to find + * a file. In the past, Dir_FindFile would simply perform an access() + * call in such a case to determine if the file could be found using + * just the name given. When this hit, however, all that was gained + * was the knowledge that the file existed. Given that an access() is + * essentially a stat() without the copyout() call, and that the same + * filesystem overhead would have to be incurred in Dir_MTime, it made + * sense to replace the access() with a stat() and record the mtime + * in a cache for when Dir_MTime was actually called. + */ + +Lst dirSearchPath; /* main search path */ + +static Lst openDirectories; /* the list of all open directories */ + +/* + * Variables for gathering statistics on the efficiency of the hashing + * mechanism. + */ +static int hits, /* Found in directory cache */ + misses, /* Sad, but not evil misses */ + 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 + * system to find the file, we might as well + * have its mtime on record. XXX: If this is done + * way early, there's a chance other rules will + * have already updated the file, in which case + * we'll update it again. Generally, there won't + * be two rules to update a single file, so this + * should be ok, but... */ + + +/*- + *----------------------------------------------------------------------- + * Dir_Init -- + * initialize things for this module + * + * Results: + * none + * + * Side Effects: + * some directories may be opened. + *----------------------------------------------------------------------- + */ +void +Dir_Init () +{ + dirSearchPath = Lst_Init (FALSE); + openDirectories = Lst_Init (FALSE); + Hash_InitTable(&mtimes, 0); + + /* + * Since the Path structure is placed on both openDirectories and + * the path we give Dir_AddDir (which in this case is openDirectories), + * we need to remove "." from openDirectories and what better time to + * do it than when we have to fetch the thing anyway? + */ + Dir_AddDir (openDirectories, "."); + dot = (Path *) Lst_DeQueue (openDirectories); + + /* + * We always need to have dot around, so we increment its reference count + * to make sure it's not destroyed. + */ + dot->refCount += 1; +} + +/*- + *----------------------------------------------------------------------- + * DirFindName -- + * See if the Path structure describes the same directory as the + * given one by comparing their names. Called from Dir_AddDir via + * Lst_Find when searching the list of open directories. + * + * Results: + * 0 if it is the same. Non-zero otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +DirFindName (p, dname) + Path *p; /* Current name */ + char *dname; /* Desired name */ +{ + return (strcmp (p->name, dname)); +} + +/*- + *----------------------------------------------------------------------- + * Dir_HasWildcards -- + * see if the given name has any wildcard characters in it + * + * Results: + * returns TRUE if the word should be expanded, FALSE otherwise + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +Boolean +Dir_HasWildcards (name) + char *name; /* name to check */ +{ + register char *cp; + + for (cp = name; *cp; cp++) { + switch(*cp) { + case '{': + case '[': + case '?': + case '*': + return (TRUE); + } + } + return (FALSE); +} + +/*- + *----------------------------------------------------------------------- + * DirMatchFiles -- + * 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 + * will do for now. + * + * Results: + * Always returns 0 + * + * Side Effects: + * File names are added to the expansions lst. The directory will be + * fully hashed when this is done. + *----------------------------------------------------------------------- + */ +static int +DirMatchFiles (pattern, p, expansions) + char *pattern; /* Pattern to look for */ + Path *p; /* Directory to search */ + Lst expansions; /* Place to store the results */ +{ + 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'); + + for (entry = Hash_EnumFirst(&p->files, &search); + entry != (Hash_Entry *)NULL; + entry = Hash_EnumNext(&search)) + { + /* + * See if the file matches the given pattern. Note we follow the UNIX + * convention that dot files will only be found if the pattern + * begins with a dot (note also that as a side effect of the hashing + * scheme, .* won't match . or .. since they aren't hashed). + */ + if (Str_Match(entry->name, pattern) && + ((entry->name[0] != '.') || + (pattern[0] == '.'))) + { + (void)Lst_AtEnd(expansions, + (isDot ? strdup(entry->name) : + str_concat(p->name, entry->name, + STR_ADDSLASH))); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * DirExpandCurly -- + * Expand curly braces like the C shell. Does this recursively. + * Note the special case: if after the piece of the curly brace is + * done there are no wildcard characters in the result, the result is + * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. + * + * Results: + * None. + * + * Side Effects: + * The given list is filled with the expansions... + * + *----------------------------------------------------------------------- + */ +static void +DirExpandCurly(word, brace, path, expansions) + char *word; /* Entire word to expand */ + char *brace; /* First curly brace in it */ + Lst path; /* Search path to use */ + Lst expansions; /* Place to store the expansions */ +{ + char *end; /* Character after the closing brace */ + char *cp; /* Current position in brace clause */ + char *start; /* Start of current piece of brace clause */ + int bracelevel; /* Number of braces we've seen. If we see a + * right brace when this is 0, we've hit the + * end of the clause. */ + char *file; /* Current expansion */ + int otherLen; /* The length of the other pieces of the + * expansion (chars before and after the + * clause in 'word') */ + char *cp2; /* Pointer for checking for wildcards in + * expansion before calling Dir_Expand */ + + start = brace+1; + + /* + * Find the end of the brace clause first, being wary of nested brace + * clauses. + */ + for (end = start, bracelevel = 0; *end != '\0'; end++) { + if (*end == '{') { + bracelevel++; + } else if ((*end == '}') && (bracelevel-- == 0)) { + break; + } + } + if (*end == '\0') { + Error("Unterminated {} clause \"%s\"", start); + return; + } else { + end++; + } + otherLen = brace - word + strlen(end); + + for (cp = start; cp < end; cp++) { + /* + * Find the end of this piece of the clause. + */ + bracelevel = 0; + while (*cp != ',') { + if (*cp == '{') { + bracelevel++; + } else if ((*cp == '}') && (bracelevel-- <= 0)) { + break; + } + cp++; + } + /* + * Allocate room for the combination and install the three pieces. + */ + file = emalloc(otherLen + cp - start + 1); + if (brace != word) { + strncpy(file, word, brace-word); + } + if (cp != start) { + strncpy(&file[brace-word], start, cp-start); + } + strcpy(&file[(brace-word)+(cp-start)], end); + + /* + * See if the result has any wildcards in it. If we find one, call + * Dir_Expand right away, telling it to place the result on our list + * of expansions. + */ + for (cp2 = file; *cp2 != '\0'; cp2++) { + switch(*cp2) { + case '*': + case '?': + case '{': + case '[': + Dir_Expand(file, path, expansions); + goto next; + } + } + if (*cp2 == '\0') { + /* + * Hit the end w/o finding any wildcards, so stick the expansion + * on the end of the list. + */ + (void)Lst_AtEnd(expansions, file); + } else { + next: + free(file); + } + start = cp+1; + } +} + + +/*- + *----------------------------------------------------------------------- + * DirExpandInt -- + * Internal expand routine. Passes through the directories in the + * path one by one, calling DirMatchFiles for each. NOTE: This still + * doesn't handle patterns in directories... + * + * Results: + * None. + * + * Side Effects: + * Things are added to the expansions list. + * + *----------------------------------------------------------------------- + */ +static void +DirExpandInt(word, path, expansions) + char *word; /* Word to expand */ + Lst path; /* Path on which to look */ + Lst expansions; /* Place to store the result */ +{ + LstNode ln; /* Current node */ + Path *p; /* Directory in the node */ + + if (Lst_Open(path) == SUCCESS) { + while ((ln = Lst_Next(path)) != NILLNODE) { + p = (Path *)Lst_Datum(ln); + DirMatchFiles(word, p, expansions); + } + Lst_Close(path); + } +} + +/*- + *----------------------------------------------------------------------- + * DirPrintWord -- + * Print a word in the list of expansions. Callback for Dir_Expand + * when DEBUG(DIR), via Lst_ForEach. + * + * Results: + * === 0 + * + * Side Effects: + * The passed word is printed, followed by a space. + * + *----------------------------------------------------------------------- + */ +static int +DirPrintWord(word) + char *word; +{ + printf("%s ", word); + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Dir_Expand -- + * Expand the given word into a list of words by globbing it looking + * in the directories on the given search path. + * + * Results: + * A list of words consisting of the files which exist along the search + * path matching the given pattern. + * + * Side Effects: + * Directories may be opened. Who knows? + *----------------------------------------------------------------------- + */ +void +Dir_Expand (word, path, expansions) + char *word; /* the word to expand */ + Lst path; /* the list of directories in which to find + * the resulting files */ + Lst expansions; /* the list on which to place the results */ +{ + char *cp; + + if (DEBUG(DIR)) { + printf("expanding \"%s\"...", word); + } + + cp = index(word, '{'); + if (cp) { + DirExpandCurly(word, cp, path, expansions); + } else { + cp = index(word, '/'); + if (cp) { + /* + * The thing has a directory component -- find the first wildcard + * in the string. + */ + for (cp = word; *cp; cp++) { + if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { + break; + } + } + if (*cp == '{') { + /* + * This one will be fun. + */ + DirExpandCurly(word, cp, path, expansions); + return; + } else if (*cp != '\0') { + /* + * Back up to the start of the component + */ + char *dirpath; + + while (cp > word && *cp != '/') { + cp--; + } + if (cp != word) { + /* + * If the glob isn't in the first component, try and find + * all the components up to the one with a wildcard. + */ + *cp = '\0'; + dirpath = Dir_FindFile(word, path); + *cp = '/'; + /* + * dirpath is null if can't find the leading component + * XXX: Dir_FindFile won't find internal components. + * i.e. if the path contains ../Etc/Object and we're + * looking for Etc, it won't be found. Ah well. + * Probably not important. + */ + if (dirpath != (char *)NULL) { + path = Lst_Init(FALSE); + Dir_AddDir(path, dirpath); + DirExpandInt(cp+1, path, expansions); + Lst_Destroy(path, NOFREE); + } + } else { + /* + * Start the search from the local directory + */ + DirExpandInt(word, path, expansions); + } + } else { + /* + * Return the file -- this should never happen. + */ + DirExpandInt(word, path, expansions); + } + } else { + /* + * First the files in dot + */ + DirMatchFiles(word, dot, expansions); + + /* + * Then the files in every other directory on the path. + */ + DirExpandInt(word, path, expansions); + } + } + if (DEBUG(DIR)) { + Lst_ForEach(expansions, DirPrintWord, NULL); + putchar('\n'); + } +} + +/*- + *----------------------------------------------------------------------- + * Dir_FindFile -- + * Find the file with the given name along the given search path. + * + * Results: + * The path to the file or NULL. This path is guaranteed to be in a + * different part of memory than name and so may be safely free'd. + * + * Side Effects: + * If the file is found in a directory which is not on the path + * already (either 'name' is absolute or it is a relative path + * [ dir1/.../dirn/file ] which exists below one of the directories + * already on the search path), its directory is added to the end + * of the path on the assumption that there will be more files in + * that directory later on. Sometimes this is true. Sometimes not. + *----------------------------------------------------------------------- + */ +char * +Dir_FindFile (name, path) + char *name; /* the file to find */ + Lst path; /* the Lst of directories to search */ +{ + register char *p1; /* pointer into p->name */ + register char *p2; /* pointer into name */ + LstNode ln; /* a list element */ + register char *file; /* the current filename to check */ + register Path *p; /* current path member */ + register char *cp; /* index of first slash, if any */ + Boolean hasSlash; /* true if 'name' contains a / */ + struct stat stb; /* Buffer for stat, if necessary */ + Hash_Entry *entry; /* Entry for mtimes table */ + + /* + * Find the final component of the name and note whether it has a + * slash in it (the name, I mean) + */ + cp = rindex (name, '/'); + if (cp) { + hasSlash = TRUE; + cp += 1; + } else { + hasSlash = FALSE; + cp = name; + } + + if (DEBUG(DIR)) { + printf("Searching for %s...", name); + } + /* + * No matter what, we always look for the file in the current directory + * before anywhere else and we *do not* add the ./ to it if it exists. + * This is so there are no conflicts between what the user specifies + * (fish.c) and what pmake finds (./fish.c). + */ + if ((!hasSlash || (cp - name == 2 && *name == '.')) && + (Hash_FindEntry (&dot->files, cp) != (Hash_Entry *)NULL)) { + if (DEBUG(DIR)) { + printf("in '.'\n"); + } + hits += 1; + dot->hits += 1; + return (strdup (name)); + } + + if (Lst_Open (path) == FAILURE) { + if (DEBUG(DIR)) { + printf("couldn't open path, file not found\n"); + } + misses += 1; + return ((char *) NULL); + } + + /* + * We look through all the directories on the path seeking one which + * contains the final component of the given name and whose final + * component(s) match the name's initial component(s). If such a beast + * is found, we concatenate the directory name and the final component + * and return the resulting string. If we don't find any such thing, + * we go on to phase two... + */ + while ((ln = Lst_Next (path)) != NILLNODE) { + p = (Path *) Lst_Datum (ln); + if (DEBUG(DIR)) { + printf("%s...", p->name); + } + if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) { + if (DEBUG(DIR)) { + printf("here..."); + } + if (hasSlash) { + /* + * If the name had a slash, its initial components and p's + * final components must match. This is false if a mismatch + * is encountered before all of the initial components + * have been checked (p2 > name at the end of the loop), or + * we matched only part of one of the components of p + * along with all the rest of them (*p1 != '/'). + */ + p1 = p->name + strlen (p->name) - 1; + p2 = cp - 2; + while (p2 >= name && *p1 == *p2) { + p1 -= 1; p2 -= 1; + } + if (p2 >= name || (p1 >= p->name && *p1 != '/')) { + if (DEBUG(DIR)) { + printf("component mismatch -- continuing..."); + } + continue; + } + } + file = str_concat (p->name, cp, STR_ADDSLASH); + if (DEBUG(DIR)) { + printf("returning %s\n", file); + } + Lst_Close (path); + p->hits += 1; + hits += 1; + return (file); + } else if (hasSlash) { + /* + * If the file has a leading path component and that component + * exactly matches the entire name of the current search + * directory, we assume the file doesn't exist and return NULL. + */ + for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) { + continue; + } + if (*p1 == '\0' && p2 == cp - 1) { + if (DEBUG(DIR)) { + printf("must be here but isn't -- returing NULL\n"); + } + Lst_Close (path); + return ((char *) NULL); + } + } + } + + /* + * We didn't find the file on any existing members of the directory. + * If the name doesn't contain a slash, that means it doesn't exist. + * If it *does* contain a slash, however, there is still hope: it + * could be in a subdirectory of one of the members of the search + * path. (eg. /usr/include and sys/types.h. The above search would + * fail to turn up types.h in /usr/include, but it *is* in + * /usr/include/sys/types.h) If we find such a beast, we assume there + * will be more (what else can we assume?) and add all but the last + * component of the resulting name onto the search path (at the + * end). This phase is only performed if the file is *not* absolute. + */ + if (!hasSlash) { + if (DEBUG(DIR)) { + printf("failed.\n"); + } + misses += 1; + return ((char *) NULL); + } + + if (*name != '/') { + Boolean checkedDot = FALSE; + + if (DEBUG(DIR)) { + printf("failed. Trying subdirectories..."); + } + (void) Lst_Open (path); + while ((ln = Lst_Next (path)) != NILLNODE) { + p = (Path *) Lst_Datum (ln); + if (p != dot) { + file = str_concat (p->name, name, STR_ADDSLASH); + } else { + /* + * Checking in dot -- DON'T put a leading ./ on the thing. + */ + file = strdup(name); + checkedDot = TRUE; + } + if (DEBUG(DIR)) { + printf("checking %s...", file); + } + + + if (stat (file, &stb) == 0) { + if (DEBUG(DIR)) { + printf("got it.\n"); + } + + Lst_Close (path); + + /* + * We've found another directory to search. We know there's + * a slash in 'file' because we put one there. We nuke it after + * finding it and call Dir_AddDir to add this new directory + * onto the existing search path. Once that's done, we restore + * the slash and triumphantly return the file name, knowing + * that should a file in this directory every be referenced + * again in such a manner, we will find it without having to do + * numerous numbers of access calls. Hurrah! + */ + cp = rindex (file, '/'); + *cp = '\0'; + Dir_AddDir (path, file); + *cp = '/'; + + /* + * Save the modification time so if it's needed, we don't have + * to fetch it again. + */ + if (DEBUG(DIR)) { + printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), + file); + } + entry = Hash_CreateEntry(&mtimes, (ClientData)file, + (Boolean *)NULL); + Hash_SetValue(entry, stb.st_mtime); + nearmisses += 1; + return (file); + } else { + free (file); + } + } + + if (DEBUG(DIR)) { + printf("failed. "); + } + Lst_Close (path); + + if (checkedDot) { + /* + * Already checked by the given name, since . was in the path, + * so no point in proceeding... + */ + if (DEBUG(DIR)) { + printf("Checked . already, returning NULL\n"); + } + return(NULL); + } + } + + /* + * Didn't find it that way, either. Sigh. Phase 3. Add its directory + * onto the search path in any case, just in case, then look for the + * thing in the hash table. If we find it, grand. We return a new + * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. + * Note that if the directory holding the file doesn't exist, this will + * do an extra search of the final directory on the path. Unless something + * weird happens, this search won't succeed and life will be groovy. + * + * Sigh. We cannot add the directory onto the search path because + * of this amusing case: + * $(INSTALLDIR)/$(FILE): $(FILE) + * + * $(FILE) exists in $(INSTALLDIR) but not in the current one. + * When searching for $(FILE), we will find it in $(INSTALLDIR) + * b/c we added it here. This is not good... + */ +#ifdef notdef + cp[-1] = '\0'; + Dir_AddDir (path, name); + cp[-1] = '/'; + + bigmisses += 1; + ln = Lst_Last (path); + if (ln == NILLNODE) { + return ((char *) NULL); + } else { + p = (Path *) Lst_Datum (ln); + } + + if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) { + return (strdup (name)); + } else { + return ((char *) NULL); + } +#else /* !notdef */ + if (DEBUG(DIR)) { + printf("Looking for \"%s\"...", name); + } + + bigmisses += 1; + entry = Hash_FindEntry(&mtimes, name); + if (entry != (Hash_Entry *)NULL) { + if (DEBUG(DIR)) { + printf("got it (in mtime cache)\n"); + } + return(strdup(name)); + } else if (stat (name, &stb) == 0) { + entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL); + if (DEBUG(DIR)) { + printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), + name); + } + Hash_SetValue(entry, stb.st_mtime); + return (strdup (name)); + } else { + if (DEBUG(DIR)) { + printf("failed. Returning NULL\n"); + } + return ((char *)NULL); + } +#endif /* notdef */ +} + +/*- + *----------------------------------------------------------------------- + * Dir_MTime -- + * Find the modification time of the file described by gn along the + * search path dirSearchPath. + * + * Results: + * The modification time or 0 if it doesn't exist + * + * Side Effects: + * The modification time is placed in the node's mtime slot. + * If the node didn't have a path entry before, and Dir_FindFile + * found one for it, the full name is placed in the path slot. + *----------------------------------------------------------------------- + */ +int +Dir_MTime (gn) + GNode *gn; /* the file whose modification time is + * desired */ +{ + char *fullName; /* the full pathname of name */ + struct stat stb; /* buffer for finding the mod time */ + Hash_Entry *entry; + + if (gn->type & OP_ARCHV) { + return Arch_MTime (gn); + } else if (gn->path == (char *)NULL) { + fullName = Dir_FindFile (gn->name, dirSearchPath); + } else { + fullName = gn->path; + } + + if (fullName == (char *)NULL) { + fullName = gn->name; + } + + entry = Hash_FindEntry(&mtimes, fullName); + if (entry != (Hash_Entry *)NULL) { + /* + * Only do this once -- the second time folks are checking to + * see if the file was actually updated, so we need to actually go + * to the file system. + */ + if (DEBUG(DIR)) { + printf("Using cached time %s for %s\n", + Targ_FmtTime(Hash_GetValue(entry)), fullName); + } + stb.st_mtime = (time_t)Hash_GetValue(entry); + Hash_DeleteEntry(&mtimes, entry); + } else if (stat (fullName, &stb) < 0) { + if (gn->type & OP_MEMBER) { + return Arch_MemMTime (gn); + } else { + stb.st_mtime = 0; + } + } + if (fullName && gn->path == (char *)NULL) { + gn->path = fullName; + } + + gn->mtime = stb.st_mtime; + return (gn->mtime); +} + +/*- + *----------------------------------------------------------------------- + * Dir_AddDir -- + * Add the given name to the end of the given path. The order of + * the arguments is backwards so ParseDoDependency can do a + * Lst_ForEach of its list of paths... + * + * Results: + * none + * + * Side Effects: + * A structure is added to the list and the directory is + * read and hashed. + *----------------------------------------------------------------------- + */ +void +Dir_AddDir (path, name) + Lst path; /* the path to which the directory should be + * added */ + char *name; /* the name of the directory to add */ +{ + LstNode ln; /* node in case Path structure is found */ + register Path *p; /* pointer to new Path structure */ + DIR *d; /* for reading directory */ + register struct direct *dp; /* entry in directory */ + Hash_Entry *he; + char *fName; + + ln = Lst_Find (openDirectories, (ClientData)name, DirFindName); + if (ln != NILLNODE) { + p = (Path *)Lst_Datum (ln); + if (Lst_Member(path, (ClientData)p) == NILLNODE) { + p->refCount += 1; + (void)Lst_AtEnd (path, (ClientData)p); + } + } else { + if (DEBUG(DIR)) { + printf("Caching %s...", name); + fflush(stdout); + } + + if ((d = opendir (name)) != (DIR *) NULL) { + p = (Path *) emalloc (sizeof (Path)); + p->name = strdup (name); + p->hits = 0; + p->refCount = 1; + Hash_InitTable (&p->files, -1); + + /* + * Skip the first two entries -- these will *always* be . and .. + */ + (void)readdir(d); + (void)readdir(d); + + while ((dp = readdir (d)) != (struct direct *) NULL) { +#ifdef sun + /* + * The sun directory library doesn't check for a 0 inode + * (0-inode slots just take up space), so we have to do + * it ourselves. + */ + if (dp->d_fileno == 0) { + continue; + } +#endif sun + (void)Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL); + } + (void) closedir (d); + (void)Lst_AtEnd (openDirectories, (ClientData)p); + (void)Lst_AtEnd (path, (ClientData)p); + } + if (DEBUG(DIR)) { + printf("done\n"); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Dir_CopyDir -- + * Callback function for duplicating a search path via Lst_Duplicate. + * Ups the reference count for the directory. + * + * Results: + * Returns the Path it was given. + * + * Side Effects: + * The refCount of the path is incremented. + * + *----------------------------------------------------------------------- + */ +ClientData +Dir_CopyDir(p) + Path *p; /* Directory descriptor to copy */ +{ + p->refCount += 1; + + return ((ClientData)p); +} + +/*- + *----------------------------------------------------------------------- + * Dir_MakeFlags -- + * Make a string by taking all the directories in the given search + * path and preceding them by the given flag. Used by the suffix + * module to create variables for compilers based on suffix search + * paths. + * + * Results: + * The string mentioned above. Note that there is no space between + * the given flag and each directory. The empty string is returned if + * Things don't go well. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Dir_MakeFlags (flag, path) + char *flag; /* flag which should precede each directory */ + Lst path; /* list of directories */ +{ + char *str; /* the string which will be returned */ + char *tstr; /* the current directory preceded by 'flag' */ + LstNode ln; /* the node of the current directory */ + Path *p; /* the structure describing the current directory */ + + str = strdup (""); + + if (Lst_Open (path) == SUCCESS) { + while ((ln = Lst_Next (path)) != NILLNODE) { + p = (Path *) Lst_Datum (ln); + tstr = str_concat (flag, p->name, 0); + str = str_concat (str, tstr, STR_ADDSPACE | STR_DOFREE); + } + Lst_Close (path); + } + + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Dir_Destroy -- + * Nuke a directory descriptor, if possible. Callback procedure + * for the suffixes module when destroying a search path. + * + * Results: + * None. + * + * Side Effects: + * If no other path references this directory (refCount == 0), + * the Path and all its data are freed. + * + *----------------------------------------------------------------------- + */ +void +Dir_Destroy (p) + Path *p; /* The directory descriptor to nuke */ +{ + Hash_Search thing1; + Hash_Entry *thing2; + + p->refCount -= 1; + + if (p->refCount == 0) { + LstNode ln; + + ln = Lst_Member (openDirectories, (ClientData)p); + (void) Lst_Remove (openDirectories, ln); + + Hash_DeleteTable (&p->files); + free((Address)p->name); + free((Address)p); + } +} + +/*- + *----------------------------------------------------------------------- + * Dir_ClearPath -- + * Clear out all elements of the given search path. This is different + * from destroying the list, notice. + * + * Results: + * None. + * + * Side Effects: + * The path is set to the empty list. + * + *----------------------------------------------------------------------- + */ +void +Dir_ClearPath(path) + Lst path; /* Path to clear */ +{ + Path *p; + while (!Lst_IsEmpty(path)) { + p = (Path *)Lst_DeQueue(path); + Dir_Destroy(p); + } +} + + +/*- + *----------------------------------------------------------------------- + * Dir_Concat -- + * Concatenate two paths, adding the second to the end of the first. + * Makes sure to avoid duplicates. + * + * Results: + * None + * + * Side Effects: + * Reference counts for added dirs are upped. + * + *----------------------------------------------------------------------- + */ +void +Dir_Concat(path1, path2) + Lst path1; /* Dest */ + Lst path2; /* Source */ +{ + LstNode ln; + Path *p; + + for (ln = Lst_First(path2); ln != NILLNODE; ln = Lst_Succ(ln)) { + p = (Path *)Lst_Datum(ln); + if (Lst_Member(path1, (ClientData)p) == NILLNODE) { + p->refCount += 1; + (void)Lst_AtEnd(path1, (ClientData)p); + } + } +} + +/********** DEBUG INFO **********/ +Dir_PrintDirectories() +{ + LstNode ln; + Path *p; + + printf ("#*** Directory Cache:\n"); + printf ("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", + hits, misses, nearmisses, bigmisses, + (hits+bigmisses+nearmisses ? + hits * 100 / (hits + bigmisses + nearmisses) : 0)); + printf ("# %-20s referenced\thits\n", "directory"); + if (Lst_Open (openDirectories) == SUCCESS) { + while ((ln = Lst_Next (openDirectories)) != NILLNODE) { + p = (Path *) Lst_Datum (ln); + printf ("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); + } + Lst_Close (openDirectories); + } +} + +static int DirPrintDir (p) Path *p; { printf ("%s ", p->name); return (0); } + +Dir_PrintPath (path) + Lst path; +{ + Lst_ForEach (path, DirPrintDir, (ClientData)0); +} diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c new file mode 100644 index 000000000000..dd7a98743111 --- /dev/null +++ b/usr.bin/make/hash.c @@ -0,0 +1,418 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)hash.c 5.5 (Berkeley) 12/28/90"; +#endif /* not lint */ + +/* hash.c -- + * + * This module contains routines to manipulate a hash table. + * See hash.h for a definition of the structure of the hash + * table. Hash tables grow automatically as the amount of + * information increases. + */ + +#include "sprite.h" +#include "hash.h" + +/* + * Forward references to local procedures that are used before they're + * defined: + */ + +static void RebuildTable(); + +/* + * The following defines the ratio of # entries to # buckets + * at which we rebuild the table to make it larger. + */ + +#define rebuildLimit 8 + +/* + *--------------------------------------------------------- + * + * Hash_InitTable -- + * + * This routine just sets up the hash table. + * + * Results: + * None. + * + * Side Effects: + * Memory is allocated for the initial bucket area. + * + *--------------------------------------------------------- + */ + +void +Hash_InitTable(t, numBuckets) + register Hash_Table *t; /* Structure to use to hold table. */ + int numBuckets; /* How many buckets to create for starters. + * This number is rounded up to a power of + * two. If <= 0, a reasonable default is + * chosen. The table will grow in size later + * as needed. */ +{ + register int i; + register struct Hash_Entry **hp; + + /* + * Round up the size to a power of two. + */ + if (numBuckets <= 0) + i = 16; + else { + for (i = 2; i < numBuckets; i <<= 1) + /* void */ ; + } + t->numEntries = 0; + t->size = i; + t->mask = i - 1; + t->bucketPtr = hp = (struct Hash_Entry **)emalloc(sizeof(*hp) * i); + while (--i >= 0) + *hp++ = NULL; +} + +/* + *--------------------------------------------------------- + * + * Hash_DeleteTable -- + * + * This routine removes everything from a hash table + * and frees up the memory space it occupied (except for + * the space in the Hash_Table structure). + * + * Results: + * None. + * + * Side Effects: + * Lots of memory is freed up. + * + *--------------------------------------------------------- + */ + +void +Hash_DeleteTable(t) + Hash_Table *t; +{ + register struct Hash_Entry **hp, *h, *nexth; + register int i; + + for (hp = t->bucketPtr, i = t->size; --i >= 0;) { + for (h = *hp++; h != NULL; h = nexth) { + nexth = h->next; + free((char *)h); + } + } + free((char *)t->bucketPtr); + + /* + * Set up the hash table to cause memory faults on any future access + * attempts until re-initialization. + */ + t->bucketPtr = NULL; +} + +/* + *--------------------------------------------------------- + * + * Hash_FindEntry -- + * + * Searches a hash table for an entry corresponding to key. + * + * Results: + * The return value is a pointer to the entry for key, + * if key was present in the table. If key was not + * present, NULL is returned. + * + * Side Effects: + * None. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_FindEntry(t, key) + Hash_Table *t; /* Hash table to search. */ + char *key; /* A hash key. */ +{ + register Hash_Entry *e; + register unsigned h; + register char *p; + + for (h = 0, p = key; *p;) + h = (h << 5) - h + *p++; + p = key; + for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) + if (e->namehash == h && strcmp(e->name, p) == 0) + return (e); + return (NULL); +} + +/* + *--------------------------------------------------------- + * + * Hash_CreateEntry -- + * + * Searches a hash table for an entry corresponding to + * key. If no entry is found, then one is created. + * + * Results: + * The return value is a pointer to the entry. If *newPtr + * isn't NULL, then *newPtr is filled in with TRUE if a + * new entry was created, and FALSE if an entry already existed + * with the given key. + * + * Side Effects: + * Memory may be allocated, and the hash buckets may be modified. + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_CreateEntry(t, key, newPtr) + register Hash_Table *t; /* Hash table to search. */ + char *key; /* A hash key. */ + Boolean *newPtr; /* Filled in with TRUE if new entry created, + * FALSE otherwise. */ +{ + register Hash_Entry *e; + register unsigned h; + register char *p; + int keylen; + struct Hash_Entry **hp; + + /* + * Hash the key. As a side effect, save the length (strlen) of the + * key in case we need to create the entry. + */ + for (h = 0, p = key; *p;) + h = (h << 5) - h + *p++; + keylen = p - key; + p = key; + for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) { + if (e->namehash == h && strcmp(e->name, p) == 0) { + if (newPtr != NULL) + *newPtr = FALSE; + return (e); + } + } + + /* + * The desired entry isn't there. Before allocating a new entry, + * expand the table if necessary (and this changes the resulting + * bucket chain). + */ + if (t->numEntries >= rebuildLimit * t->size) + RebuildTable(t); + e = (Hash_Entry *) emalloc(sizeof(*e) + keylen); + hp = &t->bucketPtr[h & t->mask]; + e->next = *hp; + *hp = e; + e->clientData = NULL; + e->namehash = h; + (void) strcpy(e->name, p); + t->numEntries++; + + if (newPtr != NULL) + *newPtr = TRUE; + return (e); +} + +/* + *--------------------------------------------------------- + * + * Hash_DeleteEntry -- + * + * Delete the given hash table entry and free memory associated with + * it. + * + * Results: + * None. + * + * Side Effects: + * Hash chain that entry lives in is modified and memory is freed. + * + *--------------------------------------------------------- + */ + +void +Hash_DeleteEntry(t, e) + Hash_Table *t; + Hash_Entry *e; +{ + register Hash_Entry **hp, *p; + + if (e == NULL) + return; + for (hp = &t->bucketPtr[e->namehash & t->mask]; + (p = *hp) != NULL; hp = &p->next) { + if (p == e) { + *hp = p->next; + free((char *)p); + t->numEntries--; + return; + } + } + (void) write(2, "bad call to Hash_DeleteEntry\n", 29); + abort(); +} + +/* + *--------------------------------------------------------- + * + * Hash_EnumFirst -- + * This procedure sets things up for a complete search + * of all entries recorded in the hash table. + * + * Results: + * The return value is the address of the first entry in + * the hash table, or NULL if the table is empty. + * + * Side Effects: + * The information in searchPtr is initialized so that successive + * calls to Hash_Next will return successive HashEntry's + * from the table. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_EnumFirst(t, searchPtr) + Hash_Table *t; /* Table to be searched. */ + register Hash_Search *searchPtr;/* Area in which to keep state + * about search.*/ +{ + searchPtr->tablePtr = t; + searchPtr->nextIndex = 0; + searchPtr->hashEntryPtr = NULL; + return Hash_EnumNext(searchPtr); +} + +/* + *--------------------------------------------------------- + * + * Hash_EnumNext -- + * This procedure returns successive entries in the hash table. + * + * Results: + * The return value is a pointer to the next HashEntry + * in the table, or NULL when the end of the table is + * reached. + * + * Side Effects: + * The information in searchPtr is modified to advance to the + * next entry. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_EnumNext(searchPtr) + register Hash_Search *searchPtr; /* Area used to keep state about + search. */ +{ + register Hash_Entry *e; + Hash_Table *t = searchPtr->tablePtr; + + /* + * The hashEntryPtr field points to the most recently returned + * entry, or is nil if we are starting up. If not nil, we have + * to start at the next one in the chain. + */ + e = searchPtr->hashEntryPtr; + if (e != NULL) + e = e->next; + /* + * If the chain ran out, or if we are starting up, we need to + * find the next nonempty chain. + */ + while (e == NULL) { + if (searchPtr->nextIndex >= t->size) + return (NULL); + e = t->bucketPtr[searchPtr->nextIndex++]; + } + searchPtr->hashEntryPtr = e; + return (e); +} + +/* + *--------------------------------------------------------- + * + * RebuildTable -- + * This local routine makes a new hash table that + * is larger than the old one. + * + * Results: + * None. + * + * Side Effects: + * The entire hash table is moved, so any bucket numbers + * from the old table are invalid. + * + *--------------------------------------------------------- + */ + +static void +RebuildTable(t) + register Hash_Table *t; +{ + register Hash_Entry *e, *next, **hp, **xp; + register int i, mask; + register Hash_Entry **oldhp; + int oldsize; + + oldhp = t->bucketPtr; + oldsize = i = t->size; + i <<= 1; + t->size = i; + t->mask = mask = i - 1; + t->bucketPtr = hp = (struct Hash_Entry **) emalloc(sizeof(*hp) * i); + while (--i >= 0) + *hp++ = NULL; + for (hp = oldhp, i = oldsize; --i >= 0;) { + for (e = *hp++; e != NULL; e = next) { + next = e->next; + xp = &t->bucketPtr[e->namehash & mask]; + e->next = *xp; + *xp = e; + } + } + free((char *)oldhp); +} diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h new file mode 100644 index 000000000000..419b60df4748 --- /dev/null +++ b/usr.bin/make/hash.h @@ -0,0 +1,124 @@ +/* + * 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. + * + * @(#)hash.h 5.4 (Berkeley) 12/28/90 + */ + +/* hash.h -- + * + * This file contains definitions used by the hash module, + * which maintains hash tables. + */ + +#ifndef _HASH +#define _HASH + +/* + * The following defines one entry in the hash table. + */ + +typedef struct Hash_Entry { + struct Hash_Entry *next; /* Used to link together all the + * entries associated with the same + * bucket. */ + ClientData clientData; /* Arbitrary piece of data associated + * with key. */ + unsigned namehash; /* hash value of key */ + char name[1]; /* key string */ +} Hash_Entry; + +typedef struct Hash_Table { + struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one + * for each bucket in the table. */ + int size; /* Actual size of array. */ + int numEntries; /* Number of entries in the table. */ + int mask; /* Used to select bits for hashing. */ +} Hash_Table; + +/* + * The following structure is used by the searching routines + * to record where we are in the search. + */ + +typedef struct Hash_Search { + Hash_Table *tablePtr; /* Table being searched. */ + int nextIndex; /* Next bucket to check (after current). */ + Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */ +} Hash_Search; + +/* + * Macros. + */ + +/* + * ClientData Hash_GetValue(h) + * Hash_Entry *h; + */ + +#define Hash_GetValue(h) ((h)->clientData) + +/* + * Hash_SetValue(h, val); + * Hash_Entry *h; + * char *val; + */ + +#define Hash_SetValue(h, val) ((h)->clientData = (ClientData) (val)) + +/* + * Hash_Size(n) returns the number of words in an object of n bytes + */ + +#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. + */ + +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 diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c new file mode 100644 index 000000000000..82b5f3f88451 --- /dev/null +++ b/usr.bin/make/job.c @@ -0,0 +1,2651 @@ +/* + * 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. + * + * PATCHES MAGIC LEVEL PATCH THAT GOT US HERE + * -------------------- ----- ---------------------- + * CURRENT PATCH LEVEL: 1 00039 + * -------------------- ----- ---------------------- + * + * 10 Aug 92 David Dawes Fixed "remove directory" bug + */ + +#ifndef lint +static char sccsid[] = "@(#)job.c 5.15 (Berkeley) 3/1/91"; +#endif /* not lint */ + +/*- + * job.c -- + * handle the creation etc. of our child processes. + * + * Interface: + * Job_Make Start the creation of the given target. + * + * Job_CatchChildren Check for and handle the termination of any + * children. This must be called reasonably + * frequently to keep the whole make going at + * a decent clip, since job table entries aren't + * removed until their process is caught this way. + * Its single argument is TRUE if the function + * should block waiting for a child to terminate. + * + * Job_CatchOutput Print any output our children have produced. + * Should also be called fairly frequently to + * keep the user informed of what's going on. + * If no output is waiting, it will block for + * a time given by the SEL_* constants, below, + * or until output is ready. + * + * Job_Init Called to intialize this module. in addition, + * any commands attached to the .BEGIN target + * are executed before this function returns. + * Hence, the makefile must have been parsed + * before this function is called. + * + * Job_Full Return TRUE if the job table is filled. + * + * Job_Empty Return TRUE if the job table is completely + * empty. + * + * Job_ParseShell Given the line following a .SHELL target, parse + * the line as a shell specification. Returns + * FAILURE if the spec was incorrect. + * + * Job_End Perform any final processing which needs doing. + * This includes the execution of any commands + * which have been/were attached to the .END + * target. It should only be called when the + * job table is empty. + * + * Job_AbortAll Abort all currently running jobs. It doesn't + * handle output or do anything for the jobs, + * just kills them. It should only be called in + * an emergency, as it were. + * + * Job_CheckCommands Verify that the commands for a target are + * ok. Provide them if necessary and possible. + * + * Job_Touch Update a target without really updating it. + * + * Job_Wait Wait for all currently-running jobs to finish. + */ + +#include "make.h" +#include <sys/signal.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include "job.h" +#include "pathnames.h" + +extern int errno; + +/* + * error handling variables + */ +int errors = 0; /* number of errors reported */ +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 */ + + +/* + * post-make command processing. The node postCommands is really just the + * .END target but we keep it around to avoid having to search for it + * all the time. + */ +static GNode *postCommands; /* node containing commands to execute when + * everything else is done */ +static int numCommands; /* The number of commands actually printed + * for a target. Should this number be + * 0, no shell will be executed. */ + + +/* + * Return values from JobStart. + */ +#define JOB_RUNNING 0 /* Job is running */ +#define JOB_ERROR 1 /* Error in starting the job */ +#define JOB_FINISHED 2 /* The job is already finished */ +#define JOB_STOPPED 3 /* The job is stopped */ + +/* + * tfile is the name of a file into which all shell commands are put. It is + * used over by removing it before the child shell is executed. The XXXXX in + * the string are replaced by the pid of the make process in a 5-character + * field with leading zeroes. + */ +static char tfile[] = TMPPAT; + + +/* + * Descriptions for various shells. + */ +static Shell shells[] = { + /* + * CSH description. The csh can do echo control by playing + * with the setting of the 'echo' shell variable. Sadly, + * however, it is unable to do error control nicely. + */ +{ + "csh", + TRUE, "unset verbose", "set verbose", "unset verbose", 10, + FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"", + "v", "e", +}, + /* + * SH description. Echo control is also possible and, under + * sun UNIX anyway, one can even control error checking. + */ +{ + "sh", + TRUE, "set -", "set -v", "set -", 5, + FALSE, "echo \"%s\"\n", "sh -c '%s || exit 0'\n", + "v", "e", +}, + /* + * UNKNOWN. + */ +{ + (char *)0, + FALSE, (char *)0, (char *)0, (char *)0, 0, + FALSE, (char *)0, (char *)0, + (char *)0, (char *)0, +} +}; +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 + * 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 + * 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 + * nLocal equals maxLocal */ +#ifndef RMT_WILL_WATCH +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 + * produced. */ +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 */ + +/* + * When JobStart attempts to run a job remotely but can't, and isn't allowed + * to run the job locally, or when Job_CatchChildren detects a job that has + * 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 + * jobs that were stopped due to concurrency + * limits or migration home */ + + +# if defined(USE_PGRP) +#define KILL(pid,sig) killpg((pid),(sig)) +# else +#define KILL(pid,sig) kill((pid),(sig)) +# endif + +static void JobRestart(); +static int JobStart(); +static void JobInterrupt(); + +/*- + *----------------------------------------------------------------------- + * JobCondPassSig -- + * Pass a signal to a job if the job is remote or if USE_PGRP + * is defined. + * + * Results: + * === 0 + * + * Side Effects: + * None, except the job may bite it. + * + *----------------------------------------------------------------------- + */ +static int +JobCondPassSig(job, signo) + Job *job; /* Job to biff */ + int signo; /* Signal to send it */ +{ +#ifdef RMT_WANTS_SIGNALS + if (job->flags & JOB_REMOTE) { + (void)Rmt_Signal(job, signo); + } else { + KILL(job->pid, signo); + } +#else + /* + * Assume that sending the signal to job->pid will signal any remote + * job as well. + */ + KILL(job->pid, signo); +#endif + return(0); +} + +/*- + *----------------------------------------------------------------------- + * JobPassSig -- + * Pass a signal on to all remote jobs and to all local jobs if + * USE_PGRP is defined, then die ourselves. + * + * Results: + * None. + * + * Side Effects: + * We die by the same signal. + * + *----------------------------------------------------------------------- + */ +static void +JobPassSig(signo) + int signo; /* The signal number we've received */ +{ + int mask; + + Lst_ForEach(jobs, JobCondPassSig, (ClientData)signo); + + /* + * Deal with proper cleanup based on the signal received. We only run + * the .INTERRUPT target if the signal was in fact an interrupt. The other + * three termination signals are more of a "get out *now*" command. + */ + if (signo == SIGINT) { + JobInterrupt(TRUE); + } else if ((signo == SIGHUP) || (signo == SIGTERM) || (signo == SIGQUIT)) { + JobInterrupt(FALSE); + } + + /* + * Leave gracefully if SIGQUIT, rather than core dumping. + */ + if (signo == SIGQUIT) { + Finish(); + } + + /* + * Send ourselves the signal now we've given the message to everyone else. + * Note we block everything else possible while we're getting the signal. + * This ensures that all our jobs get continued when we wake up before + * we take any other signal. + */ + mask = sigblock(0); + (void) sigsetmask(~0 & ~(1 << (signo-1))); + signal(signo, SIG_DFL); + + kill(getpid(), signo); + + Lst_ForEach(jobs, JobCondPassSig, (ClientData)SIGCONT); + + sigsetmask(mask); + signal(signo, JobPassSig); + +} + +/*- + *----------------------------------------------------------------------- + * JobCmpPid -- + * Compare the pid of the job with the given pid and return 0 if they + * are equal. This function is called from Job_CatchChildren via + * Lst_Find to find the job descriptor of the finished job. + * + * Results: + * 0 if the pid's match + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +JobCmpPid (job, pid) + int pid; /* process id desired */ + Job *job; /* job to examine */ +{ + return (pid - job->pid); +} + +/*- + *----------------------------------------------------------------------- + * JobPrintCommand -- + * Put out another command for the given job. If the command starts + * with an @ or a - we process it specially. In the former case, + * so long as the -s and -n flags weren't given to make, we stick + * a shell-specific echoOff command in the script. In the latter, + * we ignore errors for the entire job, unless the shell has error + * control. + * If the command is just "..." we take all future commands for this + * job to be commands to be executed once the entire graph has been + * made and return non-zero to signal that the end of the commands + * was reached. These commands are later attached to the postCommands + * node and executed by Job_End when all things are done. + * This function is called from JobStart via Lst_ForEach. + * + * Results: + * Always 0, unless the command was "..." + * + * Side Effects: + * If the command begins with a '-' and the shell has no error control, + * the JOB_IGNERR flag is set in the job descriptor. + * If the command is "..." and we're not ignoring such things, + * tailCmds is set to the successor node of the cmd. + * numCommands is incremented if the command is actually printed. + *----------------------------------------------------------------------- + */ +static int +JobPrintCommand (cmd, job) + char *cmd; /* command string to print */ + Job *job; /* job for which to print it */ +{ + Boolean noSpecials; /* true if we shouldn't worry about + * inserting special commands into + * the input stream. */ + Boolean shutUp = FALSE; /* true if we put a no echo command + * into the command file */ + Boolean errOff = FALSE; /* true if we turned error checking + * off before printing the command + * and need to turn it back on */ + char *cmdTemplate; /* Template to use when printing the + * command */ + char *cmdStart; /* Start of expanded command */ + LstNode cmdNode; /* Node for replacing the command */ + + noSpecials = (noExecute && ! (job->node->type & OP_MAKE)); + + if (strcmp (cmd, "...") == 0) { + if ((job->flags & JOB_IGNDOTS) == 0) { + job->tailCmds = Lst_Succ (Lst_Member (job->node->commands, + (ClientData)cmd)); + return (1); + } + return (0); + } + +#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) printf (fmt, arg); fprintf (job->cmdFILE, fmt, arg) + + numCommands += 1; + + /* + * For debugging, we replace each command with the result of expanding + * the variables in the command. + */ + cmdNode = Lst_Member (job->node->commands, (ClientData)cmd); + cmdStart = cmd = Var_Subst (cmd, job->node, FALSE); + Lst_Replace (cmdNode, (ClientData)cmdStart); + + cmdTemplate = "%s\n"; + + /* + * Check for leading @' and -'s to control echoing and error checking. + */ + while (*cmd == '@' || *cmd == '-') { + if (*cmd == '@') { + shutUp = TRUE; + } else { + errOff = TRUE; + } + cmd++; + } + + while (isspace(*cmd)) cmd++; + + if (shutUp) { + if (! (job->flags & JOB_SILENT) && !noSpecials && + commandShell->hasEchoCtl) { + DBPRINTF ("%s\n", commandShell->echoOff); + } else { + shutUp = FALSE; + } + } + + if (errOff) { + if ( ! (job->flags & JOB_IGNERR) && !noSpecials) { + if (commandShell->hasErrCtl) { + /* + * we don't want the error-control commands showing + * up either, so we turn off echoing while executing + * them. We could put another field in the shell + * structure to tell JobDoOutput to look for this + * string too, but why make it any more complex than + * it already is? + */ + if (! (job->flags & JOB_SILENT) && !shutUp && + commandShell->hasEchoCtl) { + DBPRINTF ("%s\n", commandShell->echoOff); + DBPRINTF ("%s\n", commandShell->ignErr); + DBPRINTF ("%s\n", commandShell->echoOn); + } else { + DBPRINTF ("%s\n", commandShell->ignErr); + } + } else if (commandShell->ignErr && + (*commandShell->ignErr != '\0')) + { + /* + * The shell has no error control, so we need to be + * weird to get it to ignore any errors from the command. + * If echoing is turned on, we turn it off and use the + * errCheck template to echo the command. Leave echoing + * off so the user doesn't see the weirdness we go through + * to ignore errors. Set cmdTemplate to use the weirdness + * instead of the simple "%s\n" template. + */ + if (! (job->flags & JOB_SILENT) && !shutUp && + commandShell->hasEchoCtl) { + DBPRINTF ("%s\n", commandShell->echoOff); + DBPRINTF (commandShell->errCheck, cmd); + shutUp = TRUE; + } + cmdTemplate = commandShell->ignErr; + /* + * The error ignoration (hee hee) is already taken care + * of by the ignErr template, so pretend error checking + * is still on. + */ + errOff = FALSE; + } else { + errOff = FALSE; + } + } else { + errOff = FALSE; + } + } + + DBPRINTF (cmdTemplate, cmd); + + if (errOff) { + /* + * If echoing is already off, there's no point in issuing the + * echoOff command. Otherwise we issue it and pretend it was on + * for the whole command... + */ + if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){ + DBPRINTF ("%s\n", commandShell->echoOff); + shutUp = TRUE; + } + DBPRINTF ("%s\n", commandShell->errCheck); + } + if (shutUp) { + DBPRINTF ("%s\n", commandShell->echoOn); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * JobSaveCommand -- + * Save a command to be executed when everything else is done. + * Callback function for JobFinish... + * + * Results: + * Always returns 0 + * + * Side Effects: + * The command is tacked onto the end of postCommands's commands list. + * + *----------------------------------------------------------------------- + */ +static int +JobSaveCommand (cmd, gn) + char *cmd; + GNode *gn; +{ + cmd = Var_Subst (cmd, gn, FALSE); + (void)Lst_AtEnd (postCommands->commands, (ClientData)cmd); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * JobFinish -- + * Do final processing for the given job including updating + * parents and starting new jobs as available/necessary. Note + * that we pay no attention to the JOB_IGNERR flag here. + * This is because when we're called because of a noexecute flag + * or something, jstat.w_status is 0 and when called from + * Job_CatchChildren, the status is zeroed if it s/b ignored. + * + * Results: + * None + * + * Side Effects: + * Some nodes may be put on the toBeMade queue. + * Final commands for the job are placed on postCommands. + * + * If we got an error and are aborting (aborting == ABORT_ERROR) and + * the job list is now empty, we are done for the day. + * If we recognized an error (errors !=0), we set the aborting flag + * to ABORT_ERROR so no more jobs will be started. + *----------------------------------------------------------------------- + */ +/*ARGSUSED*/ +void +JobFinish (job, status) + Job *job; /* job to finish */ + union wait status; /* sub-why job went away */ +{ + Boolean done; + + if ((WIFEXITED(status) && + (((status.w_retcode != 0) && !(job->flags & JOB_IGNERR)))) || + (WIFSIGNALED(status) && (status.w_termsig != SIGCONT))) + { + /* + * If it exited non-zero and either we're doing things our + * way or we're not ignoring errors, the job is finished. + * Similarly, if the shell died because of a signal + * the job is also finished. In these + * cases, finish out the job's output before printing the exit + * status... + */ + if (usePipes) { +#ifdef RMT_WILL_WATCH + Rmt_Ignore(job->inPipe); +#else + FD_CLR(job->inPipe, &outputs); +#endif /* RMT_WILL_WATCH */ + if (job->outPipe != job->inPipe) { + (void)close (job->outPipe); + } + JobDoOutput (job, TRUE); + (void)close (job->inPipe); + } else { + (void)close (job->outFd); + JobDoOutput (job, TRUE); + } + + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + fclose(job->cmdFILE); + } + done = TRUE; + } else if (WIFEXITED(status) && status.w_retcode != 0) { + /* + * Deal with ignored errors in -B mode. We need to print a message + * telling of the ignored error as well as setting status.w_status + * to 0 so the next command gets run. To do this, we set done to be + * TRUE if in -B mode and the job exited non-zero. Note we don't + * want to close down any of the streams until we know we're at the + * end. + */ + done = TRUE; + } else { + /* + * No need to close things down or anything. + */ + done = FALSE; + } + + if (done || + WIFSTOPPED(status) || + (WIFSIGNALED(status) && (status.w_termsig == SIGCONT)) || + DEBUG(JOB)) + { + FILE *out; + + if (!usePipes && (job->flags & JOB_IGNERR)) { + /* + * If output is going to a file and this job is ignoring + * errors, arrange to have the exit status sent to the + * output file as well. + */ + out = fdopen (job->outFd, "w"); + } else { + out = stdout; + } + + if (WIFEXITED(status)) { + if (status.w_retcode != 0) { + if (usePipes && job->node != lastNode) { + fprintf (out, targFmt, job->node->name); + lastNode = job->node; + } + fprintf (out, "*** Error code %d%s\n", status.w_retcode, + (job->flags & JOB_IGNERR) ? " (ignored)" : ""); + + if (job->flags & JOB_IGNERR) { + status.w_status = 0; + } + } else if (DEBUG(JOB)) { + if (usePipes && job->node != lastNode) { + fprintf (out, targFmt, job->node->name); + lastNode = job->node; + } + fprintf (out, "*** Completed successfully\n"); + } + } else if (WIFSTOPPED(status)) { + if (usePipes && job->node != lastNode) { + fprintf (out, targFmt, job->node->name); + lastNode = job->node; + } + if (! (job->flags & JOB_REMIGRATE)) { + fprintf (out, "*** Stopped -- signal %d\n", status.w_stopsig); + } + job->flags |= JOB_RESUME; + (void)Lst_AtEnd(stoppedJobs, (ClientData)job); + fflush(out); + return; + } else if (status.w_termsig == SIGCONT) { + /* + * If the beastie has continued, shift the Job from the stopped + * list to the running one (or re-stop it if concurrency is + * exceeded) and go and get another child. + */ + if (job->flags & (JOB_RESUME|JOB_REMIGRATE|JOB_RESTART)) { + if (usePipes && job->node != lastNode) { + fprintf (out, targFmt, job->node->name); + lastNode = job->node; + } + fprintf (out, "*** Continued\n"); + } + if (! (job->flags & JOB_CONTINUING)) { + JobRestart(job); + } else { + Lst_AtEnd(jobs, (ClientData)job); + nJobs += 1; + if (! (job->flags & JOB_REMOTE)) { + nLocal += 1; + } + if (nJobs == maxJobs) { + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Job queue is full.\n"); + } + } + } + fflush(out); + return; + } else { + if (usePipes && job->node != lastNode) { + fprintf (out, targFmt, job->node->name); + lastNode = job->node; + } + fprintf (out, "*** Signal %d\n", status.w_termsig); + } + + fflush (out); + } + + /* + * Now handle the -B-mode stuff. If the beast still isn't finished, + * try and restart the job on the next command. If JobStart says it's + * ok, it's ok. If there's an error, this puppy is done. + */ + if ((status.w_status == 0) && + !Lst_IsAtEnd (job->node->commands)) + { + switch (JobStart (job->node, + job->flags & JOB_IGNDOTS, + job)) + { + case JOB_RUNNING: + done = FALSE; + break; + case JOB_ERROR: + done = TRUE; + status.w_retcode = 1; + break; + case JOB_FINISHED: + /* + * If we got back a JOB_FINISHED code, JobStart has already + * called Make_Update and freed the job descriptor. We set + * done to false here to avoid fake cycles and double frees. + * JobStart needs to do the update so we can proceed up the + * graph when given the -n flag.. + */ + done = FALSE; + break; + } + } else { + done = TRUE; + } + + + if (done && + (aborting != ABORT_ERROR) && + (aborting != ABORT_INTERRUPT) && + (status.w_status == 0)) + { + /* + * As long as we aren't aborting and the job didn't return a non-zero + * status that we shouldn't ignore, we call Make_Update to update + * the parents. In addition, any saved commands for the node are placed + * on the .END target. + */ + if (job->tailCmds != NILLNODE) { + Lst_ForEachFrom (job->node->commands, job->tailCmds, + JobSaveCommand, + (ClientData)job->node); + } + job->node->made = MADE; + Make_Update (job->node); + free((Address)job); + } else if (status.w_status) { + errors += 1; + free((Address)job); + } + + while (!errors && !jobFull && !Lst_IsEmpty(stoppedJobs)) { + JobRestart((Job *)Lst_DeQueue(stoppedJobs)); + } + + /* + * Set aborting if any error. + */ + if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { + /* + * If we found any errors in this batch of children and the -k flag + * wasn't given, we set the aborting flag so no more jobs get + * started. + */ + aborting = ABORT_ERROR; + } + + if ((aborting == ABORT_ERROR) && Job_Empty()) { + /* + * If we are aborting and the job table is now empty, we finish. + */ + (void) unlink (tfile); + Finish (errors); + } +} + +/*- + *----------------------------------------------------------------------- + * Job_Touch -- + * Touch the given target. Called by JobStart when the -t flag was + * given + * + * Results: + * None + * + * Side Effects: + * The data modification of the file is changed. In addition, if the + * file did not exist, it is created. + *----------------------------------------------------------------------- + */ +void +Job_Touch (gn, silent) + GNode *gn; /* the node of the file to touch */ + Boolean silent; /* TRUE if should not print messages */ +{ + 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)) { + /* + * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets + * and, as such, shouldn't really be created. + */ + return; + } + + if (!silent) { + printf ("touch %s\n", gn->name); + } + + if (noExecute) { + return; + } + + if (gn->type & OP_ARCHV) { + Arch_Touch (gn); + } else if (gn->type & OP_LIB) { + Arch_TouchLib (gn); + } else { + char *file = gn->path ? gn->path : gn->name; + + times[0].tv_sec = times[1].tv_sec = now; + times[0].tv_usec = times[1].tv_usec = 0; + if (utimes(file, times) < 0){ + streamID = open (file, O_RDWR | O_CREAT, 0666); + + if (streamID >= 0) { + char c; + + /* + * Read and write a byte to the file to change the + * modification time, then close the file. + */ + if (read(streamID, &c, 1) == 1) { + lseek(streamID, 0L, L_SET); + write(streamID, &c, 1); + } + + (void)close (streamID); + } else + printf("*** couldn't touch %s: %s", file, strerror(errno)); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Job_CheckCommands -- + * Make sure the given node has all the commands it needs. + * + * Results: + * TRUE if the commands list is/was ok. + * + * Side Effects: + * The node will have commands from the .DEFAULT rule added to it + * if it needs them. + *----------------------------------------------------------------------- + */ +Boolean +Job_CheckCommands (gn, abortProc) + GNode *gn; /* The target whose commands need + * verifying */ + void (*abortProc)(); /* Function to abort with message */ +{ + if (OP_NOP(gn->type) && Lst_IsEmpty (gn->commands) && + (gn->type & OP_LIB) == 0) { + /* + * No commands. Look for .DEFAULT rule from which we might infer + * commands + */ + if ((DEFAULT != NILGNODE) && !Lst_IsEmpty(DEFAULT->commands)) { + /* + * Make only looks for a .DEFAULT if the node was never the + * target of an operator, so that's what we do too. If + * a .DEFAULT was given, we substitute its commands for gn's + * commands and set the IMPSRC variable to be the target's name + * The DEFAULT node acts like a transformation rule, in that + * gn also inherits any attributes or sources attached to + * .DEFAULT itself. + */ + Make_HandleUse(DEFAULT, gn); + Var_Set (IMPSRC, Var_Value (TARGET, gn), gn); + } else if (Dir_MTime (gn) == 0) { + /* + * The node wasn't the target of an operator we have no .DEFAULT + * rule to go on and the target doesn't already exist. There's + * nothing more we can do for this branch. If the -k flag wasn't + * given, we stop in our tracks, otherwise we just don't update + * this node's parents so they never get examined. + */ + if (gn->type & OP_OPTIONAL) { + printf ("make: don't know how to make %s (ignored)\n", + gn->name); + } else if (keepgoing) { + printf ("make: don't know how to make %s (continuing)\n", + gn->name); + return (FALSE); + } else { + (*abortProc) ("make: don't know how to make %s. Stop", + gn->name); + return(FALSE); + } + } + } + return (TRUE); +} +#ifdef RMT_WILL_WATCH +/*- + *----------------------------------------------------------------------- + * JobLocalInput -- + * Handle a pipe becoming readable. Callback function for Rmt_Watch + * + * Results: + * None + * + * Side Effects: + * JobDoOutput is called. + * + *----------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +JobLocalInput(stream, job) + int stream; /* Stream that's ready (ignored) */ + Job *job; /* Job to which the stream belongs */ +{ + JobDoOutput(job, FALSE); +} +#endif /* RMT_WILL_WATCH */ + +/*- + *----------------------------------------------------------------------- + * JobExec -- + * Execute the shell for the given job. Called from JobStart and + * JobRestart. + * + * Results: + * None. + * + * Side Effects: + * A shell is executed, outputs is altered and the Job structure added + * to the job table. + * + *----------------------------------------------------------------------- + */ +static void +JobExec(job, argv) + Job *job; /* Job to execute */ + char **argv; +{ + int cpid; /* ID of new child */ + + if (DEBUG(JOB)) { + int i; + + printf("Running %s %sly\n", job->node->name, + job->flags&JOB_REMOTE?"remote":"local"); + printf("\tCommand: "); + for (i = 0; argv[i] != (char *)NULL; i++) { + printf("%s ", argv[i]); + } + printf("\n"); + } + + /* + * Some jobs produce no output and it's disconcerting to have + * no feedback of their running (since they produce no output, the + * banner with their name in it never appears). This is an attempt to + * provide that feedback, even if nothing follows it. + */ + if ((lastNode != job->node) && (job->flags & JOB_FIRST) && + !(job->flags & JOB_SILENT)) + { + printf(targFmt, job->node->name); + lastNode = job->node; + } + +#ifdef RMT_NO_EXEC + if (job->flags & JOB_REMOTE) { + goto jobExecFinish; + } +#endif /* RMT_NO_EXEC */ + + if ((cpid = vfork()) == -1) { + Punt ("Cannot fork"); + } else if (cpid == 0) { + + /* + * Must duplicate the input stream down to the child's input and + * reset it to the beginning (again). Since the stream was marked + * close-on-exec, we must clear that bit in the new input. + */ + (void) dup2(fileno(job->cmdFILE), 0); + fcntl(0, F_SETFD, 0); + lseek(0, 0, L_SET); + + if (usePipes) { + /* + * Set up the child's output to be routed through the pipe + * we've created for it. + */ + (void) dup2 (job->outPipe, 1); + } else { + /* + * We're capturing output in a file, so we duplicate the + * descriptor to the temporary file into the standard + * output. + */ + (void) dup2 (job->outFd, 1); + } + /* + * The output channels are marked close on exec. This bit was + * duplicated by the dup2 (on some systems), so we have to clear + * it before routing the shell's error output to the same place as + * its standard output. + */ + fcntl(1, F_SETFD, 0); + (void) dup2 (1, 2); + +#ifdef USE_PGRP + /* + * We want to switch the child into a different process family so + * we can kill it and all its descendants in one fell swoop, + * by killing its process family, but not commit suicide. + */ + + (void) setpgrp(0, getpid()); +#endif USE_PGRP + + (void) execv (shellPath, argv); + (void) write (2, "Could not execute shell\n", + sizeof ("Could not execute shell")); + _exit (1); + } else { + job->pid = cpid; + + if (usePipes && (job->flags & JOB_FIRST) ) { + /* + * The first time a job is run for a node, we set the current + * position in the buffer to the beginning and mark another + * stream to watch in the outputs mask + */ + job->curPos = 0; + +#ifdef RMT_WILL_WATCH + Rmt_Watch(job->inPipe, JobLocalInput, job); +#else + FD_SET(job->inPipe, &outputs); +#endif /* RMT_WILL_WATCH */ + } + + if (job->flags & JOB_REMOTE) { + job->rmtID = (char *)0; + } else { + nLocal += 1; + /* + * XXX: Used to not happen if CUSTOMS. Why? + */ + if (job->cmdFILE != stdout) { + fclose(job->cmdFILE); + job->cmdFILE = NULL; + } + } + } + +jobExecFinish: + /* + * Now the job is actually running, add it to the table. + */ + nJobs += 1; + (void)Lst_AtEnd (jobs, (ClientData)job); + if (nJobs == maxJobs) { + jobFull = TRUE; + } +} + +/*- + *----------------------------------------------------------------------- + * JobMakeArgv -- + * Create the argv needed to execute the shell for a given job. + * + * + * Results: + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +static void +JobMakeArgv(job, argv) + Job *job; + char **argv; +{ + int argc; + static char args[10]; /* For merged arguments */ + + argv[0] = shellName; + argc = 1; + + if ((commandShell->exit && (*commandShell->exit != '-')) || + (commandShell->echo && (*commandShell->echo != '-'))) + { + /* + * At least one of the flags doesn't have a minus before it, so + * merge them together. Have to do this because the *(&(@*#*&#$# + * Bourne shell thinks its second argument is a file to source. + * Grrrr. Note the ten-character limitation on the combined arguments. + */ + (void)sprintf(args, "-%s%s", + ((job->flags & JOB_IGNERR) ? "" : + (commandShell->exit ? commandShell->exit : "")), + ((job->flags & JOB_SILENT) ? "" : + (commandShell->echo ? commandShell->echo : ""))); + + if (args[1]) { + argv[argc] = args; + argc++; + } + } else { + if (!(job->flags & JOB_IGNERR) && commandShell->exit) { + argv[argc] = commandShell->exit; + argc++; + } + if (!(job->flags & JOB_SILENT) && commandShell->echo) { + argv[argc] = commandShell->echo; + argc++; + } + } + argv[argc] = (char *)NULL; +} + +/*- + *----------------------------------------------------------------------- + * JobRestart -- + * Restart a job that stopped for some reason. + * + * Results: + * None. + * + * Side Effects: + * jobFull will be set if the job couldn't be run. + * + *----------------------------------------------------------------------- + */ +static void +JobRestart(job) + Job *job; /* Job to restart */ +{ + if (job->flags & JOB_REMIGRATE) { + if (DEBUG(JOB)) { + printf("Remigrating %x\n", job->pid); + } + if (nLocal != maxLocal) { + /* + * Job cannot be remigrated, but there's room on the local + * machine, so resume the job and note that another + * local job has started. + */ + if (DEBUG(JOB)) { + printf("resuming on local machine\n"); + } + KILL(job->pid, SIGCONT); + nLocal +=1; + job->flags &= ~(JOB_REMIGRATE|JOB_RESUME); + } else { + /* + * Job cannot be restarted. Mark the table as full and + * place the job back on the list of stopped jobs. + */ + if (DEBUG(JOB)) { + printf("holding\n"); + } + (void)Lst_AtFront(stoppedJobs, (ClientData)job); + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Job queue is full.\n"); + } + return; + } + + (void)Lst_AtEnd(jobs, (ClientData)job); + nJobs += 1; + if (nJobs == maxJobs) { + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Job queue is full.\n"); + } + } + } else if (job->flags & JOB_RESTART) { + /* + * Set up the control arguments to the shell. This is based on the + * flags set earlier for this job. If the JOB_IGNERR flag is clear, + * the 'exit' flag of the commandShell is used to cause it to exit + * upon receiving an error. If the JOB_SILENT flag is clear, the + * 'echo' flag of the commandShell is used to get it to start echoing + * as soon as it starts processing commands. + */ + char *argv[4]; + + JobMakeArgv(job, argv); + + if (DEBUG(JOB)) { + printf("Restarting %s...", job->node->name); + } + if (((nLocal >= maxLocal) && ! (job->flags & JOB_SPECIAL))) { + /* + * Can't be exported and not allowed to run locally -- put it + * back on the hold queue and mark the table full + */ + if (DEBUG(JOB)) { + printf("holding\n"); + } + (void)Lst_AtFront(stoppedJobs, (ClientData)job); + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Job queue is full.\n"); + } + return; + } else { + /* + * Job may be run locally. + */ + if (DEBUG(JOB)) { + printf("running locally\n"); + } + job->flags &= ~JOB_REMOTE; + } + JobExec(job, argv); + } else { + /* + * The job has stopped and needs to be restarted. Why it stopped, + * we don't know... + */ + if (DEBUG(JOB)) { + printf("Resuming %s...", job->node->name); + } + if (((job->flags & JOB_REMOTE) || + (nLocal < maxLocal) || + (((job->flags & JOB_SPECIAL)) && + (maxLocal == 0))) && + (nJobs != maxJobs)) + { + /* + * If the job is remote, it's ok to resume it as long as the + * maximum concurrency won't be exceeded. If it's local and + * we haven't reached the local concurrency limit already (or the + * job must be run locally and maxLocal is 0), it's also ok to + * resume it. + */ + Boolean error; + extern int errno; + union wait status; + +#ifdef RMT_WANTS_SIGNALS + if (job->flags & JOB_REMOTE) { + error = !Rmt_Signal(job, SIGCONT); + } else +#endif /* RMT_WANTS_SIGNALS */ + error = (KILL(job->pid, SIGCONT) != 0); + + if (!error) { + /* + * Make sure the user knows we've continued the beast and + * actually put the thing in the job table. + */ + job->flags |= JOB_CONTINUING; + status.w_termsig = SIGCONT; + JobFinish(job, status); + + job->flags &= ~(JOB_RESUME|JOB_CONTINUING); + if (DEBUG(JOB)) { + printf("done\n"); + } + } else { + Error("couldn't resume %s: %s", + job->node->name, strerror(errno)); + status.w_status = 0; + status.w_retcode = 1; + JobFinish(job, status); + } + } else { + /* + * Job cannot be restarted. Mark the table as full and + * place the job back on the list of stopped jobs. + */ + if (DEBUG(JOB)) { + printf("table full\n"); + } + (void)Lst_AtFront(stoppedJobs, (ClientData)job); + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Job queue is full.\n"); + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * JobStart -- + * Start a target-creation process going for the target described + * by the graph node gn. + * + * Results: + * JOB_ERROR if there was an error in the commands, JOB_FINISHED + * if there isn't actually anything left to do for the job and + * JOB_RUNNING if the job has been started. + * + * Side Effects: + * A new Job node is created and added to the list of running + * jobs. PMake is forked and a child shell created. + *----------------------------------------------------------------------- + */ +static int +JobStart (gn, flags, previous) + GNode *gn; /* target to create */ + short flags; /* flags for the job to override normal ones. + * e.g. JOB_SPECIAL or JOB_IGNDOTS */ + Job *previous; /* The previous Job structure for this node, + * if any. */ +{ + 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 */ + Boolean noExec; /* Set true if we decide not to run the job */ + + if (previous != (Job *)NULL) { + previous->flags &= ~ (JOB_FIRST|JOB_IGNERR|JOB_SILENT|JOB_REMOTE); + job = previous; + } else { + job = (Job *) emalloc (sizeof (Job)); + if (job == (Job *)NULL) { + Punt("JobStart out of memory"); + } + flags |= JOB_FIRST; + } + + job->node = gn; + job->tailCmds = NILLNODE; + + /* + * Set the initial value of the flags for this job based on the global + * ones and the node's attributes... Any flags supplied by the caller + * are also added to the field. + */ + job->flags = 0; + if (Targ_Ignore (gn)) { + job->flags |= JOB_IGNERR; + } + if (Targ_Silent (gn)) { + job->flags |= JOB_SILENT; + } + job->flags |= flags; + + /* + * Check the commands now so any attributes from .DEFAULT have a chance + * to migrate to the node + */ + if (job->flags & JOB_FIRST) { + cmdsOK = Job_CheckCommands(gn, Error); + } else { + cmdsOK = TRUE; + } + + /* + * If the -n flag wasn't given, we open up OUR (not the child's) + * temporary file to stuff commands in it. The thing is rd/wr so we don't + * need to reopen it to feed it to the shell. If the -n flag *was* given, + * we just set the file to be stdout. Cute, huh? + */ + if ((gn->type & OP_MAKE) || (!noExecute && !touchFlag)) { + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + DieHorribly(); + } + + job->cmdFILE = fopen (tfile, "w+"); + if (job->cmdFILE == (FILE *) NULL) { + Punt ("Could not open %s", tfile); + } + fcntl(fileno(job->cmdFILE), F_SETFD, 1); + /* + * Send the commands to the command file, flush all its buffers then + * rewind and remove the thing. + */ + noExec = FALSE; + + /* + * used to be backwards; replace when start doing multiple commands + * per shell. + */ + if (1) { + /* + * Be compatible: If this is the first time for this node, + * verify its commands are ok and open the commands list for + * sequential access by later invocations of JobStart. + * Once that is done, we take the next command off the list + * and print it to the command file. If the command was an + * ellipsis, note that there's nothing more to execute. + */ + if ((job->flags&JOB_FIRST) && (Lst_Open(gn->commands) != SUCCESS)){ + cmdsOK = FALSE; + } else { + LstNode ln = Lst_Next (gn->commands); + + if ((ln == NILLNODE) || + JobPrintCommand ((char *)Lst_Datum (ln), job)) + { + noExec = TRUE; + Lst_Close (gn->commands); + } + if (noExec && !(job->flags & JOB_FIRST)) { + /* + * If we're not going to execute anything, the job + * is done and we need to close down the various + * file descriptors we've opened for output, then + * call JobDoOutput to catch the final characters or + * send the file to the screen... Note that the i/o streams + * are only open if this isn't the first job. + * Note also that this could not be done in + * Job_CatchChildren b/c it wasn't clear if there were + * more commands to execute or not... + */ + if (usePipes) { +#ifdef RMT_WILL_WATCH + Rmt_Ignore(job->inPipe); +#else + FD_CLR(job->inPipe, &outputs); +#endif + if (job->outPipe != job->inPipe) { + (void)close (job->outPipe); + } + JobDoOutput (job, TRUE); + (void)close (job->inPipe); + } else { + (void)close (job->outFd); + JobDoOutput (job, TRUE); + } + } + } + } else { + /* + * We can do all the commands at once. hooray for sanity + */ + numCommands = 0; + Lst_ForEach (gn->commands, JobPrintCommand, (ClientData)job); + + /* + * If we didn't print out any commands to the shell script, + * there's not much point in executing the shell, is there? + */ + if (numCommands == 0) { + noExec = TRUE; + } + } + } else if (noExecute) { + /* + * Not executing anything -- just print all the commands to stdout + * in one fell swoop. This will still set up job->tailCmds correctly. + */ + if (lastNode != gn) { + printf (targFmt, gn->name); + lastNode = gn; + } + job->cmdFILE = stdout; + /* + * Only print the commands if they're ok, but don't die if they're + * not -- just let the user know they're bad and keep going. It + * doesn't do any harm in this case and may do some good. + */ + if (cmdsOK) { + Lst_ForEach(gn->commands, JobPrintCommand, (ClientData)job); + } + /* + * Don't execute the shell, thank you. + */ + noExec = TRUE; + } else { + /* + * Just touch the target and note that no shell should be executed. + * Set cmdFILE to stdout to make life easier. Check the commands, too, + * but don't die if they're no good -- it does no harm to keep working + * up the graph. + */ + job->cmdFILE = stdout; + Job_Touch (gn, job->flags&JOB_SILENT); + noExec = TRUE; + } + + /* + * If we're not supposed to execute a shell, don't. + */ + if (noExec) { + /* + * Unlink and close the command file if we opened one + */ + if (job->cmdFILE != stdout) { + (void) unlink (tfile); + fclose(job->cmdFILE); + } else { + fflush (stdout); + } + + /* + * We only want to work our way up the graph if we aren't here because + * the commands for the job were no good. + */ + if (cmdsOK) { + if (aborting == 0) { + if (job->tailCmds != NILLNODE) { + Lst_ForEachFrom(job->node->commands, job->tailCmds, + JobSaveCommand, + (ClientData)job->node); + } + Make_Update(job->node); + } + free((Address)job); + return(JOB_FINISHED); + } else { + free((Address)job); + return(JOB_ERROR); + } + } else { + fflush (job->cmdFILE); + (void) unlink (tfile); + } + + /* + * Set up the control arguments to the shell. This is based on the flags + * set earlier for this job. + */ + JobMakeArgv(job, argv); + + /* + * If we're using pipes to catch output, create the pipe by which we'll + * get the shell's output. If we're using files, print out that we're + * starting a job and then set up its temporary-file name. This is just + * tfile with two extra digits tacked on -- jobno. + */ + if (job->flags & JOB_FIRST) { + if (usePipes) { + int fd[2]; + (void) pipe(fd); + job->inPipe = fd[0]; + job->outPipe = fd[1]; + (void)fcntl (job->inPipe, F_SETFD, 1); + (void)fcntl (job->outPipe, F_SETFD, 1); + } else { + printf ("Remaking `%s'\n", gn->name); + fflush (stdout); + sprintf (job->outFile, "%s%02d", tfile, jobno); + jobno = (jobno + 1) % 100; + job->outFd = open(job->outFile,O_WRONLY|O_CREAT|O_APPEND,0600); + (void)fcntl (job->outFd, F_SETFD, 1); + } + } + + local = TRUE; + + if (local && (((nLocal >= maxLocal) && + !(job->flags & JOB_SPECIAL) && + (maxLocal != 0)))) + { + /* + * The job can only be run locally, but we've hit the limit of + * local concurrency, so put the job on hold until some other job + * finishes. Note that the special jobs (.BEGIN, .INTERRUPT and .END) + * may be run locally even when the local limit has been reached + * (e.g. when maxLocal == 0), though they will be exported if at + * all possible. + */ + jobFull = TRUE; + + if (DEBUG(JOB)) { + printf("Can only run job locally.\n"); + } + job->flags |= JOB_RESTART; + (void)Lst_AtEnd(stoppedJobs, (ClientData)job); + } else { + if ((nLocal >= maxLocal) && local) { + /* + * If we're running this job locally as a special case (see above), + * at least say the table is full. + */ + jobFull = TRUE; + if (DEBUG(JOB)) { + printf("Local job queue is full.\n"); + } + } + JobExec(job, argv); + } + return(JOB_RUNNING); +} + +/*- + *----------------------------------------------------------------------- + * JobDoOutput -- + * This function is called at different times depending on + * whether the user has specified that output is to be collected + * via pipes or temporary files. In the former case, we are called + * whenever there is something to read on the pipe. We collect more + * output from the given job and store it in the job's outBuf. If + * this makes up a line, we print it tagged by the job's identifier, + * as necessary. + * If output has been collected in a temporary file, we open the + * file and read it line by line, transfering it to our own + * output channel until the file is empty. At which point we + * remove the temporary file. + * In both cases, however, we keep our figurative eye out for the + * 'noPrint' line for the shell from which the output came. If + * we recognize a line, we don't print it. If the command is not + * alone on the line (the character after it is not \0 or \n), we + * do print whatever follows it. + * + * Results: + * None + * + * Side Effects: + * curPos may be shifted as may the contents of outBuf. + *----------------------------------------------------------------------- + */ +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 + * called for this job */ +{ + Boolean gotNL = FALSE; /* true if got a newline */ + register int nr; /* number of bytes read */ + register int i; /* auxiliary index into outBuf */ + 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]; + + + if (usePipes) { + /* + * Read as many bytes as will fit in the buffer. + */ +end_loop: + + nRead = read (job->inPipe, &job->outBuf[job->curPos], + JOB_BUFSIZE - job->curPos); + if (nRead < 0) { + if (DEBUG(JOB)) { + perror("JobDoOutput(piperead)"); + } + nr = 0; + } else { + nr = nRead; + } + + /* + * If we hit the end-of-file (the job is dead), we must flush its + * remaining output, so pretend we read a newline if there's any + * output remaining in the buffer. + * Also clear the 'finish' flag so we stop looping. + */ + if ((nr == 0) && (job->curPos != 0)) { + job->outBuf[job->curPos] = '\n'; + nr = 1; + finish = FALSE; + } else if (nr == 0) { + finish = FALSE; + } + + /* + * Look for the last newline in the bytes we just got. If there is + * one, break out of the loop with 'i' as its index and gotNL set + * TRUE. + */ + max = job->curPos + nr; + for (i = job->curPos + nr - 1; i >= job->curPos; i--) { + if (job->outBuf[i] == '\n') { + gotNL = TRUE; + break; + } else if (job->outBuf[i] == '\0') { + /* + * Why? + */ + job->outBuf[i] = ' '; + } + } + + if (!gotNL) { + job->curPos += nr; + if (job->curPos == JOB_BUFSIZE) { + /* + * If we've run out of buffer space, we have no choice + * but to print the stuff. sigh. + */ + gotNL = TRUE; + i = job->curPos; + } + } + if (gotNL) { + /* + * Need to send the output to the screen. Null terminate it + * first, overwriting the newline character if there was one. + * So long as the line isn't one we should filter (according + * to the shell description), we print the line, preceeded + * by a target banner if this target isn't the same as the + * one for which we last printed something. + * The rest of the data in the buffer are then shifted down + * to the start of the buffer and curPos is set accordingly. + */ + job->outBuf[i] = '\0'; + if (i >= job->curPos) { + register char *cp, *ecp; + + cp = job->outBuf; + if (commandShell->noPrint) { + ecp = Str_FindSubstring(job->outBuf, + commandShell->noPrint); + while (ecp != (char *)NULL) { + if (cp != ecp) { + *ecp = '\0'; + if (job->node != lastNode) { + printf (targFmt, job->node->name); + lastNode = job->node; + } + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the non-printable comes after it, + * there must be a newline, so we don't print one. + */ + printf ("%s", cp); + } + cp = ecp + commandShell->noPLen; + if (cp != &job->outBuf[i]) { + /* + * Still more to print, look again after skipping + * the whitespace following the non-printable + * command.... + */ + cp++; + while (*cp == ' ' || *cp == '\t' || *cp == '\n') { + cp++; + } + ecp = Str_FindSubstring (cp, + commandShell->noPrint); + } else { + break; + } + } + } + + /* + * There's still more in that thar buffer. This time, though, + * we know there's no newline at the end, so we add one of + * our own free will. + */ + if (*cp != '\0') { + if (job->node != lastNode) { + printf (targFmt, job->node->name); + lastNode = job->node; + } + printf ("%s\n", cp); + } + + fflush (stdout); + } + if (i < max - 1) { + bcopy (&job->outBuf[i + 1], /* shift the remaining */ + job->outBuf, /* characters down */ + max - (i + 1)); + job->curPos = max - (i + 1); + + } else { + /* + * We have written everything out, so we just start over + * from the start of the buffer. No copying. No nothing. + */ + job->curPos = 0; + } + } + if (finish) { + /* + * If the finish flag is true, we must loop until we hit + * end-of-file on the pipe. This is guaranteed to happen eventually + * since the other end of the pipe is now closed (we closed it + * explicitly and the child has exited). When we do get an EOF, + * finish will be set FALSE and we'll fall through and out. + */ + goto end_loop; + } + } else { + /* + * We've been called to retrieve the output of the job from the + * temporary file where it's been squirreled away. This consists of + * opening the file, reading the output line by line, being sure not + * to print the noPrint line for the shell we used, then close and + * remove the temporary file. Very simple. + * + * Change to read in blocks and do FindSubString type things as for + * pipes? That would allow for "@echo -n..." + */ + oFILE = fopen (job->outFile, "r"); + if (oFILE != (FILE *) NULL) { + printf ("Results of making %s:\n", job->node->name); + while (fgets (inLine, sizeof(inLine), oFILE) != NULL) { + register char *cp, *ecp, *endp; + + cp = inLine; + endp = inLine + strlen(inLine); + if (endp[-1] == '\n') { + *--endp = '\0'; + } + if (commandShell->noPrint) { + ecp = Str_FindSubstring(cp, commandShell->noPrint); + while (ecp != (char *)NULL) { + if (cp != ecp) { + *ecp = '\0'; + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the non-printable comes after it, + * there must be a newline, so we don't print one. + */ + printf ("%s", cp); + } + cp = ecp + commandShell->noPLen; + if (cp != endp) { + /* + * Still more to print, look again after skipping + * the whitespace following the non-printable + * command.... + */ + cp++; + while (*cp == ' ' || *cp == '\t' || *cp == '\n') { + cp++; + } + ecp = Str_FindSubstring(cp, commandShell->noPrint); + } else { + break; + } + } + } + + /* + * There's still more in that thar buffer. This time, though, + * we know there's no newline at the end, so we add one of + * our own free will. + */ + if (*cp != '\0') { + printf ("%s\n", cp); + } + } + fclose (oFILE); + (void) unlink (job->outFile); + } + } + fflush(stdout); +} + +/*- + *----------------------------------------------------------------------- + * Job_CatchChildren -- + * Handle the exit of a child. Called from Make_Make. + * + * Results: + * none. + * + * Side Effects: + * The job descriptor is removed from the list of children. + * + * Notes: + * We do waits, blocking or not, according to the wisdom of our + * caller, until there are no more children to report. For each + * job, call JobFinish to finish things off. This will take care of + * putting jobs on the stoppedJobs queue. + * + *----------------------------------------------------------------------- + */ +void +Job_CatchChildren (block) + Boolean block; /* TRUE if should block on the wait. */ +{ + int pid; /* pid of dead child */ + register Job *job; /* job descriptor for dead child */ + LstNode jnode; /* list element for finding job */ + union wait status; /* Exit/termination status */ + + /* + * Don't even bother if we know there's no one around. + */ + if (nLocal == 0) { + return; + } + + while ((pid = wait3((int *)&status, (block?0:WNOHANG)|WUNTRACED, + (struct rusage *)0)) > 0) + { + if (DEBUG(JOB)) + printf("Process %d exited or stopped.\n", pid); + + + jnode = Lst_Find (jobs, (ClientData)pid, JobCmpPid); + + if (jnode == NILLNODE) { + if (WIFSIGNALED(status) && (status.w_termsig == SIGCONT)) { + jnode = Lst_Find(stoppedJobs, (ClientData)pid, JobCmpPid); + if (jnode == NILLNODE) { + Error("Resumed child (%d) not in table", pid); + continue; + } + job = (Job *)Lst_Datum(jnode); + (void)Lst_Remove(stoppedJobs, jnode); + } else { + Error ("Child (%d) not in table?", pid); + continue; + } + } else { + job = (Job *) Lst_Datum (jnode); + (void)Lst_Remove (jobs, jnode); + nJobs -= 1; + if (jobFull && DEBUG(JOB)) { + printf("Job queue is no longer full.\n"); + } + jobFull = FALSE; + nLocal -= 1; + } + + JobFinish (job, status); + } +} + +/*- + *----------------------------------------------------------------------- + * Job_CatchOutput -- + * Catch the output from our children, if we're using + * pipes do so. Otherwise just block time until we get a + * signal (most likely a SIGCHLD) since there's no point in + * just spinning when there's nothing to do and the reaping + * of a child can wait for a while. + * + * Results: + * None + * + * Side Effects: + * Output is read from pipes if we're piping. + * ----------------------------------------------------------------------- + */ +void +Job_CatchOutput () +{ + int nfds; + struct timeval timeout; + fd_set readfds; + register LstNode ln; + register Job *job; + int pnJobs; /* Previous nJobs */ + + fflush(stdout); +#ifdef RMT_WILL_WATCH + pnJobs = nJobs; + + /* + * It is possible for us to be called with nJobs equal to 0. This happens + * if all the jobs finish and a job that is stopped cannot be run + * locally (eg if maxLocal is 0) and cannot be exported. The job will + * be placed back on the stoppedJobs queue, Job_Empty() will return false, + * Make_Run will call us again when there's nothing for which to wait. + * nJobs never changes, so we loop forever. Hence the check. It could + * be argued that we should sleep for a bit so as not to swamp the + * exportation system with requests. Perhaps we should. + * + * NOTE: IT IS THE RESPONSIBILITY OF Rmt_Wait TO CALL Job_CatchChildren + * IN A TIMELY FASHION TO CATCH ANY LOCALLY RUNNING JOBS THAT EXIT. + * It may use the variable nLocal to determine if it needs to call + * Job_CatchChildren (if nLocal is 0, there's nothing for which to + * wait...) + */ + while (nJobs != 0 && pnJobs == nJobs) { + Rmt_Wait(); + } +#else + if (usePipes) { + readfds = outputs; + timeout.tv_sec = SEL_SEC; + timeout.tv_usec = SEL_USEC; + + if ((nfds = select (FD_SETSIZE, &readfds, (int *) 0, (int *) 0, &timeout)) < 0) + { + return; + } else { + if (Lst_Open (jobs) == FAILURE) { + Punt ("Cannot open job table"); + } + while (nfds && (ln = Lst_Next (jobs)) != NILLNODE) { + job = (Job *) Lst_Datum (ln); + if (FD_ISSET(job->inPipe, &readfds)) { + JobDoOutput (job, FALSE); + nfds -= 1; + } + } + Lst_Close (jobs); + } + } +#endif /* RMT_WILL_WATCH */ +} + +/*- + *----------------------------------------------------------------------- + * Job_Make -- + * Start the creation of a target. Basically a front-end for + * JobStart used by the Make module. + * + * Results: + * None. + * + * Side Effects: + * Another job is started. + * + *----------------------------------------------------------------------- + */ +void +Job_Make (gn) + GNode *gn; +{ + (void)JobStart (gn, 0, (Job *)NULL); +} + +/*- + *----------------------------------------------------------------------- + * Job_Init -- + * Initialize the process module + * + * Results: + * none + * + * Side Effects: + * lists and counters are initialized + *----------------------------------------------------------------------- + */ +void +Job_Init (maxproc, maxlocal) + int maxproc; /* the greatest number of jobs which may be + * running at one time */ + int maxlocal; /* the greatest number of local jobs which may + * be running at once. */ +{ + GNode *begin; /* node for commands to do at the very start */ + + sprintf (tfile, "/tmp/make%05d", getpid()); + + jobs = Lst_Init (FALSE); + stoppedJobs = Lst_Init(FALSE); + maxJobs = maxproc; + maxLocal = maxlocal; + nJobs = 0; + nLocal = 0; + jobFull = FALSE; + + aborting = 0; + errors = 0; + + lastNode = NILGNODE; + + if (maxJobs == 1) { + /* + * If only one job can run at a time, there's no need for a banner, + * no is there? + */ + targFmt = ""; + } else { + targFmt = TARG_FMT; + } + + if (shellPath == (char *) NULL) { + /* + * The user didn't specify a shell to use, so we are using the + * default one... Both the absolute path and the last component + * must be set. The last component is taken from the 'name' field + * of the default shell description pointed-to by commandShell. + * All default shells are located in _PATH_DEFSHELLDIR. + */ + shellName = commandShell->name; + shellPath = str_concat (_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH); + } + + if (commandShell->exit == (char *)NULL) { + commandShell->exit = ""; + } + if (commandShell->echo == (char *)NULL) { + commandShell->echo = ""; + } + + /* + * Catch the four signals that POSIX specifies if they aren't ignored. + * JobPassSig will take care of calling JobInterrupt if appropriate. + */ + if (signal (SIGINT, SIG_IGN) != SIG_IGN) { + signal (SIGINT, JobPassSig); + } + if (signal (SIGHUP, SIG_IGN) != SIG_IGN) { + signal (SIGHUP, JobPassSig); + } + if (signal (SIGQUIT, SIG_IGN) != SIG_IGN) { + signal (SIGQUIT, JobPassSig); + } + if (signal (SIGTERM, SIG_IGN) != SIG_IGN) { + signal (SIGTERM, JobPassSig); + } + /* + * There are additional signals that need to be caught and passed if + * either the export system wants to be told directly of signals or if + * we're giving each job its own process group (since then it won't get + * signals from the terminal driver as we own the terminal) + */ +#if defined(RMT_WANTS_SIGNALS) || defined(USE_PGRP) + if (signal (SIGTSTP, SIG_IGN) != SIG_IGN) { + signal (SIGTSTP, JobPassSig); + } + if (signal (SIGTTOU, SIG_IGN) != SIG_IGN) { + signal (SIGTTOU, JobPassSig); + } + if (signal (SIGTTIN, SIG_IGN) != SIG_IGN) { + signal (SIGTTIN, JobPassSig); + } + if (signal (SIGWINCH, SIG_IGN) != SIG_IGN) { + signal (SIGWINCH, JobPassSig); + } +#endif + + begin = Targ_FindNode (".BEGIN", TARG_NOCREATE); + + if (begin != NILGNODE) { + JobStart (begin, JOB_SPECIAL, (Job *)0); + while (nJobs) { + Job_CatchOutput(); +#ifndef RMT_WILL_WATCH + Job_CatchChildren (!usePipes); +#endif /* RMT_WILL_WATCH */ + } + } + postCommands = Targ_FindNode (".END", TARG_CREATE); +} + +/*- + *----------------------------------------------------------------------- + * Job_Full -- + * See if the job table is full. It is considered full if it is OR + * if we are in the process of aborting OR if we have + * reached/exceeded our local quota. This prevents any more jobs + * from starting up. + * + * Results: + * TRUE if the job table is full, FALSE otherwise + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +Boolean +Job_Full () +{ + return (aborting || jobFull); +} + +/*- + *----------------------------------------------------------------------- + * Job_Empty -- + * See if the job table is empty. Because the local concurrency may + * be set to 0, it is possible for the job table to become empty, + * while the list of stoppedJobs remains non-empty. In such a case, + * we want to restart as many jobs as we can. + * + * Results: + * TRUE if it is. FALSE if it ain't. + * + * Side Effects: + * None. + * + * ----------------------------------------------------------------------- + */ +Boolean +Job_Empty () +{ + if (nJobs == 0) { + if (!Lst_IsEmpty(stoppedJobs) && !aborting) { + /* + * The job table is obviously not full if it has no jobs in + * it...Try and restart the stopped jobs. + */ + jobFull = FALSE; + while (!jobFull && !Lst_IsEmpty(stoppedJobs)) { + JobRestart((Job *)Lst_DeQueue(stoppedJobs)); + } + return(FALSE); + } else { + return(TRUE); + } + } else { + return(FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * JobMatchShell -- + * Find a matching shell in 'shells' given its final component. + * + * Results: + * A pointer to the Shell structure. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Shell * +JobMatchShell (name) + char *name; /* Final component of shell path */ +{ + register Shell *sh; /* Pointer into shells table */ + Shell *match; /* Longest-matching shell */ + register char *cp1, + *cp2; + char *eoname; + + eoname = name + strlen (name); + + match = (Shell *) NULL; + + for (sh = shells; sh->name != NULL; sh++) { + for (cp1 = eoname - strlen (sh->name), cp2 = sh->name; + *cp1 != '\0' && *cp1 == *cp2; + cp1++, cp2++) { + continue; + } + if (*cp1 != *cp2) { + continue; + } else if (match == (Shell *) NULL || + strlen (match->name) < strlen (sh->name)) { + match = sh; + } + } + return (match == (Shell *) NULL ? sh : match); +} + +/*- + *----------------------------------------------------------------------- + * Job_ParseShell -- + * Parse a shell specification and set up commandShell, shellPath + * and shellName appropriately. + * + * Results: + * FAILURE if the specification was incorrect. + * + * Side Effects: + * commandShell points to a Shell structure (either predefined or + * created from the shell spec), shellPath is the full path of the + * shell described by commandShell, while shellName is just the + * final component of shellPath. + * + * Notes: + * A shell specification consists of a .SHELL target, with dependency + * operator, followed by a series of blank-separated words. Double + * quotes can be used to use blanks in words. A backslash escapes + * anything (most notably a double-quote and a space) and + * provides the functionality it does in C. Each word consists of + * keyword and value separated by an equal sign. There should be no + * unnecessary spaces in the word. The keywords are as follows: + * name Name of shell. + * path Location of shell. Overrides "name" if given + * quiet Command to turn off echoing. + * echo Command to turn echoing on + * filter Result of turning off echoing that shouldn't be + * printed. + * echoFlag Flag to turn echoing on at the start + * errFlag Flag to turn error checking on at the start + * hasErrCtl True if shell has error checking control + * check Command to turn on error checking if hasErrCtl + * is TRUE or template of command to echo a command + * for which error checking is off if hasErrCtl is + * FALSE. + * ignore Command to turn off error checking if hasErrCtl + * is TRUE or template of command to execute a + * command so as to ignore any errors it returns if + * hasErrCtl is FALSE. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Job_ParseShell (line) + char *line; /* The shell spec */ +{ + char **words; + int wordCount; + register char **argv; + register int argc; + char *path; + Shell newShell; + Boolean fullSpec = FALSE; + + while (isspace (*line)) { + line++; + } + words = brk_string (line, &wordCount); + + bzero ((Address)&newShell, sizeof(newShell)); + + /* + * Parse the specification by keyword + */ + for (path = (char *)NULL, argc = wordCount - 1, argv = words + 1; + argc != 0; + argc--, argv++) { + if (strncmp (*argv, "path=", 5) == 0) { + path = &argv[0][5]; + } else if (strncmp (*argv, "name=", 5) == 0) { + newShell.name = &argv[0][5]; + } else { + if (strncmp (*argv, "quiet=", 6) == 0) { + newShell.echoOff = &argv[0][6]; + } else if (strncmp (*argv, "echo=", 5) == 0) { + newShell.echoOn = &argv[0][5]; + } else if (strncmp (*argv, "filter=", 7) == 0) { + newShell.noPrint = &argv[0][7]; + newShell.noPLen = strlen(newShell.noPrint); + } else if (strncmp (*argv, "echoFlag=", 9) == 0) { + newShell.echo = &argv[0][9]; + } else if (strncmp (*argv, "errFlag=", 8) == 0) { + newShell.exit = &argv[0][8]; + } else if (strncmp (*argv, "hasErrCtl=", 10) == 0) { + char c = argv[0][10]; + newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && + (c != 'T') && (c != 't')); + } else if (strncmp (*argv, "check=", 6) == 0) { + newShell.errCheck = &argv[0][6]; + } else if (strncmp (*argv, "ignore=", 7) == 0) { + newShell.ignErr = &argv[0][7]; + } else { + Parse_Error (PARSE_FATAL, "Unknown keyword \"%s\"", + *argv); + return (FAILURE); + } + fullSpec = TRUE; + } + } + + if (path == (char *)NULL) { + /* + * If no path was given, the user wants one of the pre-defined shells, + * yes? So we find the one s/he wants with the help of JobMatchShell + * and set things up the right way. shellPath will be set up by + * Job_Init. + */ + if (newShell.name == (char *)NULL) { + Parse_Error (PARSE_FATAL, "Neither path nor name specified"); + return (FAILURE); + } else { + commandShell = JobMatchShell (newShell.name); + shellName = newShell.name; + } + } else { + /* + * The user provided a path. If s/he gave nothing else (fullSpec is + * FALSE), try and find a matching shell in the ones we know of. + * Else we just take the specification at its word and copy it + * to a new location. In either case, we need to record the + * path the user gave for the shell. + */ + shellPath = path; + path = rindex (path, '/'); + if (path == (char *)NULL) { + path = shellPath; + } else { + path += 1; + } + if (newShell.name != (char *)NULL) { + shellName = newShell.name; + } else { + shellName = path; + } + if (!fullSpec) { + commandShell = JobMatchShell (shellName); + } else { + commandShell = (Shell *) emalloc(sizeof(Shell)); + *commandShell = newShell; + } + } + + if (commandShell->echoOn && commandShell->echoOff) { + commandShell->hasEchoCtl = TRUE; + } + + if (!commandShell->hasErrCtl) { + if (commandShell->errCheck == (char *)NULL) { + commandShell->errCheck = ""; + } + if (commandShell->ignErr == (char *)NULL) { + commandShell->ignErr = "%s\n"; + } + } + + /* + * Do not free up the words themselves, since they might be in use by the + * shell specification... + */ + free (words); + return SUCCESS; +} + +/*- + *----------------------------------------------------------------------- + * JobInterrupt -- + * Handle the receipt of an interrupt. + * + * Results: + * None + * + * Side Effects: + * All children are killed. Another job will be started if the + * .INTERRUPT target was given. + *----------------------------------------------------------------------- + */ +static void +JobInterrupt (runINTERRUPT) + int runINTERRUPT; /* Non-zero if commands for the .INTERRUPT + * target should be executed */ +{ + LstNode ln; /* element in job table */ + Job *job; /* job descriptor in that element */ + GNode *interrupt; /* the node describing the .INTERRUPT target */ + + aborting = ABORT_INTERRUPT; + + (void)Lst_Open (jobs); + while ((ln = Lst_Next (jobs)) != NILLNODE) { + job = (Job *) Lst_Datum (ln); + + if (!Targ_Precious (job->node)) { + 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); + } + } + } +#ifdef RMT_WANTS_SIGNALS + if (job->flags & JOB_REMOTE) { + /* + * If job is remote, let the Rmt module do the killing. + */ + if (!Rmt_Signal(job, SIGINT)) { + /* + * If couldn't kill the thing, finish it out now with an + * error code, since no exit report will come in likely. + */ + union wait status; + + status.w_status = 0; + status.w_retcode = 1; + JobFinish(job, status); + } + } else if (job->pid) { + KILL(job->pid, SIGINT); + } +#else + if (job->pid) { + KILL(job->pid, SIGINT); + } +#endif /* RMT_WANTS_SIGNALS */ + } + Lst_Close (jobs); + + if (runINTERRUPT && !touchFlag) { + interrupt = Targ_FindNode (".INTERRUPT", TARG_NOCREATE); + if (interrupt != NILGNODE) { + ignoreErrors = FALSE; + + JobStart (interrupt, JOB_IGNDOTS, (Job *)0); + while (nJobs) { + Job_CatchOutput(); +#ifndef RMT_WILL_WATCH + Job_CatchChildren (!usePipes); +#endif /* RMT_WILL_WATCH */ + } + } + } + (void) unlink (tfile); + exit (0); +} + +/* + *----------------------------------------------------------------------- + * Job_End -- + * Do final processing such as the running of the commands + * attached to the .END target. + * + * Results: + * Number of errors reported. + * + * Side Effects: + * The process' temporary file (tfile) is removed if it still + * existed. + *----------------------------------------------------------------------- + */ +int +Job_End () +{ + if (postCommands != NILGNODE && !Lst_IsEmpty (postCommands->commands)) { + if (errors) { + Error ("Errors reported so .END ignored"); + } else { + JobStart (postCommands, JOB_SPECIAL | JOB_IGNDOTS, + (Job *)0); + + while (nJobs) { + Job_CatchOutput(); +#ifndef RMT_WILL_WATCH + Job_CatchChildren (!usePipes); +#endif /* RMT_WILL_WATCH */ + } + } + } + (void) unlink (tfile); + return(errors); +} + +/*- + *----------------------------------------------------------------------- + * Job_Wait -- + * Waits for all running jobs to finish and returns. Sets 'aborting' + * to ABORT_WAIT to prevent other jobs from starting. + * + * Results: + * None. + * + * Side Effects: + * Currently running jobs finish. + * + *----------------------------------------------------------------------- + */ +void +Job_Wait() +{ + aborting = ABORT_WAIT; + while (nJobs != 0) { + Job_CatchOutput(); +#ifndef RMT_WILL_WATCH + Job_CatchChildren(!usePipes); +#endif /* RMT_WILL_WATCH */ + } + aborting = 0; +} + +/*- + *----------------------------------------------------------------------- + * Job_AbortAll -- + * Abort all currently running jobs without handling output or anything. + * This function is to be called only in the event of a major + * error. Most definitely NOT to be called from JobInterrupt. + * + * Results: + * None + * + * Side Effects: + * All children are killed, not just the firstborn + *----------------------------------------------------------------------- + */ +void +Job_AbortAll () +{ + LstNode ln; /* element in job table */ + Job *job; /* the job descriptor in that element */ + int foo; + + aborting = ABORT_ERROR; + + if (nJobs) { + + (void)Lst_Open (jobs); + while ((ln = Lst_Next (jobs)) != NILLNODE) { + job = (Job *) Lst_Datum (ln); + + /* + * kill the child process with increasingly drastic signals to make + * darn sure it's dead. + */ +#ifdef RMT_WANTS_SIGNALS + if (job->flags & JOB_REMOTE) { + Rmt_Signal(job, SIGINT); + Rmt_Signal(job, SIGKILL); + } else { + KILL(job->pid, SIGINT); + KILL(job->pid, SIGKILL); + } +#else + KILL(job->pid, SIGINT); + KILL(job->pid, SIGKILL); +#endif /* RMT_WANTS_SIGNALS */ + } + } + + /* + * Catch as many children as want to report in at first, then give up + */ + while (wait3(&foo, WNOHANG, (struct rusage *)0) > 0) { + ; + } + (void) unlink (tfile); +} diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h new file mode 100644 index 000000000000..25e81dcf2a1f --- /dev/null +++ b/usr.bin/make/job.h @@ -0,0 +1,232 @@ +/* + * 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. + * + * @(#)job.h 5.3 (Berkeley) 6/1/90 + */ + +/*- + * job.h -- + * Definitions pertaining to the running of jobs in parallel mode. + * Exported from job.c for the use of remote-execution modules. + */ +#ifndef _JOB_H_ +#define _JOB_H_ + +#define TMPPAT "/tmp/makeXXXXX" + +/* + * The SEL_ constants determine the maximum amount of time spent in select + * before coming out to see if a child has finished. SEL_SEC is the number of + * seconds and SEL_USEC is the number of micro-seconds + */ +#define SEL_SEC 0 +#define SEL_USEC 500000 + + +/*- + * Job Table definitions. + * + * Each job has several things associated with it: + * 1) The process id of the child shell + * 2) The graph node describing the target being made by this job + * 3) A LstNode for the first command to be saved after the job + * completes. This is NILLNODE if there was no "..." in the job's + * commands. + * 4) An FILE* for writing out the commands. This is only + * used before the job is actually started. + * 5) A union of things used for handling the shell's output. Different + * parts of the union are used based on the value of the usePipes + * flag. If it is true, the output is being caught via a pipe and + * the descriptors of our pipe, an array in which output is line + * buffered and the current position in that buffer are all + * maintained for each job. If, on the other hand, usePipes is false, + * the output is routed to a temporary file and all that is kept + * is the name of the file and the descriptor open to the file. + * 6) An identifier provided by and for the exclusive use of the + * Rmt module. + * 7) A word of flags which determine how the module handles errors, + * echoing, etc. for the job + * + * The job "table" is kept as a linked Lst in 'jobs', with the number of + * active jobs maintained in the 'nJobs' variable. At no time will this + * exceed the value of 'maxJobs', initialized by the Job_Init function. + * + * When a job is finished, the Make_Update function is called on each of the + * parents of the node which was just remade. This takes care of the upward + * traversal of the dependency graph. + */ +#define JOB_BUFSIZE 1024 +typedef struct Job { + int pid; /* The child's process ID */ + GNode *node; /* The target the child is making */ + LstNode tailCmds; /* The node of the first command to be + * 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 */ + short flags; /* Flags to control treatment of job */ +#define JOB_IGNERR 0x001 /* Ignore non-zero exits */ +#define JOB_SILENT 0x002 /* no output */ +#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally + * if we can't export it and maxLocal is 0 */ +#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing + * commands */ +#define JOB_REMOTE 0x010 /* Job is running remotely */ +#define JOB_FIRST 0x020 /* Job is first job for the node */ +#define JOB_REMIGRATE 0x040 /* Job needs to be remigrated */ +#define JOB_RESTART 0x080 /* Job needs to be completely restarted */ +#define JOB_RESUME 0x100 /* Job needs to be resumed b/c it stopped, + * for some reason */ +#define JOB_CONTINUING 0x200 /* We are in the process of resuming this job. + * Used to avoid infinite recursion between + * JobFinish and JobRestart */ + union { + struct { + int op_inPipe; /* Input side of pipe associated + * with job's output channel */ + int op_outPipe; /* Output side of pipe associated with + * job's output channel */ + char op_outBuf[JOB_BUFSIZE + 1]; + /* Buffer for storing the output of the + * job, line by line */ + int op_curPos; /* Current position in op_outBuf */ + } o_pipe; /* data used when catching the output via + * a pipe */ + struct { + char of_outFile[sizeof(TMPPAT)+2]; + /* Name of file to which shell output + * was rerouted */ + int of_outFd; /* Stream open to the output + * file. Used to funnel all + * from a single job to one file + * while still allowing + * multiple shell invocations */ + } o_file; /* Data used when catching the output in + * a temporary file */ + } output; /* Data for tracking a shell's output */ +} Job; + +#define outPipe output.o_pipe.op_outPipe +#define inPipe output.o_pipe.op_inPipe +#define outBuf output.o_pipe.op_outBuf +#define curPos output.o_pipe.op_curPos +#define outFile output.o_file.of_outFile +#define outFd output.o_file.of_outFd + + +/*- + * Shell Specifications: + * Each shell type has associated with it the following information: + * 1) The string which must match the last character of the shell name + * for the shell to be considered of this type. The longest match + * wins. + * 2) A command to issue to turn off echoing of command lines + * 3) A command to issue to turn echoing back on again + * 4) What the shell prints, and its length, when given the echo-off + * command. This line will not be printed when received from the shell + * 5) A boolean to tell if the shell has the ability to control + * error checking for individual commands. + * 6) The string to turn this checking on. + * 7) The string to turn it off. + * 8) The command-flag to give to cause the shell to start echoing + * commands right away. + * 9) The command-flag to cause the shell to Lib_Exit when an error is + * detected in one of the commands. + * + * Some special stuff goes on if a shell doesn't have error control. In such + * a case, errCheck becomes a printf template for echoing the command, + * should echoing be on and ignErr becomes another printf template for + * executing the command while ignoring the return status. If either of these + * strings is empty when hasErrCtl is FALSE, the command will be executed + * anyway as is and if it causes an error, so be it. + */ +typedef struct Shell { + char *name; /* the name of the shell. For Bourne and C + * shells, this is used only to find the + * shell description when used as the single + * source of a .SHELL target. For user-defined + * shells, this is the full path of the shell. + */ + Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ + char *echoOff; /* command to turn off echo */ + char *echoOn; /* command to turn it back on again */ + char *noPrint; /* command to skip when printing output from + * shell. This is usually the command which + * was executed to turn off echoing */ + int noPLen; /* length of noPrint command */ + Boolean hasErrCtl; /* set if can control error checking for + * individual commands */ + char *errCheck; /* string to turn error checking on */ + char *ignErr; /* string to turn off error checking */ + /* + * command-line flags + */ + char *echo; /* echo commands */ + char *exit; /* exit on error */ +} Shell; + + +extern char *targFmt; /* Format string for banner that separates + * output from multiple jobs. Contains a + * single %s where the name of the node being + * made should be put. */ +extern GNode *lastNode; /* Last node for which a banner was printed. + * If Rmt module finds it necessary to print + * a banner, it should set this to the node + * for which the banner was printed */ +extern int nJobs; /* Number of jobs running (local and remote) */ +extern int nLocal; /* Number of jobs running locally */ +extern Lst jobs; /* List of active job descriptors */ +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. */ +#endif /* _JOB_H_ */ diff --git a/usr.bin/make/list.h b/usr.bin/make/list.h new file mode 100644 index 000000000000..95ef86646661 --- /dev/null +++ b/usr.bin/make/list.h @@ -0,0 +1,298 @@ +/* + * 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. + * + * @(#)list.h 5.3 (Berkeley) 6/1/90 + */ + +/* + * list.h -- + * + * Structures, macros, and routines exported by the List module. + */ + +#ifndef _LIST +#define _LIST + +#ifndef _SPRITE +#include "sprite.h" +#endif _SPRITE + +/* + * This module defines the list abstraction, which enables one to link + * together arbitrary data structures. Lists are doubly-linked and + * circular. A list contains a header followed by its real members, if + * any. (An empty list therefore consists of a single element, the + * header, whose nextPtr and prevPtr fields point to itself). To refer + * to a list as a whole, the user keeps a pointer to the header; that + * header is initialized by a call to List_Init(), which creates an empty + * list given a pointer to a List_Links structure (described below). + * + * The links are contained in a two-element structure called List_Links. + * A list joins List_Links records (that is, each List_Links structure + * points to other List_Links structures), but if the List_Links is the + * first field within a larger structure, then the larger structures are + * effectively linked together as follows: + * + * header + * (List_Links) first elt. second elt. + * ----------------- ----------------- ----------------- + * ..-> | nextPtr | ----> | List_Links | ----> | List_Links |----.. + * | - - - - - - - | | | | | + * ..-- | prevPtr | <---- | | <---- | |<---.. + * ----------------- - --- --- --- - - --- --- --- - + * | rest of | | rest of | + * | structure | | structure | + * | | | | + * | ... | | ... | + * ----------------- ----------------- + * + * It is possible to link structures through List_Links fields that are + * not at the beginning of the larger structure, but it is then necessary + * to perform pointer arithmetic to find the beginning of the larger + * structure, given a pointer to some point within it. + * + * A typical structure might be something like: + * + * typedef struct { + * List_Links links; + * char ch; + * integer flags; + * } EditChar; + * + * Before an element is inserted in a list for the first time, it must + * be initialized by calling the macro List_InitElement(). + */ + + +/* + * data structure for lists + */ + +typedef struct List_Links { + struct List_Links *prevPtr; + struct List_Links *nextPtr; +} List_Links; + +/* + * procedures + */ + +void List_Init(); /* initialize a header to a list */ +void List_Insert(); /* insert an element into a list */ +void List_Remove(); /* remove an element from a list */ +void List_Move(); /* move an element elsewhere in a list */ + +/* + * ---------------------------------------------------------------------------- + * + * List_InitElement -- + * + * Initialize a list element. Must be called before an element is first + * inserted into a list. + * + * ---------------------------------------------------------------------------- + */ +#define List_InitElement(elementPtr) \ + (elementPtr)->prevPtr = (List_Links *) NIL; \ + (elementPtr)->nextPtr = (List_Links *) NIL; + +/* + * Macros for stepping through or selecting parts of lists + */ + +/* + * ---------------------------------------------------------------------------- + * + * LIST_FORALL -- + * + * Macro to loop through a list and perform an operation on each member. + * + * Usage: LIST_FORALL(headerPtr, itemPtr) { + * / * + * * operation on itemPtr, which points to successive members + * * of the list + * * + * * It may be appropriate to first assign + * * foobarPtr = (Foobar *) itemPtr; + * * to refer to the entire Foobar structure. + * * / + * } + * + * Note: itemPtr must be a List_Links pointer variable, and headerPtr + * must evaluate to a pointer to a List_Links structure. + * + * ---------------------------------------------------------------------------- + */ + +#define LIST_FORALL(headerPtr, itemPtr) \ + for (itemPtr = List_First(headerPtr); \ + !List_IsAtEnd((headerPtr),itemPtr); \ + itemPtr = List_Next(itemPtr)) + +/* + * ---------------------------------------------------------------------------- + * + * List_IsEmpty -- + * + * Macro: Boolean value, TRUE if the given list does not contain any + * members. + * + * Usage: if (List_IsEmpty(headerPtr)) ... + * + * ---------------------------------------------------------------------------- + */ + +#define List_IsEmpty(headerPtr) \ + ((headerPtr) == (headerPtr)->nextPtr) + +/* + * ---------------------------------------------------------------------------- + * + * List_IsAtEnd -- + * + * Macro: Boolean value, TRUE if itemPtr is after the end of headerPtr + * (i.e., itemPtr is the header of the list). + * + * Usage: if (List_IsAtEnd(headerPtr, itemPtr)) ... + * + * ---------------------------------------------------------------------------- + */ + + +#define List_IsAtEnd(headerPtr, itemPtr) \ + ((itemPtr) == (headerPtr)) + + +/* + * ---------------------------------------------------------------------------- + * + * List_First -- + * + * Macro to return the first member in a list, which is the header if + * the list is empty. + * + * Usage: firstPtr = List_First(headerPtr); + * + * ---------------------------------------------------------------------------- + */ + +#define List_First(headerPtr) ((headerPtr)->nextPtr) + +/* + * ---------------------------------------------------------------------------- + * + * List_Last -- + * + * Macro to return the last member in a list, which is the header if + * the list is empty. + * + * Usage: lastPtr = List_Last(headerPtr); + * + * ---------------------------------------------------------------------------- + */ + +#define List_Last(headerPtr) ((headerPtr)->prevPtr) + +/* + * ---------------------------------------------------------------------------- + * + * List_Prev -- + * + * Macro to return the member preceding the given member in its list. + * If the given list member is the first element in the list, List_Prev + * returns the list header. + * + * Usage: prevPtr = List_Prev(itemPtr); + * + * ---------------------------------------------------------------------------- + */ + +#define List_Prev(itemPtr) ((itemPtr)->prevPtr) + +/* + * ---------------------------------------------------------------------------- + * + * List_Next -- + * + * Macro to return the member following the given member in its list. + * If the given list member is the last element in the list, List_Next + * returns the list header. + * + * Usage: nextPtr = List_Next(itemPtr); + * + * ---------------------------------------------------------------------------- + */ + +#define List_Next(itemPtr) ((itemPtr)->nextPtr) + + +/* + * ---------------------------------------------------------------------------- + * The List_Insert procedure takes two arguments. The first argument + * is a pointer to the structure to be inserted into a list, and + * the second argument is a pointer to the list member after which + * the new element is to be inserted. Macros are used to determine + * which existing member will precede the new one. + * + * The List_Move procedure takes a destination argument with the same + * semantics as List_Insert. + * + * The following macros define where to insert the new element + * in the list: + * + * LIST_AFTER(itemPtr) -- insert after itemPtr + * LIST_BEFORE(itemPtr) -- insert before itemPtr + * LIST_ATFRONT(headerPtr) -- insert at front of list + * LIST_ATREAR(headerPtr) -- insert at end of list + * + * For example, + * + * List_Insert(itemPtr, LIST_AFTER(otherPtr)); + * + * will insert itemPtr following otherPtr in the list containing otherPtr. + * ---------------------------------------------------------------------------- + */ + +#define LIST_AFTER(itemPtr) ((List_Links *) itemPtr) + +#define LIST_BEFORE(itemPtr) (((List_Links *) itemPtr)->prevPtr) + +#define LIST_ATFRONT(headerPtr) ((List_Links *) headerPtr) + +#define LIST_ATREAR(headerPtr) (((List_Links *) headerPtr)->prevPtr) + +#endif _LIST diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h new file mode 100644 index 000000000000..11b6d15145ac --- /dev/null +++ b/usr.bin/make/lst.h @@ -0,0 +1,142 @@ +/* + * 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. + * + * @(#)lst.h 5.3 (Berkeley) 6/1/90 + */ + +/*- + * lst.h -- + * Header for using the list library + */ +#ifndef _LST_H_ +#define _LST_H_ + +#include <sprite.h> + +/* + * basic typedef. This is what the Lst_ functions handle + */ + +typedef struct Lst *Lst; +typedef struct LstNode *LstNode; + +#define NILLST ((Lst) NIL) +#define NILLNODE ((LstNode) NIL) + +/* + * NOFREE can be used as the freeProc to Lst_Destroy when the elements are + * not to be freed. + * NOCOPY performs similarly when given as the copyProc to Lst_Duplicate. + */ +#define NOFREE ((void (*)()) 0) +#define NOCOPY ((ClientData (*)()) 0) + +#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */ +#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */ + +/* + * Creation/destruction functions + */ +Lst Lst_Init(); /* Create a new list */ +Lst Lst_Duplicate(); /* Duplicate an existing list */ +void Lst_Destroy(); /* Destroy an old one */ + +int Lst_Length(); /* Find the length of a list */ +Boolean Lst_IsEmpty(); /* True if list is empty */ + +/* + * Functions to modify a list + */ +ReturnStatus Lst_Insert(); /* Insert an element before another */ +ReturnStatus Lst_Append(); /* Insert an element after another */ +ReturnStatus Lst_AtFront(); /* Place an element at the front of + * a lst. */ +ReturnStatus Lst_AtEnd(); /* Place an element at the end of a + * lst. */ +ReturnStatus Lst_Remove(); /* Remove an element */ +ReturnStatus Lst_Replace(); /* Replace a node with a new value */ +ReturnStatus Lst_Move(); /* Move an element to another place */ +ReturnStatus Lst_Concat(); /* Concatenate two lists */ + +/* + * Node-specific functions + */ +LstNode Lst_First(); /* Return first element in list */ +LstNode Lst_Last(); /* Return last element in list */ +LstNode Lst_Succ(); /* Return successor to given element */ +LstNode Lst_Pred(); /* Return predecessor to given + * element */ +ClientData Lst_Datum(); /* Get datum from LstNode */ + +/* + * Functions for entire lists + */ +LstNode Lst_Find(); /* Find an element in a list */ +LstNode Lst_FindFrom(); /* Find an element starting from + * somewhere */ +LstNode Lst_Member(); /* See if the given datum is on the + * list. Returns the LstNode containing + * the datum */ +int Lst_Index(); /* Returns the index of a datum in the + * list, starting from 0 */ +void Lst_ForEach(); /* Apply a function to all elements of + * a lst */ +void Lst_ForEachFrom(); /* Apply a function to all elements of + * a lst starting from a certain point. + * If the list is circular, the + * application will wrap around to the + * beginning of the list again. */ +/* + * these functions are for dealing with a list as a table, of sorts. + * An idea of the "current element" is kept and used by all the functions + * between Lst_Open() and Lst_Close(). + */ +ReturnStatus Lst_Open(); /* Open the list */ +LstNode Lst_Prev(); /* Previous element */ +LstNode Lst_Cur(); /* The current element, please */ +LstNode Lst_Next(); /* Next element please */ +Boolean Lst_IsAtEnd(); /* Done yet? */ +void Lst_Close(); /* Finish table access */ + +/* + * for using the list as a queue + */ +ReturnStatus Lst_EnQueue(); /* Place an element at tail of queue */ +ClientData Lst_DeQueue(); /* Remove an element from head of + * queue */ + +#endif _LST_H_ diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c new file mode 100644 index 000000000000..411ed803f9c2 --- /dev/null +++ b/usr.bin/make/lst.lib/lstAppend.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstAppend.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstAppend.c -- + * Add a new node with a new datum after an existing node + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Append -- + * Create a new node and add it to the given list after the given node. + * + * Results: + * SUCCESS if all went well. + * + * Side Effects: + * A new ListNode is created and linked in to the List. The lastPtr + * field of the List will be altered if ln is the last node in the + * list. lastPtr and firstPtr will alter if the list was empty and + * ln was NILLNODE. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Append (l, ln, d) + Lst l; /* affected list */ + LstNode ln; /* node after which to append the datum */ + ClientData d; /* said datum */ +{ + register List list; + register ListNode lNode; + register ListNode nLNode; + + if (LstValid (l) && (ln == NILLNODE && LstIsEmpty (l))) { + goto ok; + } + + if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) { + return (FAILURE); + } + ok: + + list = (List)l; + lNode = (ListNode)ln; + + PAlloc (nLNode, ListNode); + nLNode->datum = d; + nLNode->useCount = nLNode->flags = 0; + + if (lNode == NilListNode) { + if (list->isCirc) { + nLNode->nextPtr = nLNode->prevPtr = nLNode; + } else { + nLNode->nextPtr = nLNode->prevPtr = NilListNode; + } + list->firstPtr = list->lastPtr = nLNode; + } else { + nLNode->prevPtr = lNode; + nLNode->nextPtr = lNode->nextPtr; + + lNode->nextPtr = nLNode; + if (nLNode->nextPtr != NilListNode) { + nLNode->nextPtr->prevPtr = nLNode; + } + + if (lNode == list->lastPtr) { + list->lastPtr = nLNode; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c new file mode 100644 index 000000000000..23d63a321c0a --- /dev/null +++ b/usr.bin/make/lst.lib/lstAtEnd.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstAtEnd.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstAtEnd.c -- + * Add a node at the end of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_AtEnd -- + * Add a node to the end of the given list + * + * Results: + * SUCCESS if life is good. + * + * Side Effects: + * A new ListNode is created and added to the list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_AtEnd (l, d) + Lst l; /* List to which to add the datum */ + ClientData d; /* Datum to add */ +{ + register LstNode end; + + end = Lst_Last (l); + return (Lst_Append (l, end, d)); +} diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c new file mode 100644 index 000000000000..298b6e3c8721 --- /dev/null +++ b/usr.bin/make/lst.lib/lstAtFront.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstAtFront.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstAtFront.c -- + * Add a node at the front of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_AtFront -- + * Place a piece of data at the front of a list + * + * Results: + * SUCCESS or FAILURE + * + * Side Effects: + * A new ListNode is created and stuck at the front of the list. + * hence, firstPtr (and possible lastPtr) in the list are altered. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_AtFront (l, d) + Lst l; + ClientData d; +{ + register LstNode front; + + front = Lst_First (l); + return (Lst_Insert (l, front, d)); +} diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c new file mode 100644 index 000000000000..43de3dcf7034 --- /dev/null +++ b/usr.bin/make/lst.lib/lstClose.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstClose.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstClose.c -- + * Close a list for sequential access. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Close -- + * Close a list which was opened for sequential access. + * + * Results: + * None. + * + * Side Effects: + * The list is closed. + * + *----------------------------------------------------------------------- + */ +void +Lst_Close (l) + Lst l; /* The list to close */ +{ + register List list = (List) l; + + if (LstValid(l) == TRUE) { + list->isOpen = FALSE; + list->atEnd = Unknown; + } +} + diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c new file mode 100644 index 000000000000..6f013821bded --- /dev/null +++ b/usr.bin/make/lst.lib/lstConcat.c @@ -0,0 +1,174 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstConcat.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * listConcat.c -- + * Function to concatentate two lists. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Concat -- + * Concatenate two lists. New elements are created to hold the data + * elements, if specified, but the elements themselves are not copied. + * If the elements should be duplicated to avoid confusion with another + * list, the Lst_Duplicate function should be called first. + * If LST_CONCLINK is specified, the second list is destroyed since + * its pointers have been corrupted and the list is no longer useable. + * + * Results: + * SUCCESS if all went well. FAILURE otherwise. + * + * Side Effects: + * New elements are created and appended the the first list. + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Concat (l1, l2, flags) + Lst l1; /* The list to which l2 is to be appended */ + Lst l2; /* The list to append to l1 */ + int flags; /* LST_CONCNEW if LstNode's should be duplicated + * LST_CONCLINK if should just be relinked */ +{ + register ListNode ln; /* original LstNode */ + register ListNode nln; /* new LstNode */ + register ListNode last; /* the last element in the list. Keeps + * bookkeeping until the end */ + register List list1 = (List)l1; + register List list2 = (List)l2; + + if (!LstValid (l1) || !LstValid (l2)) { + return (FAILURE); + } + + if (flags == LST_CONCLINK) { + if (list2->firstPtr != NilListNode) { + /* + * We set the nextPtr of the + * last element of list two to be NIL to make the loop easier and + * so we don't need an extra case should the first list turn + * out to be non-circular -- the final element will already point + * to NIL space and the first element will be untouched if it + * existed before and will also point to NIL space if it didn't. + */ + list2->lastPtr->nextPtr = NilListNode; + /* + * So long as the second list isn't empty, we just link the + * first element of the second list to the last element of the + * first list. If the first list isn't empty, we then link the + * last element of the list to the first element of the second list + * The last element of the second list, if it exists, then becomes + * the last element of the first list. + */ + list2->firstPtr->prevPtr = list1->lastPtr; + if (list1->lastPtr != NilListNode) { + list1->lastPtr->nextPtr = list2->firstPtr; + } + list1->lastPtr = list2->lastPtr; + } + if (list1->isCirc && list1->firstPtr != NilListNode) { + /* + * If the first list is supposed to be circular and it is (now) + * non-empty, we must make sure it's circular by linking the + * first element to the last and vice versa + */ + list1->firstPtr->prevPtr = list1->lastPtr; + list1->lastPtr->nextPtr = list1->firstPtr; + } + free ((Address)l2); + } else if (list2->firstPtr != NilListNode) { + /* + * We set the nextPtr of the last element of list 2 to be nil to make + * the loop less difficult. The loop simply goes through the entire + * second list creating new LstNodes and filling in the nextPtr, and + * prevPtr to fit into l1 and its datum field from the + * datum field of the corresponding element in l2. The 'last' node + * follows the last of the new nodes along until the entire l2 has + * been appended. Only then does the bookkeeping catch up with the + * changes. During the first iteration of the loop, if 'last' is nil, + * the first list must have been empty so the newly-created node is + * made the first node of the list. + */ + list2->lastPtr->nextPtr = NilListNode; + for (last = list1->lastPtr, ln = list2->firstPtr; + ln != NilListNode; + ln = ln->nextPtr) + { + PAlloc (nln, ListNode); + nln->datum = ln->datum; + if (last != NilListNode) { + last->nextPtr = nln; + } else { + list1->firstPtr = nln; + } + nln->prevPtr = last; + nln->flags = nln->useCount = 0; + last = nln; + } + + /* + * Finish bookkeeping. The last new element becomes the last element + * of list one. + */ + list1->lastPtr = last; + + /* + * The circularity of both list one and list two must be corrected + * for -- list one because of the new nodes added to it; list two + * because of the alteration of list2->lastPtr's nextPtr to ease the + * above for loop. + */ + if (list1->isCirc) { + list1->lastPtr->nextPtr = list1->firstPtr; + list1->firstPtr->prevPtr = list1->lastPtr; + } else { + last->nextPtr = NilListNode; + } + + if (list2->isCirc) { + list2->lastPtr->nextPtr = list2->firstPtr; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c new file mode 100644 index 000000000000..225a030a3847 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDatum.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstDatum.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstDatum.c -- + * Return the datum associated with a list node. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Datum -- + * Return the datum stored in the given node. + * + * Results: + * The datum or (ick!) NIL if the node is invalid. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +ClientData +Lst_Datum (ln) + LstNode ln; +{ + if (ln != NILLNODE) { + return (((ListNode)ln)->datum); + } else { + return ((ClientData) NIL); + } +} + diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c new file mode 100644 index 000000000000..cce1f6593de6 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDeQueue.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstDeQueue.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstDeQueue.c -- + * Remove the node and return its datum from the head of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_DeQueue -- + * Remove and return the datum at the head of the given list. + * + * Results: + * The datum in the node at the head or (ick) NIL if the list + * is empty. + * + * Side Effects: + * The head node is removed from the list. + * + *----------------------------------------------------------------------- + */ +ClientData +Lst_DeQueue (l) + Lst l; +{ + ClientData rd; + register ListNode tln; + + tln = (ListNode) Lst_First (l); + if (tln == NilListNode) { + return ((ClientData) NIL); + } + + rd = tln->datum; + if (Lst_Remove (l, (LstNode)tln) == FAILURE) { + return ((ClientData) NIL); + } else { + return (rd); + } +} + diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c new file mode 100644 index 000000000000..4d528b1ebeb8 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDestroy.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstDestroy.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstDestroy.c -- + * Nuke a list and all its resources + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Destroy -- + * Destroy a list and free all its resources. If the freeProc is + * given, it is called with the datum from each node in turn before + * the node is freed. + * + * Results: + * None. + * + * Side Effects: + * The given list is freed in its entirety. + * + *----------------------------------------------------------------------- + */ +void +Lst_Destroy (l, freeProc) + Lst l; + register void (*freeProc)(); +{ + register ListNode ln; + register ListNode tln = NilListNode; + register List list = (List)l; + + if (l == NILLST || ! l) { + /* + * Note the check for l == (Lst)0 to catch uninitialized static Lst's. + * Gross, but useful. + */ + return; + } + + if (freeProc) { + for (ln = list->firstPtr; + ln != NilListNode && tln != list->firstPtr; + ln = tln) { + tln = ln->nextPtr; + (*freeProc) (ln->datum); + free ((Address)ln); + } + } else { + for (ln = list->firstPtr; + ln != NilListNode && tln != list->firstPtr; + ln = tln) { + tln = ln->nextPtr; + free ((Address)ln); + } + } + + free ((Address)l); +} diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c new file mode 100644 index 000000000000..5eac424119c5 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDupl.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstDupl.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * listDupl.c -- + * Duplicate a list. This includes duplicating the individual + * elements. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Duplicate -- + * Duplicate an entire list. If a function to copy a ClientData is + * given, the individual client elements will be duplicated as well. + * + * Results: + * The new Lst structure or NILLST if failure. + * + * Side Effects: + * A new list is created. + *----------------------------------------------------------------------- + */ +Lst +Lst_Duplicate (l, copyProc) + Lst l; /* the list to duplicate */ + ClientData (*copyProc)(); /* A function to duplicate each ClientData */ +{ + register Lst nl; + register ListNode ln; + register List list = (List)l; + + if (!LstValid (l)) { + return (NILLST); + } + + nl = Lst_Init (list->isCirc); + if (nl == NILLST) { + return (NILLST); + } + + ln = list->firstPtr; + while (ln != NilListNode) { + if (copyProc != NOCOPY) { + if (Lst_AtEnd (nl, (*copyProc) (ln->datum)) == FAILURE) { + return (NILLST); + } + } else if (Lst_AtEnd (nl, ln->datum) == FAILURE) { + return (NILLST); + } + + if (list->isCirc && ln == list->lastPtr) { + ln = NilListNode; + } else { + ln = ln->nextPtr; + } + } + + return (nl); +} diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c new file mode 100644 index 000000000000..3210e70f9605 --- /dev/null +++ b/usr.bin/make/lst.lib/lstEnQueue.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstEnQueue.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstEnQueue.c-- + * Treat the list as a queue and place a datum at its end + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_EnQueue -- + * Add the datum to the tail of the given list. + * + * Results: + * SUCCESS or FAILURE as returned by Lst_Append. + * + * Side Effects: + * the lastPtr field is altered all the time and the firstPtr field + * will be altered if the list used to be empty. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_EnQueue (l, d) + Lst l; + ClientData d; +{ + if (LstValid (l) == FALSE) { + return (FAILURE); + } + + return (Lst_Append (l, Lst_Last(l), d)); +} + diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c new file mode 100644 index 000000000000..d5a1ef4c3de2 --- /dev/null +++ b/usr.bin/make/lst.lib/lstFind.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstFind.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstFind.c -- + * Find a node on a list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Find -- + * Find a node on the given list using the given comparison function + * and the given datum. + * + * Results: + * The found node or NILLNODE if none matches. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Find (l, d, cProc) + Lst l; + ClientData d; + int (*cProc)(); +{ + return (Lst_FindFrom (l, Lst_First(l), d, cProc)); +} + diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c new file mode 100644 index 000000000000..bf4c36b0bf31 --- /dev/null +++ b/usr.bin/make/lst.lib/lstFindFrom.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstFindFrom.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstFindFrom.c -- + * Find a node on a list from a given starting point. Used by Lst_Find. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_FindFrom -- + * Search for a node starting and ending with the given one on the + * given list using the passed datum and comparison function to + * determine when it has been found. + * + * Results: + * The found node or NILLNODE + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_FindFrom (l, ln, d, cProc) + Lst l; + register LstNode ln; + register ClientData d; + register int (*cProc)(); +{ + register ListNode tln; + Boolean found = FALSE; + + if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { + return (NILLNODE); + } + + tln = (ListNode)ln; + + do { + if ((*cProc) (tln->datum, d) == 0) { + found = TRUE; + break; + } else { + tln = tln->nextPtr; + } + } while (tln != (ListNode)ln && tln != NilListNode); + + if (found) { + return ((LstNode)tln); + } else { + return (NILLNODE); + } +} + diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c new file mode 100644 index 000000000000..64feee2580f4 --- /dev/null +++ b/usr.bin/make/lst.lib/lstFirst.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstFirst.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstFirst.c -- + * Return the first node of a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_First -- + * Return the first node on the given list. + * + * Results: + * The first node or NILLNODE if the list is empty. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_First (l) + Lst l; +{ + if (!LstValid (l) || LstIsEmpty (l)) { + return (NILLNODE); + } else { + return ((LstNode)((List)l)->firstPtr); + } +} + diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c new file mode 100644 index 000000000000..68510b0c841d --- /dev/null +++ b/usr.bin/make/lst.lib/lstForEach.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstForEach.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstForeach.c -- + * Perform a given function on all elements of a list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_ForEach -- + * Apply the given function to each element of the given list. The + * function should return 0 if Lst_ForEach should continue and non- + * zero if it should abort. + * + * Results: + * None. + * + * Side Effects: + * Only those created by the passed-in function. + * + *----------------------------------------------------------------------- + */ +/*VARARGS2*/ +void +Lst_ForEach (l, proc, d) + Lst l; + register int (*proc)(); + register ClientData d; +{ + Lst_ForEachFrom(l, Lst_First(l), proc, d); +} + diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c new file mode 100644 index 000000000000..0165e87ecf77 --- /dev/null +++ b/usr.bin/make/lst.lib/lstForEachFrom.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstForEachFrom.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * lstForEachFrom.c -- + * Perform a given function on all elements of a list starting from + * a given point. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_ForEachFrom -- + * Apply the given function to each element of the given list. The + * function should return 0 if traversal should continue and non- + * zero if it should abort. + * + * Results: + * None. + * + * Side Effects: + * Only those created by the passed-in function. + * + *----------------------------------------------------------------------- + */ +/*VARARGS2*/ +void +Lst_ForEachFrom (l, ln, proc, d) + Lst l; + LstNode ln; + register int (*proc)(); + register ClientData d; +{ + register ListNode tln = (ListNode)ln; + register List list = (List)l; + register ListNode next; + Boolean done; + int result; + + if (!LstValid (list) || LstIsEmpty (list)) { + return; + } + + do { + /* + * Take care of having the current element deleted out from under + * us. + */ + + next = tln->nextPtr; + + tln->useCount++; + result = (*proc) (tln->datum, d); + tln->useCount--; + + /* + * We're done with the traversal if + * - nothing's been added after the current node and + * - the next node to examine is the first in the queue or + * doesn't exist. + */ + done = (next == tln->nextPtr && + (next == NilListNode || next == list->firstPtr)); + + next = tln->nextPtr; + + if (tln->flags & LN_DELETED) { + free((char *)tln); + } + tln = next; + } while (!result && !LstIsEmpty(list) && !done); + +} + diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c new file mode 100644 index 000000000000..7747662d539d --- /dev/null +++ b/usr.bin/make/lst.lib/lstInit.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstInit.c 5.4 (Berkeley) 12/28/90"; +#endif /* not lint */ + +/*- + * init.c -- + * Initialize a new linked list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Init -- + * Create and initialize a new list. + * + * Results: + * The created list. + * + * Side Effects: + * A list is created, what else? + * + *----------------------------------------------------------------------- + */ +Lst +Lst_Init(circ) + Boolean circ; /* TRUE if the list should be made circular */ +{ + register List nList; + + PAlloc (nList, List); + + nList->firstPtr = NilListNode; + nList->lastPtr = NilListNode; + nList->isOpen = FALSE; + nList->isCirc = circ; + nList->atEnd = Unknown; + + return ((Lst)nList); +} diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c new file mode 100644 index 000000000000..003211dbe05a --- /dev/null +++ b/usr.bin/make/lst.lib/lstInsert.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstInsert.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstInsert.c -- + * Insert a new datum before an old one + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Insert -- + * Insert a new node with the given piece of data before the given + * node in the given list. + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * the firstPtr field will be changed if ln is the first node in the + * list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Insert (l, ln, d) + Lst l; /* list to manipulate */ + LstNode ln; /* node before which to insert d */ + ClientData d; /* datum to be inserted */ +{ + register ListNode nLNode; /* new lnode for d */ + register ListNode lNode = (ListNode)ln; + register List list = (List)l; + + + /* + * check validity of arguments + */ + if (LstValid (l) && (LstIsEmpty (l) && ln == NILLNODE)) + goto ok; + + if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { + return (FAILURE); + } + + ok: + PAlloc (nLNode, ListNode); + + nLNode->datum = d; + nLNode->useCount = nLNode->flags = 0; + + if (ln == NILLNODE) { + if (list->isCirc) { + nLNode->prevPtr = nLNode->nextPtr = nLNode; + } else { + nLNode->prevPtr = nLNode->nextPtr = NilListNode; + } + list->firstPtr = list->lastPtr = nLNode; + } else { + nLNode->prevPtr = lNode->prevPtr; + nLNode->nextPtr = lNode; + + if (nLNode->prevPtr != NilListNode) { + nLNode->prevPtr->nextPtr = nLNode; + } + lNode->prevPtr = nLNode; + + if (lNode == list->firstPtr) { + list->firstPtr = nLNode; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h new file mode 100644 index 000000000000..5ed983d3299d --- /dev/null +++ b/usr.bin/make/lst.lib/lstInt.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + * + * @(#)lstInt.h 5.4 (Berkeley) 12/28/90 + */ + +/*- + * lstInt.h -- + * Internals for the list library + */ +#ifndef _LSTINT_H_ +#define _LSTINT_H_ + +#include "lst.h" + +typedef struct ListNode { + struct ListNode *prevPtr; /* previous element in list */ + struct ListNode *nextPtr; /* next in list */ + short useCount:8, /* Count of functions using the node. + * node may not be deleted until count + * goes to 0 */ + flags:8; /* Node status flags */ + ClientData datum; /* datum associated with this element */ +} *ListNode; +/* + * Flags required for synchronization + */ +#define LN_DELETED 0x0001 /* List node should be removed when done */ + +#define NilListNode ((ListNode)-1) + +typedef enum { + Head, Middle, Tail, Unknown +} Where; + +typedef struct { + ListNode firstPtr; /* first node in list */ + ListNode lastPtr; /* last node in list */ + Boolean isCirc; /* true if the list should be considered + * circular */ +/* + * fields for sequential access + */ + Where atEnd; /* Where in the list the last access was */ + Boolean isOpen; /* true if list has been Lst_Open'ed */ + ListNode curPtr; /* current node, if open. NilListNode if + * *just* opened */ + ListNode prevPtr; /* Previous node, if open. Used by + * Lst_Remove */ +} *List; + +#define NilList ((List)-1) + +/* + * PAlloc (var, ptype) -- + * Allocate a pointer-typedef structure 'ptype' into the variable 'var' + */ +#define PAlloc(var,ptype) var = (ptype) malloc (sizeof (*var)) + +/* + * LstValid (l) -- + * Return TRUE if the list l is valid + */ +#define LstValid(l) (((Lst)l == NILLST) ? FALSE : TRUE) + +/* + * LstNodeValid (ln, l) -- + * Return TRUE if the LstNode ln is valid with respect to l + */ +#define LstNodeValid(ln, l) ((((LstNode)ln) == NILLNODE) ? FALSE : TRUE) + +/* + * LstIsEmpty (l) -- + * TRUE if the list l is empty. + */ +#define LstIsEmpty(l) (((List)l)->firstPtr == NilListNode) + +#endif _LSTINT_H_ diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c new file mode 100644 index 000000000000..e8e25b3267fd --- /dev/null +++ b/usr.bin/make/lst.lib/lstIsAtEnd.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstIsAtEnd.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstIsAtEnd.c -- + * Tell if the current node is at the end of the list. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_IsAtEnd -- + * Return true if have reached the end of the given list. + * + * Results: + * TRUE if at the end of the list (this includes the list not being + * open or being invalid) or FALSE if not. We return TRUE if the list + * is invalid or unopend so as to cause the caller to exit its loop + * asap, the assumption being that the loop is of the form + * while (!Lst_IsAtEnd (l)) { + * ... + * } + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Lst_IsAtEnd (l) + Lst l; +{ + register List list = (List) l; + + return (!LstValid (l) || !list->isOpen || + (list->atEnd == Head) || (list->atEnd == Tail)); +} + diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c new file mode 100644 index 000000000000..dc92ae1136f1 --- /dev/null +++ b/usr.bin/make/lst.lib/lstIsEmpty.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstIsEmpty.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstIsEmpty.c -- + * A single function to decide if a list is empty + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_IsEmpty -- + * Return TRUE if the given list is empty. + * + * Results: + * TRUE if the list is empty, FALSE otherwise. + * + * Side Effects: + * None. + * + * A list is considered empty if its firstPtr == NilListNode (or if + * the list itself is NILLIST). + *----------------------------------------------------------------------- + */ +Boolean +Lst_IsEmpty (l) + Lst l; +{ + return ( ! LstValid (l) || LstIsEmpty(l)); +} + diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c new file mode 100644 index 000000000000..42f4e67ea428 --- /dev/null +++ b/usr.bin/make/lst.lib/lstLast.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstLast.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstLast.c -- + * Return the last element of a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Last -- + * Return the last node on the list l. + * + * Results: + * The requested node or NILLNODE if the list is empty. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Last (l) + Lst l; +{ + if (!LstValid(l) || LstIsEmpty (l)) { + return (NILLNODE); + } else { + return ((LstNode)((List)l)->lastPtr); + } +} + diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c new file mode 100644 index 000000000000..56e4e3b2458a --- /dev/null +++ b/usr.bin/make/lst.lib/lstMember.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstMember.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * lstMember.c -- + * See if a given datum is on a given list. + */ + +#include "lstInt.h" + +LstNode +Lst_Member (l, d) + Lst l; + ClientData d; +{ + List list = (List) l; + register ListNode lNode; + + lNode = list->firstPtr; + if (lNode == NilListNode) { + return NILLNODE; + } + + do { + if (lNode->datum == d) { + return (LstNode)lNode; + } + lNode = lNode->nextPtr; + } while (lNode != NilListNode && lNode != list->firstPtr); + + return NILLNODE; +} diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c new file mode 100644 index 000000000000..789d04b8fa7c --- /dev/null +++ b/usr.bin/make/lst.lib/lstNext.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstNext.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstNext.c -- + * Return the next node for a list. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Next -- + * Return the next node for the given list. + * + * Results: + * The next node or NILLNODE if the list has yet to be opened. Also + * if the list is non-circular and the end has been reached, NILLNODE + * is returned. + * + * Side Effects: + * the curPtr field is updated. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Next (l) + Lst l; +{ + register ListNode tln; + register List list = (List)l; + + if ((LstValid (l) == FALSE) || + (list->isOpen == FALSE)) { + return (NILLNODE); + } + + list->prevPtr = list->curPtr; + + if (list->curPtr == NilListNode) { + if (list->atEnd == Unknown) { + /* + * If we're just starting out, atEnd will be Unknown. + * Then we want to start this thing off in the right + * direction -- at the start with atEnd being Middle. + */ + list->curPtr = tln = list->firstPtr; + list->atEnd = Middle; + } else { + tln = NilListNode; + list->atEnd = Tail; + } + } else { + tln = list->curPtr->nextPtr; + list->curPtr = tln; + + if (tln == list->firstPtr || tln == NilListNode) { + /* + * If back at the front, then we've hit the end... + */ + list->atEnd = Tail; + } else { + /* + * Reset to Middle if gone past first. + */ + list->atEnd = Middle; + } + } + + return ((LstNode)tln); +} + diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c new file mode 100644 index 000000000000..be0ab899e5d6 --- /dev/null +++ b/usr.bin/make/lst.lib/lstOpen.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstOpen.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstOpen.c -- + * Open a list for sequential access. The sequential functions access the + * list in a slightly different way. CurPtr points to their idea of the + * current node in the list and they access the list based on it. + * If the list is circular, Lst_Next and Lst_Prev will go around + * the list forever. Lst_IsAtEnd must be used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Open -- + * Open a list for sequential access. A list can still be searched, + * etc., without confusing these functions. + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * isOpen is set TRUE and curPtr is set to NilListNode so the + * other sequential functions no it was just opened and can choose + * the first element accessed based on this. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Open (l) + register Lst l; +{ + if (LstValid (l) == FALSE) { + return (FAILURE); + } + ((List) l)->isOpen = TRUE; + ((List) l)->atEnd = LstIsEmpty (l) ? Head : Unknown; + ((List) l)->curPtr = NilListNode; + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c new file mode 100644 index 000000000000..89853acaf811 --- /dev/null +++ b/usr.bin/make/lst.lib/lstRemove.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstRemove.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstRemove.c -- + * Remove an element from a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Remove -- + * Remove the given node from the given list. + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * The list's firstPtr will be set to NilListNode if ln is the last + * node on the list. firsPtr and lastPtr will be altered if ln is + * either the first or last node, respectively, on the list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Remove (l, ln) + Lst l; + LstNode ln; +{ + register List list = (List) l; + register ListNode lNode = (ListNode) ln; + + if (!LstValid (l) || + !LstNodeValid (ln, l)) { + return (FAILURE); + } + + /* + * unlink it from the list + */ + if (lNode->nextPtr != NilListNode) { + lNode->nextPtr->prevPtr = lNode->prevPtr; + } + if (lNode->prevPtr != NilListNode) { + lNode->prevPtr->nextPtr = lNode->nextPtr; + } + + /* + * if either the firstPtr or lastPtr of the list point to this node, + * adjust them accordingly + */ + if (list->firstPtr == lNode) { + list->firstPtr = lNode->nextPtr; + } + if (list->lastPtr == lNode) { + list->lastPtr = lNode->prevPtr; + } + + /* + * Sequential access stuff. If the node we're removing is the current + * node in the list, reset the current node to the previous one. If the + * previous one was non-existent (prevPtr == NilListNode), we set the + * end to be Unknown, since it is. + */ + if (list->isOpen && (list->curPtr == lNode)) { + list->curPtr = list->prevPtr; + if (list->curPtr == NilListNode) { + list->atEnd = Unknown; + } + } + + /* + * the only way firstPtr can still point to ln is if ln is the last + * node on the list (the list is circular, so lNode->nextptr == lNode in + * this case). The list is, therefore, empty and is marked as such + */ + if (list->firstPtr == lNode) { + list->firstPtr = NilListNode; + } + + /* + * note that the datum is unmolested. The caller must free it as + * necessary and as expected. + */ + if (lNode->useCount == 0) { + free ((Address)ln); + } else { + lNode->flags |= LN_DELETED; + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c new file mode 100644 index 000000000000..3600b3807bbe --- /dev/null +++ b/usr.bin/make/lst.lib/lstReplace.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstReplace.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstReplace.c -- + * Replace the datum in a node with a new datum + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Replace -- + * Replace the datum in the given node with the new datum + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * The datum field fo the node is altered. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Replace (ln, d) + register LstNode ln; + ClientData d; +{ + if (ln == NILLNODE) { + return (FAILURE); + } else { + ((ListNode) ln)->datum = d; + return (SUCCESS); + } +} + diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c new file mode 100644 index 000000000000..115e896e7e92 --- /dev/null +++ b/usr.bin/make/lst.lib/lstSucc.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)lstSucc.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * LstSucc.c -- + * return the successor to a given node + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Succ -- + * Return the sucessor to the given node on its list. + * + * Results: + * The successor of the node, if it exists (note that on a circular + * list, if the node is the only one in the list, it is its own + * successor). + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Succ (ln) + LstNode ln; +{ + if (ln == NILLNODE) { + return (NILLNODE); + } else { + return ((LstNode) ((ListNode) ln)->nextPtr); + } +} + diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c new file mode 100644 index 000000000000..6a4195506cbd --- /dev/null +++ b/usr.bin/make/main.c @@ -0,0 +1,792 @@ +/* + * 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. + * + */ + +#ifndef lint +char copyright[] = +"@(#) Copyright (c) 1989 The Regents of the University of California.\n\ + All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)main.c 5.25 (Berkeley) 4/1/91"; +#endif /* not lint */ + +/*- + * main.c -- + * The main file for this entire program. Exit routines etc + * reside here. + * + * Utility functions defined in this file: + * Main_ParseArgLine Takes a line of arguments, breaks them and + * treats them as if they were given when first + * invoked. Used by the parse module to implement + * the .MFLAGS target. + * + * Error Print a tagged error message. The global + * MAKE variable must have been defined. This + * takes a format string and two optional + * arguments for it. + * + * Fatal Print an error message and exit. Also takes + * a format string and two arguments. + * + * Punt Aborts all jobs and exits with a message. Also + * takes a format string and two arguments. + * + * Finish Finish things up by printing the number of + * errors which occured, as passed to it, and + * exiting. + */ + +#include <sys/param.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <varargs.h> +#include "make.h" +#include "pathnames.h" + +#ifndef DEFMAXLOCAL +#define DEFMAXLOCAL DEFMAXJOBS +#endif DEFMAXLOCAL + +#define MAKEFLAGS ".MAKEFLAGS" + +Lst create; /* Targets to be made */ +time_t now; /* Time at start of make */ +GNode *DEFAULT; /* .DEFAULT node */ +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 */ +static int maxLocal; /* -L argument */ +Boolean debug; /* -d flag */ +Boolean noExecute; /* -n flag */ +Boolean keepgoing; /* -k flag */ +Boolean queryFlag; /* -q flag */ +Boolean touchFlag; /* -t flag */ +Boolean usePipes; /* !-P flag */ +Boolean ignoreErrors; /* -i flag */ +Boolean beSilent; /* -s flag */ +Boolean oldVars; /* variable substitution style */ +Boolean checkEnvFirst; /* -e flag */ +static Boolean jobsRunning; /* TRUE if the jobs might be running */ + +static Boolean ReadMakefile(); + +static char *curdir; /* pathname of dir where make ran */ +static int obj_is_elsewhere; /* if chdir'd for an architecture */ + +/*- + * MainParseArgs -- + * Parse a given argument vector. Called from main() and from + * Main_ParseArgLine() when the .MAKEFLAGS target is used. + * + * XXX: Deal with command line overriding .MAKEFLAGS in makefile + * + * Results: + * None + * + * Side Effects: + * Various global and local flags will be set depending on the flags + * given + */ +static void +MainParseArgs(argc, argv) + int argc; + char **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:d:ef:ij:knqrst")) != EOF) { + switch(c) { + case 'D': + Var_Set(optarg, "1", VAR_GLOBAL); + Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); + Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); + break; + case 'I': + Parse_AddIncludeDir(optarg); + Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); + Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); + break; +#ifdef notdef + case 'L': + maxLocal = atoi(optarg); + Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL); + Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); + break; + case 'P': + usePipes = FALSE; + Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL); + break; + case 'S': + keepgoing = FALSE; + Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); + break; +#endif + case 'd': { + char *modules = optarg; + + for (; *modules; ++modules) + switch (*modules) { + case 'A': + debug = ~0; + break; + case 'a': + debug |= DEBUG_ARCH; + break; + case 'c': + debug |= DEBUG_COND; + break; + case 'd': + debug |= DEBUG_DIR; + break; + case 'g': + if (modules[1] == '1') { + debug |= DEBUG_GRAPH1; + ++modules; + } + else if (modules[1] == '2') { + debug |= DEBUG_GRAPH2; + ++modules; + } + break; + case 'j': + debug |= DEBUG_JOB; + break; + case 'm': + debug |= DEBUG_MAKE; + break; + case 's': + debug |= DEBUG_SUFF; + break; + case 't': + debug |= DEBUG_TARG; + break; + case 'v': + debug |= DEBUG_VAR; + break; + default: + (void)fprintf(stderr, + "make: illegal argument to d option -- %c\n", + *modules); + usage(); + } + Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); + Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); + break; + } + case 'e': + checkEnvFirst = TRUE; + Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); + break; + case 'f': + (void)Lst_AtEnd(makefiles, (ClientData)optarg); + break; + case 'i': + ignoreErrors = TRUE; + Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); + break; + case 'j': + maxJobs = atoi(optarg); + Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); + Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL); + break; + case 'k': + keepgoing = TRUE; + Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); + break; + case 'n': + noExecute = TRUE; + Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); + break; + case 'q': + queryFlag = TRUE; + /* Kind of nonsensical, wot? */ + Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); + break; + case 'r': + noBuiltins = TRUE; + Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); + break; + case 's': + beSilent = TRUE; + Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); + break; + case 't': + touchFlag = TRUE; + Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); + break; + default: + case '?': + usage(); + } + } + + oldVars = TRUE; + + /* + * See if the rest of the arguments are variable assignments and + * perform them if so. Else take them to be targets and stuff them + * on the end of the "create" list. + */ + for (argv += optind, argc -= optind; *argv; ++argv, --argc) + if (Parse_IsVar(*argv)) + Parse_DoVar(*argv, VAR_CMD); + else { + 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; /* - */ + goto rearg; + } + (void)Lst_AtEnd(create, (ClientData)*argv); + } +} + +/*- + * Main_ParseArgLine -- + * Used by the parse module when a .MFLAGS or .MAKEFLAGS target + * is encountered and by main() when reading the .MAKEFLAGS envariable. + * Takes a line of arguments and breaks it into its + * component words and passes those words and the number of them to the + * MainParseArgs function. + * The line should have all its leading whitespace removed. + * + * Results: + * None + * + * Side Effects: + * Only those that come from the various arguments. + */ +void +Main_ParseArgLine(line) + char *line; /* Line to fracture */ +{ + char **argv; /* Manufactured argument vector */ + int argc; /* Number of arguments in argv */ + + if (line == NULL) + return; + for (; *line == ' '; ++line); + if (!*line) + return; + + argv = brk_string(line, &argc); + MainParseArgs(argc, argv); +} + +/*- + * main -- + * The main function, for obvious reasons. Initializes variables + * and a few modules, then parses the arguments give it in the + * environment and on the command line. Reads the system makefile + * followed by either Makefile, makefile or the file given by the + * -f argument. Sets the .MAKEFLAGS PMake variable based on all the + * flags it has received by then uses either the Make or the Compat + * module to create the initial list of targets. + * + * Results: + * If -q was given, exits -1 if anything was out-of-date. Else it exits + * 0. + * + * Side Effects: + * The program exits when done. Targets are created. etc. etc. etc. + */ +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 *p, *path, *getenv(); + + /* + * if the MAKEOBJDIR (or by default, the _PATH_OBJDIR) directory + * exists, change into it and build there. Once things are + * initted, have to add the original directory to the search path, + * and modify the paths for the Makefiles apropriately. The + * current directory is also placed as a variable for make scripts. + */ + if (!(path = getenv("MAKEOBJDIR"))) + path = _PATH_OBJDIR; + curdir = emalloc((u_int)MAXPATHLEN + 1); + if (!getwd(curdir)) { + (void)fprintf(stderr, "make: %s.\n", curdir); + exit(2); + } + if (!lstat(path, &sb)) { + obj_is_elsewhere = 1; + if (chdir(path)) { + (void)fprintf(stderr, "make: fatal: %s exists, but I can't chdir to it: %s.\n", + path, strerror(errno)); + exit(2); + } + } + + create = Lst_Init(FALSE); + makefiles = Lst_Init(FALSE); + beSilent = FALSE; /* Print commands as executed */ + ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + noExecute = FALSE; /* Execute all commands */ + keepgoing = FALSE; /* Stop on error */ + allPrecious = FALSE; /* Remove targets when interrupted */ + queryFlag = FALSE; /* This is not just a check-run */ + noBuiltins = FALSE; /* Read the built-in rules */ + touchFlag = FALSE; /* Actually update targets */ + usePipes = TRUE; /* Catch child output in pipes */ + debug = 0; /* No debug verbosity, please. */ + jobsRunning = FALSE; + + maxJobs = DEFMAXJOBS; /* Set default max concurrency */ + maxLocal = DEFMAXLOCAL; /* Set default local max concurrency */ + + /* + * Initialize the parsing, directory and variable modules to prepare + * for the reading of inclusion paths and variable settings on the + * command line + */ + Dir_Init(); /* Initialize directory structures so -I flags + * can be processed correctly */ + Parse_Init(); /* Need to initialize the paths of #include + * directories */ + Var_Init(); /* As well as the lists of variables for + * parsing arguments */ + + if (obj_is_elsewhere) + Dir_AddDir(dirSearchPath, curdir); + Var_Set(".CURDIR", curdir, VAR_GLOBAL); + + /* + * Initialize various variables. + * MAKE also gets this name, for compatibility + * .MAKEFLAGS gets set to the empty string just in case. + * MFLAGS also gets initialized empty, for compatibility. + */ + Var_Set("MAKE", argv[0], VAR_GLOBAL); + Var_Set(MAKEFLAGS, "", VAR_GLOBAL); + Var_Set("MFLAGS", "", VAR_GLOBAL); + Var_Set("MACHINE", MACHINE, VAR_GLOBAL); + + /* + * First snag any flags out of the MAKE environment variable. + * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's + * in a different format). + */ +#ifdef POSIX + Main_ParseArgLine(getenv("MAKEFLAGS")); +#else + Main_ParseArgLine(getenv("MAKE")); +#endif + + MainParseArgs(argc, argv); + + /* + * Initialize archive, target and suffix modules in preparation for + * parsing the makefile(s) + */ + Arch_Init(); + Targ_Init(); + Suff_Init(); + + DEFAULT = NILGNODE; + (void)time(&now); + + /* + * Set up the .TARGETS variable to contain the list of targets to be + * created. If none specified, make the variable empty -- the parser + * will fill the thing in with the default or .MAIN target. + */ + if (!Lst_IsEmpty(create)) { + LstNode ln; + + for (ln = Lst_First(create); ln != NILLNODE; + ln = Lst_Succ(ln)) { + char *name = (char *)Lst_Datum(ln); + + Var_Append(".TARGETS", name, VAR_GLOBAL); + } + } else + Var_Set(".TARGETS", "", VAR_GLOBAL); + + /* + * Read in the built-in rules first, followed by the specified makefile, + * if it was (makefile != (char *) NULL), or the default Makefile and + * makefile, in that order, if it wasn't. + */ + if (!noBuiltins && !ReadMakefile(_PATH_DEFSYSMK)) + Fatal("make: no system rules (%s).", _PATH_DEFSYSMK); + + if (!Lst_IsEmpty(makefiles)) { + LstNode ln; + + ln = Lst_Find(makefiles, (ClientData)NULL, ReadMakefile); + if (ln != NILLNODE) + Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); + } else if (!ReadMakefile("makefile")) + (void)ReadMakefile("Makefile"); + + (void)ReadMakefile(".depend"); + + 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) +#ifdef POSIX + setenv("MAKEFLAGS", p, 1); +#else + setenv("MAKE", p, 1); +#endif + + /* + * For compatibility, look at the directories in the VPATH variable + * and add them to the search path, if the variable is defined. The + * variable's value is in the same format as the PATH envariable, i.e. + * <directory>:<directory>:<directory>... + */ + if (Var_Exists("VPATH", VAR_CMD)) { + char *vpath, *path, *cp, savec; + /* + * GCC stores string constants in read-only memory, but + * Var_Subst will want to write this thing, so store it + * in an array + */ + static char VPATH[] = "${VPATH}"; + + vpath = Var_Subst(VPATH, VAR_CMD, FALSE); + path = vpath; + do { + /* skip to end of directory */ + for (cp = path; *cp != ':' && *cp != '\0'; cp++); + /* Save terminator character so know when to stop */ + savec = *cp; + *cp = '\0'; + /* Add directory to search path */ + Dir_AddDir(dirSearchPath, path); + *cp = savec; + path = cp + 1; + } while (savec == ':'); + (void)free((Address)vpath); + } + + /* + * Now that all search paths have been read for suffixes et al, it's + * time to add the default search path to their lists... + */ + Suff_DoPaths(); + + /* print the initial graph, if the user requested it */ + if (DEBUG(GRAPH1)) + Targ_PrintGraph(1); + + /* + * Have now read the entire graph and need to make a list of targets + * to create. If none was given on the command line, we consult the + * parsing module to find the main target(s) to create. + */ + if (Lst_IsEmpty(create)) + targs = Parse_MainName(); + else + targs = Targ_FindList(create, TARG_CREATE); + +/* + * this was original amMake -- want to allow parallelism, so put this + * back in, eventually. + */ + if (0) { + /* + * Initialize job module before traversing the graph, now that + * any .BEGIN and .END targets have been read. This is done + * only if the -q flag wasn't given (to prevent the .BEGIN from + * being executed should it exist). + */ + if (!queryFlag) { + if (maxLocal == -1) + maxLocal = maxJobs; + Job_Init(maxJobs, maxLocal); + jobsRunning = TRUE; + } + + /* Traverse the graph, checking on all the targets */ + outOfDate = Make_Run(targs); + } else + /* + * Compat_Init will take care of creating all the targets as + * well as initializing the module. + */ + Compat_Run(targs); + + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + if (queryFlag && outOfDate) + exit(1); + else + exit(0); +} + +/*- + * ReadMakefile -- + * Open and parse the given makefile. + * + * Results: + * TRUE if ok. FALSE if couldn't open file. + * + * Side Effects: + * lots + */ +static Boolean +ReadMakefile(fname) + char *fname; /* makefile to read */ +{ + extern Lst parseIncPath, sysIncPath; + FILE *stream; + char *name, path[MAXPATHLEN + 1]; + + if (!strcmp(fname, "-")) { + Parse_File("(stdin)", stdin); + Var_Set("MAKEFILE", "", VAR_GLOBAL); + } else { + if (stream = fopen(fname, "r")) + goto found; + /* if we've chdir'd, rebuild the path name */ + if (obj_is_elsewhere && *fname != '/') { + (void)sprintf(path, "%s/%s", curdir, fname); + if (stream = fopen(path, "r")) { + fname = path; + goto found; + } + } + /* look in -I and system include directories. */ + name = Dir_FindFile(fname, parseIncPath); + if (!name) + name = Dir_FindFile(fname, sysIncPath); + if (!name || !(stream = fopen(name, "r"))) + return(FALSE); + fname = name; + /* + * set the MAKEFILE variable desired by System V fans -- the + * placement of the setting here means it gets set to the last + * makefile specified, as it is set by SysV make. + */ +found: Var_Set("MAKEFILE", fname, VAR_GLOBAL); + Parse_File(fname, stream); + (void)fclose(stream); + } + return(TRUE); +} + +/*- + * Error -- + * Print an error message given its format. + * + * Results: + * None. + * + * Side Effects: + * The message is printed. + */ +/* VARARGS */ +void +Error(va_alist) + va_dcl +{ + va_list ap; + char *fmt; + + va_start(ap); + fmt = va_arg(ap, char *); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); +} + +/*- + * Fatal -- + * Produce a Fatal error message. If jobs are running, waits for them + * to finish. + * + * Results: + * None + * + * Side Effects: + * The program exits + */ +/* VARARGS */ +void +Fatal(va_alist) + va_dcl +{ + va_list ap; + char *fmt; + + if (jobsRunning) + Job_Wait(); + + va_start(ap); + fmt = va_arg(ap, char *); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + exit(2); /* Not 1 so -q can distinguish error */ +} + +/* + * Punt -- + * Major exception once jobs are being created. Kills all jobs, prints + * a message and exits. + * + * Results: + * None + * + * Side Effects: + * All children are killed indiscriminately and the program Lib_Exits + */ +/* VARARGS */ +void +Punt(va_alist) + va_dcl +{ + va_list ap; + char *fmt; + + (void)fprintf(stderr, "make: "); + va_start(ap); + fmt = va_arg(ap, char *); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + + DieHorribly(); +} + +/*- + * DieHorribly -- + * Exit without giving a message. + * + * Results: + * None + * + * Side Effects: + * A big one... + */ +void +DieHorribly() +{ + if (jobsRunning) + Job_AbortAll(); + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + exit(2); /* Not 1, so -q can distinguish error */ +} + +/* + * Finish -- + * Called when aborting due to errors in child shell to signal + * abnormal exit. + * + * Results: + * None + * + * Side Effects: + * The program exits + */ +void +Finish(errors) + int errors; /* number of errors encountered in Make_Make */ +{ + Fatal("%d error%s", errors, errors == 1 ? "" : "s"); +} + +/* + * emalloc -- + * malloc, but die on error. + */ +char * +emalloc(len) + u_int len; +{ + char *p, *malloc(); + + if (!(p = malloc(len))) + enomem(); + return(p); +} + +/* + * enomem -- + * die when out of memory. + */ +enomem() +{ + (void)fprintf(stderr, "make: %s.\n", strerror(errno)); + exit(2); +} + +/* + * usage -- + * exit with usage message + */ +usage() +{ + (void)fprintf(stderr, +"usage: make [-eiknqrst] [-D variable] [-d flags] [-f makefile ]\n\ + [-I directory] [-j max_jobs] [variable=value]\n"); + exit(2); +} diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 new file mode 100644 index 000000000000..2b505ac88a20 --- /dev/null +++ b/usr.bin/make/make.1 @@ -0,0 +1,855 @@ +.\" Copyright (c) 1990 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. +.\" +.\" @(#)make.1 5.7 (Berkeley) 7/24/91 +.\" +.\" PATCHES MAGIC LEVEL PATCH THAT GOT US HERE +.\" -------------------- ----- ---------------------- +.\" CURRENT PATCH LEVEL: 1 00130 +.\" -------------------- ----- ---------------------- +.\" +.\" 06 Apr 93 Sascha Wildner Misc small fixes +.\" +.Dd July 24, 1991 +.Dt MAKE 1 +.Os +.Sh NAME +.Nm make +.Nd maintain program dependencies +.Sh SYNOPSIS +.Nm make +.Op Fl eiknqrstv +.Op Fl D Ar variable +.Op Fl d Ar flags +.Op Fl f Ar makefile +.Op Fl I Ar directory +.Bk -words +.Op Fl j Ar max_jobs +.Ek +.Op Ar variable=value +.Op Ar target ... +.Sh DESCRIPTION +.Nm Make +is a program designed to simplify the maintenance of other programs. +Its input is a list of specifications as to the files upon which programs +and other files depend. +If the file +.Ql Pa makefile +exists, it is read for this list of specifications. +If it does not exist, the file +.Ql Pa Makefile +is read. +If the file +.Ql Pa .depend +exists, it is read (see +.Xr mkdep 1) . +.Pp +This manual page is intended as a reference document only. +For a more thorough description of +.Nm make +and makefiles, please refer to +.%T "Make \- A Tutorial" . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D 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 +.Nm make +are to print debugging information. +.Ar Flags +is one or more of the following: +.Bl -tag -width Ds +.It Ar A +Print all possible debugging information; +equivalent to specifying all of the debugging flags. +.It Ar a +Print debugging information about archive searching and caching. +.It Ar c +Print debugging information about conditional evaluation. +.It Ar d +Print debugging information about directory searching and caching. +.It Ar "g1" +Print the input graph before making anything. +.It Ar "g2" +Print the input graph after making everything, or before exiting +on error. +.It Ar j +Print debugging information about running multiple shells. +.It Ar m +Print debugging information about making targets, including modification +dates. +.It Ar s +Print debugging information about suffix-transformation rules. +.It Ar t +Print debugging information about target list maintenance. +.It Ar v +Print debugging information about variable assignment. +.El +.It Fl e +Specify that environmental variables override macro assignments within +makefiles. +.It Fl f Ar makefile +Specify a makefile to read instead of the default +.Ql Pa makefile +and +.Ql Pa Makefile . +If +.Ar makefile +is +.Ql Fl , +standard input is read. +Multiple makefile's may be specified, and are read in the order specified. +.It Fl I Ar directory +Specify a directory in which to search for makefiles and included makefiles. +The system makefile directory is automatically included as part of this +list. +.It Fl i +Ignore non-zero exit of shell commands in the makefile. +Equivalent to specifying +.Ql Fl +before each command line in the makefile. +.It Fl j Ar max_jobs +Specify the maximum number of jobs that +.Nm make +may have running at any one time. +.It Fl k +Continue processing after errors are encountered, but only on those targets +that do not depend on the target whose creation caused the error. +.It Fl n +Display the commands that would have been executed, but do not actually +execute them. +.It Fl q +Do not execute any commands, but exit 0 if the specified targets are +up-to-date and 1, otherwise. +.It Fl r +Do not use the built-in rules specified in the system makefile. +.It Fl s +Do not echo any commands as they are executed. +Equivalent to specifying +.Ql Ic @ +before each command line in the makefile. +.It Fl t +Rather than re-building a target as specified in the makefile, create it +or update its modification time to make it appear up-to-date. +.It Ar variable=value +Set the value of the variable +.Ar variable +to +.Ar value . +.El +.Pp +There are six different types of lines in a makefile: file dependency +specifications, shell commands, variable assignments, include statements, +conditional directives, and comments. +.Pp +In general, lines may be continued from one line to the next by ending +them with a backslash +.Pq Ql \e . +The trailing newline character and initial whitespace on the following +line are compressed into a single space. +.Sh FILE DEPENDENCY SPECIFICATIONS +Dependency lines consist of one or more targets, an operator, and zero +or more sources. +This creates a relationship where the targets ``depend'' on the sources +and are usually created from them. +The exact relationship between the target and the source is determined +by the operator that separates them. +The three operators are as follows: +.Bl -tag -width flag +.It Ic \&: +A target is considered out-of-date if its modification time is less than +those of any of its sources. +Sources for a target accumulate over dependency lines when this operator +is used. +The target is removed if +.Nm make +is interrupted. +.It Ic \&! +Targets are always re-created, but not until all sources have been +examined and re-created as necessary. +Sources for a target accumulate over dependency lines when this operator +is used. +The target is removed if +.Nm make +is interrupted. +.It Ic \&:: +If no sources are specified, the target is always re-created. +Otherwise, a target is considered out-of-date if any of its sources has +been modified more recently than the target. +Sources for a target do not accumulate over dependency lines when this +operator is used. +The target will not be removed if +.Nm make +is interrupted. +.El +.Pp +Targets and sources may contain the shell wildcard values +.Ql ? , +.Ql * , +.Ql [] +and +.Ql {} . +The values +.Ql ? , +.Ql * +and +.Ql [] +may only be used as part of the final +component of the target or source, and must be used to describe existing +files. +The value +.Ql {} +need not necessarily be used to describe existing files. +Expansion is in directory order, not alphabetically as done in the shell. +.Sh SHELL COMMANDS +Each target may have associated with it a series of shell commands, normally +used to create the target. +Each of the commands in this script +.Em must +be preceded by a tab. +While any target may appear on a dependency line, only one of these +dependencies may be followed by a creation script, unless the +.Ql Ic :: +operator is used. +.Pp +If the first or first two characters of the command line are +.Ql Ic @ +and/or +.Ql Ic \- , +the command is treated specially. +A +.Ql Ic @ +causes the command not to be echoed before it is executed. +A +.Ql Ic \- +causes any non-zero exit status of the command line to be ignored. +.Sh VARIABLE ASSIGNMENTS +Variables in make are much like variables in the shell, and, by tradition, +consist of all upper-case letters. +The five operators that can be used to assign values to variables are as +follows: +.Bl -tag -width Ds +.It Ic \&= +Assign the value to the variable. +Any previous value is overridden. +.It Ic \&+= +Append the value to the current value of the variable. +.It Ic \&?= +Assign the value to the variable if it is not already defined. +.It Ic \&:= +Assign with expansion, i.e. expand the value before assigning it +to the variable. +Normally, expansion is not done until the variable is referenced. +.It Ic \&!= +Expand the value and pass it to the shell for execution and assign +the result to the variable. +Any newlines in the result are replaced with spaces. +.El +.Pp +Any white-space before the assigned +.Ar value +is removed; if the value is being appended, a single space is inserted +between the previous contents of the variable and the appended value. +.Pp +Variables are expanded by surrounding the variable name with either +curly braces +.Pq Ql {} +or parenthesis +.Pq Ql () +and preceding it with +a dollar sign +.Pq Ql \&$ . +If the variable name contains only a single letter, the surrounding +braces or parenthesis are not required. +This shorter form is not recommended. +.Pp +Variable substitution occurs at two distinct times, depending on where +the variable is being used. +Variables in dependency lines are expanded as the line is read. +Variables in shell commands are expanded when the shell command is +executed. +.Pp +The four different classes of variables (in order of increasing precedence) +are: +.Bl -tag -width Ds +.It Environment variables +Variables defined as part of +.Nm make Ns 's +environment. +.It Global variables +Variables defined in the makefile or in included makefiles. +.It Command line variables +Variables defined as part of the command line. +.It Local variables +Variables that are defined specific to a certain target. +The seven local variables are as follows: +.Bl -tag -width ".ARCHIVE" +.It Va .ALLSRC +The list of all sources for this target; also known as +.Ql Va \&> . +.It Va .ARCHIVE +The name of the archive file. +.It Va .IMPSRC +The name/path of the source from which the target is to be transformed +(the ``implied'' source); also known as +.Ql Va \&< . +.It Va .MEMBER +The name of the archive member. +.It Va .OODATE +The list of sources for this target that were deemed out-of-date; also +known as +.Ql Va \&? . +.It Va .PREFIX +The file prefix of the file, containing only the file portion, no suffix +or preceding directory components; also known as +.Ql Va * . +.It Va .TARGET +The name of the target; also known as +.Ql Va @ . +.El +.Pp +The shorter forms +.Ql Va @ , +.Ql Va ? , +.Ql Va \&> +and +.Ql Va * +are permitted for backward +compatibility with historical makefiles and are not recommended. +The six variables +.Ql Va "@F" , +.Ql Va "@D" , +.Ql Va "<F" , +.Ql Va "<D" , +.Ql Va "*F" +and +.Ql Va "*D" +are +permitted for compatibility with +.At V +makefiles and are not recommended. +.Pp +Four of the local variables may be used in sources on dependency lines +because they expand to the proper value for each target on the line. +These variables are +.Ql Va .TARGET , +.Ql Va .PREFIX , +.Ql Va .ARCHIVE , +and +.Ql Va .MEMBER . +.Pp +In addition, +.Nm make +sets or knows about the following variables: +.Bl -tag -width MAKEFLAGS +.It Va \&$ +A single dollar sign +.Ql \&$ , +i.e. +.Ql \&$$ +expands to a single dollar +sign. +.It Va .MAKE +The name that +.Nm make +was executed with +.Pq Va argv Op 0 +.It Va .CURDIR +A path to the directory where +.Nm make +was executed. +.It Ev MAKEFLAGS +The environment variable +.Ql Ev MAKEFLAGS +may contain anything that +may be specified on +.Nm make Ns 's +command line. +Anything specified on +.Nm make Ns 's +command line is appended to the +.Ql Ev MAKEFLAGS +variable which is then +entered into the environment for all programs which +.Nm make +executes. +.El +.Pp +Variable expansion may be modified to select or modify each word of the +variable (where a ``word'' is white-space delimited sequence of characters). +The general format of a variable expansion is as follows: +.Pp +.Dl {variable[:modifier[:...]]} +.Pp +Each modifier begins with a colon and one of the following +special characters. +The colon may be escaped with a backslash +.Pq Ql \e . +.Bl -tag -width Cm E\& +.It Cm E +Replaces each word in the variable with its suffix. +.It Cm H +Replaces each word in the variable with everything but the last component. +.It Cm M Ns Ar pattern +Select only those words that match the rest of the modifier. +The standard shell wildcard characters +.Pf ( Ql * , +.Ql ? , +and +.Ql Op ) +may +be used. +The wildcard characters may be escaped with a backslash +.Pq Ql \e . +.It Cm N Ns Ar pattern +This is identical to +.Ql Cm M , +but selects all words which do not match +the rest of the modifier. +.It Cm R +Replaces each word in the variable with everything but its suffix. +.Sm off +.It Cm S No \&/ Ar old_pattern Xo +.No \&/ Ar new_pattern +.No \&/ Op Cm g +.Xc +.Sm on +Modify the first occurrence of +.Ar old_pattern +in each word to be replaced with +.Ar new_pattern . +If a +.Ql g +is appended to the last slash of the pattern, all occurrences +in each word are replaced. +If +.Ar old_pattern +begins with a carat +.Pq Ql ^ , +.Ar old_pattern +is anchored at the beginning of each word. +If +.Ar old_pattern +ends with a dollar sign +.Pq Ql \&$ , +it is anchored at the end of each word. +Inside +.Ar new_string , +an ampersand +.Pq Ql & +is replaced by +.Ar old_pattern . +Any character may be used as a delimiter for the parts of the modifier +string. +The anchoring, ampersand and delimiter characters may be escaped with a +backslash +.Pq Ql \e . +.Pp +Variable expansion occurs in the normal fashion inside both +.Ar old_string +and +.Ar new_string +with the single exception that a backslash is used to prevent the expansion +of a dollar sign +.Pq Ql \&$ +not a preceding dollar sign as is usual. +.It Cm T +Replaces each word in the variable with its last component. +.It Ar old_string=new_string +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. +.El +.Sh INCLUDE STATEMENTS AND CONDITIONALS +Makefile inclusion and conditional structures reminiscent of the C +programming language are provided in +.Nm make . +All such structures are identified by a line beginning with a single +dot +.Pq Ql \&. +character. +Files are included with either +.Ql .include <file> +or +.Ql .include \*qfile\*q . +Variables between the angle brackets or double quotes are expanded +to form the file name. +If angle brackets are used, the included makefile is expected to be in +the system makefile directory. +If double quotes are used, the including makefile's directory and any +directories specified using the +.Fl I +option are searched before the system +makefile directory. +.Pp +Conditional expressions are also preceded by a single dot as the first +chraracter of a line. +The possible conditionals are as follows: +.Bl -tag -width Ds +.It Ic .undef Ar variable +Un-define the specified global variable. +Only global variables may be un-defined. +.It Xo +.Ic \&.if +.Oo \&! Oc Ns Ar expression +.Op Ar operator expression ... +.Xc +Test the value of an expression. +.It Xo +.Ic .ifdef +.Oo \&! Oc Ns Ar variable +.Op Ar operator variable ... +.Xc +Test the value of an variable. +.It Xo +.Ic .ifndef +.Oo \&! Oc Ns Ar variable +.Op Ar operator variable ... +.Xc +Test the value of an variable. +.It Xo +.Ic .ifmake +.Oo \&! Oc Ns Ar target +.Op Ar operator target ... +.Xc +Test the the target being built. +.It Xo +.Ic .ifnmake +.Oo \&! Oc Ar target +.Op Ar operator target ... +.Xc +Test the target being built. +.It Ic .else +Reverse the sense of the last conditional. +.It Xo +.Ic .elif +.Oo \&! Oc Ar expression +.Op Ar operator expression ... +.Xc +A combination of +.Ql Ic .else +followed by +.Ql Ic .if . +.It Xo +.Ic .elifdef +.Oo \&! Oc Ns Ar variable +.Op Ar operator variable ... +.Xc +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifdef . +.It Xo +.Ic .elifndef +.Oo \&! Oc Ns Ar variable +.Op Ar operator variable ... +.Xc +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifndef . +.It Xo +.Ic .elifmake +.Oo \&! Oc Ns Ar target +.Op Ar operator target ... +.Xc +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifmake . +.It Xo +.Ic .elifnmake +.Oo \&! Oc Ns Ar target +.Op Ar operator target ... +.Xc +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifnmake . +.It Ic .endif +End the body of the conditional. +.El +.Pp +The +.Ar operator +may be any one of the following: +.Bl -tag -width "Cm XX" +.It Cm \&|\&| +logical OR +.It Cm \&&& +Logical +.Tn AND ; +of higher precedence than +.Dq . +.El +.Pp +As in C, +.Nm make +will only evaluate a conditional as far as is necessary to determine +its value. +Parenthesis may be used to change the order of evaluation. +The boolean operator +.Ql Ic \&! +may be used to logically negate an entire +conditional. +It is of higher precendence than +.Ql Ic \&&& . +.Pp +The value of +.Ar expression +may be any of the following: +.Bl -tag -width Ic defined +.It Ic defined +Takes a variable name as an argument and evaluates to true if the variable +has been defined. +.It Ic make +Takes a target name as an argument and evaluates to true if the target +was specified as part of +.Nm make Ns 's +command line or was declared the default target (either implicitly or +explicitly, see +.Va .MAIN ) +before the line containing the conditional. +.It Ic empty +Takes a variable, with possible modifiers, and evalutes to true if +the expansion of the variable would result in an empty string. +.It Ic exists +Takes a file name as an argument and evaluates to true if the file exists. +The file is searched for on the system search path (see +.Va .PATH ) . +.It Ic target +Takes a target name as an argument and evaluates to true if the target +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 +.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. +If no relational operator is given, it is assumed that the expanded +variable is being compared against 0. +.Pp +When +.Nm make +is evaluating one of these conditional expression, and it encounters +a word it doesn't recognize, either the ``make'' or ``defined'' +expression is applied to it, depending on the form of the conditional. +If the form is +.Ql Ic .ifdef +or +.Ql Ic .ifndef , +the ``defined'' expression +is applied. +Similarly, if the form is +.Ql Ic .ifmake +or +.Ql Ic .ifnmake , the ``make'' +expression is applied. +.Pp +If the conditional evaluates to true the parsing of the makefile continues +as before. +If it evaluates to false, the following lines are skipped. +In both cases this continues until a +.Ql Ic .else +or +.Ql Ic .endif +is found. +.Sh COMMENTS +Comments begin with a hash +.Pq Ql \&# +character, anywhere but in a shell +command line, and continue to the end of the line. +.Sh SPECIAL SOURCES +.Bl -tag -width Ic .IGNORE +.It Ic .IGNORE +Ignore any errors from the commands associated with this target, exactly +as if they all were preceded by a dash +.Pq Ql \- . +.It Ic .MAKE +Execute the commands associated with this target even if the +.Fl n +or +.Fl t +options were specified. +Normally used to mark recursive +.Nm make Ns 's . +.It Ic .NOTMAIN +Normally +.Nm make +selects the first target it encounters as the default target to be built +if no target was specified. +This source prevents this target from being selected. +.It Ic .OPTIONAL +If a target is marked with this attribute and +.Nm make +can't figure out how to create it, it will ignore this fact and assume +the file isn't needed or already exists. +.It Ic .PRECIOUS +When +.Nm make +is interrupted, it removes any partially made targets. +This source prevents the target from being removed. +.It Ic .SILENT +Do not echo any of the commands associated with this target, exactly +as if they all were preceded by an at sign +.Pq Ql @ . +.It Ic .USE +Turn the target into +.Nm make Ns 's . +version of a macro. +When the target is used as a source for another target, the other target +acquires the commands, sources, and attributes (except for +.Ic .USE ) +of the +source. +If the target already has commands, the +.Ic .USE +target's commands are appended +to them. +.El +.Sh "SPECIAL TARGETS" +Special targets may not be included with other targets, i.e. they must be +the only target specified. +.Bl -tag -width Ic .BEGIN +.It Ic .BEGIN +Any command lines attached to this target are executed before anything +else is done. +.It Ic .DEFAULT +This is sort of a +.Ic .USE +rule for any target (that was used only as a +source) that +.Nm make +can't figure out any other way to create. +Only the shell script is used. +The +.Ic .IMPSRC +variable of a target that inherits +.Ic .DEFAULT Ns 's +commands is set +to the target's own name. +.It Ic .END +Any command lines attached to this target are executed after everything +else is done. +.It Ic .IGNORE +Mark each of the sources with the +.Ic .IGNORE +attribute. +If no sources are specified, this is the equivalent of specifying the +.Fl i +option. +.It Ic .INTERRUPT +If +.Nm make +is interrupted, the commands for this target will be executed. +.It Ic .MAIN +If no target is specified when +.Nm make +is invoked, this target will be built. +.It Ic .MAKEFLAGS +This target provides a way to specify flags for +.Nm make +when the makefile is used. +The flags are as if typed to the shell, though the +.Fl f +option will have +no effect. +.It Ic .PATH +The sources are directories which are to be searched for files not +found in the current directory. +If no sources are specified, any previously specified directories are +deleted. +.It Ic .PRECIOUS +Apply the +.Ic .PRECIOUS +attribute to any specified sources. +If no sources are specified, the +.Ic .PRECIOUS +attribute is applied to every +target in the file. +.It Ic .SILENT +Apply the +.Ic .SILENT +attribute to any specified sources. +If no sources are specified, the +.Ic .SILENT +attribute is applied to every +command in the file. +.It Ic .SUFFIXES +Each source specifies a suffix to +.Nm make . +If no sources are specified, any previous specifies suffices are deleted. +.Sh ENVIRONMENT +.Nm Make +utilizes the following environment variables, if they exist: +.Ev MAKE , +.Ev MAKEFLAGS +and +.Ev MAKEOBJDIR . +.Sh FILES +.Bl -tag -width /usr/share/mk -compact +.It .depend +list of dependencies +.It Makefile +list of dependencies +.It makefile +list of dependencies +.It sys.mk +system makefile +.It /usr/share/mk +system makefile directory +.El +.Sh SEE ALSO +.Xr mkdep 1 +.Sh HISTORY +A +.Nm Make +command appeared in +.At v7 . diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c new file mode 100644 index 000000000000..2ed94f97a6a9 --- /dev/null +++ b/usr.bin/make/make.c @@ -0,0 +1,847 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)make.c 5.3 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * make.c -- + * The functions which perform the examination of targets and + * their suitability for creation + * + * Interface: + * Make_Run Initialize things for the module and recreate + * whatever needs recreating. Returns TRUE if + * work was (or would have been) done and FALSE + * otherwise. + * + * Make_Update Update all parents of a given child. Performs + * various bookkeeping chores like the updating + * of the cmtime field of the parent, filling + * of the IMPSRC context variable, etc. It will + * place the parent on the toBeMade queue if it + * should be. + * + * Make_TimeStamp Function to set the parent's cmtime field + * based on a child's modification time. + * + * Make_DoAllVar Set up the various local variables for a + * target, including the .ALLSRC variable, making + * sure that any variable that needs to exist + * at the very least has the empty value. + * + * Make_OODate Determine if a target is out-of-date. + * + * Make_HandleUse See if a child is a .USE node for a parent + * and perform the .USE actions if so. + */ + +#include "make.h" + +static Lst toBeMade; /* The current fringe of the graph. These + * are nodes which await examination by + * MakeOODate. It is added to by + * Make_Update and subtracted from by + * MakeStartJobs */ +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 */ + +/*- + *----------------------------------------------------------------------- + * Make_TimeStamp -- + * Set the cmtime field of a parent node based on the mtime stamp in its + * child. Called from MakeOODate via Lst_ForEach. + * + * Results: + * Always returns 0. + * + * Side Effects: + * The cmtime of the parent node will be changed if the mtime + * field of the child is greater than it. + *----------------------------------------------------------------------- + */ +int +Make_TimeStamp (pgn, cgn) + register GNode *pgn; /* the current parent */ + register GNode *cgn; /* the child we've just examined */ +{ + if (cgn->mtime > pgn->cmtime) { + pgn->cmtime = cgn->mtime; + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_OODate -- + * See if a given node is out of date with respect to its sources. + * Used by Make_Run when deciding which nodes to place on the + * toBeMade queue initially and by Make_Update to screen out USE and + * EXEC nodes. In the latter case, however, any other sort of node + * must be considered out-of-date since at least one of its children + * will have been recreated. + * + * Results: + * TRUE if the node is out of date. FALSE otherwise. + * + * Side Effects: + * The mtime field of the node and the cmtime field of its parents + * will/may be changed. + *----------------------------------------------------------------------- + */ +Boolean +Make_OODate (gn) + register GNode *gn; /* the node to check */ +{ + Boolean oodate; + + /* + * Certain types of targets needn't even be sought as their datedness + * doesn't depend on their modification time... + */ + if ((gn->type & (OP_JOIN|OP_USE|OP_EXEC)) == 0) { + (void) Dir_MTime (gn); + if (DEBUG(MAKE)) { + if (gn->mtime != 0) { + printf ("modified %s...", Targ_FmtTime(gn->mtime)); + } else { + printf ("non-existent..."); + } + } + } + + /* + * A target is remade in one of the following circumstances: + * its modification time is smaller than that of its youngest child + * and it would actually be run (has commands or type OP_NOP) + * it's the object of a force operator + * it has no children, was on the lhs of an operator and doesn't exist + * already. + * + * Libraries are only considered out-of-date if the archive module says + * they are. + * + * These weird rules are brought to you by Backward-Compatability and + * the strange people who wrote 'Make'. + */ + if (gn->type & OP_USE) { + /* + * If the node is a USE node it is *never* out of date + * no matter *what*. + */ + if (DEBUG(MAKE)) { + printf(".USE node..."); + } + oodate = FALSE; + } else if (gn->type & OP_LIB) { + if (DEBUG(MAKE)) { + printf("library..."); + } + oodate = Arch_LibOODate (gn); + } else if (gn->type & OP_JOIN) { + /* + * A target with the .JOIN attribute is only considered + * out-of-date if any of its children was out-of-date. + */ + if (DEBUG(MAKE)) { + printf(".JOIN node..."); + } + oodate = gn->childMade; + } else if (gn->type & (OP_FORCE|OP_EXEC)) { + /* + * A node which is the object of the force (!) operator or which has + * the .EXEC attribute is always considered out-of-date. + */ + if (DEBUG(MAKE)) { + if (gn->type & OP_FORCE) { + printf("! operator..."); + } else { + printf(".EXEC node..."); + } + } + oodate = TRUE; + } else if ((gn->mtime < gn->cmtime) || + ((gn->cmtime == 0) && + ((gn->mtime==0) || (gn->type & OP_DOUBLEDEP)))) + { + /* + * A node whose modification time is less than that of its + * youngest child or that has no children (cmtime == 0) and + * either doesn't exist (mtime == 0) or was the object of a + * :: operator is out-of-date. Why? Because that's the way Make does + * it. + */ + if (DEBUG(MAKE)) { + if (gn->mtime < gn->cmtime) { + printf("modified before source..."); + } else if (gn->mtime == 0) { + printf("non-existent and no sources..."); + } else { + printf(":: operator and no sources..."); + } + } + oodate = TRUE; + } else { +#if 0 + /* WHY? */ + if (DEBUG(MAKE)) { + printf("source %smade...", gn->childMade ? "" : "not "); + } + oodate = gn->childMade; +#else + oodate = FALSE; +#endif /* 0 */ + } + + /* + * If the target isn't out-of-date, the parents need to know its + * modification time. Note that targets that appear to be out-of-date + * but aren't, because they have no commands and aren't of type OP_NOP, + * have their mtime stay below their children's mtime to keep parents from + * thinking they're out-of-date. + */ + if (!oodate) { + Lst_ForEach (gn->parents, Make_TimeStamp, (ClientData)gn); + } + + return (oodate); +} + +/*- + *----------------------------------------------------------------------- + * MakeAddChild -- + * Function used by Make_Run to add a child to the list l. + * It will only add the child if its make field is FALSE. + * + * Results: + * Always returns 0 + * + * Side Effects: + * The given list is extended + *----------------------------------------------------------------------- + */ +static int +MakeAddChild (gn, l) + GNode *gn; /* the node to add */ + Lst l; /* the list to which to add it */ +{ + if (!gn->make && !(gn->type & OP_USE)) { + (void)Lst_EnQueue (l, (ClientData)gn); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_HandleUse -- + * Function called by Make_Run and SuffApplyTransform on the downward + * pass to handle .USE and transformation nodes. A callback function + * for Lst_ForEach, it implements the .USE and transformation + * functionality by copying the node's commands, type flags + * and children to the parent node. Should be called before the + * children are enqueued to be looked at by MakeAddChild. + * + * A .USE node is much like an explicit transformation rule, except + * its commands are always added to the target node, even if the + * target already has commands. + * + * Results: + * returns 0. + * + * Side Effects: + * Children and commands may be added to the parent and the parent's + * type may be changed. + * + *----------------------------------------------------------------------- + */ +int +Make_HandleUse (cgn, pgn) + register GNode *cgn; /* The .USE node */ + register GNode *pgn; /* The target of the .USE node */ +{ + register GNode *gn; /* A child of the .USE node */ + register LstNode ln; /* An element in the children list */ + + if (cgn->type & (OP_USE|OP_TRANSFORM)) { + if ((cgn->type & OP_USE) || Lst_IsEmpty(pgn->commands)) { + /* + * .USE or transformation and target has no commands -- append + * the child's commands to the parent. + */ + (void) Lst_Concat (pgn->commands, cgn->commands, LST_CONCNEW); + } + + if (Lst_Open (cgn->children) == SUCCESS) { + while ((ln = Lst_Next (cgn->children)) != NILLNODE) { + gn = (GNode *)Lst_Datum (ln); + + if (Lst_Member (pgn->children, gn) == NILLNODE) { + (void) Lst_AtEnd (pgn->children, gn); + (void) Lst_AtEnd (gn->parents, pgn); + pgn->unmade += 1; + } + } + Lst_Close (cgn->children); + } + + pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_TRANSFORM); + + /* + * This child node is now "made", so we decrement the count of + * unmade children in the parent... We also remove the child + * from the parent's list to accurately reflect the number of decent + * children the parent has. This is used by Make_Run to decide + * whether to queue the parent or examine its children... + */ + if (cgn->type & OP_USE) { + pgn->unmade -= 1; + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_Update -- + * Perform update on the parents of a node. Used by JobFinish once + * a node has been dealt with and by MakeStartJobs if it finds an + * up-to-date node. + * + * Results: + * Always returns 0 + * + * Side Effects: + * The unmade field of pgn is decremented and pgn may be placed on + * the toBeMade queue if this field becomes 0. + * + * If the child was made, the parent's childMade field will be set true + * and its cmtime set to now. + * + * If the child wasn't made, the cmtime field of the parent will be + * altered if the child's mtime is big enough. + * + * Finally, if the child is the implied source for the parent, the + * parent's IMPSRC variable is set appropriately. + * + *----------------------------------------------------------------------- + */ +void +Make_Update (cgn) + register GNode *cgn; /* the child node */ +{ + register GNode *pgn; /* the parent node */ + register char *cname; /* the child's name */ + register LstNode ln; /* Element in parents and iParents lists */ + + cname = Var_Value (TARGET, cgn); + + /* + * If the child was actually made, see what its modification time is + * now -- some rules won't actually update the file. If the file still + * doesn't exist, make its mtime now. + */ + if (cgn->made != UPTODATE) { +#ifndef RECHECK + /* + * We can't re-stat the thing, but we can at least take care of rules + * where a target depends on a source that actually creates the + * target, but only if it has changed, e.g. + * + * parse.h : parse.o + * + * parse.o : parse.y + * yacc -d parse.y + * cc -c y.tab.c + * mv y.tab.o parse.o + * cmp -s y.tab.h parse.h || mv y.tab.h parse.h + * + * In this case, if the definitions produced by yacc haven't changed + * from before, parse.h won't have been updated and cgn->mtime will + * reflect the current modification time for parse.h. This is + * something of a kludge, I admit, but it's a useful one.. + * XXX: People like to use a rule like + * + * FRC: + * + * To force things that depend on FRC to be made, so we have to + * check for gn->children being empty as well... + */ + if (!Lst_IsEmpty(cgn->commands) || Lst_IsEmpty(cgn->children)) { + cgn->mtime = now; + } +#else + /* + * This is what Make does and it's actually a good thing, as it + * allows rules like + * + * cmp -s y.tab.h parse.h || cp y.tab.h parse.h + * + * to function as intended. Unfortunately, thanks to the stateless + * nature of NFS (by which I mean the loose coupling of two clients + * using the same file from a common server), there are times + * when the modification time of a file created on a remote + * machine will not be modified before the local stat() implied by + * the Dir_MTime occurs, thus leading us to believe that the file + * is unchanged, wreaking havoc with files that depend on this one. + * + * I have decided it is better to make too much than to make too + * little, so this stuff is commented out unless you're sure it's ok. + * -- ardeb 1/12/88 + */ + if (noExecute || Dir_MTime(cgn) == 0) { + cgn->mtime = now; + } + if (DEBUG(MAKE)) { + printf("update time: %s\n", Targ_FmtTime(cgn->mtime)); + } +#endif + } + + if (Lst_Open (cgn->parents) == SUCCESS) { + while ((ln = Lst_Next (cgn->parents)) != NILLNODE) { + pgn = (GNode *)Lst_Datum (ln); + if (pgn->make) { + pgn->unmade -= 1; + + if ( ! (cgn->type & (OP_EXEC|OP_USE))) { + if (cgn->made == MADE) { + pgn->childMade = TRUE; + if (pgn->cmtime < cgn->mtime) { + pgn->cmtime = cgn->mtime; + } + } else { + (void)Make_TimeStamp (pgn, cgn); + } + } + if (pgn->unmade == 0) { + /* + * Queue the node up -- any unmade predecessors will + * be dealt with in MakeStartJobs. + */ + (void)Lst_EnQueue (toBeMade, (ClientData)pgn); + } else if (pgn->unmade < 0) { + Error ("Graph cycles through %s", pgn->name); + } + } + } + Lst_Close (cgn->parents); + } + /* + * Deal with successor nodes. If any is marked for making and has an unmade + * count of 0, has not been made and isn't in the examination queue, + * it means we need to place it in the queue as it restrained itself + * before. + */ + for (ln = Lst_First(cgn->successors); ln != NILLNODE; ln = Lst_Succ(ln)) { + GNode *succ = (GNode *)Lst_Datum(ln); + + if (succ->make && succ->unmade == 0 && succ->made == UNMADE && + Lst_Member(toBeMade, (ClientData)succ) == NILLNODE) + { + (void)Lst_EnQueue(toBeMade, (ClientData)succ); + } + } + + /* + * Set the .PREFIX and .IMPSRC variables for all the implied parents + * of this node. + */ + if (Lst_Open (cgn->iParents) == SUCCESS) { + char *cpref = Var_Value(PREFIX, cgn); + + while ((ln = Lst_Next (cgn->iParents)) != NILLNODE) { + pgn = (GNode *)Lst_Datum (ln); + if (pgn->make) { + Var_Set (IMPSRC, cname, pgn); + Var_Set (PREFIX, cpref, pgn); + } + } + Lst_Close (cgn->iParents); + } +} + +/*- + *----------------------------------------------------------------------- + * MakeAddAllSrc -- + * Add a child's name to the ALLSRC and OODATE variables of the given + * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only + * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. + * .EXEC and .USE children are very rarely going to be files, so... + * A child is added to the OODATE variable if its modification time is + * later than that of its parent, as defined by Make, except if the + * parent is a .JOIN node. In that case, it is only added to the OODATE + * variable if it was actually made (since .JOIN nodes don't have + * modification times, the comparison is rather unfair...).. + * + * Results: + * Always returns 0 + * + * Side Effects: + * The ALLSRC variable for the given node is extended. + *----------------------------------------------------------------------- + */ +static int +MakeAddAllSrc (cgn, pgn) + GNode *cgn; /* The child to add */ + GNode *pgn; /* The parent to whose ALLSRC variable it should be */ + /* added */ +{ + if ((cgn->type & (OP_EXEC|OP_USE|OP_INVISIBLE)) == 0) { + register char *child; + + child = Var_Value(TARGET, cgn); + Var_Append (ALLSRC, child, pgn); + if (pgn->type & OP_JOIN) { + if (cgn->made == MADE) { + Var_Append(OODATE, child, pgn); + } + } else if ((pgn->mtime < cgn->mtime) || + (cgn->mtime >= now && cgn->made == MADE)) + { + /* + * It goes in the OODATE variable if the parent is younger than the + * child or if the child has been modified more recently than + * the start of the make. This is to keep pmake from getting + * confused if something else updates the parent after the + * make starts (shouldn't happen, I know, but sometimes it + * does). In such a case, if we've updated the kid, the parent + * is likely to have a modification time later than that of + * the kid and anything that relies on the OODATE variable will + * be hosed. + * + * XXX: This will cause all made children to go in the OODATE + * variable, even if they're not touched, if RECHECK isn't defined, + * since cgn->mtime is set to now in Make_Update. According to + * some people, this is good... + */ + Var_Append(OODATE, child, pgn); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_DoAllVar -- + * Set up the ALLSRC and OODATE variables. Sad to say, it must be + * done separately, rather than while traversing the graph. This is + * because Make defined OODATE to contain all sources whose modification + * times were later than that of the target, *not* those sources that + * were out-of-date. Since in both compatibility and native modes, + * the modification time of the parent isn't found until the child + * has been dealt with, we have to wait until now to fill in the + * variable. As for ALLSRC, the ordering is important and not + * guaranteed when in native mode, so it must be set here, too. + * + * Results: + * None + * + * Side Effects: + * The ALLSRC and OODATE variables of the given node is filled in. + * If the node is a .JOIN node, its TARGET variable will be set to + * match its ALLSRC variable. + *----------------------------------------------------------------------- + */ +void +Make_DoAllVar (gn) + GNode *gn; +{ + Lst_ForEach (gn->children, MakeAddAllSrc, gn); + + if (!Var_Exists (OODATE, gn)) { + Var_Set (OODATE, "", gn); + } + if (!Var_Exists (ALLSRC, gn)) { + Var_Set (ALLSRC, "", gn); + } + + if (gn->type & OP_JOIN) { + Var_Set (TARGET, Var_Value (ALLSRC, gn), gn); + } +} + +/*- + *----------------------------------------------------------------------- + * MakeStartJobs -- + * Start as many jobs as possible. + * + * Results: + * If the query flag was given to pmake, no job will be started, + * but as soon as an out-of-date target is found, this function + * returns TRUE. At all other times, this function returns FALSE. + * + * Side Effects: + * Nodes are removed from the toBeMade queue and job table slots + * are filled. + * + *----------------------------------------------------------------------- + */ +static Boolean +MakeStartJobs () +{ + register GNode *gn; + + while (!Job_Full() && !Lst_IsEmpty (toBeMade)) { + gn = (GNode *) Lst_DeQueue (toBeMade); + if (DEBUG(MAKE)) { + printf ("Examining %s...", gn->name); + } + /* + * Make sure any and all predecessors that are going to be made, + * have been. + */ + if (!Lst_IsEmpty(gn->preds)) { + LstNode ln; + + for (ln = Lst_First(gn->preds); ln != NILLNODE; ln = Lst_Succ(ln)){ + GNode *pgn = (GNode *)Lst_Datum(ln); + + if (pgn->make && pgn->made == UNMADE) { + if (DEBUG(MAKE)) { + printf("predecessor %s not made yet.\n", pgn->name); + } + break; + } + } + /* + * If ln isn't nil, there's a predecessor as yet unmade, so we + * just drop this node on the floor. When the node in question + * has been made, it will notice this node as being ready to + * make but as yet unmade and will place the node on the queue. + */ + if (ln != NILLNODE) { + continue; + } + } + + numNodes--; + if (Make_OODate (gn)) { + if (DEBUG(MAKE)) { + printf ("out-of-date\n"); + } + if (queryFlag) { + return (TRUE); + } + Make_DoAllVar (gn); + Job_Make (gn); + } else { + if (DEBUG(MAKE)) { + printf ("up-to-date\n"); + } + gn->made = UPTODATE; + if (gn->type & OP_JOIN) { + /* + * Even for an up-to-date .JOIN node, we need it to have its + * context variables so references to it get the correct + * value for .TARGET when building up the context variables + * of its parent(s)... + */ + Make_DoAllVar (gn); + } + + Make_Update (gn); + } + } + return (FALSE); +} + +/*- + *----------------------------------------------------------------------- + * MakePrintStatus -- + * Print the status of a top-level node, viz. it being up-to-date + * already or not created due to an error in a lower level. + * Callback function for Make_Run via Lst_ForEach. + * + * Results: + * Always returns 0. + * + * Side Effects: + * A message may be printed. + * + *----------------------------------------------------------------------- + */ +static int +MakePrintStatus(gn, cycle) + GNode *gn; /* Node to examine */ + Boolean cycle; /* True if gn->unmade being non-zero implies + * a cycle in the graph, not an error in an + * inferior */ +{ + if (gn->made == UPTODATE) { + printf ("`%s' is up to date.\n", gn->name); + } else if (gn->unmade != 0) { + if (cycle) { + /* + * If printing cycles and came to one that has unmade children, + * print out the cycle by recursing on its children. Note a + * cycle like: + * a : b + * b : c + * c : b + * will cause this to erroneously complain about a being in + * the cycle, but this is a good approximation. + */ + if (gn->made == CYCLE) { + Error("Graph cycles through `%s'", gn->name); + gn->made = ENDCYCLE; + Lst_ForEach(gn->children, MakePrintStatus, (ClientData)TRUE); + gn->made = UNMADE; + } else if (gn->made != ENDCYCLE) { + gn->made = CYCLE; + Lst_ForEach(gn->children, MakePrintStatus, (ClientData)TRUE); + } + } else { + printf ("`%s' not remade because of errors.\n", gn->name); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_Run -- + * Initialize the nodes to remake and the list of nodes which are + * ready to be made by doing a breadth-first traversal of the graph + * starting from the nodes in the given list. Once this traversal + * is finished, all the 'leaves' of the graph are in the toBeMade + * queue. + * Using this queue and the Job module, work back up the graph, + * calling on MakeStartJobs to keep the job table as full as + * possible. + * + * Results: + * TRUE if work was done. FALSE otherwise. + * + * Side Effects: + * The make field of all nodes involved in the creation of the given + * targets is set to 1. The toBeMade list is set to contain all the + * 'leaves' of these subgraphs. + *----------------------------------------------------------------------- + */ +Boolean +Make_Run (targs) + Lst targs; /* the initial list of targets */ +{ + register GNode *gn; /* a temporary pointer */ + register Lst examine; /* List of targets to examine */ + int errors; /* Number of errors the Job module reports */ + + toBeMade = Lst_Init (FALSE); + + examine = Lst_Duplicate(targs, NOCOPY); + numNodes = 0; + + /* + * Make an initial downward pass over the graph, marking nodes to be made + * as we go down. We call Suff_FindDeps to find where a node is and + * to get some children for it if it has none and also has no commands. + * If the node is a leaf, we stick it on the toBeMade queue to + * be looked at in a minute, otherwise we add its children to our queue + * and go on about our business. + */ + while (!Lst_IsEmpty (examine)) { + gn = (GNode *) Lst_DeQueue (examine); + + if (!gn->make) { + gn->make = TRUE; + numNodes++; + + /* + * Apply any .USE rules before looking for implicit dependencies + * to make sure everything has commands that should... + */ + Lst_ForEach (gn->children, Make_HandleUse, (ClientData)gn); + Suff_FindDeps (gn); + + if (gn->unmade != 0) { + Lst_ForEach (gn->children, MakeAddChild, (ClientData)examine); + } else { + (void)Lst_EnQueue (toBeMade, (ClientData)gn); + } + } + } + + Lst_Destroy (examine, NOFREE); + + if (queryFlag) { + /* + * We wouldn't do any work unless we could start some jobs in the + * next loop... (we won't actually start any, of course, this is just + * to see if any of the targets was out of date) + */ + return (MakeStartJobs()); + } else { + /* + * Initialization. At the moment, no jobs are running and until some + * get started, nothing will happen since the remaining upward + * traversal of the graph is performed by the routines in job.c upon + * the finishing of a job. So we fill the Job table as much as we can + * before going into our loop. + */ + (void) MakeStartJobs(); + } + + /* + * Main Loop: The idea here is that the ending of jobs will take + * care of the maintenance of data structures and the waiting for output + * will cause us to be idle most of the time while our children run as + * much as possible. Because the job table is kept as full as possible, + * the only time when it will be empty is when all the jobs which need + * running have been run, so that is the end condition of this loop. + * Note that the Job module will exit if there were any errors unless the + * keepgoing flag was given. + */ + while (!Job_Empty ()) { + Job_CatchOutput (); + Job_CatchChildren (!usePipes); + (void)MakeStartJobs(); + } + + errors = Job_End(); + + /* + * Print the final status of each target. E.g. if it wasn't made + * because some inferior reported an error. + */ + Lst_ForEach(targs, MakePrintStatus, + (ClientData)((errors == 0) && (numNodes != 0))); + + return (TRUE); +} diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h new file mode 100644 index 000000000000..d76e524b4993 --- /dev/null +++ b/usr.bin/make/make.h @@ -0,0 +1,341 @@ +/* + * 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. + * + * @(#)make.h 5.13 (Berkeley) 3/1/91 + */ + +/*- + * make.h -- + * The global definitions for pmake + */ + +#ifndef _MAKE_H_ +#define _MAKE_H_ + +#include <sys/types.h> +#include <string.h> +#include <ctype.h> +#include "sprite.h" +#include "lst.h" +#include "config.h" + +/*- + * The structure for an individual graph node. Each node has several + * pieces of data associated with it. + * 1) the name of the target it describes + * 2) the location of the target file in the file system. + * 3) the type of operator used to define its sources (qv. parse.c) + * 4) whether it is involved in this invocation of make + * 5) whether the target has been remade + * 6) whether any of its children has been remade + * 7) the number of its children that are, as yet, unmade + * 8) its modification time + * 9) the modification time of its youngest child (qv. make.c) + * 10) a list of nodes for which this is a source + * 11) a list of nodes on which this depends + * 12) a list of nodes that depend on this, as gleaned from the + * transformation rules. + * 13) a list of nodes of the same name created by the :: operator + * 14) a list of nodes that must be made (if they're made) before + * this node can be, but that do no enter into the datedness of + * this node. + * 15) a list of nodes that must be made (if they're made) after + * this node is, but that do not depend on this node, in the + * normal sense. + * 16) a Lst of ``local'' variables that are specific to this target + * and this target only (qv. var.c [$@ $< $?, etc.]) + * 17) a Lst of strings that are commands to be given to a shell + * to create this target. + */ +typedef struct GNode { + char *name; /* The target's name */ + char *path; /* The full pathname of the file */ + int type; /* Its type (see the OP flags, below) */ + + Boolean make; /* TRUE if this target needs to be remade */ + enum { + UNMADE, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED, + CYCLE, ENDCYCLE, + } made; /* Set to reflect the state of processing + * on this node: + * UNMADE - Not examined yet + * BEINGMADE - Target is already being made. + * Indicates a cycle in the graph. (compat + * mode only) + * MADE - Was out-of-date and has been made + * UPTODATE - Was already up-to-date + * ERROR - An error occured while it was being + * made (used only in compat mode) + * ABORTED - The target was aborted due to + * an error making an inferior (compat). + * CYCLE - Marked as potentially being part of + * a graph cycle. If we come back to a + * node marked this way, it is printed + * and 'made' is changed to ENDCYCLE. + * ENDCYCLE - the cycle has been completely + * printed. Go back and unmark all its + * members. + */ + Boolean childMade; /* TRUE if one of this target's children was + * made */ + int unmade; /* The number of unmade children */ + + int mtime; /* Its modification time */ + int cmtime; /* The modification time of its youngest + * child */ + + Lst iParents; /* Links to parents for which this is an + * implied source, if any */ + Lst cohorts; /* Other nodes for the :: operator */ + Lst parents; /* Nodes that depend on this one */ + Lst children; /* Nodes on which this one depends */ + Lst successors; /* Nodes that must be made after this one */ + Lst preds; /* Nodes that must be made before this one */ + + Lst context; /* The local variables */ + Lst commands; /* Creation commands */ + + struct _Suff *suffix; /* Suffix for the node (determined by + * Suff_FindDeps and opaque to everyone + * but the Suff module) */ +} GNode; + +/* + * Manifest constants + */ +#define NILGNODE ((GNode *) NIL) + +/* + * The OP_ constants are used when parsing a dependency line as a way of + * communicating to other parts of the program the way in which a target + * should be made. These constants are bitwise-OR'ed together and + * placed in the 'type' field of each node. Any node that has + * a 'type' field which satisfies the OP_NOP function was never never on + * the lefthand side of an operator, though it may have been on the + * righthand side... + */ +#define OP_DEPENDS 0x00000001 /* Execution of commands depends on + * kids (:) */ +#define OP_FORCE 0x00000002 /* Always execute commands (!) */ +#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids + * per line (::) */ +#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) + +#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't + * exist and can't be created */ +#define OP_USE 0x00000010 /* Use associated commands for parents */ +#define OP_EXEC 0x00000020 /* Target is never out of date, but always + * execute commands anyway. Its time + * doesn't matter, so it has none...sort + * of */ +#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ +#define OP_PRECIOUS 0x00000080 /* Don't remove the target when + * interrupted */ +#define OP_SILENT 0x00000100 /* Don't echo commands when executed */ +#define OP_MAKE 0x00000200 /* Target is a recurrsive make so its + * commands should always be executed when + * it is out of date, regardless of the + * state of the -n or -t flags */ +#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its + * children was out-of-date */ +#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. + * I.e. it doesn't show up in the parents's + * local variables. */ +#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main + * target' processing in parse.c */ +/* Attributes applied by PMake */ +#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ +#define OP_MEMBER 0x40000000 /* Target is a member of an archive */ +#define OP_LIB 0x20000000 /* Target is a library */ +#define OP_ARCHV 0x10000000 /* Target is an archive construct */ +#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. + * Used when parsing to catch multiple + * commands for a target */ +#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ +#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ + +/* + * OP_NOP will return TRUE if the node with the given type was not the + * object of a dependency operator + */ +#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) + +/* + * The TARG_ constants are used when calling the Targ_FindNode and + * Targ_FindList functions in targ.c. They simply tell the functions what to + * do if the desired node(s) is (are) not found. If the TARG_CREATE constant + * is given, a new, empty node will be created for the target, placed in the + * table of all targets and its address returned. If TARG_NOCREATE is given, + * a NIL pointer will be returned. + */ +#define TARG_CREATE 0x01 /* create node if not found */ +#define TARG_NOCREATE 0x00 /* don't create it */ + +/* + * There are several places where expandable buffers are used (parse.c and + * var.c). This constant is merely the starting point for those buffers. If + * lines tend to be much shorter than this, it would be best to reduce BSIZE. + * If longer, it should be increased. Reducing it will cause more copying to + * be done for longer lines, but will save space for shorter ones. In any + * 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 */ + +/* + * These constants are all used by the Str_Concat function to decide how the + * final string should look. If STR_ADDSPACE is given, a space will be + * placed between the two strings. If STR_ADDSLASH is given, a '/' will + * be used instead of a space. If neither is given, no intervening characters + * will be placed between the two strings in the final output. If the + * STR_DOFREE bit is set, the two input strings will be freed before + * Str_Concat returns. + */ +#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ +#define STR_DOFREE 0x02 /* free source strings after concatenation */ +#define STR_ADDSLASH 0x04 /* add a slash when Str_Concat'ing */ + +/* + * Error levels for parsing. PARSE_FATAL means the process cannot continue + * once the makefile has been parsed. PARSE_WARNING means it can. Passed + * as the first argument to Parse_Error. + */ +#define PARSE_WARNING 2 +#define PARSE_FATAL 1 + +/* + * Values returned by Cond_Eval. + */ +#define COND_PARSE 0 /* Parse the next lines */ +#define COND_SKIP 1 /* Skip the next lines */ +#define COND_INVALID 2 /* Not a conditional statement */ + +/* + * Definitions for the "local" variables. Used only for clarity. + */ +#define TARGET "@" /* Target of dependency */ +#define OODATE "?" /* All out-of-date sources */ +#define ALLSRC ">" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ + +#define FTARGET "@F" /* file part of TARGET */ +#define DTARGET "@D" /* directory part of TARGET */ +#define FIMPSRC "<F" /* file part of IMPSRC */ +#define DIMPSRC "<D" /* directory part of IMPSRC */ +#define FPREFIX "*F" /* file part of PREFIX */ +#define DPREFIX "*D" /* directory part of PREFIX */ + +/* + * Global Variables + */ +extern Lst create; /* The list of target names specified on the + * command line. used to resolve #if + * make(...) statements */ +extern Lst dirSearchPath; /* The list of directories to search when + * looking for targets */ + +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 */ +extern Boolean allPrecious; /* True if every target is precious */ +extern Boolean keepgoing; /* True if should continue on unaffected + * portions of the graph when have an error + * in one portion */ +extern Boolean touchFlag; /* TRUE if targets should just be 'touched' + * if out of date. Set by the -t flag */ +extern Boolean usePipes; /* TRUE if should capture the output of + * subshells by means of pipes. Otherwise it + * is routed to temporary files from which it + * is retrieved when the shell exits */ +extern Boolean queryFlag; /* TRUE if we aren't supposed to really make + * anything, just see if the targets are out- + * of-date */ + +extern Boolean checkEnvFirst; /* TRUE if environment should be searched for + * variables before the global context */ + +extern GNode *DEFAULT; /* .DEFAULT rule */ + +extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g + * in the Makefile itself */ +extern GNode *VAR_CMD; /* Variables defined on the command line */ +extern char var_Error[]; /* Value returned by Var_Parse when an error + * is encountered. It actually points to + * an empty string, so naive callers needn't + * worry about it. */ + +extern time_t now; /* The time at the start of this whole + * process */ + +extern Boolean oldVars; /* Do old-style variable substitution */ + +/* + * debug control: + * There is one bit per module. It is up to the module what debug + * information to print. + */ +extern int debug; +#define DEBUG_ARCH 0x0001 +#define DEBUG_COND 0x0002 +#define DEBUG_DIR 0x0004 +#define DEBUG_GRAPH1 0x0008 +#define DEBUG_GRAPH2 0x0010 +#define DEBUG_JOB 0x0020 +#define DEBUG_MAKE 0x0040 +#define DEBUG_SUFF 0x0080 +#define DEBUG_TARG 0x0100 +#define DEBUG_VAR 0x0200 + +#ifdef __STDC__ +#define CONCAT(a,b) a##b +#else +#define I(a) a +#define CONCAT(a,b) I(a)b +#endif /* __STDC__ */ + +#define DEBUG(module) (debug & CONCAT(DEBUG_,module)) + +/* + * Since there are so many, all functions that return non-integer values are + * extracted by means of a sed script or two and stuck in the file "nonints.h" + */ +#include "nonints.h" + +#endif _MAKE_H_ diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h new file mode 100644 index 000000000000..0b2e30445df3 --- /dev/null +++ b/usr.bin/make/nonints.h @@ -0,0 +1,128 @@ +/*- + * 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. + * + * @(#)nonints.h 5.6 (Berkeley) 4/18/91 + */ + +char **brk_string(), *emalloc(), *str_concat(); + +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(); diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c new file mode 100644 index 000000000000..c5578a62bd50 --- /dev/null +++ b/usr.bin/make/parse.c @@ -0,0 +1,2212 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)parse.c 5.18 (Berkeley) 2/19/91"; +#endif /* not lint */ + +/*- + * parse.c -- + * Functions to parse a makefile. + * + * One function, Parse_Init, must be called before any functions + * in this module are used. After that, the function Parse_File is the + * main entry point and controls most of the other functions in this + * module. + * + * Most important structures are kept in Lsts. Directories for + * the #include "..." function are kept in the 'parseIncPath' Lst, while + * those for the #include <...> are kept in the 'sysIncPath' Lst. The + * targets currently being defined are kept in the 'targets' Lst. + * + * The variables 'fname' and 'lineno' are used to track the name + * of the current file and the line number in that file so that error + * messages can be more meaningful. + * + * Interface: + * Parse_Init Initialization function which must be + * called before anything else in this module + * is used. + * + * Parse_File Function used to parse a makefile. It must + * be given the name of the file, which should + * already have been opened, and a function + * to call to read a character from the file. + * + * Parse_IsVar Returns TRUE if the given line is a + * variable assignment. Used by MainParseArgs + * to determine if an argument is a target + * or a variable assignment. Used internally + * for pretty much the same thing... + * + * Parse_Error Function called when an error occurs in + * parsing. Used by the variable and + * conditional modules. + * Parse_MainName Returns a Lst of the main target to create. + */ + +#include <varargs.h> +#include <stdio.h> +#include <ctype.h> +#include "make.h" +#include "buf.h" +#include "pathnames.h" + +/* + * These values are returned by ParseEOF to tell Parse_File whether to + * CONTINUE parsing, i.e. it had only reached the end of an include file, + * or if it's DONE. + */ +#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 */ + +static char *fname; /* name of current file (for errors) */ +static int lineno; /* line number in current file */ +static FILE *curFILE; /* current makefile */ + +static int fatals = 0; + +static GNode *mainNode; /* The main target to create. This is the + * first target on the first dependency + * line in the first makefile */ +/* + * Definitions for handling #include specifications + */ +typedef struct IFile { + char *fname; /* name of previous file */ + int lineno; /* saved line number */ + FILE * F; /* the open stream */ +} IFile; + +static Lst includes; /* stack of IFiles generated by + * #includes */ +Lst parseIncPath; /* list of directories for "..." includes */ +Lst sysIncPath; /* list of directories for <...> includes */ + +/*- + * specType contains the SPECial TYPE of the current target. It is + * Not if the target is unspecial. If it *is* special, however, the children + * are linked as children of the parent but not vice versa. This variable is + * set in ParseDoDependency + */ +typedef enum { + Begin, /* .BEGIN */ + Default, /* .DEFAULT */ + End, /* .END */ + Ignore, /* .IGNORE */ + Includes, /* .INCLUDES */ + Interrupt, /* .INTERRUPT */ + Libs, /* .LIBS */ + MFlags, /* .MFLAGS or .MAKEFLAGS */ + Main, /* .MAIN and we don't have anything user-specified to + * make */ + Not, /* Not special */ + NotParallel, /* .NOTPARALELL */ + Null, /* .NULL */ + Order, /* .ORDER */ + Path, /* .PATH */ + Precious, /* .PRECIOUS */ + Shell, /* .SHELL */ + Silent, /* .SILENT */ + SingleShell, /* .SINGLESHELL */ + Suffixes, /* .SUFFIXES */ + Attribute, /* Generic attribute */ +} ParseSpecial; + +ParseSpecial specType; + +/* + * Predecessor node for handling .ORDER. Initialized to NILGNODE when .ORDER + * seen, then set to each successive source on the line. + */ +static GNode *predecessor; + +/* + * The parseKeywords table is searched using binary search when deciding + * if a target or source is special. The 'spec' field is the ParseSpecial + * type of the keyword ("Not" if the keyword isn't special as a target) while + * the 'op' field is the operator to apply to the list of targets if the + * keyword is used as a source ("0" if the keyword isn't special as a source) + */ +static struct { + char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + int op; /* Operator when used as a source */ +} parseKeywords[] = { +{ ".BEGIN", Begin, 0 }, +{ ".DEFAULT", Default, 0 }, +{ ".OPTIONAL", Attribute, OP_OPTIONAL }, +{ ".END", End, 0 }, +{ ".EXEC", Attribute, OP_EXEC }, +{ ".IGNORE", Ignore, OP_IGNORE }, +{ ".INCLUDES", Includes, 0 }, +{ ".INTERRUPT", Interrupt, 0 }, +{ ".INVISIBLE", Attribute, OP_INVISIBLE }, +{ ".JOIN", Attribute, OP_JOIN }, +{ ".LIBS", Libs, 0 }, +{ ".MAIN", Main, 0 }, +{ ".MAKE", Attribute, OP_MAKE }, +{ ".MAKEFLAGS", MFlags, 0 }, +{ ".MFLAGS", MFlags, 0 }, +{ ".NOTMAIN", Attribute, OP_NOTMAIN }, +{ ".NOTPARALLEL", NotParallel, 0 }, +{ ".NULL", Null, 0 }, +{ ".ORDER", Order, 0 }, +{ ".PATH", Path, 0 }, +{ ".PRECIOUS", Precious, OP_PRECIOUS }, +{ ".RECURSIVE", Attribute, OP_MAKE }, +{ ".SHELL", Shell, 0 }, +{ ".SILENT", Silent, OP_SILENT }, +{ ".SINGLESHELL", SingleShell, 0 }, +{ ".SUFFIXES", Suffixes, 0 }, +{ ".USE", Attribute, OP_USE }, +}; + +/*- + *---------------------------------------------------------------------- + * ParseFindKeyword -- + * Look in the table of keywords for one matching the given string. + * + * Results: + * The index of the keyword, or -1 if it isn't there. + * + * Side Effects: + * None + *---------------------------------------------------------------------- + */ +static int +ParseFindKeyword (str) + char *str; /* String to find */ +{ + register int start, + end, + cur; + register int diff; + + start = 0; + end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + + do { + cur = start + ((end - start) / 2); + diff = strcmp (str, parseKeywords[cur].name); + + if (diff == 0) { + return (cur); + } else if (diff < 0) { + end = cur - 1; + } else { + start = cur + 1; + } + } while (start <= end); + return (-1); +} + +/*- + * Parse_Error -- + * Error message abort function for parsing. Prints out the context + * of the error (line number and file) as well as the message with + * two optional arguments. + * + * Results: + * None + * + * Side Effects: + * "fatals" is incremented if the level is PARSE_FATAL. + */ +/* VARARGS */ +void +Parse_Error(type, va_alist) + int type; /* Error type (PARSE_WARNING, PARSE_FATAL) */ + va_dcl +{ + va_list ap; + char *fmt; + + (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"); + (void)fflush(stderr); + if (type == PARSE_FATAL) + fatals += 1; +} + +/*- + *--------------------------------------------------------------------- + * ParseLinkSrc -- + * Link the parent node to its new child. Used in a Lst_ForEach by + * ParseDoDependency. If the specType isn't 'Not', the parent + * isn't linked as a parent of the child. + * + * Results: + * Always = 0 + * + * Side Effects: + * New elements are added to the parents list of cgn and the + * children list of cgn. the unmade field of pgn is updated + * to reflect the additional child. + *--------------------------------------------------------------------- + */ +static int +ParseLinkSrc (pgn, cgn) + GNode *pgn; /* The parent node */ + GNode *cgn; /* The child node */ +{ + if (Lst_Member (pgn->children, (ClientData)cgn) == NILLNODE) { + (void)Lst_AtEnd (pgn->children, (ClientData)cgn); + if (specType == Not) { + (void)Lst_AtEnd (cgn->parents, (ClientData)pgn); + } + pgn->unmade += 1; + } + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoOp -- + * Apply the parsed operator to the given target node. Used in a + * Lst_ForEach call by ParseDoDependency once all targets have + * been found and their operator parsed. If the previous and new + * operators are incompatible, a major error is taken. + * + * Results: + * Always 0 + * + * Side Effects: + * The type field of the node is altered to reflect any new bits in + * the op. + *--------------------------------------------------------------------- + */ +static int +ParseDoOp (gn, op) + GNode *gn; /* The node to which the operator is to be + * applied */ + int op; /* The operator to apply */ +{ + /* + * If the dependency mask of the operator and the node don't match and + * the node has actually had an operator applied to it before, and + * the operator actually has some dependency information in it, complain. + */ + if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && + !OP_NOP(gn->type) && !OP_NOP(op)) + { + Parse_Error (PARSE_FATAL, "Inconsistent operator for %s", gn->name); + return (1); + } + + if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { + /* + * If the node was the object of a :: operator, we need to create a + * new instance of it for the children and commands on this dependency + * line. The new instance is placed on the 'cohorts' list of the + * initial one (note the initial one is not on its own cohorts list) + * and the new instance is linked to all parents of the initial + * instance. + */ + register GNode *cohort; + LstNode ln; + + cohort = Targ_NewGN(gn->name); + /* + * Duplicate links to parents so graph traversal is simple. Perhaps + * some type bits should be duplicated? + * + * Make the cohort invisible as well to avoid duplicating it into + * other variables. True, parents of this target won't tend to do + * anything with their local variables, but better safe than + * sorry. + */ + Lst_ForEach(gn->parents, ParseLinkSrc, (ClientData)cohort); + cohort->type = OP_DOUBLEDEP|OP_INVISIBLE; + (void)Lst_AtEnd(gn->cohorts, (ClientData)cohort); + + /* + * Replace the node in the targets list with the new copy + */ + ln = Lst_Member(targets, (ClientData)gn); + Lst_Replace(ln, (ClientData)cohort); + gn = cohort; + } + /* + * We don't want to nuke any previous flags (whatever they were) so we + * just OR the new operator into the old + */ + gn->type |= op; + + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoSrc -- + * Given the name of a source, figure out if it is an attribute + * and apply it to the targets if it is. Else decide if there is + * some attribute which should be applied *to* the source because + * of some special target and apply it if so. Otherwise, make the + * source be a child of the targets in the list 'targets' + * + * Results: + * None + * + * Side Effects: + * Operator bits may be added to the list of targets or to the source. + * The targets may have a new source added to their lists of children. + *--------------------------------------------------------------------- + */ +static void +ParseDoSrc (tOp, src) + int tOp; /* operator (if any) from special targets */ + char *src; /* name of the source to handle */ +{ + int op; /* operator (if any) from special source */ + GNode *gn; + + op = 0; + if (*src == '.' && isupper (src[1])) { + int keywd = ParseFindKeyword(src); + if (keywd != -1) { + op = parseKeywords[keywd].op; + } + } + if (op != 0) { + Lst_ForEach (targets, ParseDoOp, (ClientData)op); + } else if (specType == Main) { + /* + * If we have noted the existence of a .MAIN, it means we need + * to add the sources of said target to the list of things + * to create. The string 'src' is likely to be free, so we + * must make a new copy of it. Note that this will only be + * invoked if the user didn't specify a target on the command + * line. This is to allow #ifmake's to succeed, or something... + */ + (void) Lst_AtEnd (create, (ClientData)strdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user cna + * employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); + } else if (specType == Order) { + /* + * Create proper predecessor/successor links between the previous + * source and the current one. + */ + gn = Targ_FindNode(src, TARG_CREATE); + if (predecessor != NILGNODE) { + (void)Lst_AtEnd(predecessor->successors, (ClientData)gn); + (void)Lst_AtEnd(gn->preds, (ClientData)predecessor); + } + /* + * The current source now becomes the predecessor for the next one. + */ + predecessor = gn; + } else { + /* + * If the source is not an attribute, we need to find/create + * a node for it. After that we can apply any operator to it + * from a special target or link it to its parents, as + * appropriate. + * + * In the case of a source that was the object of a :: operator, + * the attribute is applied to all of its instances (as kept in + * the 'cohorts' list of the node) or all the cohorts are linked + * to all the targets. + */ + gn = Targ_FindNode (src, TARG_CREATE); + if (tOp) { + gn->type |= tOp; + } else { + Lst_ForEach (targets, ParseLinkSrc, (ClientData)gn); + } + if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) { + register GNode *cohort; + register LstNode ln; + + for (ln=Lst_First(gn->cohorts); ln != NILLNODE; ln = Lst_Succ(ln)){ + cohort = (GNode *)Lst_Datum(ln); + if (tOp) { + cohort->type |= tOp; + } else { + Lst_ForEach(targets, ParseLinkSrc, (ClientData)cohort); + } + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFindMain -- + * Find a real target in the list and set it to be the main one. + * Called by ParseDoDependency when a main target hasn't been found + * yet. + * + * Results: + * 0 if main not found yet, 1 if it is. + * + * Side Effects: + * mainNode is changed and Targ_SetMain is called. + * + *----------------------------------------------------------------------- + */ +static int +ParseFindMain(gn) + GNode *gn; /* Node to examine */ +{ + if ((gn->type & (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)) == 0) { + mainNode = gn; + Targ_SetMain(gn); + return (1); + } else { + return (0); + } +} + +/*- + *----------------------------------------------------------------------- + * ParseAddDir -- + * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_AddDir. + * + *----------------------------------------------------------------------- + */ +static int +ParseAddDir(path, name) + Lst path; + char *name; +{ + Dir_AddDir(path, name); + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseClearPath -- + * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_ClearPath + * + *----------------------------------------------------------------------- + */ +static int +ParseClearPath(path) + Lst path; +{ + Dir_ClearPath(path); + return(0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoDependency -- + * Parse the dependency line in line. + * + * Results: + * None + * + * Side Effects: + * The nodes of the sources are linked as children to the nodes of the + * targets. Some nodes may be created. + * + * We parse a dependency line by first extracting words from the line and + * finding nodes in the list of all targets with that name. This is done + * until a character is encountered which is an operator character. Currently + * these are only ! and :. At this point the operator is parsed and the + * pointer into the line advanced until the first source is encountered. + * The parsed operator is applied to each node in the 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * The sources are read in much the same way as the targets were except + * that now they are expanded using the wildcarding scheme of the C-Shell + * and all instances of the resulting words in the list of all targets + * are found. Each of the resulting nodes is then linked to each of the + * targets as one of its children. + * Certain targets are handled specially. These are the ones detailed + * by the specType variable. + * The storing of transformation rules is also taken care of here. + * A target is recognized as a transformation rule by calling + * Suff_IsTransform. If it is a transformation rule, its node is gotten + * from the suffix module via Suff_AddTransform rather than the standard + * Targ_FindNode in the target module. + *--------------------------------------------------------------------- + */ +static void +ParseDoDependency (line) + char *line; /* the line to parse */ +{ + register char *cp; /* our current position */ + register GNode *gn; /* a general purpose temporary node */ + register int op; /* the operator on the line */ + char savec; /* a place to save a character */ + Lst paths; /* List of search paths to alter when parsing + * a list of .PATH targets */ + int tOp; /* operator from special target */ + Lst sources; /* list of source names after expansion */ + Lst curTargs; /* list of target names to be found and added + * to the targets list */ + + tOp = 0; + + specType = Not; + paths = (Lst)NULL; + + curTargs = Lst_Init(FALSE); + + do { + for (cp = line; + *cp && !isspace (*cp) && + (*cp != '!') && (*cp != ':') && (*cp != '('); + cp++) + { + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the puppy + * so we can safely advance beyond it...There should be + * no errors in this, as they would have been discovered + * in the initial Var_Subst and we wouldn't be here. + */ + int length; + Boolean freeIt; + char *result; + + result=Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); + + if (freeIt) { + free(result); + } + cp += length-1; + } + continue; + } + if (*cp == '(') { + /* + * Archives must be handled specially to make sure the OP_ARCHV + * flag is set in their 'type' field, for one thing, and because + * things like "archive(file1.o file2.o file3.o)" are permissible. + * Arch_ParseArchive will set 'line' to be the first non-blank + * after the archive-spec. It creates/finds nodes for the members + * and places them on the given list, returning SUCCESS if all + * went well and FAILURE if there was an error in the + * specification. On error, line should remain untouched. + */ + if (Arch_ParseArchive (&line, targets, VAR_CMD) != SUCCESS) { + Parse_Error (PARSE_FATAL, + "Error in archive specification: \"%s\"", line); + return; + } else { + continue; + } + } + savec = *cp; + + if (!*cp) { + /* + * Ending a dependency line without an operator is a Bozo + * no-no + */ + Parse_Error (PARSE_FATAL, "Need an operator"); + return; + } + *cp = '\0'; + /* + * Have a word in line. See if it's a special target and set + * specType to match it. + */ + if (*line == '.' && isupper (line[1])) { + /* + * See if the target is a special target that must have it + * or its sources handled specially. + */ + int keywd = ParseFindKeyword(line); + if (keywd != -1) { + if (specType == Path && parseKeywords[keywd].spec != Path) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + return; + } + + specType = parseKeywords[keywd].spec; + tOp = parseKeywords[keywd].op; + + /* + * Certain special targets have special semantics: + * .PATH Have to set the dirSearchPath + * variable too + * .MAIN Its sources are only used if + * nothing has been specified to + * create. + * .DEFAULT Need to create a node to hang + * commands on, but we don't want + * it in the graph, nor do we want + * it to be the Main Target, so we + * create it, set OP_NOTMAIN and + * add it to the list, setting + * DEFAULT to the new node for + * later use. We claim the node is + * A transformation rule to make + * life easier later, when we'll + * use Make_HandleUse to actually + * apply the .DEFAULT commands. + * .BEGIN + * .END + * .INTERRUPT Are not to be considered the + * main target. + * .NOTPARALLEL Make only one target at a time. + * .SINGLESHELL Create a shell for each command. + * .ORDER Must set initial predecessor to NIL + */ + switch (specType) { + case Path: + if (paths == NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, (ClientData)dirSearchPath); + break; + case Main: + if (!Lst_IsEmpty(create)) { + specType = Not; + } + break; + case Begin: + case End: + case Interrupt: + gn = Targ_FindNode(line, TARG_CREATE); + gn->type |= OP_NOTMAIN; + (void)Lst_AtEnd(targets, (ClientData)gn); + break; + case Default: + gn = Targ_NewGN(".DEFAULT"); + gn->type |= (OP_NOTMAIN|OP_TRANSFORM); + (void)Lst_AtEnd(targets, (ClientData)gn); + DEFAULT = gn; + break; + case NotParallel: + { + extern int maxJobs; + + maxJobs = 1; + break; + } + case SingleShell: + /* backwards = 1; */ + break; + case Order: + predecessor = NILGNODE; + break; + } + } else if (strncmp (line, ".PATH", 5) == 0) { + /* + * .PATH<suffix> has to be handled specially. + * Call on the suffix module to give us a path to + * modify. + */ + Lst path; + + specType = Path; + path = Suff_GetPath (&line[5]); + if (path == NILLST) { + Parse_Error (PARSE_FATAL, + "Suffix '%s' not defined (yet)", + &line[5]); + return; + } else { + if (paths == (Lst)NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, (ClientData)path); + } + } + } + + /* + * Have word in line. Get or create its node and stick it at + * the end of the targets list + */ + if ((specType == Not) && (*line != '\0')) { + if (Dir_HasWildcards(line)) { + /* + * Targets are to be sought only in the current directory, + * so create an empty path for the thing. Note we need to + * use Dir_Destroy in the destruction of the path as the + * Dir module could have added a directory to the path... + */ + Lst emptyPath = Lst_Init(FALSE); + + Dir_Expand(line, emptyPath, curTargs); + + Lst_Destroy(emptyPath, Dir_Destroy); + } else { + /* + * No wildcards, but we want to avoid code duplication, + * so create a list with the word on it. + */ + (void)Lst_AtEnd(curTargs, (ClientData)line); + } + + while(!Lst_IsEmpty(curTargs)) { + char *targName = (char *)Lst_DeQueue(curTargs); + + if (!Suff_IsTransform (targName)) { + gn = Targ_FindNode (targName, TARG_CREATE); + } else { + gn = Suff_AddTransform (targName); + } + + (void)Lst_AtEnd (targets, (ClientData)gn); + } + } else if (specType == Path && *line != '.' && *line != '\0') { + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); + } + + *cp = savec; + /* + * If it is a special type and not .PATH, it's the only target we + * allow on this line... + */ + if (specType != Not && specType != Path) { + Boolean warn = FALSE; + + while ((*cp != '!') && (*cp != ':') && *cp) { + if (*cp != ' ' && *cp != '\t') { + warn = TRUE; + } + cp++; + } + if (warn) { + Parse_Error(PARSE_WARNING, "Extra target ignored"); + } + } else { + while (*cp && isspace (*cp)) { + cp++; + } + } + line = cp; + } while ((*line != '!') && (*line != ':') && *line); + + /* + * Don't need the list of target names anymore... + */ + Lst_Destroy(curTargs, NOFREE); + + if (!Lst_IsEmpty(targets)) { + switch(specType) { + default: + Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); + break; + case Default: + case Begin: + case End: + case Interrupt: + /* + * These four create nodes on which to hang commands, so + * targets shouldn't be empty... + */ + case Not: + /* + * Nothing special here -- targets can be empty if it wants. + */ + break; + } + } + + /* + * Have now parsed all the target names. Must parse the operator next. The + * result is left in op . + */ + if (*cp == '!') { + op = OP_FORCE; + } else if (*cp == ':') { + if (cp[1] == ':') { + op = OP_DOUBLEDEP; + cp++; + } else { + op = OP_DEPENDS; + } + } else { + Parse_Error (PARSE_FATAL, "Missing dependency operator"); + return; + } + + cp++; /* Advance beyond operator */ + + Lst_ForEach (targets, ParseDoOp, (ClientData)op); + + /* + * Get to the first source + */ + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + + /* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * a .PATH removes all directories from the search path(s). + */ + if (!*line) { + switch (specType) { + case Suffixes: + Suff_ClearSuffixes (); + break; + case Precious: + allPrecious = TRUE; + break; + case Ignore: + ignoreErrors = TRUE; + break; + case Silent: + beSilent = TRUE; + break; + case Path: + Lst_ForEach(paths, ParseClearPath, (ClientData)NULL); + break; + } + } else if (specType == MFlags) { + /* + * Call on functions in main.c to deal with these arguments and + * set the initial character to a null-character so the loop to + * get sources won't get anything + */ + Main_ParseArgLine (line); + *line = '\0'; + } else if (specType == Shell) { + if (Job_ParseShell (line) != SUCCESS) { + Parse_Error (PARSE_FATAL, "improper shell specification"); + return; + } + *line = '\0'; + } else if ((specType == NotParallel) || (specType == SingleShell)) { + *line = '\0'; + } + + /* + * NOW GO FOR THE SOURCES + */ + if ((specType == Suffixes) || (specType == Path) || + (specType == Includes) || (specType == Libs) || + (specType == Null)) + { + while (*line) { + /* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. + * + * If the target was .SUFFIXES, we take each source as a + * suffix and add it to the list of suffixes maintained by the + * Suff module. + * + * If the target was a .PATH, we add the source as a directory + * to search on the search path. + * + * If it was .INCLUDES, the source is taken to be the suffix of + * files which will be #included and whose search path should + * be present in the .INCLUDES variable. + * + * If it was .LIBS, the source is taken to be the suffix of + * files which are considered libraries and whose search path + * should be present in the .LIBS variable. + * + * If it was .NULL, the source is the suffix to use when a file + * has no valid suffix. + */ + char savec; + while (*cp && !isspace (*cp)) { + cp++; + } + savec = *cp; + *cp = '\0'; + switch (specType) { + case Suffixes: + Suff_AddSuffix (line); + break; + case Path: + Lst_ForEach(paths, ParseAddDir, (ClientData)line); + break; + case Includes: + Suff_AddInclude (line); + break; + case Libs: + Suff_AddLib (line); + break; + case Null: + Suff_SetNull (line); + break; + } + *cp = savec; + if (savec != '\0') { + cp++; + } + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + } + if (paths) { + Lst_Destroy(paths, NOFREE); + } + } else { + while (*line) { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + while (*cp && !isspace (*cp)) { + if ((*cp == '(') && (cp > line) && (cp[-1] != '$')) { + /* + * Only stop for a left parenthesis if it isn't at the + * start of a word (that'll be for variable changes + * later) and isn't preceded by a dollar sign (a dynamic + * source). + */ + break; + } else { + cp++; + } + } + + if (*cp == '(') { + GNode *gn; + + sources = Lst_Init (FALSE); + if (Arch_ParseArchive (&line, sources, VAR_CMD) != SUCCESS) { + Parse_Error (PARSE_FATAL, + "Error in source archive spec \"%s\"", line); + return; + } + + while (!Lst_IsEmpty (sources)) { + gn = (GNode *) Lst_DeQueue (sources); + ParseDoSrc (tOp, gn->name); + } + Lst_Destroy (sources, NOFREE); + cp = line; + } else { + if (*cp) { + *cp = '\0'; + cp += 1; + } + + ParseDoSrc (tOp, line); + } + while (*cp && isspace (*cp)) { + cp++; + } + line = cp; + } + } + + if (mainNode == NILGNODE) { + /* + * If we have yet to decide on a main target to make, in the + * absence of any user input, we want the first target on + * the first dependency line that is actually a real target + * (i.e. isn't a .USE or .EXEC rule) to be made. + */ + Lst_ForEach (targets, ParseFindMain, (ClientData)0); + } + +} + +/*- + *--------------------------------------------------------------------- + * Parse_IsVar -- + * Return TRUE if the passed line is a variable assignment. A variable + * assignment consists of a single word followed by optional whitespace + * followed by either a += or an = operator. + * This function is used both by the Parse_File function and main when + * parsing the command-line arguments. + * + * Results: + * TRUE if it is. FALSE if it ain't + * + * Side Effects: + * none + *--------------------------------------------------------------------- + */ +Boolean +Parse_IsVar (line) + register char *line; /* the line to check */ +{ + register Boolean wasSpace = FALSE; /* set TRUE if found a space */ + register Boolean haveName = FALSE; /* Set TRUE if have a variable name */ + + /* + * Skip to variable name + */ + while ((*line == ' ') || (*line == '\t')) { + line++; + } + + while (*line != '=') { + if (*line == '\0') { + /* + * end-of-line -- can't be a variable assignment. + */ + return (FALSE); + } else if ((*line == ' ') || (*line == '\t')) { + /* + * there can be as much white space as desired so long as there is + * only one word before the operator + */ + wasSpace = TRUE; + } else if (wasSpace && haveName) { + /* + * Stop when an = operator is found. + */ + if ((*line == '+') || (*line == ':') || (*line == '?') || + (*line == '!')) { + break; + } + + /* + * This is the start of another word, so not assignment. + */ + return (FALSE); + } else { + haveName = TRUE; + wasSpace = FALSE; + } + line++; + } + + /* + * A final check: if we stopped on a +, ?, ! or :, the next character must + * be an = or it ain't a valid assignment + */ + if (((*line == '+') || + (*line == '?') || + (*line == ':') || + (*line == '!')) && + (line[1] != '=')) + { + return (FALSE); + } else { + return (haveName); + } +} + +/*- + *--------------------------------------------------------------------- + * Parse_DoVar -- + * Take the variable assignment in the passed line and do it in the + * global context. + * + * Note: There is a lexical ambiguity with assignment modifier characters + * in variable names. This routine interprets the character before the = + * as a modifier. Therefore, an assignment like + * C++=/usr/bin/CC + * is interpreted as "C+ +=" instead of "C++ =". + * + * Results: + * none + * + * Side Effects: + * the variable structure of the given variable name is altered in the + * global context. + *--------------------------------------------------------------------- + */ +void +Parse_DoVar (line, ctxt) + char *line; /* a line guaranteed to be a variable + * assignment. This reduces error checks */ + GNode *ctxt; /* Context in which to do the assignment */ +{ + register char *cp; /* pointer into line */ + enum { + VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL + } type; /* Type of assignment */ + char *opc; /* ptr to operator character to + * null-terminate the variable name */ + + /* + * Skip to variable name + */ + while ((*line == ' ') || (*line == '\t')) { + line++; + } + + /* + * Skip to operator character, nulling out whitespace as we go + */ + for (cp = line + 1; *cp != '='; cp++) { + if (isspace (*cp)) { + *cp = '\0'; + } + } + opc = cp-1; /* operator is the previous character */ + *cp++ = '\0'; /* nuke the = */ + + /* + * Check operator type + */ + switch (*opc) { + case '+': + type = VAR_APPEND; + *opc = '\0'; + break; + + case '?': + /* + * If the variable already has a value, we don't do anything. + */ + *opc = '\0'; + if (Var_Exists(line, ctxt)) { + return; + } else { + type = VAR_NORMAL; + } + break; + + case ':': + type = VAR_SUBST; + *opc = '\0'; + break; + + case '!': + type = VAR_SHELL; + *opc = '\0'; + break; + + default: + type = VAR_NORMAL; + break; + } + + while (isspace (*cp)) { + cp++; + } + + if (type == VAR_APPEND) { + Var_Append (line, cp, ctxt); + } else if (type == VAR_SUBST) { + /* + * Allow variables in the old value to be undefined, but leave their + * invocation alone -- this is done by forcing oldVars to be false. + * XXX: This can cause recursive variables, but that's not hard to do, + * and this allows someone to do something like + * + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * + * And not get an error. + */ + Boolean oldOldVars = oldVars; + + oldVars = FALSE; + cp = Var_Subst(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 */ + int pid; /* PID from wait() */ + 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) { + /* + * 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); + freeCmd = TRUE; + } else { + args[2] = cp; + freeCmd = FALSE; + } + args[3] = (char *)NULL; + + /* + * Open a pipe for fetching its output + */ + pipe(fds); + + /* + * Fork + */ + cpid = vfork(); + if (cpid == 0) { + /* + * Close input side of pipe + */ + close(fds[0]); + + /* + * Duplicate the output stream to the shell's output, then + * shut the extra thing down. Note we don't fetch the error + * stream...why not? Why? + */ + dup2(fds[1], 1); + close(fds[1]); + + execv("/bin/sh", args); + _exit(1); + } else if (cpid < 0) { + /* + * Couldn't fork -- tell the user and make the variable null + */ + Parse_Error(PARSE_WARNING, "Couldn't exec \"%s\"", cp); + Var_Set(line, "", ctxt); + } else { + int status; + int cc; + + /* + * No need for the writing half + */ + close(fds[1]); + + /* + * Wait for the process to exit. + * + * XXX: If the child writes more than a pipe's worth, we will + * deadlock. + */ + while(((pid = wait(&status)) != cpid) && (pid >= 0)) { + ; + } + + /* + * Read all the characters the child wrote. + */ + cc = read(fds[0], result, sizeof(result)); + + 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) { + /* + * Child returned an error -- tell the user but still use + * the result. + */ + 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; + + if (*cp == '\n') { + /* + * A final newline is just stripped + */ + *cp-- = '\0'; + } + while (cp >= result) { + if (*cp == '\n') { + *cp = ' '; + } + cp--; + } + Var_Set(line, result, ctxt); + + /* + * Close the input side of the pipe. + */ + close(fds[0]); + } + if (freeCmd) { + free(args[2]); + } + } else { + /* + * Normal assignment -- just do it. + */ + Var_Set (line, cp, ctxt); + } +} + +/*- + * ParseAddCmd -- + * Lst_ForEach function to add a command line to all targets + * + * Results: + * Always 0 + * + * Side Effects: + * A new element is added to the commands list of the node. + */ +static +ParseAddCmd(gn, cmd) + GNode *gn; /* the node to which the command is to be added */ + char *cmd; /* the command to add */ +{ + /* if target already supplied, ignore commands */ + if (!(gn->type & OP_HAS_COMMANDS)) + (void)Lst_AtEnd(gn->commands, (ClientData)cmd); + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseHasCommands -- + * Callback procedure for Parse_File when destroying the list of + * targets on the last dependency line. Marks a target as already + * having commands if it does, to keep from having shell commands + * on multiple dependency lines. + * + * Results: + * Always 0. + * + * Side Effects: + * OP_HAS_COMMANDS may be set for the target. + * + *----------------------------------------------------------------------- + */ +static int +ParseHasCommands(gn) + GNode *gn; /* Node to examine */ +{ + if (!Lst_IsEmpty(gn->commands)) { + gn->type |= OP_HAS_COMMANDS; + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Parse_AddIncludeDir -- + * Add a directory to the path searched for included makefiles + * bracketed by double-quotes. Used by functions in main.c + * + * Results: + * None. + * + * Side Effects: + * The directory is appended to the list. + * + *----------------------------------------------------------------------- + */ +void +Parse_AddIncludeDir (dir) + char *dir; /* The name of the directory to add */ +{ + Dir_AddDir (parseIncPath, dir); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoInclude -- + * Push to another file. + * + * The input is the line minus the #include. A file spec is a string + * enclosed in <> or "". The former is looked for only in sysIncPath. + * The latter in . and the directories specified by -I command line + * options + * + * 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 +ParseDoInclude (file) + char *file; /* file specification */ +{ + 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 */ + + /* + * Skip to delimiter character so we know where to look + */ + while ((*file == ' ') || (*file == '\t')) { + file++; + } + + if ((*file != '"') && (*file != '<')) { + Parse_Error (PARSE_FATAL, + ".include filename must be delimited by '\"' or '<'"); + return; + } + + /* + * Set the search path on which to find the include file based on the + * characters which bracket its name. Angle-brackets imply it's + * a system Makefile while double-quotes imply it's a user makefile + */ + if (*file == '<') { + isSystem = TRUE; + endc = '>'; + } else { + isSystem = FALSE; + endc = '"'; + } + + /* + * Skip to matching delimiter + */ + for (cp = ++file; *cp && *cp != endc; cp++) { + continue; + } + + if (*cp != endc) { + Parse_Error (PARSE_FATAL, + "Unclosed .include filename. '%c' expected", endc); + return; + } + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Var_Subst (file, VAR_CMD, FALSE); + + /* + * Now we know the file's name and its search path, we attempt to + * find the durn thing. A return of NULL indicates the file don't + * exist. + */ + if (!isSystem) { + /* + * Include files contained in double-quotes 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. + */ + char *prefEnd; + + prefEnd = rindex (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; + } + } 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) { + *cp = endc; + 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->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"); + if (curFILE == (FILE * ) NULL) { + Parse_Error (PARSE_FATAL, "Cannot open %s", fullname); + /* + * Pop to previous file + */ + (void) ParseEOF(0); + } +} + +/*- + *--------------------------------------------------------------------- + * ParseEOF -- + * Called when EOF is reached in the current file. If we were reading + * an include file, the includes stack is popped and things set up + * to go back to reading the previous file at the previous location. + * + * Results: + * CONTINUE if there's more to do. DONE if not. + * + * Side Effects: + * The old curFILE, is closed. The includes list is shortened. + * lineno, curFILE, and fname are changed if CONTINUE is returned. + *--------------------------------------------------------------------- + */ +static int +ParseEOF (opened) + int opened; +{ + IFile *ifile; /* the state on the top of the includes stack */ + + if (Lst_IsEmpty (includes)) { + return (DONE); + } + + ifile = (IFile *) Lst_DeQueue (includes); + free (fname); + fname = ifile->fname; + lineno = ifile->lineno; + if (opened) + (void) fclose (curFILE); + curFILE = ifile->F; + free ((Address)ifile); + return (CONTINUE); +} + +/*- + *--------------------------------------------------------------------- + * ParseReadc -- + * Read a character from the current file and update the line number + * counter as necessary + * + * Results: + * The character that was read + * + * Side Effects: + * The lineno counter is incremented if the character is a newline + *--------------------------------------------------------------------- + */ +#ifdef notdef +static int parseReadChar; + +#define ParseReadc() (((parseReadChar = getc(curFILE)) == '\n') ? \ + (lineno++, '\n') : parseReadChar) +#else +#define ParseReadc() (getc(curFILE)) +#endif /* notdef */ + + +/*- + *--------------------------------------------------------------------- + * ParseReadLine -- + * Read an entire line from the input file. Called only by Parse_File. + * To facilitate escaped newlines and what have you, a character is + * buffered in 'lastc', which is '\0' when no characters have been + * read. When we break out of the loop, c holds the terminating + * character and lastc holds a character that should be added to + * the line (unless we don't read anything but a terminator). + * + * Results: + * A line w/o its newline + * + * Side Effects: + * Only those associated with reading a character + *--------------------------------------------------------------------- + */ +static char * +ParseReadLine () +{ + Buffer buf; /* Buffer for current line */ + register int c; /* the current character */ + register int lastc; /* The most-recent character */ + Boolean semiNL; /* treat semi-colons as newlines */ + Boolean ignDepOp; /* TRUE if should ignore dependency operators + * for the purposes of setting semiNL */ + Boolean ignComment; /* TRUE if should ignore comments (in a + * shell command */ + char *line; /* Result */ + int lineLength; /* Length of result */ + + semiNL = FALSE; + ignDepOp = FALSE; + ignComment = FALSE; + + /* + * Handle special-characters at the beginning of the line. Either a + * leading tab (shell command) or pound-sign (possible conditional) + * forces us to ignore comments and dependency operators and treat + * semi-colons as semi-colons (by leaving semiNL FALSE). This also + * discards completely blank lines. + */ + while(1) { + 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; + } else { + /* + * Anything else breaks out without doing anything + */ + break; + } + } + + if (c != EOF) { + lastc = c; + buf = Buf_Init(BSIZE); + + while (((c = ParseReadc ()) != '\n' || (lastc == '\\')) && + (c != EOF)) + { +test_char: + switch(c) { + case '\n': + /* + * Escaped newline: read characters until a non-space or an + * unescaped newline and replace them all by a single space. + * This is done by storing the space over the backslash and + * dropping through with the next nonspace. If it is a + * semi-colon and semiNL is TRUE, it will be recognized as a + * newline in the code below this... + */ + lineno++; + lastc = ' '; + while ((c = ParseReadc ()) == ' ' || c == '\t') { + continue; + } + if (c == EOF || c == '\n') { + goto line_read; + } else { + /* + * Check for comments, semiNL's, etc. -- easier than + * ungetc(c, curFILE); continue; + */ + goto test_char; + } + break; + case ';': + /* + * Semi-colon: Need to see if it should be interpreted as a + * newline + */ + if (semiNL) { + /* + * To make sure the command that may be following this + * semi-colon begins with a tab, we push one back into the + * input stream. This will overwrite the semi-colon in the + * buffer. If there is no command following, this does no + * harm, since the newline remains in the buffer and the + * whole line is ignored. + */ + ungetc('\t', curFILE); + goto line_read; + } + break; + case '=': + if (!semiNL) { + /* + * Haven't seen a dependency operator before this, so this + * must be a variable assignment -- don't pay attention to + * dependency operators after this. + */ + ignDepOp = TRUE; + } else if (lastc == ':' || lastc == '!') { + /* + * Well, we've seen a dependency operator already, but it + * was the previous character, so this is really just an + * expanded variable assignment. Revert semi-colons to + * being just semi-colons again and ignore any more + * dependency operators. + * + * XXX: Note that a line like "foo : a:=b" will blow up, + * but who'd write a line like that anyway? + */ + ignDepOp = TRUE; semiNL = FALSE; + } + break; + case '#': + if (!ignComment) { + /* + * If the character is a hash mark and it isn't escaped + * (or we're being compatible), the thing is a comment. + * Skip to the end of the line. + */ + do { + c = ParseReadc(); + } while ((c != '\n') && (c != EOF)); + goto line_read; + } + break; + case ':': + case '!': + if (!ignDepOp && (c == ':' || c == '!')) { + /* + * A semi-colon is recognized as a newline only on + * dependency lines. Dependency lines are lines with a + * colon or an exclamation point. Ergo... + */ + semiNL = TRUE; + } + break; + } + /* + * Copy in the previous character and save this one in lastc. + */ + Buf_AddByte (buf, (Byte)lastc); + lastc = c; + + } + line_read: + lineno++; + + if (lastc != '\0') { + Buf_AddByte (buf, (Byte)lastc); + } + Buf_AddByte (buf, (Byte)'\0'); + line = (char *)Buf_GetAll (buf, &lineLength); + Buf_Destroy (buf, FALSE); + + if (line[0] == '.') { + /* + * The line might be a conditional. Ask the conditional module + * about it and act accordingly + */ + switch (Cond_Eval (line)) { + case COND_SKIP: + 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); + /*FALLTHRU*/ + case COND_PARSE: + free (line); + line = ParseReadLine(); + break; + } + } + + return (line); + } else { + /* + * Hit end-of-file, so return a NULL line to indicate this. + */ + return((char *)NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFinishLine -- + * Handle the end of a dependency group. + * + * Results: + * Nothing. + * + * Side Effects: + * inLine set FALSE. 'targets' list destroyed. + * + *----------------------------------------------------------------------- + */ +static void +ParseFinishLine() +{ + extern int Suff_EndTransform(); + + if (inLine) { + Lst_ForEach(targets, Suff_EndTransform, (ClientData)NULL); + Lst_Destroy (targets, ParseHasCommands); + inLine = FALSE; + } +} + + +/*- + *--------------------------------------------------------------------- + * Parse_File -- + * Parse a file into its component parts, incorporating it into the + * current dependency graph. This is the main function and controls + * almost every other function in this module + * + * Results: + * None + * + * Side Effects: + * Loads. Nodes are added to the list of all targets, nodes and links + * are added to the dependency graph. etc. etc. etc. + *--------------------------------------------------------------------- + */ +void +Parse_File(name, stream) + char *name; /* the name of the file being read */ + FILE * stream; /* Stream open to makefile to parse */ +{ + register char *cp, /* pointer into the line */ + *line; /* the line we're working on */ + + inLine = FALSE; + fname = name; + curFILE = stream; + lineno = 0; + fatals = 0; + + do { + while (line = ParseReadLine ()) { + if (*line == '.') { + /* + * Lines that begin with the special character are either + * include or undef directives. + */ + for (cp = line + 1; isspace (*cp); cp++) { + continue; + } + if (strncmp (cp, "include", 7) == 0) { + ParseDoInclude (cp + 7); + goto nextLine; + } else if (strncmp(cp, "undef", 5) == 0) { + char *cp2; + for (cp += 5; isspace(*cp); cp++) { + continue; + } + + for (cp2 = cp; !isspace(*cp2) && (*cp2 != '\0'); cp2++) { + continue; + } + + *cp2 = '\0'; + + Var_Delete(cp, VAR_GLOBAL); + goto nextLine; + } + } + if (*line == '#') { + /* If we're this far, the line must be a comment. */ + goto nextLine; + } + + if (*line == '\t' +#ifdef POSIX + || *line == ' ' +#endif + ) + { + /* + * 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++) { + continue; + } + if (*cp) { + if (inLine) { + /* + * So long as it's not a blank line and we're actually + * in a dependency spec, add the command to the list of + * commands of all targets in the dependency spec + */ + Lst_ForEach (targets, ParseAddCmd, (ClientData)cp); + continue; + } else { + Parse_Error (PARSE_FATAL, + "Unassociated shell command \"%.20s\"", + cp); + } + } + } else if (Parse_IsVar (line)) { + ParseFinishLine(); + Parse_DoVar (line, VAR_GLOBAL); + } else { + /* + * We now know it's a dependency line so it needs to have all + * variables expanded before being parsed. Tell the variable + * module to complain if some variable is undefined... + * To make life easier on novices, if the line is indented we + * first make sure the line has a dependency operator in it. + * If it doesn't have an operator and we're in a dependency + * line's script, we assume it's actually a shell command + * and add it to the current list of targets. + * + * Note that POSIX declares all lines that start with + * whitespace are shell commands, so there's no need to check + * here... + */ + Boolean nonSpace = FALSE; + + cp = line; +#ifndef POSIX + if (line[0] == ' ') { + while ((*cp != ':') && (*cp != '!') && (*cp != '\0')) { + if (!isspace(*cp)) { + nonSpace = TRUE; + } + cp++; + } + } + + if (*cp == '\0') { + if (inLine) { + Parse_Error (PARSE_WARNING, + "Shell command needs a leading tab"); + goto shellCommand; + } else if (nonSpace) { + Parse_Error (PARSE_FATAL, "Missing operator"); + } + } else { +#endif + ParseFinishLine(); + + cp = Var_Subst (line, VAR_CMD, TRUE); + free (line); + line = cp; + + /* + * Need a non-circular list for the target nodes + */ + targets = Lst_Init (FALSE); + inLine = TRUE; + + ParseDoDependency (line); +#ifndef POSIX + } +#endif + } + + nextLine: + + free (line); + } + /* + * Reached EOF, but it may be just EOF of an include file... + */ + } while (ParseEOF(1) == CONTINUE); + + /* + * Make sure conditionals are clean + */ + Cond_End(); + + if (fatals) { + fprintf (stderr, "Fatal errors encountered -- cannot continue\n"); + exit (1); + } +} + +/*- + *--------------------------------------------------------------------- + * Parse_Init -- + * initialize the parsing module + * + * Results: + * none + * + * Side Effects: + * the parseIncPath list is initialized... + *--------------------------------------------------------------------- + */ +Parse_Init () +{ + char *cp, *start; + /* avoid faults on read-only strings */ + static char syspath[] = _PATH_DEFSYSPATH; + + mainNode = NILGNODE; + parseIncPath = Lst_Init (FALSE); + sysIncPath = Lst_Init (FALSE); + includes = Lst_Init (FALSE); + + /* + * Add the directories from the DEFSYSPATH (more than one may be given + * as dir1:...:dirn) to the system include path. + */ + for (start = syspath; *start != '\0'; start = cp) { + for (cp = start; *cp != '\0' && *cp != ':'; cp++) { + ; + } + if (*cp == '\0') { + Dir_AddDir(sysIncPath, start); + } else { + *cp++ = '\0'; + Dir_AddDir(sysIncPath, start); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Parse_MainName -- + * Return a Lst of the main target to create for main()'s sake. If + * no such target exists, we Punt with an obnoxious error message. + * + * Results: + * A Lst of the single node to create. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Lst +Parse_MainName() +{ + Lst main; /* result list */ + + main = Lst_Init (FALSE); + + if (mainNode == NILGNODE) { + Punt ("make: no target to make.\n"); + /*NOTREACHED*/ + } else if (mainNode->type & OP_DOUBLEDEP) { + Lst_Concat(main, mainNode->cohorts, LST_CONCNEW); + } + (void) Lst_AtEnd (main, (ClientData)mainNode); + return (main); +} diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h new file mode 100644 index 000000000000..645e8c138fa4 --- /dev/null +++ b/usr.bin/make/pathnames.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 1990 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. + * + * @(#)pathnames.h 5.2 (Berkeley) 6/1/90 + */ + +#define _PATH_OBJDIR "obj" +#define _PATH_DEFSHELLDIR "/bin" +#define _PATH_DEFSYSMK "/usr/share/mk/sys.mk" +#define _PATH_DEFSYSPATH "/usr/share/mk" diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h new file mode 100644 index 000000000000..32ef1e0a91d0 --- /dev/null +++ b/usr.bin/make/sprite.h @@ -0,0 +1,114 @@ +/* + * 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. + * + * @(#)sprite.h 5.3 (Berkeley) 6/1/90 + */ + +/* + * sprite.h -- + * + * Common constants and type declarations for Sprite. + */ + +#ifndef _SPRITE +#define _SPRITE + + +/* + * A boolean type is defined as an integer, not an enum. This allows a + * boolean argument to be an expression that isn't strictly 0 or 1 valued. + */ + +typedef int Boolean; +#ifndef TRUE +#define TRUE 1 +#endif TRUE +#ifndef FALSE +#define FALSE 0 +#endif FALSE + +/* + * Functions that must return a status can return a ReturnStatus to + * indicate success or type of failure. + */ + +typedef int ReturnStatus; + +/* + * The following statuses overlap with the first 2 generic statuses + * defined in status.h: + * + * SUCCESS There was no error. + * FAILURE There was a general error. + */ + +#define SUCCESS 0x00000000 +#define FAILURE 0x00000001 + + +/* + * A nil pointer must be something that will cause an exception if + * referenced. There are two nils: the kernels nil and the nil used + * by user processes. + */ + +#define NIL 0xFFFFFFFF +#define USER_NIL 0 +#ifndef NULL +#define NULL 0 +#endif NULL + +/* + * An address is just a pointer in C. It is defined as a character pointer + * so that address arithmetic will work properly, a byte at a time. + */ + +typedef char *Address; + +/* + * ClientData is an uninterpreted word. It is defined as an int so that + * kdbx will not interpret client data as a string. Unlike an "Address", + * client data will generally not be used in arithmetic. + */ + +typedef int *ClientData; + +#ifdef notdef +#include "status.h" +#endif + +#endif _SPRITE diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c new file mode 100644 index 000000000000..bb746dcecd12 --- /dev/null +++ b/usr.bin/make/str.c @@ -0,0 +1,339 @@ +/*- + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90"; +#endif /* not lint */ + +#include "make.h" + +/*- + * str_concat -- + * concatenate the two strings, inserting a space or slash between them, + * freeing them if requested. + * + * returns -- + * the resulting string in allocated space. + */ +char * +str_concat(s1, s2, flags) + char *s1, *s2; + int flags; +{ + register int len1, len2; + register char *result; + + /* get the length of both strings */ + len1 = strlen(s1); + len2 = strlen(s2); + + /* allocate length plus separator plus EOS */ + result = emalloc((u_int)(len1 + len2 + 2)); + + /* copy first string into place */ + bcopy(s1, result, len1); + + /* add separator character */ + if (flags & STR_ADDSPACE) { + result[len1] = ' '; + ++len1; + } else if (flags & STR_ADDSLASH) { + result[len1] = '/'; + ++len1; + } + + /* copy second string plus EOS into place */ + bcopy(s2, result + len1, len2 + 1); + + /* free original strings */ + if (flags & STR_DOFREE) { + (void)free(s1); + (void)free(s2); + } + return(result); +} + +/*- + * brk_string -- + * Fracture a string into an array of words (as delineated by tabs or + * spaces) taking quotation marks into account. Leading tabs/spaces + * are ignored. + * + * returns -- + * Pointer to the array of pointers to the words. To make life easier, + * the first word is always the value of the .MAKE variable. + */ +char ** +brk_string(str, store_argc) + register char *str; + int *store_argc; +{ + static int argmax, curlen; + static char **argv, *buf; + register int argc, ch; + register char inquote, *p, *start, *t; + int len; + + /* save off pmake variable */ + if (!argv) { + argv = (char **)emalloc((argmax = 50) * sizeof(char *)); + argv[0] = Var_Value(".MAKE", VAR_GLOBAL); + } + + /* skip leading space chars. + for (; *str == ' ' || *str == '\t'; ++str); + + /* allocate room for a copy of the string */ + if ((len = strlen(str) + 1) > curlen) + buf = emalloc(curlen = len); + + /* + * copy the string; at the same time, parse backslashes, + * quotes and build the argument list. + */ + argc = 1; + inquote = '\0'; + for (p = str, start = t = buf;; ++p) { + switch(ch = *p) { + case '"': + case '\'': + if (inquote) + if (inquote == ch) + inquote = NULL; + else + break; + else + inquote = ch; + continue; + case ' ': + case '\t': + if (inquote) + break; + if (!start) + continue; + /* FALLTHROUGH */ + case '\n': + case '\0': + /* + * end of a token -- make sure there's enough argv + * space and save off a pointer. + */ + *t++ = '\0'; + if (argc == argmax) { + argmax *= 2; /* ramp up fast */ + if (!(argv = (char **)realloc(argv, + argmax * sizeof(char *)))) + enomem(); + } + argv[argc++] = start; + start = (char *)NULL; + if (ch == '\n' || ch == '\0') + goto done; + continue; + case '\\': + switch (ch = *++p) { + case '\0': + case '\n': + /* hmmm; fix it up as best we can */ + ch = '\\'; + --p; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + } + break; + } + if (!start) + start = t; + *t++ = ch; + } +done: argv[argc] = (char *)NULL; + *store_argc = argc; + return(argv); +} + +/* + * Str_FindSubstring -- See if a string contains a particular substring. + * + * Results: If string contains substring, the return value is the location of + * the first matching instance of substring in string. If string doesn't + * contain substring, the return value is NULL. Matching is done on an exact + * character-for-character basis with no wildcards or special characters. + * + * Side effects: None. + */ +char * +Str_FindSubstring(string, substring) + register char *string; /* String to search. */ + char *substring; /* Substring to find in string */ +{ + register char *a, *b; + + /* + * First scan quickly through the two strings looking for a single- + * character match. When it's found, then compare the rest of the + * substring. + */ + + for (b = substring; *string != 0; string += 1) { + if (*string != *b) + continue; + a = string; + for (;;) { + if (*b == 0) + return(string); + if (*a++ != *b++) + break; + } + b = substring; + } + return((char *) NULL); +} + +/* + * Str_Match -- + * + * See if a particular string matches a particular pattern. + * + * Results: Non-zero is returned if string matches pattern, 0 otherwise. The + * matching operation permits the following special characters in the + * pattern: *?\[] (see the man page for details on what these mean). + * + * Side effects: None. + */ +Str_Match(string, pattern) + register char *string; /* String */ + register char *pattern; /* Pattern */ +{ + char c2; + + for (;;) { + /* + * See if we're at the end of both the pattern and the + * string. If, we succeeded. If we're at the end of the + * pattern but not at the end of the string, we failed. + */ + if (*pattern == 0) + return(!*string); + if (*string == 0 && *pattern != '*') + return(0); + /* + * Check for a "*" as the next pattern character. It matches + * any substring. We handle this by calling ourselves + * recursively for each postfix of string, until either we + * match or we reach the end of the string. + */ + if (*pattern == '*') { + pattern += 1; + if (*pattern == 0) + return(1); + while (*string != 0) { + if (Str_Match(string, pattern)) + return(1); + ++string; + } + return(0); + } + /* + * Check for a "?" as the next pattern character. It matches + * any single character. + */ + if (*pattern == '?') + goto thisCharOK; + /* + * Check for a "[" as the next pattern character. It is + * followed by a list of characters that are acceptable, or + * by a range (two characters separated by "-"). + */ + if (*pattern == '[') { + ++pattern; + for (;;) { + if ((*pattern == ']') || (*pattern == 0)) + return(0); + if (*pattern == *string) + break; + if (pattern[1] == '-') { + c2 = pattern[2]; + if (c2 == 0) + return(0); + if ((*pattern <= *string) && + (c2 >= *string)) + break; + if ((*pattern >= *string) && + (c2 <= *string)) + break; + pattern += 2; + } + ++pattern; + } + while ((*pattern != ']') && (*pattern != 0)) + ++pattern; + goto thisCharOK; + } + /* + * If the next pattern character is '/', just strip off the + * '/' so we do exact matching on the character that follows. + */ + if (*pattern == '\\') { + ++pattern; + if (*pattern == 0) + return(0); + } + /* + * There's no special character. Just make sure that the + * next characters of each string match. + */ + if (*pattern != *string) + return(0); +thisCharOK: ++pattern; + ++string; + } +} diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c new file mode 100644 index 000000000000..ebb7714da7bd --- /dev/null +++ b/usr.bin/make/suff.c @@ -0,0 +1,2159 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)suff.c 5.6 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * suff.c -- + * Functions to maintain suffix lists and find implicit dependents + * using suffix transformation rules + * + * Interface: + * Suff_Init Initialize all things to do with suffixes. + * + * Suff_DoPaths This function is used to make life easier + * when searching for a file according to its + * suffix. It takes the global search path, + * as defined using the .PATH: target, and appends + * its directories to the path of each of the + * defined suffixes, as specified using + * .PATH<suffix>: targets. In addition, all + * directories given for suffixes labeled as + * include files or libraries, using the .INCLUDES + * or .LIBS targets, are played with using + * Dir_MakeFlags to create the .INCLUDES and + * .LIBS global variables. + * + * Suff_ClearSuffixes Clear out all the suffixes and defined + * transformations. + * + * Suff_IsTransform Return TRUE if the passed string is the lhs + * of a transformation rule. + * + * Suff_AddSuffix Add the passed string as another known suffix. + * + * Suff_GetPath Return the search path for the given suffix. + * + * Suff_AddInclude Mark the given suffix as denoting an include + * file. + * + * Suff_AddLib Mark the given suffix as denoting a library. + * + * Suff_AddTransform Add another transformation to the suffix + * graph. Returns GNode suitable for framing, I + * mean, tacking commands, attributes, etc. on. + * + * Suff_SetNull Define the suffix to consider the suffix of + * any file that doesn't have a known one. + * + * Suff_FindDeps Find implicit sources for and the location of + * a target based on its suffix. Returns the + * bottom-most node added to the graph or NILGNODE + * if the target had no implicit sources. + */ + +#include <stdio.h> +#include "make.h" +#include "bit.h" + +static Lst sufflist; /* Lst of suffixes */ +static Lst transforms; /* Lst of transformation rules */ + +static int sNum = 0; /* Counter for assigning suffix numbers */ + +/* + * Structure describing an individual suffix. + */ +typedef struct _Suff { + char *name; /* The suffix itself */ + int nameLen; /* Length of the suffix */ + short flags; /* Type of suffix */ +#define SUFF_INCLUDE 0x01 /* One which is #include'd */ +#define SUFF_LIBRARY 0x02 /* One which contains a library */ +#define SUFF_NULL 0x04 /* The empty suffix */ + Lst searchPath; /* The path along which files of this suffix + * may be found */ + int sNum; /* The suffix number */ + Lst parents; /* Suffixes we have a transformation to */ + Lst children; /* Suffixes we have a transformation from */ +} Suff; + +/* + * Structure used in the search for implied sources. + */ +typedef struct _Src { + char *file; /* The file to look for */ + char *pref; /* Prefix from which file was formed */ + Suff *suff; /* The suffix on the file */ + struct _Src *parent; /* The Src for which this is a source */ + GNode *node; /* The node describing the file */ + int children; /* Count of existing children (so we don't free + * this thing too early or never nuke it) */ +} Src; + +static Suff *suffNull; /* The NULL suffix for this run */ +static Suff *emptySuff; /* The empty suffix required for POSIX + * single-suffix transformation rules */ + + /*************** Lst Predicates ****************/ +/*- + *----------------------------------------------------------------------- + * SuffStrIsPrefix -- + * See if pref is a prefix of str. + * + * Results: + * NULL if it ain't, pointer to character in str after prefix if so + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static char * +SuffStrIsPrefix (pref, str) + register char *pref; /* possible prefix */ + register char *str; /* string to check */ +{ + while (*str && *pref == *str) { + pref++; + str++; + } + + return (*pref ? NULL : str); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffix -- + * See if suff is a suffix of str. Str should point to THE END of the + * string to check. (THE END == the null byte) + * + * Results: + * NULL if it ain't, pointer to character in str before suffix if + * it is. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static char * +SuffSuffIsSuffix (s, str) + register Suff *s; /* possible suffix */ + char *str; /* string to examine */ +{ + register char *p1; /* Pointer into suffix name */ + register char *p2; /* Pointer into string being examined */ + + p1 = s->name + s->nameLen; + p2 = str; + + while (p1 >= s->name && *p1 == *p2) { + p1--; + p2--; + } + + return (p1 == s->name - 1 ? p2 : NULL); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffixP -- + * Predicate form of SuffSuffIsSuffix. Passed as the callback function + * to Lst_Find. + * + * Results: + * 0 if the suffix is the one desired, non-zero if not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +SuffSuffIsSuffixP(s, str) + Suff *s; + char *str; +{ + return(!SuffSuffIsSuffix(s, str)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffHasNameP -- + * Callback procedure for finding a suffix based on its name. Used by + * Suff_GetPath. + * + * Results: + * 0 if the suffix is of the given name. non-zero otherwise. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffHasNameP (s, sname) + Suff *s; /* Suffix to check */ + char *sname; /* Desired name */ +{ + return (strcmp (sname, s->name)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsPrefix -- + * See if the suffix described by s is a prefix of the string. Care + * must be taken when using this to search for transformations and + * what-not, since there could well be two suffixes, one of which + * is a prefix of the other... + * + * Results: + * 0 if s is a prefix of str. non-zero otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffIsPrefix (s, str) + Suff *s; /* suffix to compare */ + char *str; /* string to examine */ +{ + return (SuffStrIsPrefix (s->name, str) == NULL ? 1 : 0); +} + +/*- + *----------------------------------------------------------------------- + * SuffGNHasNameP -- + * See if the graph node has the desired name + * + * Results: + * 0 if it does. non-zero if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffGNHasNameP (gn, name) + GNode *gn; /* current node we're looking at */ + char *name; /* name we're looking for */ +{ + return (strcmp (name, gn->name)); +} + + /*********** Maintenance Functions ************/ +/*- + *----------------------------------------------------------------------- + * SuffFree -- + * Free up all memory associated with the given suffix structure. + * + * Results: + * none + * + * Side Effects: + * the suffix entry is detroyed + *----------------------------------------------------------------------- + */ +static void +SuffFree (s) + Suff *s; +{ + Lst_Destroy (s->children, NOFREE); + Lst_Destroy (s->parents, NOFREE); + Lst_Destroy (s->searchPath, Dir_Destroy); + free ((Address)s->name); + free ((Address)s); +} + +/*- + *----------------------------------------------------------------------- + * SuffInsert -- + * Insert the suffix into the list keeping the list ordered by suffix + * numbers. + * + * Results: + * None + * + * Side Effects: + * Not really + *----------------------------------------------------------------------- + */ +static void +SuffInsert (l, s) + Lst l; /* the list where in s should be inserted */ + Suff *s; /* the suffix to insert */ +{ + LstNode ln; /* current element in l we're examining */ + Suff *s2; /* the suffix descriptor in this element */ + + if (Lst_Open (l) == FAILURE) { + return; + } + while ((ln = Lst_Next (l)) != NILLNODE) { + s2 = (Suff *) Lst_Datum (ln); + if (s2->sNum >= s->sNum) { + break; + } + } + + Lst_Close (l); + if (DEBUG(SUFF)) { + printf("inserting %s(%d)...", s->name, s->sNum); + } + if (ln == NILLNODE) { + if (DEBUG(SUFF)) { + printf("at end of list\n"); + } + (void)Lst_AtEnd (l, (ClientData)s); + } else if (s2->sNum != s->sNum) { + if (DEBUG(SUFF)) { + printf("before %s(%d)\n", s2->name, s2->sNum); + } + (void)Lst_Insert (l, ln, (ClientData)s); + } else if (DEBUG(SUFF)) { + printf("already there\n"); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_ClearSuffixes -- + * This is gross. Nuke the list of suffixes but keep all transformation + * rules around. The transformation graph is destroyed in this process, + * but we leave the list of rules so when a new graph is formed the rules + * will remain. + * This function is called from the parse module when a + * .SUFFIXES:\n line is encountered. + * + * Results: + * none + * + * Side Effects: + * the sufflist and its graph nodes are destroyed + *----------------------------------------------------------------------- + */ +void +Suff_ClearSuffixes () +{ + Lst_Destroy (sufflist, SuffFree); + + sufflist = Lst_Init(FALSE); + sNum = 0; + suffNull = emptySuff; +} + +/*- + *----------------------------------------------------------------------- + * SuffParseTransform -- + * Parse a transformation string to find its two component suffixes. + * + * Results: + * TRUE if the string is a valid transformation and FALSE otherwise. + * + * Side Effects: + * The passed pointers are overwritten. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffParseTransform(str, srcPtr, targPtr) + char *str; /* String being parsed */ + Suff **srcPtr; /* Place to store source of trans. */ + Suff **targPtr; /* Place to store target of trans. */ +{ + register LstNode srcLn; /* element in suffix list of trans source*/ + register Suff *src; /* Source of transformation */ + register LstNode targLn; /* element in suffix list of trans target*/ + 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 + * null suffix */ + + srcLn = NILLNODE; + singleLn = NILLNODE; + + /* + * Loop looking first for a suffix that matches the start of the + * string and then for one that exactly matches the rest of it. If + * we can find two that meet these criteria, we've successfully + * parsed the string. + */ + while (1) { + if (srcLn == NILLNODE) { + srcLn = Lst_Find(sufflist, (ClientData)str, SuffSuffIsPrefix); + } else { + srcLn = Lst_FindFrom (sufflist, Lst_Succ(srcLn), (ClientData)str, + SuffSuffIsPrefix); + } + if (srcLn == NILLNODE) { + /* + * Ran out of source suffixes -- no such rule + */ + if (singleLn != NILLNODE) { + /* + * Not so fast Mr. Smith! There was a suffix that encompassed + * the entire string, so we assume it was a transformation + * to the null suffix (thank you POSIX). We still prefer to + * find a double rule over a singleton, hence we leave this + * check until the end. + * + * XXX: Use emptySuff over suffNull? + */ + *srcPtr = single; + *targPtr = suffNull; + return(TRUE); + } + return (FALSE); + } + src = (Suff *) Lst_Datum (srcLn); + str2 = str + src->nameLen; + if (*str2 == '\0') { + single = src; + singleLn = srcLn; + } else { + targLn = Lst_Find(sufflist, (ClientData)str2, SuffSuffHasNameP); + if (targLn != NILLNODE) { + *srcPtr = src; + *targPtr = (Suff *)Lst_Datum(targLn); + return (TRUE); + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_IsTransform -- + * Return TRUE if the given string is a transformation rule + * + * + * Results: + * TRUE if the string is a concatenation of two known suffixes. + * FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Suff_IsTransform (str) + char *str; /* string to check */ +{ + Suff *src, *targ; + + return (SuffParseTransform(str, &src, &targ)); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddTransform -- + * Add the transformation rule described by the line to the + * list of rules and place the transformation itself in the graph + * + * Results: + * The node created for the transformation in the transforms list + * + * Side Effects: + * The node is placed on the end of the transforms Lst and links are + * made between the two suffixes mentioned in the target name + *----------------------------------------------------------------------- + */ +GNode * +Suff_AddTransform (line) + char *line; /* name of transformation to add */ +{ + GNode *gn; /* GNode of transformation rule */ + Suff *s, /* source suffix */ + *t; /* target suffix */ + LstNode ln; /* Node for existing transformation */ + + ln = Lst_Find (transforms, (ClientData)line, SuffGNHasNameP); + if (ln == NILLNODE) { + /* + * Make a new graph node for the transformation. It will be filled in + * by the Parse module. + */ + gn = Targ_NewGN (line); + (void)Lst_AtEnd (transforms, (ClientData)gn); + } else { + /* + * New specification for transformation rule. Just nuke the old list + * of commands so they can be filled in again... We don't actually + * free the commands themselves, because a given command can be + * attached to several different transformations. + */ + gn = (GNode *) Lst_Datum (ln); + Lst_Destroy (gn->commands, NOFREE); + Lst_Destroy (gn->children, NOFREE); + gn->commands = Lst_Init (FALSE); + gn->children = Lst_Init (FALSE); + } + + gn->type = OP_TRANSFORM; + + (void)SuffParseTransform(line, &s, &t); + + /* + * link the two together in the proper relationship and order + */ + if (DEBUG(SUFF)) { + printf("defining transformation from `%s' to `%s'\n", + s->name, t->name); + } + SuffInsert (t->children, s); + SuffInsert (s->parents, t); + + return (gn); +} + +/*- + *----------------------------------------------------------------------- + * Suff_EndTransform -- + * Handle the finish of a transformation definition, removing the + * transformation from the graph if it has neither commands nor + * sources. This is a callback procedure for the Parse module via + * Lst_ForEach + * + * Results: + * === 0 + * + * Side Effects: + * If the node has no commands or children, the children and parents + * lists of the affected suffices are altered. + * + *----------------------------------------------------------------------- + */ +int +Suff_EndTransform(gn) + GNode *gn; /* Node for transformation */ +{ + if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && + Lst_IsEmpty(gn->children)) + { + Suff *s, *t; + LstNode ln; + + (void)SuffParseTransform(gn->name, &s, &t); + + if (DEBUG(SUFF)) { + printf("deleting transformation from %s to %s\n", + s->name, t->name); + } + + /* + * Remove the source from the target's children list. We check for a + * nil return to handle a beanhead saying something like + * .c.o .c.o: + * + * We'll be called twice when the next target is seen, but .c and .o + * are only linked once... + */ + ln = Lst_Member(t->children, (ClientData)s); + if (ln != NILLNODE) { + (void)Lst_Remove(t->children, ln); + } + + /* + * Remove the target from the source's parents list + */ + ln = Lst_Member(s->parents, (ClientData)t); + if (ln != NILLNODE) { + (void)Lst_Remove(s->parents, ln); + } + } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) { + printf("transformation %s complete\n", gn->name); + } + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffRebuildGraph -- + * Called from Suff_AddSuffix via Lst_ForEach to search through the + * list of existing transformation rules and rebuild the transformation + * graph when it has been destroyed by Suff_ClearSuffixes. If the + * given rule is a transformation involving this suffix and another, + * existing suffix, the proper relationship is established between + * the two. + * + * Results: + * Always 0. + * + * Side Effects: + * The appropriate links will be made between this suffix and + * others if transformation rules exist for it. + * + *----------------------------------------------------------------------- + */ +static int +SuffRebuildGraph(transform, s) + GNode *transform; /* Transformation to test */ + Suff *s; /* Suffix to rebuild */ +{ + register char *cp; + register LstNode ln; + register Suff *s2; + + /* + * First see if it is a transformation from this suffix. + */ + cp = SuffStrIsPrefix(s->name, transform->name); + if (cp != (char *)NULL) { + ln = Lst_Find(sufflist, (ClientData)cp, SuffSuffHasNameP); + if (ln != NILLNODE) { + /* + * Found target. Link in and return, since it can't be anything + * else. + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s2->children, s); + SuffInsert(s->parents, s2); + return(0); + } + } + + /* + * Not from, maybe to? + */ + cp = SuffSuffIsSuffix(s, transform->name + strlen(transform->name)); + if (cp != (char *)NULL) { + /* + * Null-terminate the source suffix in order to find it. + */ + cp[1] = '\0'; + ln = Lst_Find(sufflist, (ClientData)transform->name, SuffSuffHasNameP); + /* + * Replace the start of the target suffix + */ + cp[1] = s->name[0]; + if (ln != NILLNODE) { + /* + * Found it -- establish the proper relationship + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s->children, s2); + SuffInsert(s2->parents, s); + } + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddSuffix -- + * Add the suffix in string to the end of the list of known suffixes. + * Should we restructure the suffix graph? Make doesn't... + * + * Results: + * None + * + * Side Effects: + * A GNode is created for the suffix and a Suff structure is created and + * added to the suffixes list unless the suffix was already known. + *----------------------------------------------------------------------- + */ +void +Suff_AddSuffix (str) + char *str; /* the name of the suffix to add */ +{ + Suff *s; /* new suffix descriptor */ + LstNode ln; + + ln = Lst_Find (sufflist, (ClientData)str, SuffSuffHasNameP); + if (ln == NILLNODE) { + s = (Suff *) emalloc (sizeof (Suff)); + + s->name = strdup (str); + s->nameLen = strlen (s->name); + s->searchPath = Lst_Init (FALSE); + s->children = Lst_Init (FALSE); + s->parents = Lst_Init (FALSE); + s->sNum = sNum++; + s->flags = 0; + + (void)Lst_AtEnd (sufflist, (ClientData)s); + /* + * Look for any existing transformations from or to this suffix. + * XXX: Only do this after a Suff_ClearSuffixes? + */ + Lst_ForEach (transforms, SuffRebuildGraph, (ClientData)s); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_GetPath -- + * Return the search path for the given suffix, if it's defined. + * + * Results: + * The searchPath for the desired suffix or NILLST if the suffix isn't + * defined. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Lst +Suff_GetPath (sname) + char *sname; +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln == NILLNODE) { + return (NILLST); + } else { + s = (Suff *) Lst_Datum (ln); + return (s->searchPath); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_DoPaths -- + * Extend the search paths for all suffixes to include the default + * search path. + * + * Results: + * None. + * + * Side Effects: + * The searchPath field of all the suffixes is extended by the + * directories in dirSearchPath. If paths were specified for the + * ".h" suffix, the directories are stuffed into a global variable + * called ".INCLUDES" with each directory preceeded by a -I. The same + * is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is -L. + *----------------------------------------------------------------------- + */ +void +Suff_DoPaths() +{ + register Suff *s; + register LstNode ln; + Lst inIncludes; /* Cumulative .INCLUDES path */ + Lst inLibs; /* Cumulative .LIBS path */ + + if (Lst_Open (sufflist) == FAILURE) { + return; + } + + inIncludes = Lst_Init(FALSE); + inLibs = Lst_Init(FALSE); + + while ((ln = Lst_Next (sufflist)) != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + if (!Lst_IsEmpty (s->searchPath)) { +#ifdef INCLUDES + if (s->flags & SUFF_INCLUDE) { + Dir_Concat(inIncludes, s->searchPath); + } +#endif /* INCLUDES */ +#ifdef LIBRARIES + if (s->flags & SUFF_LIBRARY) { + Dir_Concat(inLibs, s->searchPath); + } +#endif /* LIBRARIES */ + Dir_Concat(s->searchPath, dirSearchPath); + } else { + Lst_Destroy (s->searchPath, Dir_Destroy); + s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); + } + } + + Var_Set(".INCLUDES", Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); + Var_Set(".LIBS", Dir_MakeFlags("-L", inLibs), VAR_GLOBAL); + + Lst_Destroy(inIncludes, Dir_Destroy); + Lst_Destroy(inLibs, Dir_Destroy); + + Lst_Close (sufflist); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddInclude -- + * Add the given suffix as a type of file which gets included. + * Called from the parse module when a .INCLUDES line is parsed. + * The suffix must have already been defined. + * + * Results: + * None. + * + * Side Effects: + * The SUFF_INCLUDE bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddInclude (sname) + char *sname; /* Name of suffix to mark */ +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + s->flags |= SUFF_INCLUDE; + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddLib -- + * Add the given suffix as a type of file which is a library. + * Called from the parse module when parsing a .LIBS line. The + * suffix must have been defined via .SUFFIXES before this is + * called. + * + * Results: + * None. + * + * Side Effects: + * The SUFF_LIBRARY bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddLib (sname) + char *sname; /* Name of suffix to mark */ +{ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)sname, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *) Lst_Datum (ln); + s->flags |= SUFF_LIBRARY; + } +} + + /********** 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; + +/*- + *----------------------------------------------------------------------- + * SuffAddSrc -- + * Add a suffix as a Src structure to the given list with its parent + * being the given Src structure. If the suffix is the null suffix, + * the prefix is used unaltered as the file name in the Src structure. + * + * Results: + * always returns 0 + * + * Side Effects: + * A Src structure is created and tacked onto the end of the list + *----------------------------------------------------------------------- + */ +static int +SuffAddSrc (s, ls) + Suff *s; /* suffix for which to create a Src structure */ + LstSrc *ls; /* list and parent for the new Src */ +{ + Src *s2; /* new Src structure */ + Src *targ; /* Target structure */ + + targ = ls->s; + + if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { + /* + * If the suffix has been marked as the NULL suffix, also create a Src + * structure for a file with no suffix attached. Two birds, and all + * that... + */ + s2 = (Src *) emalloc (sizeof (Src)); + s2->file = strdup(targ->pref); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NILGNODE; + s2->suff = s; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd (ls->l, (ClientData)s2); + } + s2 = (Src *) emalloc (sizeof (Src)); + s2->file = str_concat (targ->pref, s->name, 0); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NILGNODE; + s2->suff = s; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd (ls->l, (ClientData)s2); + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffAddLevel -- + * Add all the children of targ as Src structures to the given list + * + * Results: + * None + * + * Side Effects: + * Lots of structures are created and added to the list + *----------------------------------------------------------------------- + */ +static void +SuffAddLevel (l, targ) + Lst l; /* list to which to add the new level */ + Src *targ; /* Src structure to use as the parent */ +{ + LstSrc ls; + + ls.s = targ; + ls.l = l; + + Lst_ForEach (targ->suff->children, SuffAddSrc, (ClientData)&ls); +} + +/*- + *---------------------------------------------------------------------- + * SuffFreeSrc -- + * Free all memory associated with a Src structure + * + * Results: + * None + * + * Side Effects: + * The memory is free'd. + *---------------------------------------------------------------------- + */ +static void +SuffFreeSrc (s) + Src *s; +{ + free ((Address)s->file); + if (!s->parent) { + free((Address)s->pref); + } else if (--s->parent->children == 0 && s->parent->parent) { + /* + * Parent has no more children, now we're gone, and it's not + * at the top of the tree, so blow it away too. + */ + SuffFreeSrc(s->parent); + } + free ((Address)s); +} + +/*- + *----------------------------------------------------------------------- + * SuffFindThem -- + * Find the first existing file/target in the list srcs + * + * Results: + * The lowest structure in the chain of transformations + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Src * +SuffFindThem (srcs) + Lst srcs; /* list of Src structures to search through */ +{ + Src *s; /* current Src */ + Src *rs; /* returned Src */ + + rs = (Src *) NULL; + + while (!Lst_IsEmpty (srcs)) { + s = (Src *) Lst_DeQueue (srcs); + + if (DEBUG(SUFF)) { + printf ("\ttrying %s...", s->file); + } + /* + * A file is considered to exist if either a node exists in the + * graph for it or the file actually exists. + */ + if ((Targ_FindNode(s->file, TARG_NOCREATE) != NILGNODE) || + (Dir_FindFile (s->file, s->suff->searchPath) != (char *) NULL)) + { + if (DEBUG(SUFF)) { + printf ("got it\n"); + } + rs = s; + break; + } else { + if (DEBUG(SUFF)) { + printf ("not there\n"); + } + SuffAddLevel (srcs, s); + } + } + return (rs); +} + +/*- + *----------------------------------------------------------------------- + * SuffFindCmds -- + * See if any of the children of the target in the Src structure is + * one from which the target can be transformed. If there is one, + * a Src structure is put together for it and returned. + * + * Results: + * The Src structure of the "winning" child, or NIL if no such beast. + * + * Side Effects: + * A Src structure may be allocated. + * + *----------------------------------------------------------------------- + */ +static Src * +SuffFindCmds (targ) + Src *targ; /* Src structure to play with */ +{ + LstNode ln; /* General-purpose list node */ + register GNode *t, /* Target GNode */ + *s; /* Source GNode */ + int prefLen;/* The length of the defined prefix */ + Suff *suff; /* Suffix on matching beastie */ + Src *ret; /* Return value */ + char *cp; + + t = targ->node; + (void) Lst_Open (t->children); + prefLen = strlen (targ->pref); + + while ((ln = Lst_Next (t->children)) != NILLNODE) { + s = (GNode *)Lst_Datum (ln); + + cp = rindex (s->name, '/'); + if (cp == (char *)NULL) { + cp = s->name; + } else { + cp++; + } + if (strncmp (cp, targ->pref, prefLen) == 0) { + /* + * The node matches the prefix ok, see if it has a known + * suffix. + */ + ln = Lst_Find (sufflist, (ClientData)&cp[prefLen], + SuffSuffHasNameP); + if (ln != NILLNODE) { + /* + * It even has a known suffix, see if there's a transformation + * defined between the node's suffix and the target's suffix. + * + * XXX: Handle multi-stage transformations here, too. + */ + suff = (Suff *)Lst_Datum (ln); + + if (Lst_Member (suff->parents, + (ClientData)targ->suff) != NILLNODE) + { + /* + * Hot Damn! Create a new Src structure to describe + * this transformation (making sure to duplicate the + * source node's name so Suff_FindDeps can free it + * again (ick)), and return the new structure. + */ + ret = (Src *)emalloc (sizeof(Src)); + ret->file = strdup(s->name); + ret->pref = targ->pref; + ret->suff = suff; + ret->parent = targ; + ret->node = s; + ret->children = 0; + targ->children += 1; + if (DEBUG(SUFF)) { + printf ("\tusing existing source %s\n", s->name); + } + return (ret); + } + } + } + } + Lst_Close (t->children); + return ((Src *)NULL); +} + +/*- + *----------------------------------------------------------------------- + * SuffExpandChildren -- + * Expand the names of any children of a given node that contain + * variable invocations or file wildcards into actual targets. + * + * Results: + * === 0 (continue) + * + * Side Effects: + * The expanded node is removed from the parent's list of children, + * and the parent's unmade counter is decremented, but other nodes + * may be added. + * + *----------------------------------------------------------------------- + */ +static int +SuffExpandChildren(cgn, pgn) + GNode *cgn; /* Child to examine */ + GNode *pgn; /* Parent node being processed */ +{ + GNode *gn; /* New source 8) */ + LstNode prevLN; /* Node after which new source should be put */ + LstNode ln; /* List element for old source */ + char *cp; /* Expanded value */ + + /* + * New nodes effectively take the place of the child, so place them + * after the child + */ + prevLN = Lst_Member(pgn->children, (ClientData)cgn); + + /* + * First do variable expansion -- this takes precedence over + * wildcard expansion. If the result contains wildcards, they'll be gotten + * to later since the resulting words are tacked on to the end of + * the children list. + */ + if (index(cgn->name, '$') != (char *)NULL) { + if (DEBUG(SUFF)) { + printf("Expanding \"%s\"...", cgn->name); + } + cp = Var_Subst(cgn->name, pgn, TRUE); + + if (cp != (char *)NULL) { + Lst members = Lst_Init(FALSE); + + if (cgn->type & OP_ARCHV) { + /* + * Node was an archive(member) target, so we want to call + * on the Arch module to find the nodes for us, expanding + * variables in the parent's context. + */ + char *sacrifice = cp; + + (void)Arch_ParseArchive(&sacrifice, members, pgn); + } else { + /* + * Break the result into a vector of strings whose nodes + * we can find, then add those nodes to the members list. + * Unfortunately, we can't use brk_string b/c it + * doesn't understand about variable specifications with + * spaces in them... + */ + char *start; + char *initcp = cp; /* For freeing... */ + + for (start = cp; *start == ' ' || *start == '\t'; start++) { + ; + } + for (cp = start; *cp != '\0'; cp++) { + if (*cp == ' ' || *cp == '\t') { + /* + * White-space -- terminate element, find the node, + * add it, skip any further spaces. + */ + *cp++ = '\0'; + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, (ClientData)gn); + while (*cp == ' ' || *cp == '\t') { + cp++; + } + /* + * Adjust cp for increment at start of loop, but + * set start to first non-space. + */ + start = cp--; + } else if (*cp == '$') { + /* + * Start of a variable spec -- contact variable module + * to find the end so we can skip over it. + */ + char *junk; + int len; + Boolean doFree; + + junk = Var_Parse(cp, pgn, TRUE, &len, &doFree); + if (junk != var_Error) { + cp += len - 1; + } + + if (doFree) { + free(junk); + } + } else if (*cp == '\\' && *cp != '\0') { + /* + * Escaped something -- skip over it + */ + cp++; + } + } + + if (cp != start) { + /* + * Stuff left over -- add it to the list too + */ + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, (ClientData)gn); + } + /* + * Point cp back at the beginning again so the variable value + * can be freed. + */ + cp = initcp; + } + /* + * Add all elements of the members list to the parent node. + */ + while(!Lst_IsEmpty(members)) { + gn = (GNode *)Lst_DeQueue(members); + + if (DEBUG(SUFF)) { + printf("%s...", gn->name); + } + if (Lst_Member(pgn->children, (ClientData)gn) == NILLNODE) { + (void)Lst_Append(pgn->children, prevLN, (ClientData)gn); + prevLN = Lst_Succ(prevLN); + (void)Lst_AtEnd(gn->parents, (ClientData)pgn); + pgn->unmade++; + } + } + Lst_Destroy(members, NOFREE); + /* + * Free the result + */ + free((char *)cp); + } + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + ln = Lst_Member(pgn->children, (ClientData)cgn); + pgn->unmade--; + Lst_Remove(pgn->children, ln); + if (DEBUG(SUFF)) { + printf("\n"); + } + } else if (Dir_HasWildcards(cgn->name)) { + Lst exp; /* List of expansions */ + Lst path; /* Search path along which to expand */ + + /* + * Find a path along which to expand the word. + * + * If the word has a known suffix, use that path. + * If it has no known suffix and we're allowed to use the null + * suffix, use its path. + * Else use the default system search path. + */ + cp = cgn->name + strlen(cgn->name); + ln = Lst_Find(sufflist, (ClientData)cp, SuffSuffIsSuffixP); + + if (DEBUG(SUFF)) { + printf("Wildcard expanding \"%s\"...", cgn->name); + } + + if (ln != NILLNODE) { + Suff *s = (Suff *)Lst_Datum(ln); + + if (DEBUG(SUFF)) { + printf("suffix is \"%s\"...", s->name); + } + path = s->searchPath; + } else { + /* + * Use default search path + */ + path = dirSearchPath; + } + + /* + * Expand the word along the chosen path + */ + exp = Lst_Init(FALSE); + Dir_Expand(cgn->name, path, exp); + + while (!Lst_IsEmpty(exp)) { + /* + * Fetch next expansion off the list and find its GNode + */ + cp = (char *)Lst_DeQueue(exp); + + if (DEBUG(SUFF)) { + printf("%s...", cp); + } + gn = Targ_FindNode(cp, TARG_CREATE); + + /* + * If gn isn't already a child of the parent, make it so and + * up the parent's count of unmade children. + */ + if (Lst_Member(pgn->children, (ClientData)gn) == NILLNODE) { + (void)Lst_Append(pgn->children, prevLN, (ClientData)gn); + prevLN = Lst_Succ(prevLN); + (void)Lst_AtEnd(gn->parents, (ClientData)pgn); + pgn->unmade++; + } + } + + /* + * Nuke what's left of the list + */ + Lst_Destroy(exp, NOFREE); + + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + ln = Lst_Member(pgn->children, (ClientData)cgn); + pgn->unmade--; + Lst_Remove(pgn->children, ln); + if (DEBUG(SUFF)) { + printf("\n"); + } + } + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffApplyTransform -- + * Apply a transformation rule, given the source and target nodes + * and suffixes. + * + * Results: + * TRUE if successful, FALSE if not. + * + * Side Effects: + * The source and target are linked and the commands from the + * transformation are added to the target node's commands list. + * All attributes but OP_DEPMASK and OP_TRANSFORM are applied + * to the target. The target also inherits all the sources for + * the transformation rule. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffApplyTransform(tGn, sGn, t, s) + GNode *tGn; /* Target node */ + GNode *sGn; /* Source node */ + Suff *t; /* Target suffix */ + Suff *s; /* Source suffix */ +{ + LstNode ln; /* General node */ + char *tname; /* Name of transformation rule */ + GNode *gn; /* Node for same */ + + if (Lst_Member(tGn->children, (ClientData)sGn) == NILLNODE) { + /* + * Not already linked, so form the proper links between the + * target and source. + */ + (void)Lst_AtEnd(tGn->children, (ClientData)sGn); + (void)Lst_AtEnd(sGn->parents, (ClientData)tGn); + tGn->unmade += 1; + } + + if ((sGn->type & OP_OPMASK) == OP_DOUBLEDEP) { + /* + * When a :: node is used as the implied source of a node, we have + * to link all its cohorts in as sources as well. Only the initial + * sGn gets the target in its iParents list, however, as that + * will be sufficient to get the .IMPSRC variable set for tGn + */ + for (ln=Lst_First(sGn->cohorts); ln != NILLNODE; ln=Lst_Succ(ln)) { + gn = (GNode *)Lst_Datum(ln); + + if (Lst_Member(tGn->children, (ClientData)gn) == NILLNODE) { + /* + * Not already linked, so form the proper links between the + * target and source. + */ + (void)Lst_AtEnd(tGn->children, (ClientData)gn); + (void)Lst_AtEnd(gn->parents, (ClientData)tGn); + tGn->unmade += 1; + } + } + } + /* + * Locate the transformation rule itself + */ + tname = str_concat(s->name, t->name, 0); + ln = Lst_Find(transforms, (ClientData)tname, SuffGNHasNameP); + free(tname); + + if (ln == NILLNODE) { + /* + * Not really such a transformation rule (can happen when we're + * called to link an OP_MEMBER and OP_ARCHV node), so return + * FALSE. + */ + return(FALSE); + } + + gn = (GNode *)Lst_Datum(ln); + + if (DEBUG(SUFF)) { + printf("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); + } + + /* + * Record last child for expansion purposes + */ + ln = Lst_Last(tGn->children); + + /* + * Pass the buck to Make_HandleUse to apply the rule + */ + (void)Make_HandleUse(gn, tGn); + + /* + * Deal with wildcards and variables in any acquired sources + */ + ln = Lst_Succ(ln); + if (ln != NILLNODE) { + Lst_ForEachFrom(tGn->children, ln, + SuffExpandChildren, (ClientData)tGn); + } + + /* + * Keep track of another parent to which this beast is transformed so + * the .IMPSRC variable can be set correctly for the parent. + */ + (void)Lst_AtEnd(sGn->iParents, (ClientData)tGn); + + return(TRUE); +} + + +/*- + *----------------------------------------------------------------------- + * SuffFindArchiveDeps -- + * Locate dependencies for an OP_ARCHV node. + * + * Results: + * None + * + * Side Effects: + * Same as Suff_FindDeps + * + *----------------------------------------------------------------------- + */ +static void +SuffFindArchiveDeps(gn) + GNode *gn; /* Node for which to locate dependencies */ +{ + char *eoarch; /* End of archive portion */ + char *eoname; /* End of member portion */ + GNode *mem; /* Node for member */ + static char *copy[] = { /* Variables to be copied from the member node */ + TARGET, /* Must be first */ + PREFIX, /* Must be second */ + }; + 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 */ + + /* + * The node is an archive(member) pair. so we must find a + * suffix for both of them. + */ + eoarch = index (gn->name, '('); + eoname = index (eoarch, ')'); + + *eoname = '\0'; /* Nuke parentheses during suffix search */ + *eoarch = '\0'; /* So a suffix can be found */ + + name = eoarch + 1; + + /* + * To simplify things, call Suff_FindDeps recursively on the member now, + * so we can simply compare the member's .PREFIX and .TARGET variables + * to locate its suffix. This allows us to figure out the suffix to + * use for the archive without having to do a quadratic search over the + * suffix list, backtracking for each one... + */ + mem = Targ_FindNode(name, TARG_CREATE); + Suff_FindDeps(mem); + + /* + * Create the link between the two nodes right off + */ + if (Lst_Member(gn->children, (ClientData)mem) == NILLNODE) { + (void)Lst_AtEnd(gn->children, (ClientData)mem); + (void)Lst_AtEnd(mem->parents, (ClientData)gn); + gn->unmade += 1; + } + + /* + * Copy in the variables from the member node to this one. + */ + for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) { + vals[i] = Var_Value(copy[i], mem); + Var_Set(copy[i], vals[i], gn); + } + + ms = mem->suffix; + if (ms == NULL) { + /* + * Didn't know what it was -- use .NULL suffix if not in make mode + */ + if (DEBUG(SUFF)) { + printf("using null suffix\n"); + } + ms = suffNull; + } + + + /* + * Set the other two local variables required for this target. + */ + Var_Set (MEMBER, name, gn); + Var_Set (ARCHIVE, gn->name, gn); + + if (ms != NULL) { + /* + * Member has a known suffix, so look for a transformation rule from + * it to a possible suffix of the archive. Rather than searching + * through the entire list, we just look at suffixes to which the + * member's suffix may be transformed... + */ + LstNode ln; + + /* + * Use first matching suffix... + */ + ln = Lst_Find(ms->parents, eoarch, SuffSuffIsSuffixP); + + if (ln != NILLNODE) { + /* + * Got one -- apply it + */ + if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) && + DEBUG(SUFF)) + { + printf("\tNo transformation from %s -> %s\n", + ms->name, ((Suff *)Lst_Datum(ln))->name); + } + } + } + + /* + * Replace the opening and closing parens now we've no need of the separate + * pieces. + */ + *eoarch = '('; *eoname = ')'; + + /* + * Pretend gn appeared to the left of a dependency operator so + * the user needn't provide a transformation from the member to the + * archive. + */ + if (OP_NOP(gn->type)) { + gn->type |= OP_DEPENDS; + } + + /* + * Flag the member as such so we remember to look in the archive for + * its modification time. + */ + mem->type |= OP_MEMBER; +} + +/*- + *----------------------------------------------------------------------- + * SuffFindNormalDeps -- + * Locate implicit dependencies for regular targets. + * + * Results: + * None. + * + * Side Effects: + * Same as Suff_FindDeps... + * + *----------------------------------------------------------------------- + */ +static void +SuffFindNormalDeps(gn) + GNode *gn; /* Node for which to find sources */ +{ + 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 + * transformed. They all have the same file, + * but different suff and pref fields */ + Src *bottom; /* Start of found transformation path */ + Src *src; /* General Src pointer */ + char *pref; /* Prefix to use */ + Src *targ; /* General Src target pointer */ + + + eoname = gn->name + strlen(gn->name); + + sopref = gn->name; + + /* + * Begin at the beginning... + */ + ln = Lst_First(sufflist); + srcs = Lst_Init(FALSE); + targs = Lst_Init(FALSE); + + /* + * We're caught in a catch-22 here. On the one hand, we want to use any + * transformation implied by the target's sources, but we can't examine + * the sources until we've expanded any variables/wildcards they may hold, + * and we can't do that until we've set up the target's local variables + * and we can't do that until we know what the proper suffix for the + * target is (in case there are two suffixes one of which is a suffix of + * the other) and we can't know that until we've found its implied + * source, which we may not want to use if there's an existing source + * that implies a different transformation. + * + * In an attempt to get around this, which may not work all the time, + * but should work most of the time, we look for implied sources first, + * checking transformations to all possible suffixes of the target, + * use what we find to set the target's local variables, expand the + * children, then look for any overriding transformations they imply. + * Should we find one, we discard the one we found before. + */ + while(ln != NILLNODE) { + /* + * Look for next possible suffix... + */ + ln = Lst_FindFrom(sufflist, ln, eoname, SuffSuffIsSuffixP); + + if (ln != NILLNODE) { + int prefLen; /* Length of the prefix */ + Src *targ; + + /* + * Allocate a Src structure to which things can be transformed + */ + targ = (Src *)emalloc(sizeof(Src)); + targ->file = strdup(gn->name); + targ->suff = (Suff *)Lst_Datum(ln); + targ->node = gn; + targ->parent = (Src *)NULL; + + /* + * Allocate room for the prefix, whose end is found by subtracting + * the length of the suffix from the end of the name. + */ + prefLen = (eoname - targ->suff->nameLen) - sopref; + targ->pref = emalloc(prefLen + 1); + bcopy(sopref, targ->pref, prefLen); + targ->pref[prefLen] = '\0'; + + /* + * Add nodes from which the target can be made + */ + SuffAddLevel(srcs, targ); + + /* + * Record the target so we can nuke it + */ + (void)Lst_AtEnd(targs, (ClientData)targ); + + /* + * Search from this suffix's successor... + */ + ln = Lst_Succ(ln); + } + } + + /* + * Handle target of unknown suffix... + */ + if (Lst_IsEmpty(targs) && suffNull != NULL) { + if (DEBUG(SUFF)) { + printf("\tNo known suffix on %s. Using .NULL suffix\n", gn->name); + } + + targ = (Src *)emalloc(sizeof(Src)); + targ->file = strdup(gn->name); + targ->suff = suffNull; + targ->node = gn; + targ->parent = (Src *)NULL; + targ->pref = strdup(sopref); + + SuffAddLevel(srcs, targ); + (void)Lst_AtEnd(targs, (ClientData)targ); + } + + /* + * Using the list of possible sources built up from the target suffix(es), + * try and find an existing file/target that matches. + */ + bottom = SuffFindThem(srcs); + + if (bottom == (Src *)NULL) { + /* + * No known transformations -- use the first suffix found for setting + * the local variables. + */ + if (!Lst_IsEmpty(targs)) { + targ = (Src *)Lst_Datum(Lst_First(targs)); + } else { + targ = (Src *)NULL; + } + } else { + /* + * 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) { + ; + } + } + + /* + * The .TARGET variable we always set to be the name at this point, + * since it's only set to the path if the thing is only a source and + * if it's only a source, it doesn't matter what we put here as far + * as expanding sources is concerned, since it has none... + */ + Var_Set(TARGET, gn->name, gn); + + pref = (targ != NULL) ? targ->pref : gn->name; + Var_Set(PREFIX, pref, gn); + + /* + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. + */ + Lst_ForEach(gn->children, SuffExpandChildren, (ClientData)gn); + + if (targ == NULL) { + if (DEBUG(SUFF)) { + printf("\tNo valid suffix on %s\n", gn->name); + } + +sfnd_abort: + /* + * Deal with finding the thing on the default search path if the + * node is only a source (not on the lhs of a dependency operator + * or [XXX] it has neither children or commands). + */ + if (OP_NOP(gn->type) || + (Lst_IsEmpty(gn->children) && Lst_IsEmpty(gn->commands))) + { + gn->path = Dir_FindFile(gn->name, + (targ == NULL ? dirSearchPath : + targ->suff->searchPath)); + if (gn->path != NULL) { + Var_Set(TARGET, gn->path, gn); + + if (targ != NULL) { + /* + * Suffix known for the thing -- trim the suffix off + * the path to form the proper .PREFIX variable. + */ + int len = strlen(gn->path); + char savec; + + gn->suffix = targ->suff; + + savec = gn->path[len-targ->suff->nameLen]; + gn->path[len-targ->suff->nameLen] = '\0'; + + Var_Set(PREFIX, gn->path, gn); + + gn->path[len-targ->suff->nameLen] = savec; + } else { + /* + * The .PREFIX gets the full path if the target has + * no known suffix. + */ + gn->suffix = NULL; + + Var_Set(PREFIX, gn->path, gn); + } + } + } else { + /* + * Not appropriate to search for the thing -- set the + * path to be the name so Dir_MTime won't go grovelling for + * it. + */ + gn->suffix = (targ == NULL) ? NULL : targ->suff; + gn->path = gn->name; + } + + goto sfnd_return; + } + + /* + * If the suffix indicates that the target is a library, mark that in + * the node's type field. + */ + if (targ->suff->flags & SUFF_LIBRARY) { + gn->type |= OP_LIB; + } + + /* + * Check for overriding transformation rule implied by sources + */ + if (!Lst_IsEmpty(gn->children)) { + src = SuffFindCmds(targ); + + if (src != (Src *)NULL) { + /* + * Free up all the Src structures in the transformation path + * up to, but not including, the parent node. + */ + while (bottom && bottom->parent != NULL) { + Src *p = bottom->parent; + + SuffFreeSrc(bottom); + bottom = p; + } + bottom = src; + } + } + + if (bottom == NULL) { + /* + * No idea from where it can come -- return now. + */ + goto sfnd_abort; + } + + /* + * We now have a list of Src structures headed by 'bottom' and linked via + * their 'parent' pointers. What we do next is create links between + * source and target nodes (which may or may not have been created) + * and set the necessary local variables in each target. The + * commands for each target are set from the commands of the + * transformation rule used to get from the src suffix to the targ + * suffix. Note that this causes the commands list of the original + * node, gn, to be replaced by the commands of the final + * transformation rule. Also, the unmade field of gn is incremented. + * Etc. + */ + if (bottom->node == NILGNODE) { + bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); + } + + for (src = bottom; src->parent != (Src *)NULL; src = src->parent) { + targ = src->parent; + + src->node->suffix = src->suff; + + if (targ->node == NILGNODE) { + targ->node = Targ_FindNode(targ->file, TARG_CREATE); + } + + SuffApplyTransform(targ->node, src->node, + targ->suff, src->suff); + + if (targ->node != gn) { + /* + * Finish off the dependency-search process for any nodes + * between bottom and gn (no point in questing around the + * filesystem for their implicit source when it's already + * known). Note that the node can't have any sources that + * need expanding, since SuffFindThem will stop on an existing + * node, so all we need to do is set the standard and System V + * variables. + */ + targ->node->type |= OP_DEPS_FOUND; + + Var_Set(PREFIX, targ->pref, targ->node); + + Var_Set(TARGET, targ->node->name, targ->node); + } + } + + gn->suffix = src->suff; + + /* + * So Dir_MTime doesn't go questing for it... + */ + gn->path = gn->name; + + /* + * Nuke the transformation path and the Src structures left over in the + * two lists. + */ + SuffFreeSrc(bottom); + +sfnd_return: + Lst_Destroy(srcs, SuffFreeSrc); + Lst_Destroy(targs, SuffFreeSrc); + +} + + + + +/*- + *----------------------------------------------------------------------- + * Suff_FindDeps -- + * Find implicit sources for the target described by the graph node + * gn + * + * Results: + * Nothing. + * + * Side Effects: + * Nodes are added to the graph below the passed-in node. The nodes + * are marked to have their IMPSRC variable filled in. The + * PREFIX variable is set for the given node and all its + * implied children. + * + * Notes: + * The path found by this target is the shortest path in the + * transformation graph, which may pass through non-existent targets, + * to an existing target. The search continues on all paths from the + * root suffix until a file is found. I.e. if there's a path + * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but + * the .c and .l files don't, the search will branch out in + * all directions from .o and again from all the nodes on the + * next level until the .l,v node is encountered. + * + *----------------------------------------------------------------------- + */ +void +Suff_FindDeps (gn) + GNode *gn; /* node we're dealing with */ +{ + if (gn->type & OP_DEPS_FOUND) { + /* + * If dependencies already found, no need to do it again... + */ + return; + } else { + gn->type |= OP_DEPS_FOUND; + } + + if (DEBUG(SUFF)) { + printf ("Suff_FindDeps (%s)\n", gn->name); + } + + if (gn->type & OP_ARCHV) { + SuffFindArchiveDeps(gn); + } else if (gn->type & OP_LIB) { + /* + * If the node is a library, it is the arch module's job to find it + * and set the TARGET variable accordingly. We merely provide the + * search path, assuming all libraries end in ".a" (if the suffix + * hasn't been defined, there's nothing we can do for it, so we just + * set the TARGET variable to the node's name in order to give it a + * value). + */ + LstNode ln; + Suff *s; + + ln = Lst_Find (sufflist, (ClientData)LIBSUFF, SuffSuffHasNameP); + if (ln != NILLNODE) { + gn->suffix = s = (Suff *) Lst_Datum (ln); + Arch_FindLib (gn, s->searchPath); + } else { + gn->suffix = NULL; + Var_Set (TARGET, gn->name, gn); + } + /* + * Because a library (-lfoo) target doesn't follow the standard + * filesystem conventions, we don't set the regular variables for + * the thing. .PREFIX is simply made empty... + */ + Var_Set(PREFIX, "", gn); + } else { + SuffFindNormalDeps(gn); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_SetNull -- + * Define which suffix is the null suffix. + * + * Results: + * None. + * + * Side Effects: + * 'suffNull' is altered. + * + * Notes: + * Need to handle the changing of the null suffix gracefully so the + * old transformation rules don't just go away. + * + *----------------------------------------------------------------------- + */ +void +Suff_SetNull(name) + char *name; /* Name of null suffix */ +{ + Suff *s; + LstNode ln; + + ln = Lst_Find(sufflist, (ClientData)name, SuffSuffHasNameP); + if (ln != NILLNODE) { + s = (Suff *)Lst_Datum(ln); + if (suffNull != (Suff *)NULL) { + suffNull->flags &= ~SUFF_NULL; + } + s->flags |= SUFF_NULL; + /* + * XXX: Here's where the transformation mangling would take place + */ + suffNull = s; + } else { + Parse_Error (PARSE_WARNING, "Desired null suffix %s not defined.", + name); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_Init -- + * Initialize suffixes module + * + * Results: + * None + * + * Side Effects: + * Many + *----------------------------------------------------------------------- + */ +void +Suff_Init () +{ + sufflist = Lst_Init (FALSE); + transforms = Lst_Init (FALSE); + + sNum = 0; + /* + * Create null suffix for single-suffix rules (POSIX). The thing doesn't + * actually go on the suffix list or everyone will think that's its + * suffix. + */ + emptySuff = suffNull = (Suff *) emalloc (sizeof (Suff)); + + suffNull->name = strdup (""); + suffNull->nameLen = 0; + suffNull->searchPath = Lst_Init (FALSE); + suffNull->children = Lst_Init (FALSE); + suffNull->parents = Lst_Init (FALSE); + suffNull->sNum = sNum++; + suffNull->flags = SUFF_NULL; + +} + +/********************* DEBUGGING FUNCTIONS **********************/ + +static int SuffPrintName(s) Suff *s; {printf ("%s ", s->name); return (0);} + +static int +SuffPrintSuff (s) + Suff *s; +{ + int flags; + int flag; + + printf ("# `%s'", s->name); + + flags = s->flags; + if (flags) { + fputs (" (", stdout); + while (flags) { + flag = 1 << (ffs(flags) - 1); + flags &= ~flag; + switch (flag) { + case SUFF_NULL: + printf ("NULL"); + break; + case SUFF_INCLUDE: + printf ("INCLUDE"); + break; + case SUFF_LIBRARY: + printf ("LIBRARY"); + break; + } + putc(flags ? '|' : ')', stdout); + } + } + putc ('\n', stdout); + printf ("#\tTo: "); + Lst_ForEach (s->parents, SuffPrintName, (ClientData)0); + putc ('\n', stdout); + printf ("#\tFrom: "); + Lst_ForEach (s->children, SuffPrintName, (ClientData)0); + putc ('\n', stdout); + printf ("#\tSearch Path: "); + Dir_PrintPath (s->searchPath); + putc ('\n', stdout); + return (0); +} + +static int +SuffPrintTrans (t) + GNode *t; +{ + extern int Targ_PrintCmd(); + + printf ("%-16s: ", t->name); + Targ_PrintType (t->type); + putc ('\n', stdout); + Lst_ForEach (t->commands, Targ_PrintCmd, (ClientData)0); + putc ('\n', stdout); + return(0); +} + +Suff_PrintAll() +{ + printf ("#*** Suffixes:\n"); + Lst_ForEach (sufflist, SuffPrintSuff, (ClientData)0); + + printf ("#*** Transformations:\n"); + Lst_ForEach (transforms, SuffPrintTrans, (ClientData)0); +} + diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c new file mode 100644 index 000000000000..824a3d6cd0ac --- /dev/null +++ b/usr.bin/make/targ.c @@ -0,0 +1,581 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)targ.c 5.9 (Berkeley) 3/1/91"; +#endif /* not lint */ + +/*- + * targ.c -- + * Functions for maintaining the Lst allTargets. Target nodes are + * kept in two structures: a Lst, maintained by the list library, and a + * hash table, maintained by the hash library. + * + * Interface: + * Targ_Init Initialization procedure. + * + * Targ_NewGN Create a new GNode for the passed target + * (string). The node is *not* placed in the + * hash table, though all its fields are + * initialized. + * + * Targ_FindNode Find the node for a given target, creating + * and storing it if it doesn't exist and the + * flags are right (TARG_CREATE) + * + * Targ_FindList Given a list of names, find nodes for all + * of them. If a name doesn't exist and the + * TARG_NOCREATE flag was given, an error message + * is printed. Else, if a name doesn't exist, + * its node is created. + * + * Targ_Ignore Return TRUE if errors should be ignored when + * creating the given target. + * + * Targ_Silent Return TRUE if we should be silent when + * creating the given target. + * + * Targ_Precious Return TRUE if the target is precious and + * should not be removed if we are interrupted. + * + * Debugging: + * Targ_PrintGraph Print out the entire graphm all variables + * and statistics for the directory cache. Should + * print something for suffixes, too, but... + */ + +#include <stdio.h> +#include <time.h> +#include "make.h" +#include "hash.h" + +static Lst allTargets; /* the list of all targets found so far */ +static Hash_Table targets; /* a hash table of same */ + +#define HTSIZE 191 /* initial size of hash table */ + +/*- + *----------------------------------------------------------------------- + * Targ_Init -- + * Initialize this module + * + * Results: + * None + * + * Side Effects: + * The allTargets list and the targets hash table are initialized + *----------------------------------------------------------------------- + */ +void +Targ_Init () +{ + allTargets = Lst_Init (FALSE); + Hash_InitTable (&targets, HTSIZE); +} + +/*- + *----------------------------------------------------------------------- + * Targ_NewGN -- + * Create and initialize a new graph node + * + * Results: + * An initialized graph node with the name field filled with a copy + * of the passed name + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +GNode * +Targ_NewGN (name) + char *name; /* the name to stick in the new node */ +{ + register GNode *gn; + + gn = (GNode *) emalloc (sizeof (GNode)); + gn->name = strdup (name); + gn->path = (char *) 0; + if (name[0] == '-' && name[1] == 'l') { + gn->type = OP_LIB; + } else { + gn->type = 0; + } + gn->unmade = 0; + gn->make = FALSE; + gn->made = UNMADE; + gn->childMade = FALSE; + gn->mtime = gn->cmtime = 0; + gn->iParents = Lst_Init (FALSE); + gn->cohorts = Lst_Init (FALSE); + gn->parents = Lst_Init (FALSE); + gn->children = Lst_Init (FALSE); + gn->successors = Lst_Init(FALSE); + gn->preds = Lst_Init(FALSE); + gn->context = Lst_Init (FALSE); + gn->commands = Lst_Init (FALSE); + + return (gn); +} + +/*- + *----------------------------------------------------------------------- + * Targ_FindNode -- + * Find a node in the list using the given name for matching + * + * Results: + * The node in the list if it was. If it wasn't, return NILGNODE of + * flags was TARG_NOCREATE or the newly created and initialized node + * if it was TARG_CREATE + * + * Side Effects: + * Sometimes a node is created and added to the list + *----------------------------------------------------------------------- + */ +GNode * +Targ_FindNode (name, flags) + char *name; /* the name to find */ + int flags; /* flags governing events when target not + * found */ +{ + GNode *gn; /* node in that element */ + Hash_Entry *he; /* New or used hash entry for node */ + Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ + /* an entry for the node */ + + + if (flags & TARG_CREATE) { + he = Hash_CreateEntry (&targets, name, &isNew); + if (isNew) { + gn = Targ_NewGN (name); + Hash_SetValue (he, gn); + (void) Lst_AtEnd (allTargets, (ClientData)gn); + } + } else { + he = Hash_FindEntry (&targets, name); + } + + if (he == (Hash_Entry *) NULL) { + return (NILGNODE); + } else { + return ((GNode *) Hash_GetValue (he)); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_FindList -- + * Make a complete list of GNodes from the given list of names + * + * Results: + * A complete list of graph nodes corresponding to all instances of all + * the names in names. + * + * Side Effects: + * If flags is TARG_CREATE, nodes will be created for all names in + * names which do not yet have graph nodes. If flags is TARG_NOCREATE, + * an error message will be printed for each name which can't be found. + * ----------------------------------------------------------------------- + */ +Lst +Targ_FindList (names, flags) + Lst names; /* list of names to find */ + int flags; /* flags used if no node is found for a given + * name */ +{ + Lst nodes; /* result list */ + register LstNode ln; /* name list element */ + register GNode *gn; /* node in tLn */ + char *name; + + nodes = Lst_Init (FALSE); + + if (Lst_Open (names) == FAILURE) { + return (nodes); + } + while ((ln = Lst_Next (names)) != NILLNODE) { + name = (char *)Lst_Datum(ln); + gn = Targ_FindNode (name, flags); + if (gn != NILGNODE) { + /* + * Note: Lst_AtEnd must come before the Lst_Concat so the nodes + * are added to the list in the order in which they were + * encountered in the makefile. + */ + (void) Lst_AtEnd (nodes, (ClientData)gn); + if (gn->type & OP_DOUBLEDEP) { + (void)Lst_Concat (nodes, gn->cohorts, LST_CONCNEW); + } + } else if (flags == TARG_NOCREATE) { + Error ("\"%s\" -- target unknown.", name); + } + } + Lst_Close (names); + return (nodes); +} + +/*- + *----------------------------------------------------------------------- + * Targ_Ignore -- + * Return true if should ignore errors when creating gn + * + * Results: + * TRUE if should ignore errors + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Ignore (gn) + GNode *gn; /* node to check for */ +{ + if (ignoreErrors || gn->type & OP_IGNORE) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Silent -- + * Return true if be silent when creating gn + * + * Results: + * TRUE if should be silent + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Silent (gn) + GNode *gn; /* node to check for */ +{ + if (beSilent || gn->type & OP_SILENT) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Precious -- + * See if the given target is precious + * + * Results: + * TRUE if it is precious. FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Precious (gn) + GNode *gn; /* the node to check */ +{ + if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) { + return (TRUE); + } else { + return (FALSE); + } +} + +/******************* DEBUG INFO PRINTING ****************/ + +static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ +/*- + *----------------------------------------------------------------------- + * Targ_SetMain -- + * Set our idea of the main target we'll be creating. Used for + * debugging output. + * + * Results: + * None. + * + * Side Effects: + * "mainTarg" is set to the main target's node. + *----------------------------------------------------------------------- + */ +void +Targ_SetMain (gn) + GNode *gn; /* The main target we'll create */ +{ + mainTarg = gn; +} + +static int +TargPrintName (gn, ppath) + GNode *gn; + int ppath; +{ + printf ("%s ", gn->name); +#ifdef notdef + if (ppath) { + if (gn->path) { + printf ("[%s] ", gn->path); + } + if (gn == mainTarg) { + printf ("(MAIN NAME) "); + } + } +#endif notdef + return (0); +} + + +int +Targ_PrintCmd (cmd) + char *cmd; +{ + printf ("\t%s\n", cmd); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Targ_FmtTime -- + * Format a modification time in some reasonable way and return it. + * + * Results: + * The time reformatted. + * + * Side Effects: + * The time is placed in a static area, so it is overwritten + * with each call. + * + *----------------------------------------------------------------------- + */ +char * +Targ_FmtTime (time) + time_t time; +{ + struct tm *parts; + static char buf[40]; + static char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + parts = localtime(&time); + + sprintf (buf, "%d:%02d:%02d %s %d, 19%d", + parts->tm_hour, parts->tm_min, parts->tm_sec, + months[parts->tm_mon], parts->tm_mday, parts->tm_year); + return(buf); +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintType -- + * Print out a type field giving only those attributes the user can + * set. + * + * Results: + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +void +Targ_PrintType (type) + register int type; +{ + register int tbit; + +#ifdef __STDC__ +#define PRINTBIT(attr) case CONCAT(OP_,attr): printf("." #attr " "); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG)) printf("." #attr " "); break +#else +#define PRINTBIT(attr) case CONCAT(OP_,attr): printf(".attr "); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG)) printf(".attr "); break +#endif /* __STDC__ */ + + type &= ~OP_OPMASK; + + while (type) { + tbit = 1 << (ffs(type) - 1); + type &= ~tbit; + + switch(tbit) { + PRINTBIT(OPTIONAL); + PRINTBIT(USE); + PRINTBIT(EXEC); + PRINTBIT(IGNORE); + PRINTBIT(PRECIOUS); + PRINTBIT(SILENT); + PRINTBIT(MAKE); + PRINTBIT(JOIN); + PRINTBIT(INVISIBLE); + PRINTBIT(NOTMAIN); + PRINTDBIT(LIB); + /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ + case OP_MEMBER: if (DEBUG(TARG)) printf(".MEMBER "); break; + PRINTDBIT(ARCHV); + } + } +} + +/*- + *----------------------------------------------------------------------- + * TargPrintNode -- + * print the contents of a node + *----------------------------------------------------------------------- + */ +static int +TargPrintNode (gn, pass) + GNode *gn; + int pass; +{ + if (!OP_NOP(gn->type)) { + printf("#\n"); + if (gn == mainTarg) { + printf("# *** MAIN TARGET ***\n"); + } + if (pass == 2) { + if (gn->unmade) { + printf("# %d unmade children\n", gn->unmade); + } else { + printf("# No unmade children\n"); + } + if (! (gn->type & (OP_JOIN|OP_USE|OP_EXEC))) { + if (gn->mtime != 0) { + printf("# last modified %s: %s\n", + Targ_FmtTime(gn->mtime), + (gn->made == UNMADE ? "unmade" : + (gn->made == MADE ? "made" : + (gn->made == UPTODATE ? "up-to-date" : + "error when made")))); + } else if (gn->made != UNMADE) { + printf("# non-existent (maybe): %s\n", + (gn->made == MADE ? "made" : + (gn->made == UPTODATE ? "up-to-date" : + (gn->made == ERROR ? "error when made" : + "aborted")))); + } else { + printf("# unmade\n"); + } + } + if (!Lst_IsEmpty (gn->iParents)) { + printf("# implicit parents: "); + Lst_ForEach (gn->iParents, TargPrintName, (ClientData)0); + putc ('\n', stdout); + } + } + if (!Lst_IsEmpty (gn->parents)) { + printf("# parents: "); + Lst_ForEach (gn->parents, TargPrintName, (ClientData)0); + putc ('\n', stdout); + } + + printf("%-16s", gn->name); + switch (gn->type & OP_OPMASK) { + case OP_DEPENDS: + printf(": "); break; + case OP_FORCE: + printf("! "); break; + case OP_DOUBLEDEP: + printf(":: "); break; + } + Targ_PrintType (gn->type); + Lst_ForEach (gn->children, TargPrintName, (ClientData)0); + putc ('\n', stdout); + Lst_ForEach (gn->commands, Targ_PrintCmd, (ClientData)0); + printf("\n\n"); + if (gn->type & OP_DOUBLEDEP) { + Lst_ForEach (gn->cohorts, TargPrintNode, (ClientData)pass); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * TargPrintOnlySrc -- + * Print only those targets that are just a source. + * + * Results: + * 0. + * + * Side Effects: + * The name of each file is printed preceeded by #\t + * + *----------------------------------------------------------------------- + */ +static int +TargPrintOnlySrc(gn) + GNode *gn; +{ + if (OP_NOP(gn->type)) { + printf("#\t%s [%s]\n", gn->name, + gn->path ? gn->path : gn->name); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintGraph -- + * print the entire graph. heh heh + * + * Results: + * none + * + * Side Effects: + * lots o' output + *----------------------------------------------------------------------- + */ +Targ_PrintGraph (pass) + int pass; /* Which pass this is. 1 => no processing + * 2 => processing done */ +{ + printf("#*** Input graph:\n"); + Lst_ForEach (allTargets, TargPrintNode, (ClientData)pass); + printf("\n\n"); + printf("#\n# Files that are only sources:\n"); + Lst_ForEach (allTargets, TargPrintOnlySrc); + printf("#*** Global Variables:\n"); + Var_Dump (VAR_GLOBAL); + printf("#*** Command-line Variables:\n"); + Var_Dump (VAR_CMD); + printf("\n"); + Dir_PrintDirectories(); + printf("\n"); + Suff_PrintAll(); +} diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c new file mode 100644 index 000000000000..86a8b87675e7 --- /dev/null +++ b/usr.bin/make/var.c @@ -0,0 +1,1879 @@ +/* + * 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. + */ + +#ifndef lint +static char sccsid[] = "@(#)var.c 5.7 (Berkeley) 6/1/90"; +#endif /* not lint */ + +/*- + * var.c -- + * Variable-handling functions + * + * Interface: + * Var_Set Set the value of a variable in the given + * context. The variable is created if it doesn't + * yet exist. The value and variable name need not + * be preserved. + * + * Var_Append Append more characters to an existing variable + * in the given context. The variable needn't + * exist already -- it will be created if it doesn't. + * A space is placed between the old value and the + * new one. + * + * Var_Exists See if a variable exists. + * + * 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 + * the given context as the top-most one. If the + * third argument is non-zero, Parse_Error is + * called if any variables are undefined. + * + * Var_Parse Parse a variable expansion from a string and + * return the result and the number of characters + * consumed. + * + * Var_Delete Delete a variable in a context. + * + * Var_Init Initialize this module. + * + * Debugging: + * Var_Dump Print out all variables defined in the given + * context. + * + * XXX: There's a lot of duplication in these functions. + */ + +#include <ctype.h> +#include "make.h" +#include "buf.h" +extern char *getenv(); + +/* + * This is a harmless return value for Var_Parse that can be used by Var_Subst + * to determine if there was an error in parsing -- easier than returning + * a flag, as things outside this module don't give a hoot. + */ +char var_Error[] = ""; + +/* + * Similar to var_Error, but returned when the 'err' flag for Var_Parse is + * set false. Why not just use a constant? Well, gcc likes to condense + * identical string instances... + */ +char varNoError[] = ""; + +/* + * Internally, variables are contained in four different contexts. + * 1) the environment. They may not be changed. If an environment + * variable is appended-to, the result is placed in the global + * context. + * 2) the global context. Variables set in the Makefile are located in + * the global context. It is the penultimate context searched when + * substituting. + * 3) the command-line context. All variables set on the command line + * are placed in this context. They are UNALTERABLE once placed here. + * 4) the local context. Each target has associated with it a context + * list. On this list are located the structures describing such + * local variables as $(@) and $(*) + * The four contexts are searched in the reverse order from which they are + * listed. + */ +GNode *VAR_GLOBAL; /* variables from the makefile */ +GNode *VAR_CMD; /* variables defined on the command-line */ + +#define FIND_CMD 0x1 /* look in VAR_CMD when searching */ +#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ +#define FIND_ENV 0x4 /* look in the environment also */ + +typedef struct Var { + char *name; /* the variable's name */ + Buffer val; /* its value */ + int flags; /* miscellaneous status flags */ +#define VAR_IN_USE 1 /* Variable's value currently being used. + * Used to avoid recursion */ +#define VAR_FROM_ENV 2 /* Variable comes from the environment */ +#define VAR_JUNK 4 /* Variable is a junk variable that + * should be destroyed when done with + * it. Used by Var_Parse for undefined, + * modified variables */ +} Var; + +/*- + *----------------------------------------------------------------------- + * VarCmp -- + * See if the given variable matches the named one. Called from + * Lst_Find when searching for a variable of a given name. + * + * Results: + * 0 if they match. non-zero otherwise. + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +static int +VarCmp (v, name) + Var *v; /* VAR structure to compare */ + char *name; /* name to look for */ +{ + return (strcmp (name, v->name)); +} + +/*- + *----------------------------------------------------------------------- + * VarFind -- + * Find the given variable in the given context and any other contexts + * indicated. + * + * Results: + * A pointer to the structure describing the desired variable or + * NIL if the variable does not exist. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Var * +VarFind (name, ctxt, flags) + char *name; /* name to find */ + GNode *ctxt; /* context in which to find it */ + int flags; /* FIND_GLOBAL set means to look in the + * VAR_GLOBAL context as well. + * FIND_CMD set means to look in the VAR_CMD + * context also. + * FIND_ENV set means to look in the + * environment */ +{ + LstNode var; + Var *v; + + /* + * If the variable name begins with a '.', it could very well be one of + * the local ones. We check the name against all the local variables + * and substitute the short version in for 'name' if it matches one of + * them. + */ + if (*name == '.' && isupper(name[1])) + switch (name[1]) { + case 'A': + if (!strcmp(name, ".ALLSRC")) + name = ALLSRC; + if (!strcmp(name, ".ARCHIVE")) + name = ARCHIVE; + break; + case 'I': + if (!strcmp(name, ".IMPSRC")) + name = IMPSRC; + break; + case 'M': + if (!strcmp(name, ".MEMBER")) + name = MEMBER; + break; + case 'O': + if (!strcmp(name, ".OODATE")) + name = OODATE; + break; + case 'P': + if (!strcmp(name, ".PREFIX")) + name = PREFIX; + break; + case 'T': + if (!strcmp(name, ".TARGET")) + name = TARGET; + break; + } + /* + * First look for the variable in the given context. If it's not there, + * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, + * depending on the FIND_* flags in 'flags' + */ + var = Lst_Find (ctxt->context, (ClientData)name, VarCmp); + + if ((var == NILLNODE) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { + var = Lst_Find (VAR_CMD->context, (ClientData)name, VarCmp); + } + if (!checkEnvFirst && (var == NILLNODE) && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + } + if ((var == NILLNODE) && (flags & FIND_ENV)) { + char *env; + + if ((env = getenv (name)) != NULL) { + /* + * If the variable is found in the environment, we only duplicate + * its value (since eVarVal was allocated on the stack). The name + * doesn't need duplication since it's always in the environment + */ + int len; + + v = (Var *) emalloc(sizeof(Var)); + v->name = name; + + len = strlen(env); + + v->val = Buf_Init(len); + Buf_AddBytes(v->val, len, (Byte *)env); + + v->flags = VAR_FROM_ENV; + return (v); + } else if (checkEnvFirst && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Lst_Find (VAR_GLOBAL->context, (ClientData)name, VarCmp); + if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *)Lst_Datum(var)); + } + } else { + return((Var *)NIL); + } + } else if (var == NILLNODE) { + return ((Var *) NIL); + } else { + return ((Var *) Lst_Datum (var)); + } +} + +/*- + *----------------------------------------------------------------------- + * VarAdd -- + * Add a new variable of name name and value val to the given context + * + * Results: + * None + * + * Side Effects: + * The new variable is placed at the front of the given context + * The name and val arguments are duplicated so they may + * safely be freed. + *----------------------------------------------------------------------- + */ +static +VarAdd (name, val, ctxt) + char *name; /* name of variable to add */ + char *val; /* value to set it to */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + int len; + + v = (Var *) emalloc (sizeof (Var)); + + v->name = strdup (name); + + len = strlen(val); + v->val = Buf_Init(len+1); + Buf_AddBytes(v->val, len, (Byte *)val); + + v->flags = 0; + + (void) Lst_AtFront (ctxt->context, (ClientData)v); + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Delete -- + * Remove a variable from a context. + * + * Results: + * None. + * + * Side Effects: + * The Var structure is removed and freed. + * + *----------------------------------------------------------------------- + */ +void +Var_Delete(name, ctxt) + char *name; + GNode *ctxt; +{ + LstNode ln; + + if (DEBUG(VAR)) { + printf("%s:delete %s\n", ctxt->name, name); + } + ln = Lst_Find(ctxt->context, (ClientData)name, VarCmp); + if (ln != NILLNODE) { + register Var *v; + + v = (Var *)Lst_Datum(ln); + Lst_Remove(ctxt->context, ln); + Buf_Destroy(v->val, TRUE); + free(v->name); + free((char *)v); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Set -- + * Set the variable name to the value val in the given context. + * + * Results: + * None. + * + * Side Effects: + * If the variable doesn't yet exist, a new record is created for it. + * Else the old value is freed and the new one stuck in its place + * + * Notes: + * The variable is searched for only in its context before being + * created in that context. I.e. if the context is VAR_GLOBAL, + * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only + * VAR_CMD->context is searched. This is done to avoid the literally + * thousands of unnecessary strcmp's that used to be done to + * set, say, $(@) or $(<). + *----------------------------------------------------------------------- + */ +void +Var_Set (name, val, ctxt) + char *name; /* name of variable to set */ + char *val; /* value to give to the variable */ + GNode *ctxt; /* context in which to set it */ +{ + register Var *v; + + /* + * We only look for a variable in the given context since anything set + * here will override anything in a lower context, so there's not much + * point in searching them all just to save a bit of memory... + */ + v = VarFind (name, ctxt, 0); + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_Discard(v->val, Buf_Size(v->val)); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, val); + } + } + /* + * Any variables given on the command line are automatically exported + * to the environment (as per POSIX standard) + */ + if (ctxt == VAR_CMD) { + setenv(name, val); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Append -- + * The variable of the given name has the given value appended to it in + * the given context. + * + * Results: + * None + * + * Side Effects: + * If the variable doesn't exist, it is created. Else the strings + * are concatenated (with a space in between). + * + * Notes: + * Only if the variable is being sought in the global context is the + * environment searched. + * XXX: Knows its calling circumstances in that if called with ctxt + * an actual target, it will only search that context since only + * a local variable could be being appended to. This is actually + * a big win and must be tolerated. + *----------------------------------------------------------------------- + */ +void +Var_Append (name, val, ctxt) + char *name; /* Name of variable to modify */ + char *val; /* String to append to it */ + GNode *ctxt; /* Context in which this should occur */ +{ + register Var *v; + register char *cp; + + v = VarFind (name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); + + if (v == (Var *) NIL) { + VarAdd (name, val, ctxt); + } else { + Buf_AddByte(v->val, (Byte)' '); + Buf_AddBytes(v->val, strlen(val), (Byte *)val); + + if (DEBUG(VAR)) { + printf("%s:%s = %s\n", ctxt->name, name, + Buf_GetAll(v->val, (int *)NULL)); + } + + if (v->flags & VAR_FROM_ENV) { + /* + * If the original variable came from the environment, we + * have to install it in the global context (we could place + * it in the environment, but then we should provide a way to + * export other variables...) + */ + v->flags &= ~VAR_FROM_ENV; + Lst_AtFront(ctxt->context, (ClientData)v); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Exists -- + * See if the given variable exists. + * + * Results: + * TRUE if it does, FALSE if it doesn't + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Var_Exists(name, ctxt) + char *name; /* Variable to find */ + GNode *ctxt; /* Context in which to start search */ +{ + Var *v; + + v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); + + if (v == (Var *)NIL) { + return(FALSE); + } else if (v->flags & VAR_FROM_ENV) { + Buf_Destroy(v->val, TRUE); + free((char *)v); + } + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * Var_Value -- + * Return the value of the named variable in the given context + * + * Results: + * The value if the variable exists, NULL if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Var_Value (name, ctxt) + char *name; /* name to find */ + GNode *ctxt; /* context in which to search for it */ +{ + Var *v; + + v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v != (Var *) NIL) { + return ((char *)Buf_GetAll(v->val, (int *)NULL)); + } else { + return ((char *) NULL); + } +} + +/*- + *----------------------------------------------------------------------- + * VarHead -- + * Remove the tail of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarHead (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* True if need to add a space to the buffer + * before sticking in the head */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + slash = rindex (word, '/'); + if (slash != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *slash = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *slash = '/'; + return (TRUE); + } else { + /* + * If no directory part, give . (q.v. the POSIX standard) + */ + if (addSpace) { + Buf_AddBytes(buf, 2, (Byte *)" ."); + } else { + Buf_AddByte(buf, (Byte)'.'); + } + return(TRUE); + } +} + +/*- + *----------------------------------------------------------------------- + * VarTail -- + * Remove the head of the given word and place the result in the given + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarTail (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to stick a space in the + * buffer before adding the tail */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *slash; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + slash = rindex (word, '/'); + if (slash != (char *)NULL) { + *slash++ = '\0'; + Buf_AddBytes (buf, strlen(slash), (Byte *)slash); + slash[-1] = '/'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarSuffix -- + * Place the suffix of the given word in the given buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The suffix from the word is placed in the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSuffix (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space before placing + * the suffix in the buffer */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + dot = rindex (word, '.'); + if (dot != (char *)NULL) { + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + *dot++ = '\0'; + Buf_AddBytes (buf, strlen (dot), (Byte *)dot); + dot[-1] = '.'; + return (TRUE); + } else { + return (addSpace); + } +} + +/*- + *----------------------------------------------------------------------- + * VarRoot -- + * Remove the suffix of the given word and place the result in the + * buffer. + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarRoot (word, addSpace, buf) + char *word; /* Word to trim */ + Boolean addSpace; /* TRUE if need to add a space to the buffer + * before placing the root in it */ + Buffer buf; /* Buffer in which to store it */ +{ + register char *dot; + + if (addSpace) { + Buf_AddByte (buf, (Byte)' '); + } + + dot = rindex (word, '.'); + if (dot != (char *)NULL) { + *dot = '\0'; + Buf_AddBytes (buf, strlen (word), (Byte *)word); + *dot = '.'; + } else { + Buf_AddBytes (buf, strlen(word), (Byte *)word); + } + return (TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the :M modifier. + * + * 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 +VarMatch (word, addSpace, buf, pattern) + 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 */ + char *pattern; /* Pattern the word must match */ +{ + if (Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + return(addSpace); +} + +/*- + *----------------------------------------------------------------------- + * VarNoMatch -- + * Place the word in the buffer if it doesn't match the given pattern. + * Callback function for VarModify to implement the :N modifier. + * + * 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 +VarNoMatch (word, addSpace, buf, pattern) + 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 */ + char *pattern; /* Pattern the word must match */ +{ + if (!Str_Match(word, pattern)) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), (Byte *)word); + } + 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; + +/*- + *----------------------------------------------------------------------- + * VarSubstitute -- + * Perform a string-substitution on the given word, placing the + * result in the passed buffer. + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSubstitute (word, addSpace, buf, pattern) + char *word; /* Word to modify */ + Boolean addSpace; /* True if space should be added before + * other characters */ + Buffer buf; /* Buffer for result */ + register VarPattern *pattern; /* Pattern for substitution */ +{ + register int wordLen; /* Length of word */ + register char *cp; /* General pointer */ + + wordLen = strlen(word); + if ((pattern->flags & VAR_NO_SUB) == 0) { + /* + * Still substituting -- break it down into simple anchored cases + * and if none of them fits, perform the general substitution case. + */ + if ((pattern->flags & VAR_MATCH_START) && + (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Anchored at start and beginning of word matches pattern + */ + if ((pattern->flags & VAR_MATCH_END) && + (wordLen == pattern->leftLen)) { + /* + * Also anchored at end and matches to the end (word + * is same length as pattern) add space and rhs only + * if rhs is non-null. + */ + if (pattern->rightLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + Buf_AddBytes(buf, pattern->rightLen, + (Byte *)pattern->rhs); + } + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Doesn't match to end -- copy word wholesale + */ + goto nosub; + } else { + /* + * Matches at start but need to copy in trailing characters + */ + if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){ + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + Buf_AddBytes(buf, wordLen - pattern->leftLen, + (Byte *)(word + pattern->leftLen)); + } + } else if (pattern->flags & VAR_MATCH_START) { + /* + * Had to match at start of word and didn't -- copy whole word. + */ + goto nosub; + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Anchored at end, Find only place match could occur (leftLen + * characters from the end of the word) and see if it does. Note + * that because the $ will be left at the end of the lhs, we have + * to use strncmp. + */ + cp = word + (wordLen - pattern->leftLen); + if ((cp >= word) && + (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Match found. If we will place characters in the buffer, + * add a space before hand as indicated by addSpace, then + * stuff in the initial, unmatched part of the word followed + * by the right-hand-side. + */ + if (((cp - word) + pattern->rightLen) != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, cp - word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + } else { + /* + * Had to match at end and didn't. Copy entire word. + */ + goto nosub; + } + } else { + /* + * Pattern is unanchored: search for the pattern in the word using + * String_FindSubstring, copying unmatched portions and the + * right-hand-side for each match found, handling non-global + * subsititutions correctly, etc. When the loop is done, any + * remaining part of the word (word and wordLen are adjusted + * accordingly through the loop) is copied straight into the + * buffer. + * addSpace is set FALSE as soon as a space is added to the + * buffer. + */ + register Boolean done; + int origSize; + + done = FALSE; + origSize = Buf_Size(buf); + while (!done) { + cp = Str_FindSubstring(word, pattern->lhs); + if (cp != (char *)NULL) { + if (addSpace && (((cp - word) + pattern->rightLen) != 0)){ + Buf_AddByte(buf, (Byte)' '); + addSpace = FALSE; + } + Buf_AddBytes(buf, cp-word, (Byte *)word); + Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs); + wordLen -= (cp - word) + pattern->leftLen; + word = cp + pattern->leftLen; + if (wordLen == 0) { + done = TRUE; + } + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + done = TRUE; + pattern->flags |= VAR_NO_SUB; + } + } else { + done = TRUE; + } + } + if (wordLen != 0) { + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + } + /* + * If added characters to the buffer, need to add a space + * before we add any more. If we didn't add any, just return + * the previous value of addSpace. + */ + return ((Buf_Size(buf) != origSize) || addSpace); + } + /* + * Common code for anchored substitutions: if performed a substitution + * and it's not supposed to be global, mark the pattern as requiring + * no more substitutions. addSpace was set TRUE if characters were + * added to the buffer. + */ + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + pattern->flags |= VAR_NO_SUB; + } + return (addSpace); + } + nosub: + if (addSpace) { + Buf_AddByte(buf, (Byte)' '); + } + Buf_AddBytes(buf, wordLen, (Byte *)word); + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * VarModify -- + * Modify each of the words of the passed string using the given + * function. Used to implement all modifiers. + * + * Results: + * A string of all the words modified appropriately. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarModify (str, modProc, datum) + char *str; /* String whose words should be trimmed */ + Boolean (*modProc)(); /* Function to use to modify them */ + ClientData datum; /* Datum to pass it */ +{ + Buffer buf; /* Buffer for the new string */ + register char *cp; /* Pointer to end of current word */ + char endc; /* Character that ended the word */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the trimmed + * word */ + + buf = Buf_Init (0); + cp = str; + addSpace = FALSE; + + while (1) { + /* + * Skip to next word and place cp at its end. + */ + while (isspace (*str)) { + str++; + } + for (cp = str; *cp != '\0' && !isspace (*cp); cp++) { + /* void */ ; + } + if (cp == str) { + /* + * If we didn't go anywhere, we must be done! + */ + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); + } + /* + * Nuke terminating character, but save it in endc b/c if str was + * some variable's value, it would not be good to screw it + * over... + */ + endc = *cp; + *cp = '\0'; + + addSpace = (* modProc) (str, addSpace, buf, datum); + + if (endc) { + *cp++ = endc; + } + str = cp; + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Parse -- + * Given the start of a variable invocation, extract the variable + * name and find its value, then modify it according to the + * specification. + * + * Results: + * The (possibly-modified) value of the variable or var_Error if the + * specification is invalid. The length of the specification is + * placed in *lengthPtr (for invalid specifications, this is just + * 2...?). + * A Boolean in *freePtr telling whether the returned string should + * be freed by the caller. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_Parse (str, ctxt, err, lengthPtr, freePtr) + char *str; /* The string to parse */ + GNode *ctxt; /* The context for the variable */ + Boolean err; /* TRUE if undefined variables are an error */ + int *lengthPtr; /* OUT: The length of the specification */ + Boolean *freePtr; /* OUT: TRUE if caller should free result */ +{ + register char *tstr; /* Pointer into str */ + Var *v; /* Variable in invocation */ + register char *cp; /* Secondary pointer into str (place marker + * for tstr) */ + Boolean haveModifier;/* TRUE if have modifiers for the variable */ + register char endc; /* Ending character when variable in parens + * or braces */ + char *start; + Boolean dynamic; /* TRUE if the variable is local and we're + * expanding it in a non-local context. This + * is done to support dynamic sources. The + * result is just the invocation, unaltered */ + + *freePtr = FALSE; + dynamic = FALSE; + start = str; + + if (str[1] != '(' && str[1] != '{') { + /* + * If it's not bounded by braces of some sort, life is much simpler. + * We just need to check for the first character and return the + * value if it exists. + */ + char name[2]; + + name[0] = str[1]; + name[1] = '\0'; + + v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v == (Var *)NIL) { + *lengthPtr = 2; + + if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[1]) { + case '@': + return("$(.TARGET)"); + case '%': + return("$(.ARCHIVE)"); + case '*': + return("$(.PREFIX)"); + case '!': + return("$(.MEMBER)"); + } + } + /* + * Error + */ + return (err ? var_Error : varNoError); + } else { + haveModifier = FALSE; + tstr = &str[1]; + endc = str[1]; + } + } else { + endc = str[1] == '(' ? ')' : '}'; + + /* + * Skip to the end character or a colon, whichever comes first. + */ + for (tstr = str + 2; + *tstr != '\0' && *tstr != endc && *tstr != ':'; + tstr++) + { + continue; + } + if (*tstr == ':') { + haveModifier = TRUE; + } else if (*tstr != '\0') { + haveModifier = FALSE; + } else { + /* + * If we never did find the end character, return NULL + * right now, setting the length to be the distance to + * the end of the string, since that's what make does. + */ + *lengthPtr = tstr - str; + return (var_Error); + } + *tstr = '\0'; + + v = VarFind (str + 2, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if ((v == (Var *)NIL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && + ((tstr-str) == 4) && (str[3] == 'F' || str[3] == 'D')) + { + /* + * Check for bogus D and F forms of local variables since we're + * in a local context and the name is the right length. + */ + switch(str[2]) { + case '@': + case '%': + case '*': + case '!': + case '>': + case '<': + { + char vname[2]; + char *val; + + /* + * Well, it's local -- go look for it. + */ + vname[0] = str[2]; + vname[1] = '\0'; + v = VarFind(vname, ctxt, 0); + + if (v != (Var *)NIL) { + /* + * No need for nested expansion or anything, as we're + * the only one who sets these things and we sure don't + * but nested invocations in them... + */ + val = (char *)Buf_GetAll(v->val, (int *)NULL); + + if (str[3] == 'D') { + val = VarModify(val, VarHead, (ClientData)0); + } else { + val = VarModify(val, VarTail, (ClientData)0); + } + /* + * Resulting string is dynamically allocated, so + * tell caller to free it. + */ + *freePtr = TRUE; + *lengthPtr = tstr-start+1; + *tstr = endc; + return(val); + } + break; + } + } + } + + if (v == (Var *)NIL) { + if ((((tstr-str) == 3) || + ((((tstr-str) == 4) && (str[3] == 'F' || + str[3] == 'D')))) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[2]) { + case '@': + case '%': + case '*': + case '!': + dynamic = TRUE; + break; + } + } else if (((tstr-str) > 4) && (str[2] == '.') && + isupper(str[3]) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + int len; + + len = (tstr-str) - 3; + if ((strncmp(str+2, ".TARGET", len) == 0) || + (strncmp(str+2, ".ARCHIVE", len) == 0) || + (strncmp(str+2, ".PREFIX", len) == 0) || + (strncmp(str+2, ".MEMBER", len) == 0)) + { + dynamic = TRUE; + } + } + + if (!haveModifier) { + /* + * No modifiers -- have specification length so we can return + * now. + */ + *lengthPtr = tstr - start + 1; + *tstr = endc; + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + return(str); + } else { + return (err ? var_Error : varNoError); + } + } else { + /* + * Still need to get to the end of the variable specification, + * so kludge up a Var structure for the modifications + */ + v = (Var *) emalloc(sizeof(Var)); + v->name = &str[1]; + v->val = Buf_Init(1); + v->flags = VAR_JUNK; + } + } + } + + if (v->flags & VAR_IN_USE) { + Fatal("Variable %s is recursive.", v->name); + /*NOTREACHED*/ + } else { + v->flags |= VAR_IN_USE; + } + /* + * Before doing any modification, we have to make sure the value + * has been fully expanded. If it looks like recursion might be + * necessary (there's a dollar sign somewhere in the variable's value) + * we just call Var_Subst to do any other substitutions that are + * necessary. Note that the value returned by Var_Subst will have + * been dynamically-allocated, so it will need freeing when we + * return. + */ + str = (char *)Buf_GetAll(v->val, (int *)NULL); + if (index (str, '$') != (char *)NULL) { + str = Var_Subst(str, ctxt, err); + *freePtr = TRUE; + } + + v->flags &= ~VAR_IN_USE; + + /* + * Now we need to apply any modifiers the user wants applied. + * These are: + * :M<pattern> words which match the given <pattern>. + * <pattern> is of the standard file + * wildcarding form. + * :S<d><pat1><d><pat2><d>[g] + * Substitute <pat2> for <pat1> in the value + * :H Substitute the head of each word + * :T Substitute the tail of each word + * :E Substitute the extension (minus '.') of + * each word + * :R Substitute the root of each word + * (pathname minus the suffix). + * :lhs=rhs Like :S, but the rhs goes to the end of + * the invocation. + */ + if ((str != (char *)NULL) && haveModifier) { + /* + * Skip initial colon while putting it back. + */ + *tstr++ = ':'; + while (*tstr != endc) { + char *newStr; /* New value to return */ + char termc; /* Character which terminated scan */ + + if (DEBUG(VAR)) { + printf("Applying :%c to \"%s\"\n", *tstr, str); + } + switch (*tstr) { + case 'N': + case 'M': + { + char *pattern; + char *cp2; + Boolean copy; + + copy = FALSE; + for (cp = tstr + 1; + *cp != '\0' && *cp != ':' && *cp != endc; + cp++) + { + if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)){ + copy = TRUE; + cp++; + } + } + termc = *cp; + *cp = '\0'; + if (copy) { + /* + * Need to compress the \:'s out of the pattern, so + * allocate enough room to hold the uncompressed + * pattern (note that cp started at tstr+1, so + * cp - tstr takes the null byte into account) and + * compress the pattern into the space. + */ + pattern = emalloc(cp - tstr); + for (cp2 = pattern, cp = tstr + 1; + *cp != '\0'; + cp++, cp2++) + { + if ((*cp == '\\') && + (cp[1] == ':' || cp[1] == endc)) { + cp++; + } + *cp2 = *cp; + } + *cp2 = '\0'; + } else { + pattern = &tstr[1]; + } + if (*tstr == 'M' || *tstr == 'm') { + newStr = VarModify(str, VarMatch, (ClientData)pattern); + } else { + newStr = VarModify(str, VarNoMatch, + (ClientData)pattern); + } + if (copy) { + free(pattern); + } + break; + } + case 'S': + { + VarPattern pattern; + register char delim; + Buffer buf; /* Buffer for patterns */ + register char *cp2; + int lefts; + + pattern.flags = 0; + delim = tstr[1]; + tstr += 2; + /* + * If pattern begins with '^', it is anchored to the + * start of the word -- skip over it and flag pattern. + */ + if (*tstr == '^') { + pattern.flags |= VAR_MATCH_START; + tstr += 1; + } + + buf = Buf_Init(0); + + /* + * Pass through the lhs looking for 1) escaped delimiters, + * '$'s and backslashes (place the escaped character in + * uninterpreted) and 2) unescaped $'s that aren't before + * the delimiter (expand the variable substitution). + * The result is left in the Buffer buf. + */ + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '$') || + (cp[1] == '\\'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if (*cp == '$') { + if (cp[1] != delim) { + /* + * If unescaped dollar sign not before the + * delimiter, assume it's a variable + * substitution and recurse. + */ + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + if (freeIt) { + free(cp2); + } + cp += len - 1; + } else { + /* + * Unescaped $ at end of pattern => anchor + * pattern at end. + */ + pattern.flags |= VAR_MATCH_END; + } + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If lhs didn't end with the delimiter, complain and + * return NULL + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + /* + * Fetch pattern and destroy buffer, but preserve the data + * in it, since that's our lhs. Note that Buf_GetAll + * will return the actual number of bytes, which includes + * the null byte, so we have to decrement the length by + * one. + */ + pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen); + pattern.leftLen--; + Buf_Destroy(buf, FALSE); + + /* + * Now comes the replacement string. Three things need to + * be done here: 1) need to compress escaped delimiters and + * ampersands and 2) need to replace unescaped ampersands + * with the l.h.s. (since this isn't regexp, we can do + * it right here) and 3) expand any variable substitutions. + */ + buf = Buf_Init(0); + + tstr = cp + 1; + for (cp = tstr; *cp != '\0' && *cp != delim; cp++) { + if ((*cp == '\\') && + ((cp[1] == delim) || + (cp[1] == '&') || + (cp[1] == '\\') || + (cp[1] == '$'))) + { + Buf_AddByte(buf, (Byte)cp[1]); + cp++; + } else if ((*cp == '$') && (cp[1] != delim)) { + char *cp2; + int len; + Boolean freeIt; + + cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); + Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); + cp += len - 1; + if (freeIt) { + free(cp2); + } + } else if (*cp == '&') { + Buf_AddBytes(buf, pattern.leftLen, + (Byte *)pattern.lhs); + } else { + Buf_AddByte(buf, (Byte)*cp); + } + } + + Buf_AddByte(buf, (Byte)'\0'); + + /* + * If didn't end in delimiter character, complain + */ + if (*cp != delim) { + *lengthPtr = cp - start + 1; + if (*freePtr) { + free(str); + } + Buf_Destroy(buf, TRUE); + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + return (var_Error); + } + + pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen); + pattern.rightLen--; + Buf_Destroy(buf, FALSE); + + /* + * Check for global substitution. If 'g' after the final + * delimiter, substitution is global and is marked that + * way. + */ + cp++; + if (*cp == 'g') { + pattern.flags |= VAR_SUB_GLOBAL; + cp++; + } + + termc = *cp; + newStr = VarModify(str, VarSubstitute, + (ClientData)&pattern); + /* + * Free the two strings. + */ + free(pattern.lhs); + free(pattern.rhs); + break; + } + case 'T': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarTail, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'H': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarHead, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'E': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarSuffix, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + case 'R': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify (str, VarRoot, (ClientData)0); + cp = tstr + 1; + termc = *cp; + break; + } + /*FALLTHRU*/ + default: { + /* + * This can either be a bogus modifier or a System-V + * substitution command. + */ + VarPattern pattern; + Boolean eqFound; + + pattern.flags = 0; + eqFound = FALSE; + /* + * First we make a pass through the string trying + * to verify it is a SYSV-make-style translation: + * it must be: <string1>=<string2>) + */ + for (cp = tstr; *cp != '\0' && *cp != endc; cp++) { + if (*cp == '=') { + eqFound = TRUE; + /* continue looking for endc */ + } + } + if (*cp == endc && eqFound) { + + /* + * Now we break this sucker into the lhs and + * rhs. We must null terminate them of course. + */ + for (cp = tstr; *cp != '='; cp++) { + ; + } + pattern.lhs = tstr; + pattern.leftLen = cp - tstr; + *cp++ = '\0'; + + pattern.rhs = cp; + while (*cp != endc) { + cp++; + } + pattern.rightLen = cp - pattern.rhs; + *cp = '\0'; + + /* + * 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, + (ClientData)&pattern); + + /* + * Restore the nulled characters + */ + pattern.lhs[pattern.leftLen] = '='; + pattern.rhs[pattern.rightLen] = endc; + termc = endc; + } else { + Error ("Unknown modifier '%c'\n", *tstr); + for (cp = tstr+1; + *cp != ':' && *cp != endc && *cp != '\0'; + cp++) { + ; + } + termc = *cp; + newStr = var_Error; + } + } + } + if (DEBUG(VAR)) { + printf("Result is \"%s\"\n", newStr); + } + + if (*freePtr) { + free (str); + } + str = newStr; + if (str != var_Error) { + *freePtr = TRUE; + } else { + *freePtr = FALSE; + } + if (termc == '\0') { + Error("Unclosed variable specification for %s", v->name); + } else if (termc == ':') { + *cp++ = termc; + } else { + *cp = termc; + } + tstr = cp; + } + *lengthPtr = tstr - start + 1; + } else { + *lengthPtr = tstr - start + 1; + *tstr = endc; + } + + if (v->flags & VAR_FROM_ENV) { + Boolean destroy = FALSE; + + if (str != (char *)Buf_GetAll(v->val, (int *)NULL)) { + destroy = TRUE; + } else { + /* + * Returning the value unmodified, so tell the caller to free + * the thing. + */ + *freePtr = TRUE; + } + Buf_Destroy(v->val, destroy); + free((Address)v); + } else if (v->flags & VAR_JUNK) { + /* + * Perform any free'ing needed and set *freePtr to FALSE so the caller + * doesn't try to free a static pointer. + */ + if (*freePtr) { + free(str); + } + *freePtr = FALSE; + free((Address)v); + if (dynamic) { + str = emalloc(*lengthPtr + 1); + strncpy(str, start, *lengthPtr); + str[*lengthPtr] = '\0'; + *freePtr = TRUE; + } else { + str = var_Error; + } + } + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_Subst -- + * Substitute for all variables in the given string in the given context + * If undefErr is TRUE, Parse_Error will be called when an undefined + * variable is encountered. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. The old string must be freed by the caller + *----------------------------------------------------------------------- + */ +char * +Var_Subst (str, ctxt, undefErr) + register char *str; /* the string in which to substitute */ + GNode *ctxt; /* the context wherein to find variables */ + Boolean undefErr; /* TRUE if undefineds are an error */ +{ + Buffer buf; /* Buffer for forming things */ + char *val; /* Value to substitute for a variable */ + int length; /* Length of the variable invocation */ + Boolean doFree; /* Set true if val should be freed */ + static Boolean errorReported; /* Set true if an error has already + * been reported to prevent a plethora + * of messages when recursing */ + + buf = Buf_Init (BSIZE); + errorReported = FALSE; + + while (*str) { + if ((*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 + * dollar sign into the buffer directly. + */ + str++; + Buf_AddByte(buf, (Byte)*str); + str++; + } else if (*str != '$') { + /* + * Skip as many characters as possible -- either to the end of + * the string or to the next dollar sign (variable invocation). + */ + char *cp; + + for (cp = str++; *str != '$' && *str != '\0'; str++) { + ; + } + Buf_AddBytes(buf, str - cp, (Byte *)cp); + } else { + val = Var_Parse (str, ctxt, undefErr, &length, &doFree); + + /* + * When we come down here, val should either point to the + * value of this variable, suitably modified, or be NULL. + * Length should be the total length of the potential + * variable invocation (from $ to end character...) + */ + if (val == var_Error || val == varNoError) { + /* + * If performing old-time variable substitution, skip over + * the variable and continue with the substitution. Otherwise, + * store the dollar sign and advance str so we continue with + * the string... + */ + if (oldVars) { + str += length; + } else if (undefErr) { + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!errorReported) { + Parse_Error (PARSE_FATAL, + "Undefined variable \"%.*s\"",length,str); + } + str += length; + errorReported = TRUE; + } else { + Buf_AddByte (buf, (Byte)*str); + str += 1; + } + } else { + /* + * We've now got a variable structure to store in. But first, + * advance the string pointer. + */ + str += length; + + /* + * Copy all the characters from the variable value straight + * into the new string. + */ + Buf_AddBytes (buf, strlen (val), (Byte *)val); + if (doFree) { + free ((Address)val); + } + } + } + } + + Buf_AddByte (buf, '\0'); + str = (char *)Buf_GetAll (buf, (int *)NULL); + Buf_Destroy (buf, FALSE); + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetTail -- + * Return the tail from each of a list of words. Used to set the + * System V local variables. + * + * Results: + * The resulting string. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetTail(file) + char *file; /* Filename to modify */ +{ + return(VarModify(file, VarTail, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetHead -- + * Find the leading components of a (list of) filename(s). + * XXX: VarHead does not replace foo by ., as (sun) System V make + * does. + * + * Results: + * The leading components. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetHead(file) + char *file; /* Filename to manipulate */ +{ + return(VarModify(file, VarHead, (ClientData)0)); +} + +/*- + *----------------------------------------------------------------------- + * Var_Init -- + * Initialize the module + * + * Results: + * None + * + * Side Effects: + * The VAR_CMD and VAR_GLOBAL contexts are created + *----------------------------------------------------------------------- + */ +void +Var_Init () +{ + VAR_GLOBAL = Targ_NewGN ("Global"); + VAR_CMD = Targ_NewGN ("Command"); + +} + +/****************** PRINT DEBUGGING INFO *****************/ +static +VarPrintVar (v) + Var *v; +{ + printf ("%-16s = %s\n", v->name, Buf_GetAll(v->val, (int *)NULL)); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Var_Dump -- + * print all variables in a context + *----------------------------------------------------------------------- + */ +Var_Dump (ctxt) + GNode *ctxt; +{ + Lst_ForEach (ctxt->context, VarPrintVar); +} |
