Index: head/usr.bin/make/Makefile =================================================================== --- head/usr.bin/make/Makefile (revision 138563) +++ head/usr.bin/make/Makefile (revision 138564) @@ -1,43 +1,43 @@ # @(#)Makefile 5.2 (Berkeley) 12/28/90 # $Id: Makefile,v 1.6 1994/06/30 05:33:39 cgd Exp $ # $FreeBSD$ PROG= make CFLAGS+=-I${.CURDIR} SRCS= arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \ make.c parse.c str.c suff.c targ.c util.c var.c var_modify.c -SRCS+= lstAppend.c lstClose.c lstConcat.c lstDeQueue.c lstDestroy.c \ +SRCS+= lstAppend.c lstConcat.c lstDeQueue.c lstDestroy.c \ lstDupl.c lstFindFrom.c lstForEachFrom.c lstInit.c lstInsert.c \ - lstIsAtEnd.c lstMember.c lstNext.c lstOpen.c lstRemove.c + lstMember.c lstRemove.c .PATH: ${.CURDIR}/lst.lib WARNS?= 3 NOSHARED?= YES CFLAGS+=-DMAKE_VERSION=\"5200408120\" .if defined(_UPGRADING) CFLAGS+=-D__FBSDID=__RCSID .endif # There is no obvious performance improvement currently. # CFLAGS+=-DUSE_KQUEUE main.o: ${MAKEFILE} # Set the shell which make(1) uses. Bourne is the default, but a decent # Korn shell works fine, and much faster. Using the C shell for this # will almost certainly break everything, but it's Unix tradition to # allow you to shoot yourself in the foot if you want to :-) MAKE_SHELL?= sh .if ${MAKE_SHELL} == "csh" CFLAGS+= -DDEFSHELL=0 .elif ${MAKE_SHELL} == "sh" CFLAGS+= -DDEFSHELL=1 .elif ${MAKE_SHELL} == "ksh" CFLAGS+= -DDEFSHELL=2 .else .error "MAKE_SHELL must be set to one of \"csh\", \"sh\" or \"ksh\"." .endif .include Index: head/usr.bin/make/arch.c =================================================================== --- head/usr.bin/make/arch.c (revision 138563) +++ head/usr.bin/make/arch.c (revision 138564) @@ -1,1217 +1,1210 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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. * * @(#)arch.c 8.2 (Berkeley) 1/2/94 */ #include __FBSDID("$FreeBSD$"); /*- * 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 format. * * Arch_LibOODate Special function to decide if a library node * is out-of-date. * * Arch_Init Initialize this module. * * Arch_End Cleanup this module. */ #include #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "config.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 key/value pairs */ char *fnametab; /* Extended name table strings */ size_t fnamesize; /* Size of the string table */ } Arch; static void ArchFree(void *); static struct ar_hdr *ArchStatMember(char *, char *, Boolean); static FILE *ArchFindMember(char *, char *, struct ar_hdr *, char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) #define SVR4ARCHIVES static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); #endif /*- *----------------------------------------------------------------------- * ArchFree -- * Free memory used by an archive * * Results: * None. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static void ArchFree(void *ap) { Arch *a = ap; Hash_Search search; Hash_Entry *entry; /* Free memory from hash entries */ for (entry = Hash_EnumFirst(&a->members, &search); entry != NULL; entry = Hash_EnumNext(&search)) free(Hash_GetValue(entry)); free(a->name); free(a->fnametab); Hash_DeleteTable(&a->members); free(a); } /*- *----------------------------------------------------------------------- * 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, given the pointer to the start of the * specification, a Lst on which to place the nodes, and a context * in which to expand variables. * * 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(char **linePtr, Lst *nodeLst, GNode *ctxt) { char *cp; /* Pointer into line */ GNode *gn; /* New node */ char *libName; /* Library-part of specification */ char *memName; /* Member-part of specification */ char *nameBuf; /* 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... */ size_t 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(NULL, libName, ctxt, TRUE); } for (;;) { /* * First skip to the start of the member's name, mark that * place and skip to the end of it (either white-space or * a close paren). */ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ while (*cp != '\0' && *cp != ')' && isspace((unsigned char)*cp)) { cp++; } memName = cp; while (*cp != '\0' && *cp != ')' && !isspace((unsigned char)*cp)) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ size_t 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; size_t sz; memName = Var_Subst(NULL, 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. */ sz = strlen(memName) + strlen(libName) + 3; buf = sacrifice = emalloc(sz); snprintf(buf, sz, "%s(%s)", libName, memName); if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { /* * Must contain dynamic sources, so we can't deal with it now. * Just create an ARCHV node for the thing and let * SuffExpandChildren handle it... */ gn = Targ_FindNode(buf, TARG_CREATE); if (gn == NULL) { free(buf); return (FAILURE); } else { gn->type |= OP_ARCHV; Lst_AtEnd(nodeLst, (void *)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(); char *member; size_t sz = MAXPATHLEN; size_t nsz; nameBuf = emalloc(sz); Dir_Expand(memName, dirSearchPath, members); while (!Lst_IsEmpty(members)) { member = Lst_DeQueue(members); nsz = strlen(libName) + strlen(member) + 3; if (nsz > sz) { sz = nsz * 2; nameBuf = erealloc(nameBuf, sz); } snprintf(nameBuf, sz, "%s(%s)", libName, member); free(member); gn = Targ_FindNode(nameBuf, TARG_CREATE); if (gn == NULL) { free(nameBuf); 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; Lst_AtEnd(nodeLst, (void *)gn); } } Lst_Destroy(members, NOFREE); free(nameBuf); } else { size_t sz = strlen(libName) + strlen(memName) + 3; nameBuf = emalloc(sz); snprintf(nameBuf, sz, "%s(%s)", libName, memName); gn = Targ_FindNode(nameBuf, TARG_CREATE); free(nameBuf); if (gn == NULL) { 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; Lst_AtEnd(nodeLst, 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((unsigned char)*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 with the * current list element and the name we want. * * Results: * 0 if it is, non-zero if it isn't. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int ArchFindArchive(const void *ar, const void *archName) { return (strcmp(archName, ((const Arch *)ar)->name)); } /*- *----------------------------------------------------------------------- * ArchStatMember -- * Locate a member of an archive, given the path of the archive and * the path of the desired member, and a boolean representing whether * or not the archive should be hashed (if not already hashed). * * 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(char *archive, char *member, Boolean hash) { #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]; 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[MAXPATHLEN + 1]; /* Current member name while hashing. */ /* * 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 = strrchr(member, '/'); if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0)) member = cp + 1; ln = Lst_Find(archives, archive, ArchFindArchive); if (ln != NULL) { ar = Lst_Datum(ln); he = Hash_FindEntry(&ar->members, member); if (he != NULL) { return ((struct ar_hdr *)Hash_GetValue (he)); } else { /* Try truncated name */ char copy[AR_MAX_NAME_LEN + 1]; size_t 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'; } if ((he = Hash_FindEntry(&ar->members, copy)) != NULL) return (Hash_GetValue(he)); return (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 == NULL) { return (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 == NULL) { return (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 (NULL); } ar = emalloc(sizeof(Arch)); ar->name = estrdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; Hash_InitTable(&ar->members, -1); memName[AR_MAX_NAME_LEN] = '\0'; while (fread(&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... */ goto badarch; } else { /* * 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'; size = (int)strtol(arh.ar_size, NULL, 10); strncpy(memName, arh.ar_name, sizeof(arh.ar_name)); for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) { continue; } cp[1] = '\0'; #ifdef SVR4ARCHIVES /* * svr4 names are slash terminated. Also svr4 extended AR format. */ if (memName[0] == '/') { /* * svr4 magic mode; handle it */ switch (ArchSVR4Entry(ar, memName, size, arch)) { case -1: /* Invalid data */ goto badarch; case 0: /* List of files entry */ continue; default: /* Got the entry */ break; } } else { if (cp[0] == '/') cp[0] = '\0'; } #endif #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && isdigit(memName[sizeof(AR_EFMT1) - 1])) { unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]); if (elen > MAXPATHLEN) goto badarch; if (fread(memName, elen, 1, arch) != 1) goto badarch; memName[elen] = '\0'; fseek(arch, -elen, SEEK_CUR); /* XXX Multiple levels may be asked for, make this conditional * on one, and use DEBUGF. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { fprintf(stderr, "ArchStat: Extended format entry for %s\n", memName); } } #endif he = Hash_CreateEntry(&ar->members, memName, NULL); Hash_SetValue(he, emalloc(sizeof(struct ar_hdr))); memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr)); } fseek(arch, (size + 1) & ~1, SEEK_CUR); } fclose(arch); Lst_AtEnd(archives, 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 != NULL) { return (Hash_GetValue (he)); } else { return (NULL); } badarch: fclose(arch); Hash_DeleteTable(&ar->members); free(ar->fnametab); free(ar); return (NULL); } #ifdef SVR4ARCHIVES /*- *----------------------------------------------------------------------- * ArchSVR4Entry -- * Parse an SVR4 style entry that begins with a slash. * If it is "//", then load the table of filenames * If it is "/", then try to substitute the long file name * from offset of a table previously read. * * Results: * -1: Bad data in archive * 0: A table was loaded from the file * 1: Name was successfully substituted from table * 2: Name was not successfully substituted from table * * Side Effects: * If a table is read, the file pointer is moved to the next archive * member * *----------------------------------------------------------------------- */ static int ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" size_t entry; char *ptr, *eptr; if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { if (ar->fnametab != NULL) { DEBUGF(ARCH, ("Attempted to redefine an SVR4 name table\n")); return (-1); } /* * This is a table of archive names, so we build one for * ourselves */ ar->fnametab = emalloc(size); ar->fnamesize = size; if (fread(ar->fnametab, size, 1, arch) != 1) { DEBUGF(ARCH, ("Reading an SVR4 name table failed\n")); return (-1); } eptr = ar->fnametab + size; for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) switch (*ptr) { case '/': entry++; *ptr = '\0'; break; case '\n': break; default: break; } DEBUGF(ARCH, ("Found svr4 archive name table with %zu entries\n", entry)); return (0); } if (name[1] == ' ' || name[1] == '\0') return (2); entry = (size_t)strtol(&name[1], &eptr, 0); if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { DEBUGF(ARCH, ("Could not parse SVR4 name %s\n", name)); return (2); } if (entry >= ar->fnamesize) { DEBUGF(ARCH, ("SVR4 entry offset %s is greater than %zu\n", name, ar->fnamesize)); return (2); } DEBUGF(ARCH, ("Replaced %s with %s\n", name, &ar->fnametab[entry])); strncpy(name, &ar->fnametab[entry], MAXPATHLEN); name[MAXPATHLEN] = '\0'; return (1); } #endif /*- *----------------------------------------------------------------------- * 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". arhPtr is a * poitner to the header structure to fill in. * * 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(char *archive, char *member, struct ar_hdr *arhPtr, char *mode) { FILE * arch; /* Stream to archive */ int size; /* Size of archive member */ char *cp; /* Useful character pointer */ char magic[SARMAG]; size_t len, tlen; arch = fopen(archive, mode); if (arch == NULL) { return (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 (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 = strrchr(member, '/'); if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0)) { member = cp + 1; } len = tlen = strlen(member); if (len > sizeof(arhPtr->ar_name)) { tlen = sizeof(arhPtr->ar_name); } while (fread(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 (NULL); } else if (strncmp(member, arhPtr->ar_name, tlen) == 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 (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){ goto skip; } 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), SEEK_CUR); return (arch); } } else #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(arhPtr->ar_name, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && isdigit(arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) { unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]); char ename[MAXPATHLEN]; if (elen > MAXPATHLEN) { fclose(arch); return NULL; } if (fread(ename, elen, 1, arch) != 1) { fclose(arch); return NULL; } ename[elen] = '\0'; /* * XXX choose one. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { printf("ArchFind: Extended format entry for %s\n", ename); } if (strncmp(ename, member, len) == 0) { /* Found as extended name */ fseek(arch, -sizeof(struct ar_hdr) - elen, SEEK_CUR); return (arch); } fseek(arch, -elen, SEEK_CUR); goto skip; } else #endif { skip: /* * 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'; size = (int)strtol(arhPtr->ar_size, NULL, 10); fseek(arch, (size + 1) & ~1, SEEK_CUR); } } /* * We've looked everywhere, but the member is not to be found. Close the * archive and return NULL -- an error. */ fclose(arch); return (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(GNode *gn) { FILE * arch; /* Stream open to archive, positioned properly */ struct ar_hdr arh; /* Current header describing member */ char *p1, *p2; arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1), Var_Value(TARGET, gn, &p2), &arh, "r+"); free(p1); free(p2); snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long)now); if (arch != NULL) { fwrite(&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(GNode *gn) { #ifdef RANLIBMAG FILE * arch; /* Stream open to archive */ struct ar_hdr arh; /* Header describing table of contents */ struct utimbuf times; /* Times for utime() call */ arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); if (arch != NULL) { fwrite(&arh, sizeof(struct ar_hdr), 1, arch); fclose(arch); times.actime = times.modtime = now; utime(gn->path, ×); } #endif } /*- *----------------------------------------------------------------------- * Arch_MTime -- * Return the modification time of a member of an archive, given its * name. * * 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(GNode *gn) { struct ar_hdr *arhPtr; /* Header of desired member */ int modTime; /* Modification time as an integer */ char *p1, *p2; arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1), Var_Value(TARGET, gn, &p2), TRUE); free(p1); free(p2); if (arhPtr != NULL) { modTime = (int)strtol(arhPtr->ar_date, NULL, 10); } 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(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)) != NULL) { + for (ln = Lst_First(gn->parents); ln != NULL; ln = Lst_Succ(ln)) { pgn = 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 = strchr(pgn->name, '(') + 1; nameEnd = strchr(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 named 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(GNode *gn, Lst *path) { char *libName; /* file name for archive */ size_t sz; sz = strlen(gn->name) + 4; libName = emalloc(sz); snprintf(libName, sz, "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 == 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, with the library's * graph node. * * 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). * The modification time of one of its sources is greater than * the one of its RANLIBMAG member (i.e. its table of contents * is out-of-date). We don't compare of the archive time * vs. TOC time because they can be too close. In my * opinion we should not bother with the TOC at all since * this is used by 'ar' rules that affect the data contents * of the archive, not by ranlib rules, which affect the * TOC. * * 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(GNode *gn) { 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 { #ifdef RANLIBMAG struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents's mod time */ arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); if (arhPtr != NULL) { modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10); /* XXX choose one. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); } oodate = (gn->cmtime > 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; } #else oodate = (gn->mtime == 0); /* out-of-date if not present */ #endif } return (oodate); } /*- *----------------------------------------------------------------------- * Arch_Init -- * Initialize things for this module. * * Results: * None. * * Side Effects: * The 'archives' list is initialized. * *----------------------------------------------------------------------- */ void Arch_Init(void) { archives = Lst_Init(); } /*- *----------------------------------------------------------------------- * Arch_End -- * Cleanup things for this module. * * Results: * None. * * Side Effects: * The 'archives' list is freed * *----------------------------------------------------------------------- */ void Arch_End(void) { Lst_Destroy(archives, ArchFree); } Index: head/usr.bin/make/dir.c =================================================================== --- head/usr.bin/make/dir.c (revision 138563) +++ head/usr.bin/make/dir.c (revision 138564) @@ -1,1264 +1,1239 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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. * * @(#)dir.c 8.2 (Berkeley) 1/2/94 */ #include __FBSDID("$FreeBSD$"); /*- * 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_End Cleanup 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 #include #include #include #include #include "make.h" #include "hash.h" #include "dir.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 */ static int misses; /* Sad, but not evil misses */ static int nearmisses; /* Found under search path */ static int bigmisses; /* Sought by itself */ static Path *dot; /* contents of current directory */ /* 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... */ static Hash_Table mtimes; static int DirPrintWord(void *, void *); static int DirPrintDir(void *, void *); /*- *----------------------------------------------------------------------- * Dir_Init -- * initialize things for this module * * Results: * none * * Side Effects: * none *----------------------------------------------------------------------- */ void Dir_Init(void) { dirSearchPath = Lst_Init(); openDirectories = Lst_Init(); Hash_InitTable(&mtimes, 0); } /*- *----------------------------------------------------------------------- * Dir_InitDot -- * initialize the "." directory * * Results: * none * * Side Effects: * some directories may be opened. *----------------------------------------------------------------------- */ void Dir_InitDot(void) { LstNode *ln; Dir_AddDir(openDirectories, "."); if ((ln = Lst_Last(openDirectories)) == NULL) err(1, "cannot open current directory"); dot = Lst_Datum(ln); /* * We always need to have dot around, so we increment its * reference count to make sure it's not destroyed. */ dot->refCount += 1; } /*- *----------------------------------------------------------------------- * Dir_End -- * cleanup things for this module * * Results: * none * * Side Effects: * none *----------------------------------------------------------------------- */ void Dir_End(void) { dot->refCount -= 1; Dir_Destroy(dot); Dir_ClearPath(dirSearchPath); Lst_Destroy(dirSearchPath, NOFREE); Dir_ClearPath(openDirectories); Lst_Destroy(openDirectories, NOFREE); Hash_DeleteTable(&mtimes); } /*- *----------------------------------------------------------------------- * 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(const void *p, const void *dname) { return (strcmp(((const Path *)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(const char *name) { const char *cp; int wild = 0, brace = 0, bracket = 0; for (cp = name; *cp; cp++) { switch (*cp) { case '{': brace++; wild = 1; break; case '}': brace--; break; case '[': bracket++; wild = 1; break; case ']': bracket--; break; case '?': case '*': wild = 1; break; default: break; } } return (wild && bracket == 0 && brace == 0); } /*- *----------------------------------------------------------------------- * 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(const char *pattern, const Path *p, Lst *expansions) { Hash_Search search; /* Index into the directory's table */ Hash_Entry *entry; /* Current entry in the table */ Boolean isDot; /* TRUE if the directory being searched is . */ isDot = (*p->name == '.' && p->name[1] == '\0'); for (entry = Hash_EnumFirst(&p->files, &search); 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] == '.'))) { Lst_AtEnd(expansions, (isDot ? estrdup(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. The * given arguments are the entire word to expand, the first curly * brace in the word, the search path, and the list to store the * expansions in. * * Results: * None. * * Side Effects: * The given list is filled with the expansions... * *----------------------------------------------------------------------- */ static void DirExpandCurly(const char *word, const char *brace, Lst *path, Lst *expansions) { const char *end; /* Character after the closing brace */ const char *cp; /* Current position in brace clause */ const 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; default: break; } } if (*cp2 == '\0') { /* * Hit the end w/o finding any wildcards, so stick * the expansion on the end of the list. */ 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... Works given a word to * expand, a path to look in, and a list to store expansions in. * * Results: * None. * * Side Effects: * Things are added to the expansions list. * *----------------------------------------------------------------------- */ static void DirExpandInt(const char *word, Lst *path, Lst *expansions) { LstNode *ln; /* Current node */ - Path *p; /* Directory in the node */ - if (Lst_Open(path) == SUCCESS) { - while ((ln = Lst_Next(path)) != NULL) { - p = Lst_Datum(ln); - DirMatchFiles(word, p, expansions); - } - Lst_Close(path); - } + for (ln = Lst_First(path); ln != NULL; ln = Lst_Succ(ln)) + DirMatchFiles(word, (Path *)Lst_Datum(ln), expansions); } /*- *----------------------------------------------------------------------- * 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(void *word, void *dummy __unused) { DEBUGF(DIR, ("%s ", (char *)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 is placed in expansions. * * Side Effects: * Directories may be opened. Who knows? *----------------------------------------------------------------------- */ void Dir_Expand(char *word, Lst *path, Lst *expansions) { char *cp; DEBUGF(DIR, ("expanding \"%s\"...", word)); cp = strchr(word, '{'); if (cp != NULL) DirExpandCurly(word, cp, path, expansions); else { cp = strchr(word, '/'); if (cp != NULL) { /* * The thing has a directory component -- find the * first wildcard in the string. */ for (cp = word; *cp != '\0'; 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) { char sc; /* * If the glob isn't in the first * component, try and find all the * components up to the one with a * wildcard. */ sc = cp[1]; cp[1] = '\0'; dirpath = Dir_FindFile(word, path); cp[1] = sc; /* * 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 != NULL) { char *dp = &dirpath[strlen(dirpath) - 1]; if (*dp == '/') *dp = '\0'; path = Lst_Init(); 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, (void *)NULL); DEBUGF(DIR, ("\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(char *name, Lst *path) { char *p1; /* pointer into p->name */ char *p2; /* pointer into name */ LstNode *ln; /* a list element */ char *file; /* the current filename to check */ Path *p; /* current path member */ char *cp; /* final component of the name */ 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 = strrchr(name, '/'); if (cp != NULL) { hasSlash = TRUE; cp += 1; } else { hasSlash = FALSE; cp = name; } DEBUGF(DIR, ("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) != NULL)) { DEBUGF(DIR, ("in '.'\n")); hits += 1; dot->hits += 1; return (estrdup(name)); } - if (Lst_Open(path) == FAILURE) { - DEBUGF(DIR, ("couldn't open path, file not found\n")); - misses += 1; - return (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)) != NULL) { + for (ln = Lst_First(path); ln != NULL; ln = Lst_Succ(ln)) { p = Lst_Datum(ln); DEBUGF(DIR, ("%s...", p->name)); if (Hash_FindEntry(&p->files, cp) != NULL) { DEBUGF(DIR, ("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 >= p->name && *p1 == *p2) { p1 -= 1; p2 -= 1; } if (p2 >= name || (p1 >= p->name && *p1 != '/')) { DEBUGF(DIR, ("component mismatch -- " "continuing...")); continue; } } file = str_concat(p->name, cp, STR_ADDSLASH); DEBUGF(DIR, ("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) { - Lst_Close(path); if (*cp == '\0' || ISDOT(cp) || ISDOTDOT(cp)) { DEBUGF(DIR, ("returning %s\n", name)); return (estrdup(name)); } else { DEBUGF(DIR, ("must be here but isn't --" " returning NULL\n")); return (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) { DEBUGF(DIR, ("failed.\n")); misses += 1; return (NULL); } if (*name != '/') { Boolean checkedDot = FALSE; DEBUGF(DIR, ("failed. Trying subdirectories...")); - Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { + for (ln = Lst_First(path); ln != NULL; ln = Lst_Succ(ln)) { p = 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 = estrdup(name); checkedDot = TRUE; } DEBUGF(DIR, ("checking %s...", file)); if (stat(file, &stb) == 0) { DEBUGF(DIR, ("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 = strrchr(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. */ DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), file)); entry = Hash_CreateEntry(&mtimes, file, (Boolean *)NULL); Hash_SetValue(entry, (void *)(long)stb.st_mtime); nearmisses += 1; return (file); } else { free(file); } } DEBUGF(DIR, ("failed. ")); - Lst_Close(path); if (checkedDot) { /* * Already checked by the given name, since . was in * the path, so no point in proceeding... */ DEBUGF(DIR, ("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 == NULL) return (NULL); p = Lst_Datum(ln); if (Hash_FindEntry(&p->files, cp) != NULL) { return (estrdup(name)); return (NULL); #else /* !notdef */ DEBUGF(DIR, ("Looking for \"%s\"...", name)); bigmisses += 1; entry = Hash_FindEntry(&mtimes, name); if (entry != NULL) { DEBUGF(DIR, ("got it (in mtime cache)\n")); return (estrdup(name)); } else if (stat (name, &stb) == 0) { entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL); DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), name)); Hash_SetValue(entry, (void *)(long)stb.st_mtime); return (estrdup(name)); } else { DEBUGF(DIR, ("failed. Returning NULL\n")); return (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(GNode *gn) { 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 == NULL) fullName = Dir_FindFile(gn->name, dirSearchPath); else fullName = gn->path; if (fullName == NULL) fullName = estrdup(gn->name); entry = Hash_FindEntry(&mtimes, fullName); if (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 filesystem. */ DEBUGF(DIR, ("Using cached time %s for %s\n", Targ_FmtTime((time_t)(long)Hash_GetValue(entry)), fullName)); stb.st_mtime = (time_t)(long)Hash_GetValue(entry); Hash_DeleteEntry(&mtimes, entry); } else if (stat(fullName, &stb) < 0) { if (gn->type & OP_MEMBER) { if (fullName != gn->path) free(fullName); 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(Lst *path, char *name) { LstNode *ln; /* node in case Path structure is found */ Path *p; /* pointer to new Path structure */ DIR *d; /* for reading directory */ struct dirent *dp; /* entry in directory */ ln = Lst_Find(openDirectories, name, DirFindName); if (ln != NULL) { p = Lst_Datum(ln); if (Lst_Member(path, p) == NULL) { p->refCount += 1; Lst_AtEnd(path, p); } } else { DEBUGF(DIR, ("Caching %s...", name)); if ((d = opendir(name)) != NULL) { p = emalloc(sizeof(Path)); p->name = estrdup(name); p->hits = 0; p->refCount = 1; Hash_InitTable(&p->files, -1); while ((dp = readdir(d)) != NULL) { #if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ /* * 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 && d_ino */ /* Skip the '.' and '..' entries by checking * for them specifically instead of assuming * readdir() reuturns them in that order when * first going through a directory. This is * needed for XFS over NFS filesystems since * SGI does not guarantee that these are the * first two entries returned from readdir(). */ if (ISDOT(dp->d_name) || ISDOTDOT(dp->d_name)) continue; Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL); } closedir(d); Lst_AtEnd(openDirectories, p); if (path != openDirectories) Lst_AtEnd(path, p); } DEBUGF(DIR, ("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. * *----------------------------------------------------------------------- */ void * Dir_CopyDir(void *p) { ((Path *)p)->refCount += 1; return (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(char *flag, Lst *path) { char *str; /* the string which will be returned */ char *tstr; /* the current directory preceded by 'flag' */ char *nstr; LstNode *ln; /* the node of the current directory */ Path *p; /* the structure describing the current directory */ str = estrdup(""); - if (Lst_Open(path) == SUCCESS) { - while ((ln = Lst_Next(path)) != NULL) { - p = Lst_Datum(ln); - tstr = str_concat(flag, p->name, 0); - nstr = str_concat(str, tstr, STR_ADDSPACE); - free(str); - free(tstr); - str = nstr; - } - Lst_Close(path); + for (ln = Lst_First(path); ln != NULL; ln = Lst_Succ(ln)) { + p = Lst_Datum(ln); + tstr = str_concat(flag, p->name, 0); + nstr = str_concat(str, tstr, STR_ADDSPACE); + free(str); + free(tstr); + str = nstr; } 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(void *pp) { Path *p = pp; p->refCount -= 1; if (p->refCount == 0) { LstNode *ln; ln = Lst_Member(openDirectories, p); Lst_Remove(openDirectories, ln); Hash_DeleteTable(&p->files); free(p->name); free(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(Lst *path) { Path *p; while (!Lst_IsEmpty(path)) { p = 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(Lst *path1, Lst *path2) { LstNode *ln; Path *p; for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) { p = Lst_Datum(ln); if (Lst_Member(path1, p) == NULL) { p->refCount += 1; Lst_AtEnd(path1, p); } } } /********** DEBUG INFO **********/ void Dir_PrintDirectories(void) { 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)) != NULL) { - p = Lst_Datum(ln); - printf("# %-20s %10d\t%4d\n", p->name, p->refCount, - p->hits); - } - Lst_Close(openDirectories); + for (ln = Lst_First(openDirectories); ln != NULL; ln = Lst_Succ(ln)) { + p = Lst_Datum(ln); + printf("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); } } static int DirPrintDir(void *p, void *dummy __unused) { printf("%s ", ((Path *)p)->name); return (0); } void Dir_PrintPath(Lst *path) { Lst_ForEach(path, DirPrintDir, (void *)NULL); } Index: head/usr.bin/make/job.c =================================================================== --- head/usr.bin/make/job.c (revision 138563) +++ head/usr.bin/make/job.c (revision 138564) @@ -1,2787 +1,2778 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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.c 8.2 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); #ifndef OLD_JOKE #define OLD_JOKE 0 #endif /* OLD_JOKE */ /*- * 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_Finish 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 #include #include #include #ifdef USE_KQUEUE #include #endif #include #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #define STATIC static /* * error handling variables */ static int errors = 0; /* number of errors reported */ static int aborting = 0; /* why is the make aborting? */ #define ABORT_ERROR 1 /* Because of an error */ #define ABORT_INTERRUPT 2 /* Because it was interrupted */ #define ABORT_WAIT 3 /* Waiting for jobs to finish */ /* * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file * is a char! So when we go above 127 we turn negative! */ #define FILENO(a) ((unsigned)fileno(a)) /* * 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 used to build temp file names to store shell commands to * execute. */ static char tfile[sizeof(TMPPAT)]; /* * Descriptions for various shells. */ static const DEF_SHELL_STRUCT(CShell, const) 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", 13, 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, TRUE, "set -e", "set +e", #ifdef OLDBOURNESHELL FALSE, "echo \"%s\"\n", "sh -c '%s || exit 0'\n", #endif "v", "e", }, /* * KSH description. The Korn shell has a superset of * the Bourne shell's functionality. */ { "ksh", TRUE, "set -", "set -v", "set -", 5, TRUE, "set -e", "set +e", "v", "e", }, }; static Shell *commandShell = NULL; /* this is the shell to which we pass * all commands in the Makefile. It is * set by the Job_ParseShell function */ char *shellPath = NULL, /* full pathname of executable image */ *shellName = NULL; /* last component of shell */ static int maxJobs; /* The most children we can run at once */ STATIC int nJobs; /* The number of children currently running */ STATIC Lst *jobs; /* The structures that describe them */ STATIC 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 */ #ifdef USE_KQUEUE static int kqfd; /* File descriptor obtained by kqueue() */ #else static fd_set outputs; /* Set of descriptors of pipes connected to * the output channels of children */ #endif STATIC GNode *lastNode; /* The node for which output was most recently * produced. */ STATIC 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 */ #define MESSAGE(fp, gn) \ fprintf(fp, targFmt, gn->name); /* * When JobStart attempts to run a job but isn't allowed to * or when Job_CatchChildren detects a job that has * been stopped somehow, the job is placed on the stoppedJobs queue to be run * when the next job finishes. */ STATIC Lst *stoppedJobs; /* Lst of Job structures describing * jobs that were stopped due to concurrency * limits or externally */ STATIC int fifoFd; /* Fd of our job fifo */ STATIC char fifoName[] = "/tmp/make_fifo_XXXXXXXXX"; STATIC int fifoMaster; static sig_atomic_t interrupted; #if defined(USE_PGRP) && defined(SYSV) # define KILL(pid, sig) killpg(-(pid), (sig)) #else # if defined(USE_PGRP) # define KILL(pid, sig) killpg((pid), (sig)) # else # define KILL(pid, sig) kill((pid), (sig)) # endif #endif /* * Grmpf... There is no way to set bits of the wait structure * anymore with the stupid W*() macros. I liked the union wait * stuff much more. So, we devise our own macros... This is * really ugly, use dramamine sparingly. You have been warned. */ #define W_SETMASKED(st, val, fun) \ { \ int sh = (int)~0; \ int mask = fun(sh); \ \ for (sh = 0; ((mask >> sh) & 1) == 0; sh++) \ continue; \ *(st) = (*(st) & ~mask) | ((val) << sh); \ } #define W_SETTERMSIG(st, val) W_SETMASKED(st, val, WTERMSIG) #define W_SETEXITSTATUS(st, val) W_SETMASKED(st, val, WEXITSTATUS) static int JobCondPassSig(void *, void *); static void JobPassSig(int); static int JobPrintCommand(void *, void *); static int JobSaveCommand(void *, void *); static void JobClose(Job *); static void JobFinish(Job *, int *); static void JobExec(Job *, char **); static void JobMakeArgv(Job *, char **); static void JobRestart(Job *); static int JobStart(GNode *, int, Job *); static char *JobOutput(Job *, char *, char *, int); static void JobDoOutput(Job *, Boolean); static Shell *JobMatchShell(const char *); static void JobInterrupt(int, int); static void JobRestartJobs(void); /* * JobCatchSignal * * Got a signal. Set global variables and hope that someone will * handle it. */ static void JobCatchSig(int signo) { interrupted = signo; } /*- *----------------------------------------------------------------------- * JobCondPassSig -- * Pass a signal to a job if USE_PGRP is defined. * * Results: * === 0 * * Side Effects: * None, except the job may bite it. * *----------------------------------------------------------------------- */ static int JobCondPassSig(void *jobp, void *signop) { Job *job = jobp; int signo = *(int *)signop; DEBUGF(JOB, ("JobCondPassSig passing signal %d to child %d.\n", signo, job->pid)); KILL(job->pid, signo); return (0); } /*- *----------------------------------------------------------------------- * JobPassSig -- * Pass a signal on 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(int signo) { sigset_t nmask, omask; struct sigaction act; sigemptyset(&nmask); sigaddset(&nmask, signo); sigprocmask(SIG_SETMASK, &nmask, &omask); DEBUGF(JOB, ("JobPassSig(%d) called.\n", signo)); Lst_ForEach(jobs, JobCondPassSig, &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, signo); } else if ((signo == SIGHUP) || (signo == SIGTERM) || (signo == SIGQUIT)) { JobInterrupt(FALSE, signo); } /* * Leave gracefully if SIGQUIT, rather than core dumping. */ if (signo == SIGQUIT) { signo = SIGINT; } /* * 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. * XXX this comment seems wrong. */ act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(signo, &act, NULL); DEBUGF(JOB, ("JobPassSig passing signal to self, mask = %x.\n", ~0 & ~(1 << (signo - 1)))); signal(signo, SIG_DFL); KILL(getpid(), signo); signo = SIGCONT; Lst_ForEach(jobs, JobCondPassSig, &signo); sigprocmask(SIG_SETMASK, &omask, NULL); sigprocmask(SIG_SETMASK, &omask, NULL); act.sa_handler = JobPassSig; sigaction(signo, &act, NULL); } /*- *----------------------------------------------------------------------- * 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(const void *job, const void *pid) { return (*(const int *)pid - ((const Job *)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_Finish 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(void *cmdp, void *jobp) { 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 */ char *cmd = cmdp; Job *job = jobp; noSpecials = (noExecute && !(job->node->type & OP_MAKE)); if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; if ((job->flags & JOB_IGNDOTS) == 0) { job->tailCmds = Lst_Succ(Lst_Member(job->node->commands, cmd)); return (1); } return (0); } #define DBPRINTF(fmt, arg) \ DEBUGF(JOB, (fmt, arg)); \ fprintf(job->cmdFILE, fmt, arg); \ fflush(job->cmdFILE); numCommands += 1; /* * For debugging, we replace each command with the result of expanding * the variables in the command. */ cmdNode = Lst_Member(job->node->commands, cmd); cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE); Lst_Replace(cmdNode, cmdStart); cmdTemplate = "%s\n"; /* * Check for leading @', -' or +'s to control echoing, error checking, * and execution on -n. */ while (*cmd == '@' || *cmd == '-' || *cmd == '+') { switch (*cmd) { case '@': shutUp = DEBUG(LOUD) ? FALSE : TRUE; break; case '-': errOff = TRUE; break; case '+': if (noSpecials) { /* * We're not actually exececuting anything... * but this one needs to be - use compat mode just for it. */ Compat_RunCommand(cmdp, job->node); return (0); } break; } cmd++; } while (isspace((unsigned char)*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(void *cmd, void *gn) { cmd = Var_Subst(NULL, cmd, gn, FALSE); Lst_AtEnd(postCommands->commands, cmd); return (0); } /*- *----------------------------------------------------------------------- * JobClose -- * Called to close both input and output pipes when a job is finished. * * Results: * Nada * * Side Effects: * The file descriptors associated with the job are closed. * *----------------------------------------------------------------------- */ static void JobClose(Job *job) { if (usePipes) { #if !defined(USE_KQUEUE) FD_CLR(job->inPipe, &outputs); #endif if (job->outPipe != job->inPipe) { close(job->outPipe); } JobDoOutput(job, TRUE); close(job->inPipe); } else { close(job->outFd); JobDoOutput(job, TRUE); } } /*- *----------------------------------------------------------------------- * 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*/ static void JobFinish(Job *job, int *status) { Boolean done; if ((WIFEXITED(*status) && (((WEXITSTATUS(*status) != 0) && !(job->flags & JOB_IGNERR)))) || (WIFSIGNALED(*status) && (WTERMSIG(*status) != 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... */ JobClose(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { fclose(job->cmdFILE); } done = TRUE; } else if (WIFEXITED(*status)) { /* * 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. */ done = WEXITSTATUS(*status) != 0; /* * Old comment said: "Note we don't * want to close down any of the streams until we know we're at the * end." * But we do. Otherwise when are we going to print the rest of the * stuff? */ JobClose(job); } else { /* * No need to close things down or anything. */ done = FALSE; } if (done || WIFSTOPPED(*status) || (WIFSIGNALED(*status) && (WTERMSIG(*status) == SIGCONT)) || DEBUG(JOB)) { FILE *out; if (compatMake && !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"); if (out == NULL) Punt("Cannot fdopen"); } else { out = stdout; } if (WIFEXITED(*status)) { DEBUGF(JOB, ("Process %d exited.\n", job->pid)); if (WEXITSTATUS(*status) != 0) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Error code %d%s\n", WEXITSTATUS(*status), (job->flags & JOB_IGNERR) ? "(ignored)" : ""); if (job->flags & JOB_IGNERR) { *status = 0; } } else if (DEBUG(JOB)) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Completed successfully\n"); } } else if (WIFSTOPPED(*status)) { DEBUGF(JOB, ("Process %d stopped.\n", job->pid)); if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Stopped -- signal %d\n", WSTOPSIG(*status)); job->flags |= JOB_RESUME; Lst_AtEnd(stoppedJobs, job); fflush(out); return; } else if (WTERMSIG(*status) == 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_RESTART)) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Continued\n"); } if (!(job->flags & JOB_CONTINUING)) { DEBUGF(JOB, ("Warning: process %d was not continuing.\n", job->pid)); #ifdef notdef /* * We don't really want to restart a job from scratch just * because it continued, especially not without killing the * continuing process! That's why this is ifdef'ed out. * FD - 9/17/90 */ JobRestart(job); #endif } job->flags &= ~JOB_CONTINUING; Lst_AtEnd(jobs, job); nJobs += 1; DEBUGF(JOB, ("Process %d is continuing locally.\n", job->pid)); if (nJobs == maxJobs) { jobFull = TRUE; DEBUGF(JOB, ("Job queue is full.\n")); } fflush(out); return; } else { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Signal %d\n", WTERMSIG(*status)); } 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 (compatMake && (WIFEXITED(*status) && - !Lst_IsAtEnd(job->node->commands))) { + if (compatMake && WIFEXITED(*status) && + Lst_Succ(job->node->compat_command) != NULL) { switch (JobStart(job->node, job->flags & JOB_IGNDOTS, job)) { case JOB_RUNNING: done = FALSE; break; case JOB_ERROR: done = TRUE; W_SETEXITSTATUS(status, 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; default: break; } } else { done = TRUE; } if (done && (aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (*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 != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; Make_Update(job->node); free(job); } else if (*status != 0) { errors += 1; free(job); } JobRestartJobs(); /* * 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. */ Finish(errors); } /*- *----------------------------------------------------------------------- * Job_Touch -- * Touch the given target. Called by JobStart when the -t flag was * given. Prints messages unless told to be silent. * * 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(GNode *gn, Boolean silent) { int streamID; /* ID of stream opened to do the touch */ struct utimbuf times; /* Times for utime() call */ 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) { fprintf(stdout, "touch %s\n", gn->name); fflush(stdout); } 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.actime = times.modtime = now; if (utime(file, ×) < 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, (off_t)0, SEEK_SET); write(streamID, &c, 1); } close(streamID); } else { fprintf(stdout, "*** couldn't touch %s: %s", file, strerror(errno)); fflush(stdout); } } } } /*- *----------------------------------------------------------------------- * 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(GNode *gn, void (*abortProc)(const char *, ...)) { 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 != NULL) && !Lst_IsEmpty(DEFAULT->commands)) { char *p1; /* * 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, &p1), gn); free(p1); } 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. */ static const char msg[] = "make: don't know how to make"; if (gn->type & OP_OPTIONAL) { fprintf(stdout, "%s %s(ignored)\n", msg, gn->name); fflush(stdout); } else if (keepgoing) { fprintf(stdout, "%s %s(continuing)\n", msg, gn->name); fflush(stdout); return (FALSE); } else { #if OLD_JOKE if (strcmp(gn->name,"love") == 0) (*abortProc)("Not war."); else #endif (*abortProc)("%s %s. Stop", msg, gn->name); return (FALSE); } } } return (TRUE); } /*- *----------------------------------------------------------------------- * 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 *job, char **argv) { int cpid; /* ID of new child */ if (DEBUG(JOB)) { int i; DEBUGF(JOB, ("Running %s\n", job->node->name)); DEBUGF(JOB, ("\tCommand: ")); for (i = 0; argv[i] != NULL; i++) { DEBUGF(JOB, ("%s ", argv[i])); } DEBUGF(JOB, ("\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)) { MESSAGE(stdout, job->node); lastNode = job->node; } if ((cpid = vfork()) == -1) { Punt("Cannot fork"); } else if (cpid == 0) { if (fifoFd >= 0) close(fifoFd); /* * 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. */ if (dup2(FILENO(job->cmdFILE), 0) == -1) Punt("Cannot dup2: %s", strerror(errno)); fcntl(0, F_SETFD, 0); lseek(0, (off_t)0, SEEK_SET); if (usePipes) { /* * Set up the child's output to be routed through the pipe * we've created for it. */ if (dup2(job->outPipe, 1) == -1) Punt("Cannot dup2: %s", strerror(errno)); } else { /* * We're capturing output in a file, so we duplicate the * descriptor to the temporary file into the standard * output. */ if (dup2(job->outFd, 1) == -1) Punt("Cannot dup2: %s", strerror(errno)); } /* * 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); if (dup2(1, 2) == -1) Punt("Cannot dup2: %s", strerror(errno)); #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. */ # if defined(SYSV) setsid(); # else setpgid(0, getpid()); # endif #endif /* USE_PGRP */ execv(shellPath, argv); write(STDERR_FILENO, "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 */ #ifdef USE_KQUEUE struct kevent kev[2]; #endif job->curPos = 0; #if defined(USE_KQUEUE) EV_SET(&kev[0], job->inPipe, EVFILT_READ, EV_ADD, 0, 0, job); EV_SET(&kev[1], job->pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL); if (kevent(kqfd, kev, 2, NULL, 0, NULL) != 0) { /* kevent() will fail if the job is already finished */ if (errno != EINTR && errno != EBADF && errno != ESRCH) Punt("kevent: %s", strerror(errno)); } #else FD_SET(job->inPipe, &outputs); #endif /* USE_KQUEUE */ } if (job->cmdFILE != NULL && job->cmdFILE != stdout) { fclose(job->cmdFILE); job->cmdFILE = NULL; } } /* * Now the job is actually running, add it to the table. */ nJobs += 1; Lst_AtEnd(jobs, 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 *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. */ 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] = 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) { 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); DEBUGF(JOB, ("Restarting %s...", job->node->name)); if (((nJobs >= maxJobs) && !(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 */ DEBUGF(JOB, ("holding\n")); Lst_AtFront(stoppedJobs, (void *)job); jobFull = TRUE; DEBUGF(JOB, ("Job queue is full.\n")); return; } else { /* * Job may be run locally. */ DEBUGF(JOB, ("running locally\n")); } JobExec(job, argv); } else { /* * The job has stopped and needs to be restarted. Why it stopped, * we don't know... */ DEBUGF(JOB, ("Resuming %s...", job->node->name)); if (((nJobs < maxJobs) || ((job->flags & JOB_SPECIAL) && (maxJobs == 0))) && (nJobs != maxJobs)) { /* * If we haven't reached the concurrency limit already (or the * job must be run and maxJobs is 0), it's ok to resume it. */ Boolean error; int status; 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; W_SETTERMSIG(&status, SIGCONT); JobFinish(job, &status); job->flags &= ~(JOB_RESUME|JOB_CONTINUING); DEBUGF(JOB, ("done\n")); } else { Error("couldn't resume %s: %s", job->node->name, strerror(errno)); status = 0; W_SETEXITSTATUS(&status, 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. */ DEBUGF(JOB, ("table full\n")); Lst_AtFront(stoppedJobs, (void *)job); jobFull = TRUE; DEBUGF(JOB, ("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(GNode *gn, int flags, Job *previous) { Job *job; /* new job descriptor */ char *argv[4]; /* Argument vector to shell */ Boolean cmdsOK; /* true if the nodes commands were all right */ Boolean noExec; /* Set true if we decide not to run the job */ int tfd; /* File descriptor for temp file */ if (interrupted) { JobPassSig(interrupted); return (JOB_ERROR); } if (previous != NULL) { previous->flags &= ~(JOB_FIRST|JOB_IGNERR|JOB_SILENT); job = previous; } else { job = emalloc(sizeof(Job)); flags |= JOB_FIRST; } job->node = gn; job->tailCmds = NULL; /* * 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 (!compatMake && 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(); } strcpy(tfile, TMPPAT); if ((tfd = mkstemp(tfile)) == -1) Punt("Cannot create temp file: %s", strerror(errno)); job->cmdFILE = fdopen(tfd, "w+"); eunlink(tfile); if (job->cmdFILE == NULL) { close(tfd); 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 (compatMake) { /* * 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 (job->flags & JOB_FIRST) + gn->compat_command = Lst_First(gn->commands); + else + gn->compat_command = Lst_Succ(gn->compat_command); - if ((ln == NULL) || - JobPrintCommand(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... - */ - JobClose(job); - } + if (gn->compat_command == NULL || + JobPrintCommand(Lst_Datum(gn->compat_command), job)) + noExec = TRUE; + + 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... + */ + JobClose(job); } } else { /* * We can do all the commands at once. hooray for sanity */ numCommands = 0; Lst_ForEach(gn->commands, JobPrintCommand, 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) { MESSAGE(stdout, gn); 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, 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) { if (job->cmdFILE != NULL) 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 != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; Make_Update(job->node); } free(job); return(JOB_FINISHED); } else { free(job); return(JOB_ERROR); } } else { fflush(job->cmdFILE); } /* * 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. */ if (!compatMake || (job->flags & JOB_FIRST)) { if (usePipes) { int fd[2]; if (pipe(fd) == -1) Punt("Cannot create pipe: %s", strerror(errno)); job->inPipe = fd[0]; job->outPipe = fd[1]; fcntl(job->inPipe, F_SETFD, 1); fcntl(job->outPipe, F_SETFD, 1); } else { fprintf(stdout, "Remaking `%s'\n", gn->name); fflush(stdout); strcpy(job->outFile, TMPPAT); if ((job->outFd = mkstemp(job->outFile)) == -1) Punt("cannot create temp file: %s", strerror(errno)); fcntl(job->outFd, F_SETFD, 1); } } if ((nJobs >= maxJobs) && !(job->flags & JOB_SPECIAL) && (maxJobs != 0)) { /* * We've hit the limit of concurrency, so put the job on hold until * some other job finishes. Note that the special jobs (.BEGIN, * .INTERRUPT and .END) may be run even when the limit has been reached * (e.g. when maxJobs == 0). */ jobFull = TRUE; DEBUGF(JOB, ("Can only run job locally.\n")); job->flags |= JOB_RESTART; Lst_AtEnd(stoppedJobs, job); } else { if (nJobs >= maxJobs) { /* * If we're running this job locally as a special case (see above), * at least say the table is full. */ jobFull = TRUE; DEBUGF(JOB, ("Local job queue is full.\n")); } JobExec(job, argv); } return (JOB_RUNNING); } static char * JobOutput(Job *job, char *cp, char *endp, int msg) { char *ecp; if (commandShell->noPrint) { ecp = strstr(cp, commandShell->noPrint); while (ecp != NULL) { if (cp != ecp) { *ecp = '\0'; if (msg && job->node != lastNode) { MESSAGE(stdout, job->node); 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. */ fprintf(stdout, "%s", cp); fflush(stdout); } 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 = strstr(cp, commandShell->noPrint); } else { return (cp); } } } return (cp); } /*- *----------------------------------------------------------------------- * 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. *----------------------------------------------------------------------- */ STATIC void JobDoOutput(Job *job, Boolean finish) { Boolean gotNL = FALSE; /* true if got a newline */ Boolean fbuf; /* true if our buffer filled up */ int nr; /* number of bytes read */ int i; /* auxiliary index into outBuf */ int max; /* limit for i (end of current data) */ int nRead; /* (Temporary) number of bytes read */ 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: gotNL = FALSE; fbuf = FALSE; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); /* * Check for interrupt here too, because the above read may block * when the child process is stopped. In this case the interrupt * will unblock it (we don't use SA_RESTART). */ if (interrupted) JobPassSig(interrupted); if (nRead < 0) { DEBUGF(JOB, ("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. */ fbuf = TRUE; i = job->curPos; } } if (gotNL || fbuf) { /* * 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, preceded * 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) { char *cp; cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); /* * 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) { MESSAGE(stdout, job->node); lastNode = job->node; } fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); fflush(stdout); } } if (i < max - 1) { /* shift the remaining characters down */ memcpy(job->outBuf, &job->outBuf[i + 1], 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 != NULL) { fprintf(stdout, "Results of making %s:\n", job->node->name); fflush(stdout); while (fgets(inLine, sizeof(inLine), oFILE) != NULL) { char *cp, *endp, *oendp; cp = inLine; oendp = endp = inLine + strlen(inLine); if (endp[-1] == '\n') { *--endp = '\0'; } cp = JobOutput(job, inLine, endp, FALSE); /* * 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. */ fprintf(stdout, "%s", cp); fflush(stdout); if (endp != oendp) { fprintf(stdout, "\n"); fflush(stdout); } } fclose(oFILE); eunlink(job->outFile); } } } /*- *----------------------------------------------------------------------- * 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(Boolean block) { int pid; /* pid of dead child */ Job *job; /* job descriptor for dead child */ LstNode *jnode; /* list element for finding job */ int status; /* Exit/termination status */ /* * Don't even bother if we know there's no one around. */ if (nJobs == 0) { return; } for (;;) { pid = waitpid((pid_t)-1, &status, (block ? 0 : WNOHANG) | WUNTRACED); if (pid <= 0) break; DEBUGF(JOB, ("Process %d exited or stopped.\n", pid)); jnode = Lst_Find(jobs, &pid, JobCmpPid); if (jnode == NULL) { if (WIFSIGNALED(status) && (WTERMSIG(status) == SIGCONT)) { jnode = Lst_Find(stoppedJobs, &pid, JobCmpPid); if (jnode == NULL) { Error("Resumed child (%d) not in table", pid); continue; } job = Lst_Datum(jnode); Lst_Remove(stoppedJobs, jnode); } else { Error("Child (%d) not in table?", pid); continue; } } else { job = Lst_Datum(jnode); Lst_Remove(jobs, jnode); nJobs -= 1; if (fifoFd >= 0 && maxJobs > 1) { write(fifoFd, "+", 1); maxJobs--; if (nJobs >= maxJobs) jobFull = TRUE; else jobFull = FALSE; } else { DEBUGF(JOB, ("Job queue is no longer full.\n")); jobFull = FALSE; } } JobFinish(job, &status); } if (interrupted) JobPassSig(interrupted); } /*- *----------------------------------------------------------------------- * 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 flag) { int nfds; #ifdef USE_KQUEUE #define KEV_SIZE 4 struct kevent kev[KEV_SIZE]; int i; #else struct timeval timeout; fd_set readfds; LstNode *ln; Job *job; #endif fflush(stdout); if (usePipes) { #ifdef USE_KQUEUE if ((nfds = kevent(kqfd, NULL, 0, kev, KEV_SIZE, NULL)) == -1) { if (errno != EINTR) Punt("kevent: %s", strerror(errno)); if (interrupted) JobPassSig(interrupted); } else { for (i = 0; i < nfds; i++) { if (kev[i].flags & EV_ERROR) { warnc(kev[i].data, "kevent"); continue; } switch (kev[i].filter) { case EVFILT_READ: JobDoOutput(kev[i].udata, FALSE); break; case EVFILT_PROC: /* Just wake up and let Job_CatchChildren() collect the * terminated job. */ break; } } } #else readfds = outputs; timeout.tv_sec = SEL_SEC; timeout.tv_usec = SEL_USEC; if (flag && jobFull && fifoFd >= 0) FD_SET(fifoFd, &readfds); nfds = select(FD_SETSIZE, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timeout); if (nfds <= 0) { if (interrupted) JobPassSig(interrupted); return; } if (fifoFd >= 0 && FD_ISSET(fifoFd, &readfds)) { if (--nfds <= 0) return; } - if (Lst_Open(jobs) == FAILURE) { - Punt("Cannot open job table"); - } - while (nfds && (ln = Lst_Next(jobs)) != NULL) { + for (ln = Lst_First(jobs); nfds != 0 && ln != NULL; ln = Lst_Succ(ln)) { job = Lst_Datum(ln); if (FD_ISSET(job->inPipe, &readfds)) { JobDoOutput(job, FALSE); nfds -= 1; } } - Lst_Close(jobs); #endif /* !USE_KQUEUE */ } } /*- *----------------------------------------------------------------------- * 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(GNode *gn) { JobStart(gn, 0, NULL); } /* * JobCopyShell: * * Make a new copy of the shell structure including a copy of the strings * in it. This also defaults some fields in case they are NULL. * * The function returns a pointer to the new shell structure otherwise. */ static Shell * JobCopyShell(const Shell *osh) { Shell *nsh; nsh = emalloc(sizeof(*nsh)); nsh->name = estrdup(osh->name); if (osh->echoOff != NULL) nsh->echoOff = estrdup(osh->echoOff); else nsh->echoOff = NULL; if (osh->echoOn != NULL) nsh->echoOn = estrdup(osh->echoOn); else nsh->echoOn = NULL; nsh->hasEchoCtl = osh->hasEchoCtl; if (osh->noPrint != NULL) nsh->noPrint = estrdup(osh->noPrint); else nsh->noPrint = NULL; nsh->noPLen = osh->noPLen; nsh->hasErrCtl = osh->hasErrCtl; if (osh->errCheck == NULL) nsh->errCheck = estrdup(""); else nsh->errCheck = estrdup(osh->errCheck); if (osh->ignErr == NULL) nsh->ignErr = estrdup("%s"); else nsh->ignErr = estrdup(osh->ignErr); if (osh->echo == NULL) nsh->echo = estrdup(""); else nsh->echo = estrdup(osh->echo); if (osh->exit == NULL) nsh->exit = estrdup(""); else nsh->exit = estrdup(osh->exit); return (nsh); } /* * JobFreeShell: * * Free a shell structure and all associated strings. */ static void JobFreeShell(Shell *sh) { if (sh != NULL) { free(sh->name); free(sh->echoOff); free(sh->echoOn); free(sh->noPrint); free(sh->errCheck); free(sh->ignErr); free(sh->echo); free(sh->exit); free(sh); } } void Shell_Init(void) { if (commandShell == NULL) commandShell = JobMatchShell(shells[DEFSHELL].name); if (shellPath == 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); } } /*- *----------------------------------------------------------------------- * Job_Init -- * Initialize the process module, given a maximum number of jobs. * * Results: * none * * Side Effects: * lists and counters are initialized *----------------------------------------------------------------------- */ void Job_Init(int maxproc) { GNode *begin; /* node for commands to do at the very start */ const char *env; struct sigaction sa; fifoFd = -1; jobs = Lst_Init(); stoppedJobs = Lst_Init(); env = getenv("MAKE_JOBS_FIFO"); if (env == NULL && maxproc > 1) { /* * We did not find the environment variable so we are the leader. * Create the fifo, open it, write one char per allowed job into * the pipe. */ mktemp(fifoName); if (!mkfifo(fifoName, 0600)) { fifoFd = open(fifoName, O_RDWR | O_NONBLOCK, 0); if (fifoFd >= 0) { fifoMaster = 1; fcntl(fifoFd, F_SETFL, O_NONBLOCK); env = fifoName; setenv("MAKE_JOBS_FIFO", env, 1); while (maxproc-- > 0) { write(fifoFd, "+", 1); } /* The master make does not get a magic token */ jobFull = TRUE; maxJobs = 0; } else { unlink(fifoName); env = NULL; } } } else if (env != NULL) { /* * We had the environment variable so we are a slave. * Open fifo and give ourselves a magic token which represents * the token our parent make has grabbed to start his make process. * Otherwise the sub-makes would gobble up tokens and the proper * number of tokens to specify to -j would depend on the depth of * the tree and the order of execution. */ fifoFd = open(env, O_RDWR, 0); if (fifoFd >= 0) { fcntl(fifoFd, F_SETFL, O_NONBLOCK); maxJobs = 1; jobFull = FALSE; } } if (fifoFd <= 0) { maxJobs = maxproc; jobFull = FALSE; } else { } nJobs = 0; aborting = 0; errors = 0; lastNode = NULL; if ((maxJobs == 1 && fifoFd < 0) || beVerbose == 0) { /* * If only one job can run at a time, there's no need for a banner, * no is there? */ targFmt = ""; } else { targFmt = TARG_FMT; } Shell_Init(); /* * Catch the four signals that POSIX specifies if they aren't ignored. * JobCatchSignal will just set global variables and hope someone * else is going to handle the interrupt. */ sa.sa_handler = JobCatchSig; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (signal(SIGINT, SIG_IGN) != SIG_IGN) { sigaction(SIGINT, &sa, NULL); } if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { sigaction(SIGHUP, &sa, NULL); } if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) { sigaction(SIGQUIT, &sa, NULL); } if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { sigaction(SIGTERM, &sa, NULL); } /* * 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(USE_PGRP) if (signal(SIGTSTP, SIG_IGN) != SIG_IGN) { sigaction(SIGTSTP, &sa, NULL); } if (signal(SIGTTOU, SIG_IGN) != SIG_IGN) { sigaction(SIGTTOU, &sa, NULL); } if (signal(SIGTTIN, SIG_IGN) != SIG_IGN) { sigaction(SIGTTIN, &sa, NULL); } if (signal(SIGWINCH, SIG_IGN) != SIG_IGN) { sigaction(SIGWINCH, &sa, NULL); } #endif #ifdef USE_KQUEUE if ((kqfd = kqueue()) == -1) { Punt("kqueue: %s", strerror(errno)); } #endif begin = Targ_FindNode(".BEGIN", TARG_NOCREATE); if (begin != NULL) { JobStart(begin, JOB_SPECIAL, (Job *)NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } 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(void) { char c; int i; if (aborting) return (aborting); if (fifoFd >= 0 && jobFull) { i = read(fifoFd, &c, 1); if (i > 0) { maxJobs++; jobFull = FALSE; } } return (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(void) { 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; JobRestartJobs(); return (FALSE); } else { return (TRUE); } } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * JobMatchShell -- * Find a matching shell in 'shells' given its final component. * * Results: * A pointer to a freshly allocated Shell structure with a copy * of the static structure or NULL if no shell with the given name * is found. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Shell * JobMatchShell(const char *name) { const struct CShell *sh; /* Pointer into shells table */ struct Shell *nsh; for (sh = shells; sh < shells + sizeof(shells) / sizeof(shells[0]); sh++) if (strcmp(sh->name, name) == 0) break; if (sh == shells + sizeof(shells) / sizeof(shells[0])) return (NULL); /* make a copy */ nsh = emalloc(sizeof(*nsh)); nsh->name = estrdup(sh->name); nsh->echoOff = estrdup(sh->echoOff); nsh->echoOn = estrdup(sh->echoOn); nsh->hasEchoCtl = sh->hasEchoCtl; nsh->noPrint = estrdup(sh->noPrint); nsh->noPLen = sh->noPLen; nsh->hasErrCtl = sh->hasErrCtl; nsh->errCheck = estrdup(sh->errCheck); nsh->ignErr = estrdup(sh->ignErr); nsh->echo = estrdup(sh->echo); nsh->exit = estrdup(sh->exit); return (nsh); } /*- *----------------------------------------------------------------------- * 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(char *line) { char **words; int wordCount; char **argv; int argc; char *path; Shell newShell; Shell *sh; Boolean fullSpec = FALSE; while (isspace((unsigned char)*line)) { line++; } words = brk_string(line, &wordCount, TRUE); memset(&newShell, 0, sizeof(newShell)); /* * Parse the specification by keyword */ for (path = 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; } } /* * Some checks (could be more) */ if (fullSpec) { if ((newShell.echoOn != NULL) ^ (newShell.echoOff != NULL)) Parse_Error(PARSE_FATAL, "Shell must have either both echoOff and " "echoOn or none of them"); if (newShell.echoOn != NULL && newShell.echoOff) newShell.hasEchoCtl = TRUE; } if (path == 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 == NULL) { Parse_Error(PARSE_FATAL, "Neither path nor name specified"); return (FAILURE); } if ((sh = JobMatchShell(newShell.name)) == NULL) { Parse_Error(PARSE_FATAL, "%s: no matching shell", newShell.name); return (FAILURE); } } 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. */ free(shellPath); shellPath = estrdup(path); if (newShell.name == NULL) { /* get the base name as the name */ path = strrchr(path, '/'); if (path == NULL) { path = shellPath; } else { path += 1; } newShell.name = path; } if (!fullSpec) { if ((sh = JobMatchShell(newShell.name)) == NULL) { Parse_Error(PARSE_FATAL, "%s: no matching shell", newShell.name); return (FAILURE); } } else { sh = JobCopyShell(&newShell); } } /* set the new shell */ JobFreeShell(commandShell); commandShell = sh; shellName = commandShell->name; 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(int runINTERRUPT, int signo) { LstNode *ln; /* element in job table */ - Job *job = NULL; /* job descriptor in that element */ + Job *job; /* job descriptor in that element */ GNode *interrupt; /* the node describing the .INTERRUPT target */ aborting = ABORT_INTERRUPT; - Lst_Open(jobs); - while ((ln = Lst_Next(jobs)) != NULL) { + for (ln = Lst_First(jobs); ln != NULL; ln = Lst_Succ(ln)) { job = Lst_Datum(ln); if (!Targ_Precious(job->node)) { char *file = (job->node->path == NULL ? job->node->name : job->node->path); if (!noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } } if (job->pid) { DEBUGF(JOB, ("JobInterrupt passing signal to child %d.\n", job->pid)); KILL(job->pid, signo); } } if (runINTERRUPT && !touchFlag) { /* clear the interrupted flag because we would get an * infinite loop otherwise */ interrupted = 0; interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); if (interrupt != NULL) { ignoreErrors = FALSE; JobStart(interrupt, JOB_IGNDOTS, (Job *)NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } } } /* *----------------------------------------------------------------------- * Job_Finish -- * Do final processing such as the running of the commands * attached to the .END target. * * Results: * Number of errors reported. *----------------------------------------------------------------------- */ int Job_Finish(void) { if (postCommands != NULL && !Lst_IsEmpty(postCommands->commands)) { if (errors) { Error("Errors reported so .END ignored"); } else { JobStart(postCommands, JOB_SPECIAL | JOB_IGNDOTS, NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } } if (fifoFd >= 0) { close(fifoFd); fifoFd = -1; if (fifoMaster) unlink(fifoName); } 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(void) { aborting = ABORT_WAIT; while (nJobs != 0) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } 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(void) { LstNode *ln; /* element in job table */ Job *job; /* the job descriptor in that element */ int foo; aborting = ABORT_ERROR; if (nJobs) { - Lst_Open(jobs); - while ((ln = Lst_Next(jobs)) != NULL) { + for (ln = Lst_First(jobs); ln != NULL; ln = Lst_Succ(ln)) { job = Lst_Datum(ln); /* * kill the child process with increasingly drastic signals to make * darn sure it's dead. */ KILL(job->pid, SIGINT); KILL(job->pid, SIGKILL); } } /* * Catch as many children as want to report in at first, then give up */ while (waitpid((pid_t)-1, &foo, WNOHANG) > 0) continue; } /*- *----------------------------------------------------------------------- * JobRestartJobs -- * Tries to restart stopped jobs if there are slots available. * Note that this tries to restart them regardless of pending errors. * It's not good to leave stopped jobs lying around! * * Results: * None. * * Side Effects: * Resumes(and possibly migrates) jobs. * *----------------------------------------------------------------------- */ static void JobRestartJobs(void) { while (!jobFull && !Lst_IsEmpty(stoppedJobs)) { DEBUGF(JOB, ("Job queue is not full. Restarting a stopped job.\n")); JobRestart(Lst_DeQueue(stoppedJobs)); } } Index: head/usr.bin/make/lst.h =================================================================== --- head/usr.bin/make/lst.h (revision 138563) +++ head/usr.bin/make/lst.h (revision 138564) @@ -1,218 +1,196 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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 8.2 (Berkeley) 4/28/95 * $FreeBSD$ */ /*- * lst.h -- * Header for using the list library */ #ifndef _LST_H_ #define _LST_H_ #include #include #include "sprite.h" /* * Structure of a list node. */ struct LstNode { struct LstNode *prevPtr; /* previous element in list */ struct LstNode *nextPtr; /* next in list */ int useCount:8; /* Count of functions using the node. Node may not * be deleted until count goes to 0 */ int flags:8; /* Node status flags */ void *datum; /* datum associated with this element */ }; typedef struct LstNode LstNode; /* * Flags required for synchronization */ #define LN_DELETED 0x0001 /* List node should be removed when done */ typedef enum { LstHead, LstMiddle, LstTail, LstUnknown } LstWhere; /* * The list itself */ struct Lst { LstNode *firstPtr; /* first node in list */ LstNode *lastPtr; /* last node in list */ - /* - * fields for sequential access - */ - LstWhere atEnd; /* Where in the list the last access was */ - Boolean isOpen; /* true if list has been Lst_Open'ed */ - LstNode *curPtr; /* current node, if open. NULL if - * *just* opened */ - LstNode *prevPtr; /* Previous node, if open. Used by - * Lst_Remove */ }; typedef struct Lst Lst; typedef int CompareProc(const void *, const void *); typedef int DoProc(void *, void *); typedef void *DuplicateProc(void *); typedef void FreeProc(void *); /* * 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 ((FreeProc *)NULL) #define NOCOPY ((DuplicateProc *)NULL) #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 */ /* Create a new list */ Lst *Lst_Init(void); /* Duplicate an existing list */ Lst *Lst_Duplicate(Lst *, DuplicateProc *); /* Destroy an old one */ void Lst_Destroy(Lst *, FreeProc *); /* * Functions to modify a list */ /* Insert an element before another */ ReturnStatus Lst_Insert(Lst *, LstNode *, void *); /* Insert an element after another */ ReturnStatus Lst_Append(Lst *, LstNode *, void *); /* Place an element at the front of a lst. */ #define Lst_AtFront(LST, D) (Lst_Insert((LST), Lst_First(LST), (D))) /* Place an element at the end of a lst. */ #define Lst_AtEnd(LST, D) (Lst_Append((LST), Lst_Last(LST), (D))) /* Remove an element */ ReturnStatus Lst_Remove(Lst *, LstNode *); /* Replace a node with a new value */ #define Lst_Replace(NODE, D) (((NODE) == NULL) ? FAILURE : \ (((NODE)->datum = (D)), SUCCESS)) /* Concatenate two lists */ ReturnStatus Lst_Concat(Lst *, Lst *, int); /* * Node-specific functions */ /* Return first element in list */ #define Lst_First(LST) ((Lst_Valid(LST) && !Lst_IsEmpty(LST)) \ ? (LST)->firstPtr : NULL) /* Return last element in list */ #define Lst_Last(LST) ((Lst_Valid(LST) && !Lst_IsEmpty(LST)) \ ? (LST)->lastPtr : NULL) /* Return successor to given element */ #define Lst_Succ(NODE) (((NODE) == NULL) ? NULL : (NODE)->nextPtr) /* Get datum from LstNode */ #define Lst_Datum(NODE) ((NODE)->datum) /* * Functions for entire lists */ /* Find an element in a list */ #define Lst_Find(LST, D, FN) (Lst_FindFrom((LST), Lst_First(LST), (D), (FN))) /* Find an element starting from somewhere */ LstNode *Lst_FindFrom(Lst *, LstNode *, const void *, CompareProc *); /* * See if the given datum is on the list. Returns the LstNode containing * the datum */ LstNode *Lst_Member(Lst *, void *); /* Apply a function to all elements of a lst */ void Lst_ForEach(Lst *, DoProc *, void *); #define Lst_ForEach(LST, FN, D) (Lst_ForEachFrom((LST), Lst_First(LST), \ (FN), (D))) /* * 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. */ void Lst_ForEachFrom(Lst *, LstNode *, DoProc *, void *); -/* - * 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(). - */ -/* Open the list */ -ReturnStatus Lst_Open(Lst *); -/* Next element please */ -LstNode *Lst_Next(Lst *); -/* Done yet? */ -Boolean Lst_IsAtEnd(Lst *); -/* Finish table access */ -void Lst_Close(Lst *); /* * for using the list as a queue */ /* Place an element at tail of queue */ #define Lst_EnQueue(LST, D) (Lst_Valid(LST) \ ? Lst_Append((LST), Lst_Last(LST), (D)) \ : FAILURE) /* Remove an element from head of queue */ void *Lst_DeQueue(Lst *); /* * LstValid (L) -- * Return TRUE if the list L is valid */ #define Lst_Valid(L) (((L) == NULL) ? FALSE : TRUE) /* * LstNodeValid (LN, L) -- * Return TRUE if the LstNode LN is valid with respect to L */ #define Lst_NodeValid(LN, L) (((LN) == NULL) ? FALSE : TRUE) /* * Lst_IsEmpty(L) -- * TRUE if the list L is empty. */ #define Lst_IsEmpty(L) (!Lst_Valid(L) || (L)->firstPtr == NULL) #endif /* _LST_H_ */ Index: head/usr.bin/make/lst.lib/lstOpen.c =================================================================== --- head/usr.bin/make/lst.lib/lstOpen.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstOpen.c (nonexistent) @@ -1,83 +0,0 @@ -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * 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. - * - * @(#)lstOpen.c 8.1 (Berkeley) 6/6/93 - */ - -#ifndef lint -#include -__FBSDID("$FreeBSD$"); -#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. - * Lst_IsAtEnd must be used to determine when to stop. - */ - -#include "make.h" -#include "lst.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 NULL so the - * other sequential functions no it was just opened and can choose - * the first element accessed based on this. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_Open(Lst *l) -{ - - if (Lst_Valid(l) == FALSE) { - return (FAILURE); - } - l->isOpen = TRUE; - l->atEnd = Lst_IsEmpty(l) ? LstHead : LstUnknown; - l->curPtr = NULL; - - return (SUCCESS); -} Property changes on: head/usr.bin/make/lst.lib/lstOpen.c ___________________________________________________________________ Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Index: head/usr.bin/make/lst.lib/lstIsAtEnd.c =================================================================== --- head/usr.bin/make/lst.lib/lstIsAtEnd.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstIsAtEnd.c (nonexistent) @@ -1,81 +0,0 @@ -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * 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. - * - * @(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93 - */ - -#ifndef lint -#include -__FBSDID("$FreeBSD$"); -#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. Lst_IsAtEnd must be used to determine - * when to stop. - */ - -#include "make.h" -#include "lst.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(Lst *list) -{ - - return (!Lst_Valid(list) || !list->isOpen || - (list->atEnd == LstHead) || (list->atEnd == LstTail)); -} Property changes on: head/usr.bin/make/lst.lib/lstIsAtEnd.c ___________________________________________________________________ Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Index: head/usr.bin/make/lst.lib/lstNext.c =================================================================== --- head/usr.bin/make/lst.lib/lstNext.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstNext.c (nonexistent) @@ -1,106 +0,0 @@ -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * 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. - * - * @(#)lstNext.c 8.1 (Berkeley) 6/6/93 - */ - -#ifndef lint -#include -__FBSDID("$FreeBSD$"); -#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. Lst_IsAtEnd must be used to determine - * when to stop. - */ - -#include "make.h" -#include "lst.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Next -- - * Return the next node for the given list. - * - * Results: - * The next node or NULL if the list has yet to be opened or the end - * has been reached. - * - * Side Effects: - * the curPtr field is updated. - * - *----------------------------------------------------------------------- - */ -LstNode * -Lst_Next(Lst *list) -{ - LstNode *tln; - - if ((Lst_Valid(list) == FALSE) || (list->isOpen == FALSE)) { - return (NULL); - } - - list->prevPtr = list->curPtr; - - if (list->curPtr == NULL) { - if (list->atEnd == LstUnknown) { - /* - * 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 = LstMiddle; - } else { - tln = NULL; - list->atEnd = LstTail; - } - } else { - tln = list->curPtr->nextPtr; - list->curPtr = tln; - - if (tln == NULL) { - list->atEnd = LstTail; - } else { - list->atEnd = LstMiddle; - } - } - - return (tln); -} Property changes on: head/usr.bin/make/lst.lib/lstNext.c ___________________________________________________________________ Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Index: head/usr.bin/make/lst.lib/lstClose.c =================================================================== --- head/usr.bin/make/lst.lib/lstClose.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstClose.c (nonexistent) @@ -1,80 +0,0 @@ -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * 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. - * - * @(#)lstClose.c 8.1 (Berkeley) 6/6/93 - */ - -#ifndef lint -#include -__FBSDID("$FreeBSD$"); -#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. Lst_IsAtEnd must be used to determine - * when to stop. - */ - -#include "make.h" -#include "lst.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Close -- - * Close a list which was opened for sequential access. - * - * Results: - * None. - * - * Arguments: - * l The list to close - * - * Side Effects: - * The list is closed. - * - *----------------------------------------------------------------------- - */ -void -Lst_Close(Lst *list) -{ - - if (Lst_Valid(list) == TRUE) { - list->isOpen = FALSE; - list->atEnd = LstUnknown; - } -} Property changes on: head/usr.bin/make/lst.lib/lstClose.c ___________________________________________________________________ Deleted: svn:keywords ## -1 +0,0 ## -FreeBSD=%H \ No newline at end of property Index: head/usr.bin/make/lst.lib/lstInit.c =================================================================== --- head/usr.bin/make/lst.lib/lstInit.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstInit.c (revision 138564) @@ -1,78 +1,76 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * 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. * * @(#)lstInit.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * init.c -- * Initialize a new linked list. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Init -- * Create and initialize a new list. * * Results: * The created list. * * Side Effects: * A list is created, what else? * *----------------------------------------------------------------------- */ Lst * Lst_Init(void) { Lst *nList; nList = emalloc(sizeof(*nList)); nList->firstPtr = NULL; nList->lastPtr = NULL; - nList->isOpen = FALSE; - nList->atEnd = LstUnknown; return (nList); } Index: head/usr.bin/make/lst.lib/lstRemove.c =================================================================== --- head/usr.bin/make/lst.lib/lstRemove.c (revision 138563) +++ head/usr.bin/make/lst.lib/lstRemove.c (revision 138564) @@ -1,128 +1,115 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * 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. * * @(#)lstRemove.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstRemove.c -- * Remove an element from a list */ #include "make.h" #include "lst.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 NULL 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(Lst *list, LstNode *ln) { if (!Lst_Valid(list) || !Lst_NodeValid(ln, list)) { return (FAILURE); } /* * unlink it from the list */ if (ln->nextPtr != NULL) { ln->nextPtr->prevPtr = ln->prevPtr; } if (ln->prevPtr != NULL) { ln->prevPtr->nextPtr = ln->nextPtr; } /* * if either the firstPtr or lastPtr of the list point to this node, * adjust them accordingly */ if (list->firstPtr == ln) { list->firstPtr = ln->nextPtr; } if (list->lastPtr == ln) { list->lastPtr = ln->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 == NULL), we set the - * end to be Unknown, since it is. - */ - if (list->isOpen && (list->curPtr == ln)) { - list->curPtr = list->prevPtr; - if (list->curPtr == NULL) { - list->atEnd = LstUnknown; - } - } - - /* * the only way firstPtr can still point to ln is if ln is the last * node on the list. The list is, therefore, empty and is marked as such */ if (list->firstPtr == ln) { list->firstPtr = NULL; } /* * note that the datum is unmolested. The caller must free it as * necessary and as expected. */ if (ln->useCount == 0) { free(ln); } else { ln->flags |= LN_DELETED; } return (SUCCESS); } Index: head/usr.bin/make/make.c =================================================================== --- head/usr.bin/make/make.c (revision 138563) +++ head/usr.bin/make/make.c (revision 138564) @@ -1,867 +1,859 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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.c 8.1 (Berkeley) 6/6/93 */ #include __FBSDID("$FreeBSD$"); /*- * 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" #include "hash.h" #include "dir.h" #include "job.h" static Lst *toBeMade; /* The current fringe of the graph. These * are nodes which await examination by * 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 */ static int MakeAddChild(void *, void *); static int MakeAddAllSrc(void *, void *); static int MakeTimeStamp(void *, void *); static int MakeHandleUse(void *, void *); static Boolean MakeStartJobs(void); static int MakePrintStatus(void *, void *); /*- *----------------------------------------------------------------------- * 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(GNode *pgn, GNode *cgn) { if (cgn->mtime > pgn->cmtime) { pgn->cmtime = cgn->mtime; } return (0); } static int MakeTimeStamp(void *pgn, void *cgn) { return (Make_TimeStamp(pgn, cgn)); } /*- *----------------------------------------------------------------------- * 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(GNode *gn) { 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) { Dir_MTime(gn); if (gn->mtime != 0) { DEBUGF(MAKE, ("modified %s...", Targ_FmtTime(gn->mtime))); } else { DEBUGF(MAKE, ("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*. */ DEBUGF(MAKE, (".USE node...")); oodate = FALSE; } else if (gn->type & OP_LIB) { DEBUGF(MAKE, ("library...")); /* * always out of date if no children and :: target */ oodate = Arch_LibOODate(gn) || ((gn->cmtime == 0) && (gn->type & OP_DOUBLEDEP)); } 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. */ DEBUGF(MAKE, (".JOIN node...")); oodate = gn->childMade; } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { /* * A node which is the object of the force (!) operator or which has * the .EXEC attribute is always considered out-of-date. */ if (gn->type & OP_FORCE) { DEBUGF(MAKE, ("! operator...")); } else if (gn->type & OP_PHONY) { DEBUGF(MAKE, (".PHONY node...")); } else { DEBUGF(MAKE, (".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 (gn->mtime < gn->cmtime) { DEBUGF(MAKE, ("modified before source...")); } else if (gn->mtime == 0) { DEBUGF(MAKE, ("non-existent and no sources...")); } else { DEBUGF(MAKE, (":: operator and no sources...")); } oodate = TRUE; } else oodate = FALSE; /* * 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, MakeTimeStamp, 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(void *gnp, void *lp) { GNode *gn = gnp; Lst *l = lp; if (!gn->make && !(gn->type & OP_USE)) { Lst_EnQueue(l, 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(GNode *cgn, GNode *pgn) { GNode *gn; /* A child of the .USE node */ 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. */ Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW); } - if (Lst_Open(cgn->children) == SUCCESS) { - while ((ln = Lst_Next(cgn->children)) != NULL) { - gn = Lst_Datum(ln); + for (ln = Lst_First(cgn->children); ln != NULL; ln = Lst_Succ(ln)) { + gn = Lst_Datum(ln); - if (Lst_Member(pgn->children, gn) == NULL) { - Lst_AtEnd(pgn->children, gn); - Lst_AtEnd(gn->parents, pgn); - pgn->unmade += 1; - } + if (Lst_Member(pgn->children, gn) == NULL) { + Lst_AtEnd(pgn->children, gn); + 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--; } } return (0); } static int MakeHandleUse(void *pgn, void *cgn) { return (Make_HandleUse(pgn, cgn)); } /*- *----------------------------------------------------------------------- * 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(GNode *cgn) { GNode *pgn; /* the parent node */ char *cname; /* the child's name */ LstNode *ln; /* Element in parents and iParents lists */ char *p1; + char *ptr; + char *cpref; cname = Var_Value(TARGET, cgn, &p1); free(p1); /* * 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 */ /* * Christos, 4/9/92: If we are saving commands pretend that * the target is made now. Otherwise archives with ... rules * don't work! */ if (noExecute || (cgn->type & OP_SAVE_CMDS) || Dir_MTime(cgn) == 0) { cgn->mtime = now; } DEBUGF(MAKE, ("update time: %s\n", Targ_FmtTime(cgn->mtime))); #endif } - if (Lst_Open(cgn->parents) == SUCCESS) { - while ((ln = Lst_Next(cgn->parents)) != NULL) { - pgn = Lst_Datum(ln); - if (pgn->make) { - pgn->unmade -= 1; + for (ln = Lst_First(cgn->parents); ln != NULL; ln = Lst_Succ(ln)) { + pgn = 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 { - Make_TimeStamp(pgn, cgn); + if (!(cgn->type & (OP_EXEC | OP_USE))) { + if (cgn->made == MADE) { + pgn->childMade = TRUE; + if (pgn->cmtime < cgn->mtime) { + pgn->cmtime = cgn->mtime; } + } else { + Make_TimeStamp(pgn, cgn); } - if (pgn->unmade == 0) { - /* - * Queue the node up -- any unmade predecessors will - * be dealt with in MakeStartJobs. - */ - Lst_EnQueue(toBeMade, pgn); - } else if (pgn->unmade < 0) { - Error("Graph cycles through %s", pgn->name); - } } + if (pgn->unmade == 0) { + /* + * Queue the node up -- any unmade predecessors will + * be dealt with in MakeStartJobs. + */ + Lst_EnQueue(toBeMade, 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 != NULL; ln = Lst_Succ(ln)) { GNode *succ = Lst_Datum(ln); if (succ->make && succ->unmade == 0 && succ->made == UNMADE && Lst_Member(toBeMade, succ) == NULL) { Lst_EnQueue(toBeMade, succ); } } /* * Set the .PREFIX and .IMPSRC variables for all the implied parents * of this node. */ - if (Lst_Open(cgn->iParents) == SUCCESS) { - char *ptr; - char *cpref = Var_Value(PREFIX, cgn, &ptr); - - while ((ln = Lst_Next(cgn->iParents)) != NULL) { - pgn = Lst_Datum (ln); - if (pgn->make) { - Var_Set(IMPSRC, cname, pgn); - Var_Set(PREFIX, cpref, pgn); - } + cpref = Var_Value(PREFIX, cgn, &ptr); + for (ln = Lst_First(cgn->iParents); ln != NULL; ln = Lst_Succ(ln)) { + pgn = Lst_Datum (ln); + if (pgn->make) { + Var_Set(IMPSRC, cname, pgn); + Var_Set(PREFIX, cpref, pgn); } - free(ptr); - Lst_Close(cgn->iParents); } + free(ptr); } /*- *----------------------------------------------------------------------- * 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(void *cgnp, void *pgnp) { GNode *cgn = (GNode *) cgnp; GNode *pgn = (GNode *) pgnp; if ((cgn->type & (OP_EXEC | OP_USE | OP_INVISIBLE)) == 0) { char *child; char *p1 = NULL; if (OP_NOP(cgn->type)) { /* * this node is only source; use the specific pathname for it */ child = cgn->path ? cgn->path : cgn->name; } else child = Var_Value(TARGET, cgn, &p1); 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); } free(p1); } 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(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) { char *p1; Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn); free(p1); } } /*- *----------------------------------------------------------------------- * 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(void) { GNode *gn; while (!Lst_IsEmpty(toBeMade) && !Job_Full()) { gn = Lst_DeQueue(toBeMade); DEBUGF(MAKE, ("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 != NULL; ln = Lst_Succ(ln)){ GNode *pgn = Lst_Datum(ln); if (pgn->make && pgn->made == UNMADE) { DEBUGF(MAKE, ("predecessor %s not made yet.\n", pgn->name)); break; } } /* * If ln isn't NULL, 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 != NULL) { continue; } } numNodes--; if (Make_OODate(gn)) { DEBUGF(MAKE, ("out-of-date\n")); if (queryFlag) { return (TRUE); } Make_DoAllVar(gn); Job_Make(gn); } else { DEBUGF(MAKE, ("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. If gn->unmade is * nonzero and that is meant to imply a cycle in the graph, then * cycle is TRUE. * * Results: * Always returns 0. * * Side Effects: * A message may be printed. * *----------------------------------------------------------------------- */ static int MakePrintStatus(void *gnp, void *cyclep) { GNode *gn = gnp; Boolean cycle = *(Boolean *)cyclep; if (gn->made == UPTODATE) { printf("`%s' is up to date.\n", gn->name); } else if (gn->unmade != 0) { if (cycle) { Boolean t = TRUE; /* * 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, &t); gn->made = UNMADE; } else if (gn->made != ENDCYCLE) { gn->made = CYCLE; Lst_ForEach(gn->children, MakePrintStatus, &t); } } 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(Lst *targs) { GNode *gn; /* a temporary pointer */ Lst *examine; /* List of targets to examine */ int errors; /* Number of errors the Job module reports */ toBeMade = Lst_Init(); 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 = 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, MakeHandleUse, gn); Suff_FindDeps(gn); if (gn->unmade != 0) { Lst_ForEach(gn->children, MakeAddChild, examine); } else { Lst_EnQueue(toBeMade, 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. */ 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(!Lst_IsEmpty(toBeMade)); Job_CatchChildren(!usePipes); MakeStartJobs(); } errors = Job_Finish(); /* * Print the final status of each target. E.g. if it wasn't made * because some inferior reported an error. */ errors = ((errors == 0) && (numNodes != 0)); Lst_ForEach(targs, MakePrintStatus, &errors); return (TRUE); } Index: head/usr.bin/make/make.h =================================================================== --- head/usr.bin/make/make.h (revision 138563) +++ head/usr.bin/make/make.h (revision 138564) @@ -1,379 +1,382 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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 8.3 (Berkeley) 6/13/95 * $FreeBSD$ */ /*- * make.h -- * The global definitions for pmake */ #ifndef _MAKE_H_ #define _MAKE_H_ #include #include #include #include #include #include #include "sprite.h" #include "lst.h" #include "config.h" #include "buf.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 filesystem. * 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) */ int order; /* Its wait weight */ 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 */ + /* current command executing in compat mode */ + LstNode *compat_command; + struct _Suff *suffix; /* Suffix for the node (determined by * Suff_FindDeps and opaque to everyone * but the Suff module) */ } GNode; /* * Definitions for handling #include specifications */ typedef struct { char *str; char *ptr; } PTR; typedef struct IFile { char *fname; /* name of previous file */ int lineno; /* saved line number */ FILE *F; /* the open stream */ PTR *p; /* the char pointer */ } IFile; /* * 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 */ #define OP_PHONY 0x00010000 /* Not a file target; run always */ /* 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 NULL 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 MAKE_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. */ #define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ #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 " __FBSDID("$FreeBSD$"); /*- * 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_End Cleanup the module * * 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: 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 NULL * if the target had no implicit sources. */ #include #include "make.h" #include "hash.h" #include "dir.h" static Lst *sufflist; /* Lst of suffixes */ static Lst *suffClean; /* Lst of suffixes to be cleaned */ static Lst *srclist; /* Lst of sources */ 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 */ int refCount; /* Reference count of list membership */ Lst *parents; /* Suffixes we have a transformation to */ Lst *children; /* Suffixes we have a transformation from */ Lst *ref; /* List of lists this suffix is referenced */ } 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) */ #ifdef DEBUG_SRC Lst *cp; /* Debug; children list */ #endif } Src; /* * A structure for passing more than one argument to the Lst-library-invoked * function... */ typedef struct { Lst *l; Src *s; } LstSrc; static Suff *suffNull; /* The NULL suffix for this run */ static Suff *emptySuff; /* The empty suffix required for POSIX * single-suffix transformation rules */ static void SuffFree(void *); static void SuffInsert(Lst *, Suff *); static void SuffRemove(Lst *, Suff *); static Boolean SuffParseTransform(char *, Suff **, Suff **); static int SuffRebuildGraph(void *, void *); static int SuffAddSrc(void *, void *); static int SuffRemoveSrc(Lst *); static void SuffAddLevel(Lst *, Src *); static Src *SuffFindThem(Lst *, Lst *); static Src *SuffFindCmds(Src *, Lst *); static int SuffExpandChildren(void *, void *); static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); static void SuffFindDeps(GNode *, Lst *); static void SuffFindArchiveDeps(GNode *, Lst *); static void SuffFindNormalDeps(GNode *, Lst *); static int SuffPrintName(void *, void *); static int SuffPrintSuff(void *, void *); static int SuffPrintTrans(void *, void *); /*************** 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(const char *pref, char *str) { 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(const Suff *s, char *str) { const char *p1; /* Pointer into suffix name */ 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. * * XXX use the function above once constification is complete. *----------------------------------------------------------------------- */ static int SuffSuffIsSuffixP(const void *is, const void *str) { const Suff *s = is; const char *p1; /* Pointer into suffix name */ const char *p2 = str; /* Pointer into string being examined */ p1 = s->name + s->nameLen; while (p1 >= s->name && *p1 == *p2) { p1--; p2--; } return (p1 != s->name - 1); } /*- *----------------------------------------------------------------------- * 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(const void *s, const void *sname) { return (strcmp(sname, ((const Suff *)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 * * XXX use the function above once constification is complete. *----------------------------------------------------------------------- */ static int SuffSuffIsPrefix(const void *s, const void *istr) { const char *pref = ((const Suff *)s)->name; const char *str = istr; while (*str != '\0' && *pref == *str) { pref++; str++; } return (*pref != '\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(const void *gn, const void *name) { return (strcmp(name, ((const GNode *)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(void *sp) { Suff *s = sp; if (s == suffNull) suffNull = NULL; if (s == emptySuff) emptySuff = NULL; Lst_Destroy(s->ref, NOFREE); Lst_Destroy(s->children, NOFREE); Lst_Destroy(s->parents, NOFREE); Lst_Destroy(s->searchPath, Dir_Destroy); free(s->name); free(s); } /*- *----------------------------------------------------------------------- * SuffRemove -- * Remove the suffix into the list * * Results: * None * * Side Effects: * The reference count for the suffix is decremented *----------------------------------------------------------------------- */ static void SuffRemove(Lst *l, Suff *s) { LstNode *ln = Lst_Member(l, s); if (ln != NULL) { Lst_Remove(l, ln); s->refCount--; } } /*- *----------------------------------------------------------------------- * SuffInsert -- * Insert the suffix into the list keeping the list ordered by suffix * numbers. * * Results: * None * * Side Effects: * The reference count of the suffix is incremented *----------------------------------------------------------------------- */ static void SuffInsert(Lst *l, Suff *s) { - LstNode *ln; /* current element in l we're examining */ - Suff *s2 = NULL; /* the suffix descriptor in this element */ + 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)) != NULL) { + s2 = NULL; + for (ln = Lst_First(l); ln != NULL; ln = Lst_Succ(ln)) { s2 = Lst_Datum(ln); - if (s2->sNum >= s->sNum) { + if (s2->sNum >= s->sNum) break; - } } if (s2 == NULL) { DEBUGF(SUFF, ("inserting an empty list?...")); } - Lst_Close(l); DEBUGF(SUFF, ("inserting %s(%d)...", s->name, s->sNum)); if (ln == NULL) { DEBUGF(SUFF, ("at end of list\n")); Lst_AtEnd (l, s); s->refCount++; Lst_AtEnd(s->ref, l); } else if (s2->sNum != s->sNum) { DEBUGF(SUFF, ("before %s(%d)\n", s2->name, s2->sNum)); Lst_Insert(l, ln, s); s->refCount++; Lst_AtEnd(s->ref, l); } else { DEBUGF(SUFF, ("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(void) { Lst_Concat(suffClean, sufflist, LST_CONCLINK); sufflist = Lst_Init(); sNum = 1; suffNull = emptySuff; /* * Clear suffNull's children list (the other suffixes are built new, but * suffNull is used as is). * NOFREE is used because all suffixes are are on the suffClean list. * suffNull should not have parents. */ Lst_Destroy(suffNull->children, NOFREE); suffNull->children = Lst_Init(); } /*- *----------------------------------------------------------------------- * 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(char *str, Suff **srcPtr, Suff **targPtr) { LstNode *srcLn; /* element in suffix list of trans source*/ Suff *src; /* Source of transformation */ LstNode *targLn; /* element in suffix list of trans target*/ char *str2; /* Extra pointer (maybe target suffix) */ LstNode *singleLn; /* element in suffix list of any suffix * that exactly matches str */ Suff *single = NULL;/* Source of possible transformation to * null suffix */ srcLn = NULL; singleLn = NULL; /* * 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. */ for (;;) { if (srcLn == NULL) { srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix); } else { srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str, SuffSuffIsPrefix); } if (srcLn == NULL) { /* * Ran out of source suffixes -- no such rule */ if (singleLn != NULL) { /* * 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 = Lst_Datum (srcLn); str2 = str + src->nameLen; if (*str2 == '\0') { single = src; singleLn = srcLn; } else { targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP); if (targLn != NULL) { *srcPtr = src; *targPtr = 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(char *str) { 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(char *line) { GNode *gn; /* GNode of transformation rule */ Suff *s, /* source suffix */ *t; /* target suffix */ LstNode *ln; /* Node for existing transformation */ ln = Lst_Find(transforms, line, SuffGNHasNameP); if (ln == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ gn = Targ_NewGN(line); Lst_AtEnd(transforms, 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 = Lst_Datum(ln); Lst_Destroy(gn->commands, NOFREE); Lst_Destroy(gn->children, NOFREE); gn->commands = Lst_Init(); gn->children = Lst_Init(); } gn->type = OP_TRANSFORM; SuffParseTransform(line, &s, &t); /* * link the two together in the proper relationship and order */ DEBUGF(SUFF, ("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(void *gnp, void *dummy __unused) { GNode *gn = (GNode *)gnp; if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { Suff *s, *t; /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules (e.g., .DEFAULT). */ if (!SuffParseTransform(gn->name, &s, &t)) return (0); DEBUGF(SUFF, ("deleting transformation from `%s' to `%s'\n", s->name, t->name)); /* * Remove the source from the target's children list. We check for a * NULL 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... */ SuffRemove(t->children, s); /* * Remove the target from the source's parents list */ SuffRemove(s->parents, t); } else if (gn->type & OP_TRANSFORM) { DEBUGF(SUFF, ("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(void *transformp, void *sp) { GNode *transform = transformp; Suff *s = sp; char *cp; LstNode *ln; Suff *s2 = NULL; /* * First see if it is a transformation from this suffix. */ cp = SuffStrIsPrefix(s->name, transform->name); if (cp != (char *)NULL) { if (cp[0] == '\0') /* null rule */ s2 = suffNull; else { ln = Lst_Find(sufflist, cp, SuffSuffHasNameP); if (ln != NULL) s2 = Lst_Datum(ln); } if (s2 != NULL) { /* * Found target. Link in and return, since it can't be anything * else. */ SuffInsert(s2->children, s); SuffInsert(s->parents, s2); return (0); } } /* * Not from, maybe to? */ cp = SuffSuffIsSuffix(s, transform->name + strlen(transform->name)); if (cp != NULL) { /* * Null-terminate the source suffix in order to find it. */ cp[1] = '\0'; ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP); /* * Replace the start of the target suffix */ cp[1] = s->name[0]; if (ln != NULL) { /* * Found it -- establish the proper relationship */ s2 = 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(char *str) { Suff *s; /* new suffix descriptor */ LstNode *ln; ln = Lst_Find(sufflist, str, SuffSuffHasNameP); if (ln == NULL) { s = emalloc(sizeof(Suff)); s->name = estrdup(str); s->nameLen = strlen (s->name); s->searchPath = Lst_Init(); s->children = Lst_Init(); s->parents = Lst_Init(); s->ref = Lst_Init(); s->sNum = sNum++; s->flags = 0; s->refCount = 0; Lst_AtEnd(sufflist, s); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ Lst_ForEach(transforms, SuffRebuildGraph, s); } } /*- *----------------------------------------------------------------------- * Suff_GetPath -- * Return the search path for the given suffix, if it's defined. * * Results: * The searchPath for the desired suffix or NULL if the suffix isn't * defined. * * Side Effects: * None *----------------------------------------------------------------------- */ Lst * Suff_GetPath(char *sname) { LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln == NULL) { return (NULL); } else { s = 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 preceded 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(void) { Suff *s; LstNode *ln; char *ptr; Lst *inIncludes; /* Cumulative .INCLUDES path */ Lst *inLibs; /* Cumulative .LIBS path */ - if (Lst_Open(sufflist) == FAILURE) { - return; - } - inIncludes = Lst_Init(); inLibs = Lst_Init(); - while ((ln = Lst_Next(sufflist)) != NULL) { + for (ln = Lst_First(sufflist); ln != NULL; ln = Lst_Succ(ln)) { s = 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", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); free(ptr); Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL); free(ptr); 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(char *sname) { LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = 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(char *sname) { LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = Lst_Datum(ln); s->flags |= SUFF_LIBRARY; } } /********** Implicit Source Search Functions *********/ /*- *----------------------------------------------------------------------- * 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(void *sp, void *lsp) { Suff *s = sp; LstSrc *ls = lsp; 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 = emalloc(sizeof(Src)); s2->file = estrdup(targ->pref); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(); Lst_AtEnd(targ->cp, s2); printf("1 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, (void *)NULL); printf("\n"); #endif } s2 = emalloc(sizeof(Src)); s2->file = str_concat(targ->pref, s->name, 0); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(); Lst_AtEnd(targ->cp, s2); printf("2 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, (void *)NULL); printf("\n"); #endif 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(Lst *l, Src *targ) { LstSrc ls; ls.s = targ; ls.l = l; Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); } /*- *---------------------------------------------------------------------- * SuffRemoveSrc -- * Free all src structures in list that don't have a reference count + * XXX this actually frees only the first of these. * * Results: * True if a src was removed * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ static int SuffRemoveSrc(Lst *l) { - LstNode *ln; + LstNode *ln, *ln1; Src *s; int t = 0; - if (Lst_Open(l) == FAILURE) { - return (0); - } #ifdef DEBUG_SRC printf("cleaning %lx: ", (unsigned long) l); Lst_ForEach(l, PrintAddr, (void *)NULL); printf("\n"); #endif + for (ln = Lst_First(l); ln != NULL; ln = ln1) { + ln1 = Lst_Succ(ln); - while ((ln = Lst_Next(l)) != NULL) { s = (Src *)Lst_Datum(ln); if (s->children == 0) { free(s->file); if (!s->parent) free(s->pref); else { #ifdef DEBUG_SRC LstNode *ln = Lst_Member(s->parent->cp, s); if (ln != NULL) Lst_Remove(s->parent->cp, ln); #endif --s->parent->children; } #ifdef DEBUG_SRC printf("free: [l=%x] p=%x %d\n", l, s, s->children); Lst_Destroy(s->cp, NOFREE); #endif Lst_Remove(l, ln); free(s); t |= 1; - Lst_Close(l); - return TRUE; + return (TRUE); } #ifdef DEBUG_SRC else { printf("keep: [l=%x] p=%x %d: ", l, s, s->children); Lst_ForEach(s->cp, PrintAddr, (void *)NULL); printf("\n"); } #endif } - Lst_Close(l); - return (t); } /*- *----------------------------------------------------------------------- * 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(Lst *srcs, Lst *slst) { Src *s; /* current Src */ Src *rs; /* returned Src */ char *ptr; rs = NULL; while (!Lst_IsEmpty (srcs)) { s = Lst_DeQueue(srcs); DEBUGF(SUFF, ("\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) != NULL) { #ifdef DEBUG_SRC printf("remove %x from %x\n", s, srcs); #endif rs = s; break; } if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { rs = s; #ifdef DEBUG_SRC printf("remove %x from %x\n", s, srcs); #endif free(ptr); break; } DEBUGF(SUFF, ("not there\n")); SuffAddLevel(srcs, s); Lst_AtEnd(slst, s); } if (rs) { DEBUGF(SUFF, ("got it\n")); } 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 NULL if no such beast. * * Side Effects: * A Src structure may be allocated. * *----------------------------------------------------------------------- */ static Src * SuffFindCmds (Src *targ, Lst *slst) { LstNode *ln; /* General-purpose list node */ 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; - Lst_Open(t->children); prefLen = strlen(targ->pref); - while ((ln = Lst_Next(t->children)) != NULL) { + for (ln = Lst_First(t->children); ln != NULL; ln = Lst_Succ(ln)) { s = Lst_Datum(ln); cp = strrchr(s->name, '/'); if (cp == 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, &cp[prefLen], SuffSuffHasNameP); if (ln != NULL) { /* * 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 = Lst_Datum(ln); if (Lst_Member(suff->parents, targ->suff) != NULL) { /* * 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 = emalloc(sizeof(Src)); ret->file = estrdup(s->name); ret->pref = targ->pref; ret->suff = suff; suff->refCount++; ret->parent = targ; ret->node = s; ret->children = 0; targ->children += 1; #ifdef DEBUG_SRC ret->cp = Lst_Init(); printf("3 add %x %x\n", targ, ret); Lst_AtEnd(targ->cp, ret); #endif Lst_AtEnd(slst, ret); DEBUGF(SUFF, ("\tusing existing source %s\n", s->name)); return (ret); } } } } - Lst_Close(t->children); return (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(void *cgnp, void *pgnp) { GNode *cgn = cgnp; GNode *pgn = pgnp; 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, 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 (strchr(cgn->name, '$') != NULL) { DEBUGF(SUFF, ("Expanding \"%s\"...", cgn->name)); cp = Var_Subst(NULL, cgn->name, pgn, TRUE); if (cp != NULL) { Lst *members = Lst_Init(); 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; 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++) continue; 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); Lst_AtEnd(members, 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; size_t 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); Lst_AtEnd(members, 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 = Lst_DeQueue(members); DEBUGF(SUFF, ("%s...", gn->name)); if (Lst_Member(pgn->children, gn) == NULL) { Lst_Append(pgn->children, prevLN, gn); prevLN = Lst_Succ(prevLN); Lst_AtEnd(gn->parents, pgn); pgn->unmade++; } } Lst_Destroy(members, NOFREE); /* * Free the result */ free(cp); } /* * Now the source is expanded, remove it from the list of children to * keep it from being processed. */ ln = Lst_Member(pgn->children, cgn); pgn->unmade--; Lst_Remove(pgn->children, ln); DEBUGF(SUFF, ("\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, cp, SuffSuffIsSuffixP); DEBUGF(SUFF, ("Wildcard expanding \"%s\"...", cgn->name)); if (ln != NULL) { Suff *s = Lst_Datum(ln); DEBUGF(SUFF, ("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(); Dir_Expand(cgn->name, path, exp); while (!Lst_IsEmpty(exp)) { /* * Fetch next expansion off the list and find its GNode */ cp = Lst_DeQueue(exp); DEBUGF(SUFF, ("%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, gn) == NULL) { Lst_Append(pgn->children, prevLN, gn); prevLN = Lst_Succ(prevLN); Lst_AtEnd(gn->parents, 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, cgn); pgn->unmade--; Lst_Remove(pgn->children, ln); DEBUGF(SUFF, ("\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(GNode *tGn, GNode *sGn, Suff *t, Suff *s) { LstNode *ln; /* General node */ char *tname; /* Name of transformation rule */ GNode *gn; /* Node for same */ if (Lst_Member(tGn->children, sGn) == NULL) { /* * Not already linked, so form the proper links between the * target and source. */ Lst_AtEnd(tGn->children, sGn); Lst_AtEnd(sGn->parents, 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 != NULL; ln = Lst_Succ(ln)) { gn = Lst_Datum(ln); if (Lst_Member(tGn->children, gn) == NULL) { /* * Not already linked, so form the proper links between the * target and source. */ Lst_AtEnd(tGn->children, gn); Lst_AtEnd(gn->parents, tGn); tGn->unmade += 1; } } } /* * Locate the transformation rule itself */ tname = str_concat(s->name, t->name, 0); ln = Lst_Find(transforms, tname, SuffGNHasNameP); free(tname); if (ln == NULL) { /* * 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 = Lst_Datum(ln); DEBUGF(SUFF, ("\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 */ Make_HandleUse(gn, tGn); /* * Deal with wildcards and variables in any acquired sources */ ln = Lst_Succ(ln); if (ln != NULL) { Lst_ForEachFrom(tGn->children, ln, SuffExpandChildren, tGn); } /* * Keep track of another parent to which this beast is transformed so * the .IMPSRC variable can be set correctly for the parent. */ Lst_AtEnd(sGn->iParents, tGn); return (TRUE); } /*- *----------------------------------------------------------------------- * SuffFindArchiveDeps -- * Locate dependencies for an OP_ARCHV node. * * Results: * None * * Side Effects: * Same as Suff_FindDeps * *----------------------------------------------------------------------- */ static void SuffFindArchiveDeps(GNode *gn, Lst *slst) { 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 */ }; int i; /* Index into copy and vals */ 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 = strchr(gn->name, '('); eoname = strchr(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); SuffFindDeps(mem, slst); /* * Create the link between the two nodes right off */ if (Lst_Member(gn->children, mem) == NULL) { Lst_AtEnd(gn->children, mem); Lst_AtEnd(mem->parents, 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--) { char *p1; Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn); free(p1); } ms = mem->suffix; if (ms == NULL) { /* * Didn't know what it was -- use .NULL suffix if not in make mode */ DEBUGF(SUFF, ("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 != NULL) { /* * Got one -- apply it */ if (!SuffApplyTransform(gn, mem, Lst_Datum(ln), ms)) { DEBUGF(SUFF, ("\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(GNode *gn, Lst *slst) { char *eoname; /* End of name */ char *sopref; /* Start of prefix */ 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(); targs = Lst_Init(); /* * 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 != NULL) { /* * Look for next possible suffix... */ ln = Lst_FindFrom(sufflist, ln, eoname, SuffSuffIsSuffixP); if (ln != NULL) { int prefLen; /* Length of the prefix */ Src *target; /* * Allocate a Src structure to which things can be transformed */ target = emalloc(sizeof(Src)); target->file = estrdup(gn->name); target->suff = Lst_Datum(ln); target->suff->refCount++; target->node = gn; target->parent = NULL; target->children = 0; #ifdef DEBUG_SRC target->cp = Lst_Init(); #endif /* * Allocate room for the prefix, whose end is found by subtracting * the length of the suffix from the end of the name. */ prefLen = (eoname - target->suff->nameLen) - sopref; target->pref = emalloc(prefLen + 1); memcpy(target->pref, sopref, prefLen); target->pref[prefLen] = '\0'; /* * Add nodes from which the target can be made */ SuffAddLevel(srcs, target); /* * Record the target so we can nuke it */ Lst_AtEnd(targs, target); /* * Search from this suffix's successor... */ ln = Lst_Succ(ln); } } /* * Handle target of unknown suffix... */ if (Lst_IsEmpty(targs) && suffNull != NULL) { DEBUGF(SUFF, ("\tNo known suffix on %s. Using .NULL suffix\n", gn->name)); targ = emalloc(sizeof(Src)); targ->file = estrdup(gn->name); targ->suff = suffNull; targ->suff->refCount++; targ->node = gn; targ->parent = NULL; targ->children = 0; targ->pref = estrdup(sopref); #ifdef DEBUG_SRC targ->cp = Lst_Init(); #endif /* * Only use the default suffix rules if we don't have commands * or dependencies defined for this gnode */ if (Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) SuffAddLevel(srcs, targ); else { DEBUGF(SUFF, ("not ")); } DEBUGF(SUFF, ("adding suffix rules\n")); Lst_AtEnd(targs, 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, slst); if (bottom == NULL) { /* * No known transformations -- use the first suffix found for setting * the local variables. */ if (!Lst_IsEmpty(targs)) { targ = Lst_Datum(Lst_First(targs)); } else { targ = 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) continue; } /* * 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, (void *)gn); if (targ == NULL) { DEBUGF(SUFF, ("\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) { char *ptr; 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 savep = strlen(gn->path) - targ->suff->nameLen; char savec; if (gn->suffix) gn->suffix->refCount--; gn->suffix = targ->suff; gn->suffix->refCount++; savec = gn->path[savep]; gn->path[savep] = '\0'; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, gn); gn->path[savep] = savec; } else { /* * The .PREFIX gets the full path if the target has * no known suffix. */ if (gn->suffix) gn->suffix->refCount--; gn->suffix = NULL; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, 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. */ if (gn->suffix) gn->suffix->refCount--; gn->suffix = (targ == NULL) ? NULL : targ->suff; if (gn->suffix) gn->suffix->refCount++; free(gn->path); gn->path = estrdup(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, slst); if (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) { if (Lst_Member(slst, bottom) == NULL) { Lst_AtEnd(slst, bottom); } bottom = bottom->parent; } 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 == NULL) { bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); } for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; if (src->node->suffix) src->node->suffix->refCount--; src->node->suffix = src->suff; src->node->suffix->refCount++; if (targ->node == NULL) { 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); } } if (gn->suffix) gn->suffix->refCount--; gn->suffix = src->suff; gn->suffix->refCount++; /* * So Dir_MTime doesn't go questing for it... */ free(gn->path); gn->path = estrdup(gn->name); /* * Nuke the transformation path and the Src structures left over in the * two lists. */ sfnd_return: if (bottom) if (Lst_Member(slst, bottom) == NULL) Lst_AtEnd(slst, bottom); while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) continue; Lst_Concat(slst, srcs, LST_CONCLINK); Lst_Concat(slst, targs, LST_CONCLINK); } /*- *----------------------------------------------------------------------- * 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(GNode *gn) { SuffFindDeps(gn, srclist); while (SuffRemoveSrc(srclist)) continue; } static void SuffFindDeps(GNode *gn, Lst *slst) { if (gn->type & OP_DEPS_FOUND) { /* * If dependencies already found, no need to do it again... */ return; } else { gn->type |= OP_DEPS_FOUND; } DEBUGF(SUFF, ("SuffFindDeps (%s)\n", gn->name)); if (gn->type & OP_ARCHV) { SuffFindArchiveDeps(gn, slst); } 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, LIBSUFF, SuffSuffHasNameP); if (gn->suffix) gn->suffix->refCount--; if (ln != NULL) { gn->suffix = s = Lst_Datum (ln); gn->suffix->refCount++; 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, slst); } } /*- *----------------------------------------------------------------------- * 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(char *name) { Suff *s; LstNode *ln; ln = Lst_Find(sufflist, name, SuffSuffHasNameP); if (ln != NULL) { s = Lst_Datum(ln); if (suffNull != 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(void) { sufflist = Lst_Init(); suffClean = Lst_Init(); srclist = Lst_Init(); transforms = Lst_Init(); 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 = emalloc(sizeof(Suff)); suffNull->name = estrdup(""); suffNull->nameLen = 0; suffNull->searchPath = Lst_Init(); Dir_Concat(suffNull->searchPath, dirSearchPath); suffNull->children = Lst_Init(); suffNull->parents = Lst_Init(); suffNull->ref = Lst_Init(); suffNull->sNum = sNum++; suffNull->flags = SUFF_NULL; suffNull->refCount = 1; } /*- *---------------------------------------------------------------------- * Suff_End -- * Cleanup the this module * * Results: * None * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ void Suff_End(void) { Lst_Destroy(sufflist, SuffFree); Lst_Destroy(suffClean, SuffFree); if (suffNull) SuffFree(suffNull); Lst_Destroy(srclist, NOFREE); Lst_Destroy(transforms, NOFREE); } /********************* DEBUGGING FUNCTIONS **********************/ static int SuffPrintName(void *s, void *dummy __unused) { printf("`%s' ", ((Suff *)s)->name); return (0); } static int SuffPrintSuff(void *sp, void *dummy __unused) { Suff *s = sp; int flags; int flag; printf("# `%s' [%d] ", s->name, s->refCount); 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; default: break; } fputc(flags ? '|' : ')', stdout); } } fputc('\n', stdout); printf("#\tTo: "); Lst_ForEach(s->parents, SuffPrintName, (void *)NULL); fputc('\n', stdout); printf("#\tFrom: "); Lst_ForEach(s->children, SuffPrintName, (void *)NULL); fputc('\n', stdout); printf("#\tSearch Path: "); Dir_PrintPath(s->searchPath); fputc('\n', stdout); return (0); } static int SuffPrintTrans(void *tp, void *dummy __unused) { GNode *t = tp; printf("%-16s: ", t->name); Targ_PrintType(t->type); fputc('\n', stdout); Lst_ForEach(t->commands, Targ_PrintCmd, (void *)NULL); fputc('\n', stdout); return (0); } void Suff_PrintAll(void) { printf("#*** Suffixes:\n"); Lst_ForEach(sufflist, SuffPrintSuff, (void *)NULL); printf("#*** Transformations:\n"); Lst_ForEach(transforms, SuffPrintTrans, (void *)NULL); } Index: head/usr.bin/make/targ.c =================================================================== --- head/usr.bin/make/targ.c (revision 138563) +++ head/usr.bin/make/targ.c (revision 138564) @@ -1,635 +1,631 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * 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. * * @(#)targ.c 8.2 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * 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_End Cleanup the module * * 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 #include #include "make.h" #include "hash.h" #include "dir.h" static Lst *allTargets; /* the list of all targets found so far */ static Lst *allGNs; /* List of all the GNodes */ static Hash_Table targets; /* a hash table of same */ #define HTSIZE 191 /* initial size of hash table */ static int TargPrintOnlySrc(void *, void *); static int TargPrintName(void *, void *); static int TargPrintNode(void *, void *); static void TargFreeGN(void *); /*- *----------------------------------------------------------------------- * Targ_Init -- * Initialize this module * * Results: * None * * Side Effects: * The allTargets list and the targets hash table are initialized *----------------------------------------------------------------------- */ void Targ_Init(void) { allTargets = Lst_Init(); Hash_InitTable(&targets, HTSIZE); } /*- *----------------------------------------------------------------------- * Targ_End -- * Finalize this module * * Results: * None * * Side Effects: * All lists and gnodes are cleared *----------------------------------------------------------------------- */ void Targ_End(void) { Lst_Destroy(allTargets, NOFREE); if (allGNs) Lst_Destroy(allGNs, TargFreeGN); Hash_DeleteTable(&targets); } /*- *----------------------------------------------------------------------- * 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: * The gnode is added to the list of all gnodes. *----------------------------------------------------------------------- */ GNode * Targ_NewGN(char *name) { GNode *gn; gn = emalloc(sizeof(GNode)); gn->name = estrdup(name); gn->path = NULL; 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->order = 0; gn->mtime = gn->cmtime = 0; gn->iParents = Lst_Init(); gn->cohorts = Lst_Init(); gn->parents = Lst_Init(); gn->children = Lst_Init(); gn->successors = Lst_Init(); gn->preds = Lst_Init(); gn->context = Lst_Init(); gn->commands = Lst_Init(); gn->suffix = NULL; if (allGNs == NULL) allGNs = Lst_Init(); Lst_AtEnd(allGNs, (void *)gn); return (gn); } /*- *----------------------------------------------------------------------- * TargFreeGN -- * Destroy a GNode * * Results: * None. * * Side Effects: * None. *----------------------------------------------------------------------- */ static void TargFreeGN(void *gnp) { GNode *gn = gnp; free(gn->name); free(gn->path); Lst_Destroy(gn->iParents, NOFREE); Lst_Destroy(gn->cohorts, NOFREE); Lst_Destroy(gn->parents, NOFREE); Lst_Destroy(gn->children, NOFREE); Lst_Destroy(gn->successors, NOFREE); Lst_Destroy(gn->preds, NOFREE); Lst_Destroy(gn->context, NOFREE); Lst_Destroy(gn->commands, NOFREE); free(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 NULL 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(char *name, int flags) { 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); Lst_AtEnd(allTargets, gn); } } else { he = Hash_FindEntry(&targets, name); } if (he == NULL) { return (NULL); } else { return (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(Lst *names, int flags) { Lst *nodes; /* result list */ LstNode *ln; /* name list element */ GNode *gn; /* node in tLn */ char *name; nodes = Lst_Init(); - if (Lst_Open(names) == FAILURE) { - return (nodes); - } - while ((ln = Lst_Next(names)) != NULL) { + for (ln = Lst_First(names); ln != NULL; ln = Lst_Succ(ln)) { name = Lst_Datum(ln); gn = Targ_FindNode(name, flags); if (gn != NULL) { /* * 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. */ Lst_AtEnd(nodes, gn); if (gn->type & OP_DOUBLEDEP) { 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(GNode *gn) { 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(GNode *gn) { 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(GNode *gn) { 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(GNode *gn) { mainTarg = gn; } static int TargPrintName(void *gnp, void *ppath) { GNode *gn = (GNode *) gnp; printf("%s ", gn->name); #ifdef notdef if (ppath) { if (gn->path) { printf("[%s] ", gn->path); } if (gn == mainTarg) { printf("(MAIN NAME) "); } } #endif /* notdef */ return (ppath ? 0 : 0); } int Targ_PrintCmd(void *cmd, void *dummy __unused) { printf("\t%s\n", (char *)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_t modtime) { struct tm *parts; static char buf[128]; parts = localtime(&modtime); strftime(buf, sizeof buf, "%H:%M:%S %b %d, %Y", parts); buf[sizeof(buf) - 1] = '\0'; return (buf); } /*- *----------------------------------------------------------------------- * Targ_PrintType -- * Print out a type field giving only those attributes the user can * set. * * Results: * * Side Effects: * *----------------------------------------------------------------------- */ void Targ_PrintType(int type) { int tbit; #define PRINTBIT(attr) case CONCAT(OP_,attr): printf("." #attr " "); break #define PRINTDBIT(attr) case CONCAT(OP_,attr): DEBUGF(TARG, ("." #attr " ")); break 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: DEBUGF(TARG, (".MEMBER ")); break; PRINTDBIT(ARCHV); } } } /*- *----------------------------------------------------------------------- * TargPrintNode -- * print the contents of a node *----------------------------------------------------------------------- */ static int TargPrintNode(void *gnp, void *passp) { GNode *gn = gnp; int pass = *(int *)passp; 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, (void *)NULL); fputc('\n', stdout); } } if (!Lst_IsEmpty (gn->parents)) { printf("# parents: "); Lst_ForEach(gn->parents, TargPrintName, (void *)NULL); fputc('\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; default: break; } Targ_PrintType(gn->type); Lst_ForEach(gn->children, TargPrintName, (void *)NULL); fputc('\n', stdout); Lst_ForEach(gn->commands, Targ_PrintCmd, (void *)NULL); printf("\n\n"); if (gn->type & OP_DOUBLEDEP) { Lst_ForEach(gn->cohorts, TargPrintNode, &pass); } } return (0); } /*- *----------------------------------------------------------------------- * TargPrintOnlySrc -- * Print only those targets that are just a source. * * Results: * 0. * * Side Effects: * The name of each file is printed preceded by #\t * *----------------------------------------------------------------------- */ static int TargPrintOnlySrc(void *gnp, void *dummy __unused) { GNode *gn = gnp; 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. * * Results: * none * * Side Effects: * lots o' output *----------------------------------------------------------------------- */ void Targ_PrintGraph(int pass) { printf("#*** Input graph:\n"); Lst_ForEach(allTargets, TargPrintNode, &pass); printf("\n\n"); printf("#\n# Files that are only sources:\n"); Lst_ForEach(allTargets, TargPrintOnlySrc, (void *)NULL); 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(); }