diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c index 6947acc09531..17def65fd54c 100644 --- a/usr.bin/make/arch.c +++ b/usr.bin/make/arch.c @@ -1,1206 +1,1151 @@ /* * 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" /* Lst of archives we've already examined */ static Lst archives = Lst_Initializer(archives); 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_Initializer(members); 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); /* XXXHB Lst_Destroy(&members) */ 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); } } 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; 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; } } 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. * *----------------------------------------------------------------------- */ void Arch_Init(void) { } - -/*- - *----------------------------------------------------------------------- - * Arch_End -- - * Cleanup things for this module. - * - * Results: - * None. - * - * Side Effects: - * The 'archives' list is freed - * - *----------------------------------------------------------------------- - */ -void -Arch_End(void) -{ - - Lst_Destroy(&archives, ArchFree); -} diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c index c3536297c81e..6734b67984f3 100644 --- a/usr.bin/make/dir.c +++ b/usr.bin/make/dir.c @@ -1,1239 +1,1212 @@ /* * 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. */ /* main search path */ Lst dirSearchPath = Lst_Initializer(dirSearchPath); /* the list of all open directories */ static Lst openDirectories = Lst_Initializer(openDirectories); /* * 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) { 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 */ 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]; Lst tp = Lst_Initializer(tp); if (*dp == '/') *dp = '\0'; Dir_AddDir(&tp, dirpath); DirExpandInt(cp + 1, &tp, expansions); Lst_Destroy(&tp, 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)); } /* * 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... */ 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)); p->hits += 1; hits += 1; return (file); } else if (hasSlash) { /* * If the file has a leading path component and that * component exactly matches the entire name of the * current search directory, we assume the file * doesn't exist and return NULL. */ for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) continue; if (*p1 == '\0' && p2 == cp - 1) { if (*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...")); 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")); /* * 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. ")); 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(""); 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; if ((ln = Lst_Member(&openDirectories, p)) != NULL) 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"); 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); } diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h index bd37f99c90e3..4287f2135cbd 100644 --- a/usr.bin/make/dir.h +++ b/usr.bin/make/dir.h @@ -1,73 +1,72 @@ /* * 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.h 8.2 (Berkeley) 4/28/95 * $FreeBSD$ */ /* dir.h -- */ #ifndef _DIR #define _DIR typedef struct Path { char *name; /* Name of directory */ int refCount; /* Number of paths with this directory */ int hits; /* Number of times a file in this dirextory has * been found */ Hash_Table files; /* Hash table of files in directory */ } Path; void Dir_Init(void); void Dir_InitDot(void); -void Dir_End(void); Boolean Dir_HasWildcards(const char *); void Dir_Expand(char *, Lst *, Lst *); char *Dir_FindFile(char *, Lst *); int Dir_MTime(GNode *); void Dir_AddDir(Lst *, char *); char *Dir_MakeFlags(char *, Lst *); void Dir_ClearPath(Lst *); void Dir_Concat(Lst *, Lst *); void Dir_PrintDirectories(void); void Dir_PrintPath(Lst *); void Dir_Destroy(void *); void *Dir_CopyDir(void *); #endif /* _DIR */ diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c index 8de4d1f254a0..2c3ba6558b64 100644 --- a/usr.bin/make/main.c +++ b/usr.bin/make/main.c @@ -1,1140 +1,1132 @@ /* * 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. * * @(#)main.c 8.3 (Berkeley) 3/19/94 */ #ifndef lint #if 0 static char copyright[] = "@(#) Copyright (c) 1988, 1989, 1990, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /*- * main.c -- * The main file for this entire program. Exit routines etc * reside here. * * Utility functions defined in this file: * Main_ParseArgLine Takes a line of arguments, breaks them and * treats them as if they were given when first * invoked. Used by the parse module to implement * the .MFLAGS target. */ #include #include #include #include #include #include #ifndef MACHINE #include #endif #include #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 WANT_ENV_MKLVL 1 #define MKLVL_MAXVAL 500 #define MKLVL_ENVVAR "__MKLVL__" #define MAKEFLAGS ".MAKEFLAGS" /* Targets to be made */ Lst create = Lst_Initializer(create); time_t now; /* Time at start of make */ GNode *DEFAULT; /* .DEFAULT node */ Boolean allPrecious; /* .PRECIOUS given on line by itself */ static Boolean noBuiltins; /* -r flag */ /* ordered list of makefiles to read */ static Lst makefiles = Lst_Initializer(makefiles); static Boolean expandVars; /* fully expand printed variables */ /* list of variables to print */ static Lst variables = Lst_Initializer(variables); int maxJobs; /* -j argument */ static Boolean forceJobs; /* -j argument given */ Boolean compatMake; /* -B argument */ Boolean debug; /* -d flag */ Boolean noExecute; /* -n flag */ Boolean keepgoing; /* -k flag */ Boolean queryFlag; /* -q flag */ Boolean touchFlag; /* -t flag */ Boolean usePipes; /* !-P flag */ Boolean ignoreErrors; /* -i flag */ Boolean beSilent; /* -s flag */ Boolean beVerbose; /* -v flag */ Boolean oldVars; /* variable substitution style */ Boolean checkEnvFirst; /* -e flag */ /* (-E) vars to override from env */ Lst envFirstVars = Lst_Initializer(envFirstVars); Boolean jobsRunning; /* TRUE if the jobs might be running */ static void MainParseArgs(int, char **); char * chdir_verify_path(char *, char *); static int ReadMakefile(const void *, const void *); static void usage(void); static char *curdir; /* startup directory */ static char *objdir; /* where we chdir'ed to */ /* * Append a flag with an optional argument to MAKEFLAGS and MFLAGS */ static void MFLAGS_append(char *flag, char *arg) { Var_Append(MAKEFLAGS, flag, VAR_GLOBAL); if (arg != NULL) Var_Append(MAKEFLAGS, arg, VAR_GLOBAL); Var_Append("MFLAGS", flag, VAR_GLOBAL); if (arg != NULL) Var_Append("MFLAGS", arg, VAR_GLOBAL); } /*- * MainParseArgs -- * Parse a given argument vector. Called from main() and from * Main_ParseArgLine() when the .MAKEFLAGS target is used. * * XXX: Deal with command line overriding .MAKEFLAGS in makefile * * Results: * None * * Side Effects: * Various global and local flags will be set depending on the flags * given */ static void MainParseArgs(int argc, char **argv) { int c; optind = 1; /* since we're called more than once */ #define OPTFLAGS "BC:D:E:I:PSV:Xd:ef:ij:km:nqrstv" rearg: while((c = getopt(argc, argv, OPTFLAGS)) != -1) { switch(c) { case 'C': if (chdir(optarg) == -1) err(1, "chdir %s", optarg); break; case 'D': Var_Set(optarg, "1", VAR_GLOBAL); MFLAGS_append("-D", optarg); break; case 'I': Parse_AddIncludeDir(optarg); MFLAGS_append("-I", optarg); break; case 'V': Lst_AtEnd(&variables, estrdup(optarg)); MFLAGS_append("-V", optarg); break; case 'X': expandVars = FALSE; break; case 'B': compatMake = TRUE; MFLAGS_append("-B", NULL); unsetenv("MAKE_JOBS_FIFO"); break; case 'P': usePipes = FALSE; MFLAGS_append("-P", NULL); break; case 'S': keepgoing = FALSE; MFLAGS_append("-S", NULL); break; case 'd': { char *modules = optarg; for (; *modules; ++modules) switch (*modules) { case 'A': debug = ~0; break; case 'a': debug |= DEBUG_ARCH; break; case 'c': debug |= DEBUG_COND; break; case 'd': debug |= DEBUG_DIR; break; case 'f': debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; ++modules; } else if (modules[1] == '2') { debug |= DEBUG_GRAPH2; ++modules; } break; case 'j': debug |= DEBUG_JOB; break; case 'l': debug |= DEBUG_LOUD; break; case 'm': debug |= DEBUG_MAKE; break; case 's': debug |= DEBUG_SUFF; break; case 't': debug |= DEBUG_TARG; break; case 'v': debug |= DEBUG_VAR; break; default: warnx("illegal argument to d option -- %c", *modules); usage(); } MFLAGS_append("-d", optarg); break; } case 'E': Lst_AtEnd(&envFirstVars, estrdup(optarg)); MFLAGS_append("-E", optarg); break; case 'e': checkEnvFirst = TRUE; MFLAGS_append("-e", NULL); break; case 'f': Lst_AtEnd(&makefiles, estrdup(optarg)); break; case 'i': ignoreErrors = TRUE; MFLAGS_append("-i", NULL); break; case 'j': { char *endptr; forceJobs = TRUE; maxJobs = strtol(optarg, &endptr, 10); if (maxJobs <= 0 || *endptr != '\0') { warnx("illegal number, -j argument -- %s", optarg); usage(); } MFLAGS_append("-j", optarg); break; } case 'k': keepgoing = TRUE; MFLAGS_append("-k", NULL); break; case 'm': Dir_AddDir(&sysIncPath, optarg); MFLAGS_append("-m", optarg); break; case 'n': noExecute = TRUE; MFLAGS_append("-n", NULL); break; case 'q': queryFlag = TRUE; /* Kind of nonsensical, wot? */ MFLAGS_append("-q", NULL); break; case 'r': noBuiltins = TRUE; MFLAGS_append("-r", NULL); break; case 's': beSilent = TRUE; MFLAGS_append("-s", NULL); break; case 't': touchFlag = TRUE; MFLAGS_append("-t", NULL); break; case 'v': beVerbose = TRUE; MFLAGS_append("-v", NULL); break; default: case '?': usage(); } } oldVars = TRUE; /* * See if the rest of the arguments are variable assignments and * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ for (argv += optind, argc -= optind; *argv; ++argv, --argc) if (Parse_IsVar(*argv)) { char *ptr = Var_Quote(*argv); Var_Append(MAKEFLAGS, ptr, VAR_GLOBAL); free(ptr); Parse_DoVar(*argv, VAR_CMD); } else { if (!**argv) Punt("illegal (null) argument."); if (**argv == '-') { if ((*argv)[1]) optind = 0; /* -flag... */ else optind = 1; /* - */ goto rearg; } Lst_AtEnd(&create, estrdup(*argv)); } } /*- * Main_ParseArgLine -- * Used by the parse module when a .MFLAGS or .MAKEFLAGS target * is encountered and by main() when reading the .MAKEFLAGS envariable. * Takes a line of arguments and breaks it into its * component words and passes those words and the number of them to the * MainParseArgs function. * The line should have all its leading whitespace removed. * * Results: * None * * Side Effects: * Only those that come from the various arguments. */ void Main_ParseArgLine(char *line) { char **argv; /* Manufactured argument vector */ int argc; /* Number of arguments in argv */ if (line == NULL) return; for (; *line == ' '; ++line) continue; if (!*line) return; argv = brk_string(line, &argc, TRUE); MainParseArgs(argc, argv); } char * chdir_verify_path(char *path, char *obpath) { struct stat sb; if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { if (chdir(path) == -1 || getcwd(obpath, MAXPATHLEN) == NULL) { warn("warning: %s", path); return (0); } return (obpath); } return (0); } static void catch_child(int sig __unused) { } /* * In lieu of a good way to prevent every possible looping in * make(1), stop there from being more than MKLVL_MAXVAL processes forked * by make(1), to prevent a forkbomb from happening, in a dumb and * mechanical way. */ static void check_make_level(void) { #ifdef WANT_ENV_MKLVL char *value = getenv(MKLVL_ENVVAR); int level = (value == NULL) ? 0 : atoi(value); if (level < 0) { errc(2, EAGAIN, "Invalid value for recursion level (%d).", level); } else if (level > MKLVL_MAXVAL) { errc(2, EAGAIN, "Max recursion level (%d) exceeded.", MKLVL_MAXVAL); } else { char new_value[32]; sprintf(new_value, "%d", level + 1); setenv(MKLVL_ENVVAR, new_value, 1); } #endif /* WANT_ENV_MKLVL */ } /*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the .MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { Boolean outOfDate = TRUE; /* FALSE if all targets up to date */ struct stat sa; char *p, *p1, *path, *pathp; char mdpath[MAXPATHLEN]; char obpath[MAXPATHLEN]; char cdpath[MAXPATHLEN]; char *machine = getenv("MACHINE"); char *machine_arch = getenv("MACHINE_ARCH"); char *machine_cpu = getenv("MACHINE_CPU"); char *cp = NULL, *start; /* avoid faults on read-only strings */ static char syspath[] = _PATH_DEFSYSPATH; { /* * Catch SIGCHLD so that we get kicked out of select() when we * need to look at a child. This is only known to matter for the * -j case (perhaps without -P). * * XXX this is intentionally misplaced. */ struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; sa.sa_handler = catch_child; sigaction(SIGCHLD, &sa, NULL); } check_make_level(); #if DEFSHELL == 2 /* * Turn off ENV to make ksh happier. */ unsetenv("ENV"); #endif #ifdef RLIMIT_NOFILE /* * get rid of resource limit on file descriptors */ { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); } } #endif /* * PC-98 kernel sets the `i386' string to the utsname.machine and * it cannot be distinguished from IBM-PC by uname(3). Therefore, * we check machine.ispc98 and adjust the machine variable before * using usname(3) below. * NOTE: machdep.ispc98 was defined on 1998/8/31. At that time, * __FreeBSD_version was defined as 300003. So, this check can * safely be done with any kernel with version > 300003. */ if (!machine) { int ispc98; size_t len; len = sizeof(ispc98); if (!sysctlbyname("machdep.ispc98", &ispc98, &len, NULL, 0)) { if (ispc98) machine = "pc98"; } } /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that while MACHINE is decided at run-time, * MACHINE_ARCH is always known at compile time. */ if (!machine) { #ifndef MACHINE struct utsname utsname; if (uname(&utsname) == -1) err(2, "uname"); machine = utsname.machine; #else machine = MACHINE; #endif } if (!machine_arch) { #ifndef MACHINE_ARCH machine_arch = "unknown"; #else machine_arch = MACHINE_ARCH; #endif } /* * Set machine_cpu to the minumum supported CPU revision based * on the target architecture, if not already set. */ if (!machine_cpu) { if (!strcmp(machine_arch, "i386")) machine_cpu = "i386"; else if (!strcmp(machine_arch, "alpha")) machine_cpu = "ev4"; else machine_cpu = "unknown"; } expandVars = TRUE; beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ usePipes = TRUE; /* Catch child output in pipes */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; maxJobs = DEFMAXJOBS; forceJobs = FALSE; /* No -j flag */ compatMake = FALSE; /* No compat mode */ /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ Dir_Init(); /* Initialize directory structures so -I flags * can be processed correctly */ Parse_Init(); /* Need to initialize the paths of #include * directories */ Var_Init(); /* As well as the lists of variables for * parsing arguments */ str_init(); /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Var_Set("MAKE", argv[0], VAR_GLOBAL); Var_Set(MAKEFLAGS, "", VAR_GLOBAL); Var_Set("MFLAGS", "", VAR_GLOBAL); Var_Set("MACHINE", machine, VAR_GLOBAL); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL); Var_Set("MACHINE_CPU", machine_cpu, VAR_GLOBAL); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL); #endif /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ Main_ParseArgLine(getenv("MAKEFLAGS")); MainParseArgs(argc, argv); /* * Find where we are... * All this code is so that we know where we are when we start up * on a different machine with pmake. */ curdir = cdpath; if (getcwd(curdir, MAXPATHLEN) == NULL) err(2, NULL); if (stat(curdir, &sa) == -1) err(2, "%s", curdir); /* * The object directory location is determined using the * following order of preference: * * 1. MAKEOBJDIRPREFIX`cwd` * 2. MAKEOBJDIR * 3. _PATH_OBJDIR.${MACHINE} * 4. _PATH_OBJDIR * 5. _PATH_OBJDIRPREFIX`cwd` * * If one of the first two fails, use the current directory. * If the remaining three all fail, use the current directory. * * Once things are initted, * have to add the original directory to the search path, * and modify the paths for the Makefiles apropriately. The * current directory is also placed as a variable for make scripts. */ if (!(pathp = getenv("MAKEOBJDIRPREFIX"))) { if (!(path = getenv("MAKEOBJDIR"))) { path = _PATH_OBJDIR; pathp = _PATH_OBJDIRPREFIX; snprintf(mdpath, MAXPATHLEN, "%s.%s", path, machine); if (!(objdir = chdir_verify_path(mdpath, obpath))) if (!(objdir=chdir_verify_path(path, obpath))) { snprintf(mdpath, MAXPATHLEN, "%s%s", pathp, curdir); if (!(objdir=chdir_verify_path(mdpath, obpath))) objdir = curdir; } } else if (!(objdir = chdir_verify_path(path, obpath))) objdir = curdir; } else { snprintf(mdpath, MAXPATHLEN, "%s%s", pathp, curdir); if (!(objdir = chdir_verify_path(mdpath, obpath))) objdir = curdir; } Dir_InitDot(); /* Initialize the "." directory */ if (objdir != curdir) Dir_AddDir(&dirSearchPath, curdir); Var_Set(".CURDIR", curdir, VAR_GLOBAL); Var_Set(".OBJDIR", objdir, VAR_GLOBAL); if (getenv("MAKE_JOBS_FIFO") != NULL) forceJobs = TRUE; /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ if (!compatMake && !forceJobs) compatMake = TRUE; /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); DEFAULT = NULL; time(&now); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(&create)) { LstNode *ln; for (ln = Lst_First(&create); ln != NULL; ln = Lst_Succ(ln)) { char *name = Lst_Datum(ln); Var_Append(".TARGETS", name, VAR_GLOBAL); } } else Var_Set(".TARGETS", "", VAR_GLOBAL); /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (Lst_IsEmpty(&sysIncPath)) { for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; if (*cp == '\0') { Dir_AddDir(&sysIncPath, start); } else { *cp++ = '\0'; Dir_AddDir(&sysIncPath, start); } } } /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != (char *) NULL), or the default * Makefile and makefile, in that order, if it wasn't. */ if (!noBuiltins) { /* Path of sys.mk */ Lst sysMkPath = Lst_Initializer(sysMkPath); LstNode *ln; Dir_Expand(_PATH_DEFSYSMK, &sysIncPath, &sysMkPath); if (Lst_IsEmpty(&sysMkPath)) Fatal("make: no system rules (%s).", _PATH_DEFSYSMK); ln = Lst_Find(&sysMkPath, NULL, ReadMakefile); if (ln != NULL) Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); Lst_Destroy(&sysMkPath, free); } if (!Lst_IsEmpty(&makefiles)) { LstNode *ln; ln = Lst_Find(&makefiles, NULL, ReadMakefile); if (ln != NULL) Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); } else if (!ReadMakefile("BSDmakefile", NULL)) if (!ReadMakefile("makefile", NULL)) ReadMakefile("Makefile", NULL); ReadMakefile(".depend", NULL); /* Install all the flags into the MAKE envariable. */ if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1)) != NULL) && *p) setenv("MAKEFLAGS", p, 1); free(p1); /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * ::... */ if (Var_Exists("VPATH", VAR_CMD)) { char *vpath, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++) continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ Dir_AddDir(&dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ if (!Lst_IsEmpty(&variables)) { LstNode *ln; for (ln = Lst_First(&variables); ln != NULL; ln = Lst_Succ(ln)) { char *value; if (expandVars) { p1 = emalloc(strlen(Lst_Datum(ln)) + 1 + 3); /* This sprintf is safe, because of the malloc above */ sprintf(p1, "${%s}", (char *)Lst_Datum(ln)); value = Var_Subst(NULL, p1, VAR_GLOBAL, FALSE); } else { value = Var_Value(Lst_Datum(ln), VAR_GLOBAL, &p1); } printf("%s\n", value ? value : ""); if (p1) free(p1); } } else { /* * Have now read the entire graph and need to make a list of targets * to create. If none was given on the command line, we consult the * parsing module to find the main target(s) to create. */ Lst targs = Lst_Initializer(targs); if (Lst_IsEmpty(&create)) Parse_MainName(&targs); else Targ_FindList(&targs, &create, TARG_CREATE); if (!compatMake) { /* * Initialize job module before traversing the graph, now that * any .BEGIN and .END targets have been read. This is done * only if the -q flag wasn't given (to prevent the .BEGIN from * being executed should it exist). */ if (!queryFlag) { Job_Init(maxJobs); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(&targs); } else { /* * Compat_Init will take care of creating all the targets as * well as initializing the module. */ Compat_Run(&targs); outOfDate = 0; } Lst_Destroy(&targs, NOFREE); } Lst_Destroy(&variables, free); Lst_Destroy(&makefiles, free); Lst_Destroy(&create, free); /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); - Suff_End(); - Targ_End(); - Arch_End(); - str_end(); - Var_End(); - Parse_End(); - Dir_End(); - if (queryFlag && outOfDate) return (1); else return (0); } /*- * ReadMakefile -- * Open and parse the given makefile. * * Results: * TRUE if ok. FALSE if couldn't open file. * * Side Effects: * lots */ static Boolean ReadMakefile(const void *p, const void *q __unused) { char *fname; /* makefile to read */ FILE *stream; char *name, path[MAXPATHLEN]; char *MAKEFILE; int setMAKEFILE; /* XXX - remove this once constification is done */ fname = estrdup(p); if (!strcmp(fname, "-")) { Parse_File("(stdin)", stdin); Var_Set("MAKEFILE", "", VAR_GLOBAL); } else { setMAKEFILE = strcmp(fname, ".depend"); /* if we've chdir'd, rebuild the path name */ if (curdir != objdir && *fname != '/') { snprintf(path, MAXPATHLEN, "%s/%s", curdir, fname); /* * XXX The realpath stuff breaks relative includes * XXX in some cases. The problem likely is in * XXX parse.c where it does special things in * XXX ParseDoInclude if the file is relateive * XXX or absolute and not a system file. There * XXX it assumes that if the current file that's * XXX being included is absolute, that any files * XXX that it includes shouldn't do the -I path * XXX stuff, which is inconsistant with historical * XXX behavior. However, I can't pentrate the mists * XXX further, so I'm putting this workaround in * XXX here until such time as the underlying bug * XXX can be fixed. */ #if THIS_BREAKS_THINGS if (realpath(path, path) != NULL && (stream = fopen(path, "r")) != NULL) { MAKEFILE = fname; fname = path; goto found; } } else if (realpath(fname, path) != NULL) { MAKEFILE = fname; fname = path; if ((stream = fopen(fname, "r")) != NULL) goto found; } #else if ((stream = fopen(path, "r")) != NULL) { MAKEFILE = fname; fname = path; goto found; } } else { MAKEFILE = fname; if ((stream = fopen(fname, "r")) != NULL) goto found; } #endif /* look in -I and system include directories. */ name = Dir_FindFile(fname, &parseIncPath); if (!name) name = Dir_FindFile(fname, &sysIncPath); if (!name || !(stream = fopen(name, "r"))) return (FALSE); MAKEFILE = fname = name; /* * set the MAKEFILE variable desired by System V fans -- the * placement of the setting here means it gets set to the last * makefile specified, as it is set by SysV make. */ found: if (setMAKEFILE) Var_Set("MAKEFILE", MAKEFILE, VAR_GLOBAL); Parse_File(fname, stream); fclose(stream); } return (TRUE); } /*- * Cmd_Exec -- * Execute the command in cmd, and return the output of that command * in a string. * * Results: * A string containing the output of the command, or the empty string * If error is not NULL, it contains the reason for the command failure * * Side Effects: * The string must be freed by the caller. */ char * Cmd_Exec(char *cmd, char **error) { char *args[4]; /* Args for invoking the shell */ int fds[2]; /* Pipe streams */ int cpid; /* Child PID */ int pid; /* PID from wait() */ char *res; /* result */ int status; /* command exit status */ Buffer buf; /* buffer to store the result */ char *cp; size_t blen; ssize_t rcnt; *error = NULL; if (shellPath == NULL) Shell_Init(); /* * Set up arguments for shell */ args[0] = shellName; args[1] = "-c"; args[2] = cmd; args[3] = NULL; /* * Open a pipe for fetching its output */ if (pipe(fds) == -1) { *error = "Couldn't create pipe for \"%s\""; goto bad; } /* * Fork */ switch (cpid = vfork()) { case 0: /* * Close input side of pipe */ close(fds[0]); /* * Duplicate the output stream to the shell's output, then * shut the extra thing down. Note we don't fetch the error * stream...why not? Why? */ dup2(fds[1], 1); close(fds[1]); execv(shellPath, args); _exit(1); /*NOTREACHED*/ case -1: *error = "Couldn't exec \"%s\""; goto bad; default: /* * No need for the writing half */ close(fds[1]); buf = Buf_Init(MAKE_BSIZE); do { char result[BUFSIZ]; rcnt = read(fds[0], result, sizeof(result)); if (rcnt != -1) Buf_AddBytes(buf, (size_t)rcnt, (Byte *)result); } while (rcnt > 0 || (rcnt == -1 && errno == EINTR)); /* * Close the input side of the pipe. */ close(fds[0]); /* * Wait for the process to exit. */ while (((pid = wait(&status)) != cpid) && (pid >= 0)) continue; if (rcnt == -1) *error = "Error reading shell's output for \"%s\""; res = (char *)Buf_GetAll(buf, &blen); Buf_Destroy(buf, FALSE); if (status) *error = "\"%s\" returned non-zero status"; /* * Null-terminate the result, convert newlines to spaces and * install it in the variable. */ res[blen] = '\0'; cp = &res[blen] - 1; if (*cp == '\n') { /* * A final newline is just stripped */ *cp-- = '\0'; } while (cp >= res) { if (*cp == '\n') { *cp = ' '; } cp--; } break; } return (res); bad: res = emalloc(1); *res = '\0'; return (res); } /* * usage -- * exit with usage message */ static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n", "usage: make [-BPSXeiknqrstv] [-C directory] [-D variable] [-d flags]", " [-E variable] [-f makefile] [-I directory] [-j max_jobs]", " [-m directory] [-V variable] [variable=value] [target ...]"); exit(2); } diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h index aa73f6a4c81b..cc64313622c4 100644 --- a/usr.bin/make/nonints.h +++ b/usr.bin/make/nonints.h @@ -1,146 +1,140 @@ /*- * 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. * * @(#)nonints.h 8.4 (Berkeley) 4/28/95 * $FreeBSD$ */ /* arch.c */ ReturnStatus Arch_ParseArchive(char **, Lst *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); int Arch_MTime(GNode *); int Arch_MemMTime(GNode *); void Arch_FindLib(GNode *, Lst *); Boolean Arch_LibOODate(GNode *); void Arch_Init(void); -void Arch_End(void); /* compat.c */ void Compat_Run(Lst *); int Compat_RunCommand(void *, void *); /* cond.c */ int Cond_Eval(char *); void Cond_End(void); /* for.c */ int For_Eval(char *); void For_Run(int); /* main.c */ void Main_ParseArgLine(char *); char *Cmd_Exec(char *, char **); void Debug(const char *, ...); void Error(const char *, ...); void Fatal(const char *, ...); void Punt(const char *, ...); void DieHorribly(void); int PrintAddr(void *, void *); void Finish(int); char *estrdup(const char *); void *emalloc(size_t); void *erealloc(void *, size_t); void enomem(void); int eunlink(const char *); /* parse.c */ void Parse_Error(int, const char *, ...); Boolean Parse_AnyExport(void); Boolean Parse_IsVar(char *); void Parse_DoVar(char *, GNode *); void Parse_AddIncludeDir(char *); void Parse_File(char *, FILE *); void Parse_Init(void); -void Parse_End(void); void Parse_FromString(char *, int); void Parse_MainName(Lst *); /* str.c */ void str_init(void); -void str_end(void); char *str_concat(const char *, const char *, int); char **brk_string(char *, int *, Boolean); int Str_Match(const char *, const char *); const char *Str_SYSVMatch(const char *, const char *, int *); void Str_SYSVSubst(Buffer, const char *, const char *, int); /* suff.c */ void Suff_ClearSuffixes(void); Boolean Suff_IsTransform(char *); GNode *Suff_AddTransform(char *); int Suff_EndTransform(void *, void *); void Suff_AddSuffix(char *); Lst *Suff_GetPath(char *); void Suff_DoPaths(void); void Suff_AddInclude(char *); void Suff_AddLib(char *); void Suff_FindDeps(GNode *); void Suff_SetNull(char *); void Suff_Init(void); -void Suff_End(void); void Suff_PrintAll(void); /* targ.c */ void Targ_Init(void); -void Targ_End(void); GNode *Targ_NewGN(char *); GNode *Targ_FindNode(char *, int); void Targ_FindList(Lst *, Lst *, int); Boolean Targ_Ignore(GNode *); Boolean Targ_Silent(GNode *); Boolean Targ_Precious(GNode *); void Targ_SetMain(GNode *); int Targ_PrintCmd(void *, void *); char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); /* var.c */ void Var_Delete(char *, GNode *); void Var_Set(char *, char *, GNode *); void Var_Append(char *, char *, GNode *); Boolean Var_Exists(char *, GNode *); char *Var_Value(char *, GNode *, char **); char *Var_Quote(const char *); char *Var_Parse(char *, GNode *, Boolean, size_t *, Boolean *); char *Var_Subst(char *, char *, GNode *, Boolean); char *Var_GetTail(char *); char *Var_GetHead(char *); void Var_Init(void); -void Var_End(void); void Var_Dump(GNode *); diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c index 4aa33131e650..b9c8c05d8dc2 100644 --- a/usr.bin/make/parse.c +++ b/usr.bin/make/parse.c @@ -1,2563 +1,2550 @@ /* * 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. * * @(#)parse.c 8.3 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * parse.c -- * Functions to parse a makefile. * * One function, Parse_Init, must be called before any functions * in this module are used. After that, the function Parse_File is the * main entry point and controls most of the other functions in this * module. * * Most important structures are kept in Lsts. Directories for * the #include "..." function are kept in the 'parseIncPath' Lst, while * those for the #include <...> are kept in the 'sysIncPath' Lst. The * targets currently being defined are kept in the 'targets' Lst. * * The variables 'curFile.fname' and 'curFile.lineno' are used to track * the name of the current file and the line number in that file so that * error messages can be more meaningful. * * Interface: * Parse_Init Initialization function which must be * called before anything else in this module * is used. * - * Parse_End Cleanup the module - * * Parse_File Function used to parse a makefile. It must * be given the name of the file, which should * already have been opened, and a function * to call to read a character from the file. * * Parse_IsVar Returns TRUE if the given line is a * variable assignment. Used by MainParseArgs * to determine if an argument is a target * or a variable assignment. Used internally * for pretty much the same thing... * * Parse_Error Function called when an error occurs in * parsing. Used by the variable and * conditional modules. * Parse_MainName Returns a Lst of the main target to create. */ #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "buf.h" #include "pathnames.h" /* * These values are returned by ParseEOF to tell Parse_File whether to * CONTINUE parsing, i.e. it had only reached the end of an include file, * or if it's DONE. */ #define CONTINUE 1 #define DONE 0 /* targets we're working on */ static Lst targets = Lst_Initializer(targets); static Boolean inLine; /* true if currently in a dependency * line or its commands */ static int fatals = 0; static GNode *mainNode; /* The main target to create. This is the * first target on the first dependency * line in the first makefile */ IFile curFile; /* current makefile */ /* stack of IFiles generated by * #includes */ static Lst includes = Lst_Initializer(includes); /* list of directories for "..." includes */ Lst parseIncPath = Lst_Initializer(parseIncPath); /* list of directories for <...> includes */ Lst sysIncPath = Lst_Initializer(sysIncPath); /*- * specType contains the SPECial TYPE of the current target. It is * Not if the target is unspecial. If it *is* special, however, the children * are linked as children of the parent but not vice versa. This variable is * set in ParseDoDependency */ typedef enum { Begin, /* .BEGIN */ Default, /* .DEFAULT */ End, /* .END */ Ignore, /* .IGNORE */ Includes, /* .INCLUDES */ Interrupt, /* .INTERRUPT */ Libs, /* .LIBS */ MFlags, /* .MFLAGS or .MAKEFLAGS */ Main, /* .MAIN and we don't have anything user-specified to * make */ NoExport, /* .NOEXPORT */ Not, /* Not special */ NotParallel, /* .NOTPARALELL */ Null, /* .NULL */ Order, /* .ORDER */ Parallel, /* .PARALLEL */ ExPath, /* .PATH */ Phony, /* .PHONY */ Posix, /* .POSIX */ Precious, /* .PRECIOUS */ ExShell, /* .SHELL */ Silent, /* .SILENT */ SingleShell, /* .SINGLESHELL */ Suffixes, /* .SUFFIXES */ Wait, /* .WAIT */ Attribute /* Generic attribute */ } ParseSpecial; static ParseSpecial specType; static int waiting; /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ static GNode *predecessor; /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial * type of the keyword ("Not" if the keyword isn't special as a target) while * the 'op' field is the operator to apply to the list of targets if the * keyword is used as a source ("0" if the keyword isn't special as a source) */ static struct { char *name; /* Name of keyword */ ParseSpecial spec; /* Type when used as a target */ int op; /* Operator when used as a source */ } parseKeywords[] = { { ".BEGIN", Begin, 0 }, { ".DEFAULT", Default, 0 }, { ".END", End, 0 }, { ".EXEC", Attribute, OP_EXEC }, { ".IGNORE", Ignore, OP_IGNORE }, { ".INCLUDES", Includes, 0 }, { ".INTERRUPT", Interrupt, 0 }, { ".INVISIBLE", Attribute, OP_INVISIBLE }, { ".JOIN", Attribute, OP_JOIN }, { ".LIBS", Libs, 0 }, { ".MAIN", Main, 0 }, { ".MAKE", Attribute, OP_MAKE }, { ".MAKEFLAGS", MFlags, 0 }, { ".MFLAGS", MFlags, 0 }, { ".NOTMAIN", Attribute, OP_NOTMAIN }, { ".NOTPARALLEL", NotParallel, 0 }, { ".NO_PARALLEL", NotParallel, 0 }, { ".NULL", Null, 0 }, { ".OPTIONAL", Attribute, OP_OPTIONAL }, { ".ORDER", Order, 0 }, { ".PARALLEL", Parallel, 0 }, { ".PATH", ExPath, 0 }, { ".PHONY", Phony, OP_PHONY }, { ".POSIX", Posix, 0 }, { ".PRECIOUS", Precious, OP_PRECIOUS }, { ".RECURSIVE", Attribute, OP_MAKE }, { ".SHELL", ExShell, 0 }, { ".SILENT", Silent, OP_SILENT }, { ".SINGLESHELL", SingleShell, 0 }, { ".SUFFIXES", Suffixes, 0 }, { ".USE", Attribute, OP_USE }, { ".WAIT", Wait, 0 }, }; static int ParseFindKeyword(char *); static int ParseLinkSrc(void *, void *); static int ParseDoOp(void *, void *); static int ParseAddDep(void *, void *); static void ParseDoSrc(int, char *, Lst *); static int ParseFindMain(void *, void *); static int ParseAddDir(void *, void *); static int ParseClearPath(void *, void *); static void ParseDoDependency(char *); static int ParseAddCmd(void *, void *); static int ParseReadc(void); static void ParseUnreadc(int); static void ParseHasCommands(void *); static void ParseDoInclude(char *); static void ParseDoError(char *); static void ParseDoWarning(char *); #ifdef SYSVINCLUDE static void ParseTraditionalInclude(char *); #endif static int ParseEOF(int); static char *ParseReadLine(void); static char *ParseSkipLine(int, int); static void ParseFinishLine(void); /*- *---------------------------------------------------------------------- * ParseFindKeyword -- * Look in the table of keywords for one matching the given string. * * Results: * The index of the keyword, or -1 if it isn't there. * * Side Effects: * None *---------------------------------------------------------------------- */ static int ParseFindKeyword(char *str) { int start, end, cur; int diff; start = 0; end = (sizeof(parseKeywords) / sizeof(parseKeywords[0])) - 1; do { cur = start + ((end - start) / 2); diff = strcmp(str, parseKeywords[cur].name); if (diff == 0) { return (cur); } else if (diff < 0) { end = cur - 1; } else { start = cur + 1; } } while (start <= end); return (-1); } /*- * Parse_Error -- * Error message abort function for parsing. Prints out the context * of the error (line number and file) as well as the message with * two optional arguments. * * Results: * None * * Side Effects: * "fatals" is incremented if the level is PARSE_FATAL. */ /* VARARGS */ void Parse_Error(int type, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\"%s\", line %d: ", curFile.fname, curFile.lineno); if (type == PARSE_WARNING) fprintf(stderr, "warning: "); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); fflush(stderr); if (type == PARSE_FATAL) fatals += 1; } /*- *--------------------------------------------------------------------- * ParseLinkSrc -- * Link the parent node to its new child. Used in a Lst_ForEach by * ParseDoDependency. If the specType isn't 'Not', the parent * isn't linked as a parent of the child. * * Results: * Always = 0 * * Side Effects: * New elements are added to the parents list of cgn and the * children list of cgn. the unmade field of pgn is updated * to reflect the additional child. *--------------------------------------------------------------------- */ static int ParseLinkSrc(void *pgnp, void *cgnp) { GNode *pgn = pgnp; GNode *cgn = cgnp; if (Lst_Member(&pgn->children, cgn) == NULL) { Lst_AtEnd(&pgn->children, cgn); if (specType == Not) { Lst_AtEnd(&cgn->parents, pgn); } pgn->unmade += 1; } return (0); } /*- *--------------------------------------------------------------------- * ParseDoOp -- * Apply the parsed operator to the given target node. Used in a * Lst_ForEach call by ParseDoDependency once all targets have * been found and their operator parsed. If the previous and new * operators are incompatible, a major error is taken. * * Results: * Always 0 * * Side Effects: * The type field of the node is altered to reflect any new bits in * the op. *--------------------------------------------------------------------- */ static int ParseDoOp(void *gnp, void *opp) { GNode *gn = gnp; int op = *(int *)opp; /* * If the dependency mask of the operator and the node don't match and * the node has actually had an operator applied to it before, and * the operator actually has some dependency information in it, complain. */ if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && !OP_NOP(gn->type) && !OP_NOP(op)) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); return (1); } if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { /* * If the node was the object of a :: operator, we need to create a * new instance of it for the children and commands on this dependency * line. The new instance is placed on the 'cohorts' list of the * initial one (note the initial one is not on its own cohorts list) * and the new instance is linked to all parents of the initial * instance. */ GNode *cohort; LstNode *ln; cohort = Targ_NewGN(gn->name); /* * Duplicate links to parents so graph traversal is simple. Perhaps * some type bits should be duplicated? * * Make the cohort invisible as well to avoid duplicating it into * other variables. True, parents of this target won't tend to do * anything with their local variables, but better safe than * sorry. */ Lst_ForEach(&gn->parents, ParseLinkSrc, cohort); cohort->type = OP_DOUBLEDEP|OP_INVISIBLE; Lst_AtEnd(&gn->cohorts, cohort); /* * Replace the node in the targets list with the new copy */ ln = Lst_Member(&targets, gn); Lst_Replace(ln, cohort); gn = cohort; } /* * We don't want to nuke any previous flags (whatever they were) so we * just OR the new operator into the old */ gn->type |= op; return (0); } /*- *--------------------------------------------------------------------- * ParseAddDep -- * Check if the pair of GNodes given needs to be synchronized. * This has to be when two nodes are on different sides of a * .WAIT directive. * * Results: * Returns 1 if the two targets need to be ordered, 0 otherwise. * If it returns 1, the search can stop * * Side Effects: * A dependency can be added between the two nodes. * *--------------------------------------------------------------------- */ static int ParseAddDep(void *pp, void *sp) { GNode *p = pp; GNode *s = sp; if (p->order < s->order) { /* * XXX: This can cause loops, and loops can cause unmade targets, * but checking is tedious, and the debugging output can show the * problem */ Lst_AtEnd(&p->successors, s); Lst_AtEnd(&s->preds, p); return (0); } else return (1); } /*- *--------------------------------------------------------------------- * ParseDoSrc -- * Given the name of a source, figure out if it is an attribute * and apply it to the targets if it is. Else decide if there is * some attribute which should be applied *to* the source because * of some special target and apply it if so. Otherwise, make the * source be a child of the targets in the list 'targets' * * Results: * None * * Side Effects: * Operator bits may be added to the list of targets or to the source. * The targets may have a new source added to their lists of children. *--------------------------------------------------------------------- */ static void ParseDoSrc(int tOp, char *src, Lst *allsrc) { GNode *gn = NULL; if (*src == '.' && isupper ((unsigned char) src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { int op = parseKeywords[keywd].op; if (op != 0) { Lst_ForEach(&targets, ParseDoOp, &op); return; } if (parseKeywords[keywd].spec == Wait) { waiting++; return; } } } switch (specType) { case Main: /* * If we have noted the existence of a .MAIN, it means we need * to add the sources of said target to the list of things * to create. The string 'src' is likely to be free, so we * must make a new copy of it. Note that this will only be * invoked if the user didn't specify a target on the command * line. This is to allow #ifmake's to succeed, or something... */ Lst_AtEnd(&create, estrdup(src)); /* * Add the name to the .TARGETS variable as well, so the user cna * employ that, if desired. */ Var_Append(".TARGETS", src, VAR_GLOBAL); return; case Order: /* * Create proper predecessor/successor links between the previous * source and the current one. */ gn = Targ_FindNode(src, TARG_CREATE); if (predecessor != NULL) { Lst_AtEnd(&predecessor->successors, gn); Lst_AtEnd(&gn->preds, predecessor); } /* * The current source now becomes the predecessor for the next one. */ predecessor = gn; break; default: /* * If the source is not an attribute, we need to find/create * a node for it. After that we can apply any operator to it * from a special target or link it to its parents, as * appropriate. * * In the case of a source that was the object of a :: operator, * the attribute is applied to all of its instances (as kept in * the 'cohorts' list of the node) or all the cohorts are linked * to all the targets. */ gn = Targ_FindNode(src, TARG_CREATE); if (tOp) { gn->type |= tOp; } else { Lst_ForEach(&targets, ParseLinkSrc, gn); } if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) { GNode *cohort; LstNode *ln; for (ln = Lst_First(&gn->cohorts); ln != NULL; ln = Lst_Succ(ln)) { cohort = Lst_Datum(ln); if (tOp) { cohort->type |= tOp; } else { Lst_ForEach(&targets, ParseLinkSrc, cohort); } } } break; } gn->order = waiting; Lst_AtEnd(allsrc, gn); if (waiting) { Lst_ForEach(allsrc, ParseAddDep, gn); } } /*- *----------------------------------------------------------------------- * ParseFindMain -- * Find a real target in the list and set it to be the main one. * Called by ParseDoDependency when a main target hasn't been found * yet. * * Results: * 0 if main not found yet, 1 if it is. * * Side Effects: * mainNode is changed and Targ_SetMain is called. * *----------------------------------------------------------------------- */ static int ParseFindMain(void *gnp, void *dummy __unused) { GNode *gn = gnp; if ((gn->type & (OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM)) == 0) { mainNode = gn; Targ_SetMain(gn); return (1); } else { return (0); } } /*- *----------------------------------------------------------------------- * ParseAddDir -- * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_AddDir. * *----------------------------------------------------------------------- */ static int ParseAddDir(void *path, void *name) { Dir_AddDir(path, name); return(0); } /*- *----------------------------------------------------------------------- * ParseClearPath -- * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_ClearPath * *----------------------------------------------------------------------- */ static int ParseClearPath(void *path, void *dummy __unused) { Dir_ClearPath(path); return (0); } /*- *--------------------------------------------------------------------- * ParseDoDependency -- * Parse the dependency line in line. * * Results: * None * * Side Effects: * The nodes of the sources are linked as children to the nodes of the * targets. Some nodes may be created. * * We parse a dependency line by first extracting words from the line and * finding nodes in the list of all targets with that name. This is done * until a character is encountered which is an operator character. Currently * these are only ! and :. At this point the operator is parsed and the * pointer into the line advanced until the first source is encountered. * The parsed operator is applied to each node in the 'targets' list, * which is where the nodes found for the targets are kept, by means of * the ParseDoOp function. * The sources are read in much the same way as the targets were except * that now they are expanded using the wildcarding scheme of the C-Shell * and all instances of the resulting words in the list of all targets * are found. Each of the resulting nodes is then linked to each of the * targets as one of its children. * Certain targets are handled specially. These are the ones detailed * by the specType variable. * The storing of transformation rules is also taken care of here. * A target is recognized as a transformation rule by calling * Suff_IsTransform. If it is a transformation rule, its node is gotten * from the suffix module via Suff_AddTransform rather than the standard * Targ_FindNode in the target module. *--------------------------------------------------------------------- */ static void ParseDoDependency (char *line) { char *cp; /* our current position */ GNode *gn; /* a general purpose temporary node */ int op; /* the operator on the line */ char savec; /* a place to save a character */ Lst paths; /* Search paths to alter when parsing a list of .PATH targets */ int tOp; /* operator from special target */ tOp = 0; specType = Not; waiting = 0; Lst_Init(&paths); do { for (cp = line; *cp && !isspace((unsigned char)*cp) && *cp != '('; cp++) { if (*cp == '$') { /* * Must be a dynamic source (would have been expanded * otherwise), so call the Var module to parse the puppy * so we can safely advance beyond it...There should be * no errors in this, as they would have been discovered * in the initial Var_Subst and we wouldn't be here. */ size_t length; Boolean freeIt; char *result; result = Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); if (freeIt) { free(result); } cp += length - 1; } else if (*cp == '!' || *cp == ':') { /* * We don't want to end a word on ':' or '!' if there is a * better match later on in the string (greedy matching). * This allows the user to have targets like: * fie::fi:fo: fum * foo::bar: * where "fie::fi:fo" and "foo::bar" are the targets. In * real life this is used for perl5 library man pages where * "::" separates an object from its class. * Ie: "File::Spec::Unix". This behaviour is also consistent * with other versions of make. */ char *p = cp + 1; if (*cp == ':' && *p == ':') p++; /* Found the best match already. */ if (*p == '\0' || isspace(*p)) break; p += strcspn(p, "!:"); /* No better match later on... */ if (*p == '\0') break; } continue; } if (*cp == '(') { /* * Archives must be handled specially to make sure the OP_ARCHV * flag is set in their 'type' field, for one thing, and because * things like "archive(file1.o file2.o file3.o)" are permissible. * Arch_ParseArchive will set 'line' to be the first non-blank * after the archive-spec. It creates/finds nodes for the members * and places them on the given list, returning SUCCESS if all * went well and FAILURE if there was an error in the * specification. On error, line should remain untouched. */ if (Arch_ParseArchive(&line, &targets, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", line); return; } else { continue; } } savec = *cp; if (!*cp) { /* * Ending a dependency line without an operator is a Bozo * no-no. As a heuristic, this is also often triggered by * undetected conflicts from cvs/rcs merges. */ if ((strncmp(line, "<<<<<<", 6) == 0) || (strncmp(line, "======", 6) == 0) || (strncmp(line, ">>>>>>", 6) == 0)) Parse_Error(PARSE_FATAL, "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); else Parse_Error(PARSE_FATAL, "Need an operator"); return; } *cp = '\0'; /* * Have a word in line. See if it's a special target and set * specType to match it. */ if (*line == '.' && isupper((unsigned char)line[1])) { /* * See if the target is a special target that must have it * or its sources handled specially. */ int keywd = ParseFindKeyword(line); if (keywd != -1) { if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return; } specType = parseKeywords[keywd].spec; tOp = parseKeywords[keywd].op; /* * Certain special targets have special semantics: * .PATH Have to set the dirSearchPath * variable too * .MAIN Its sources are only used if * nothing has been specified to * create. * .DEFAULT Need to create a node to hang * commands on, but we don't want * it in the graph, nor do we want * it to be the Main Target, so we * create it, set OP_NOTMAIN and * add it to the list, setting * DEFAULT to the new node for * later use. We claim the node is * A transformation rule to make * life easier later, when we'll * use Make_HandleUse to actually * apply the .DEFAULT commands. * .PHONY The list of targets * .BEGIN * .END * .INTERRUPT Are not to be considered the * main target. * .NOTPARALLEL Make only one target at a time. * .SINGLESHELL Create a shell for each command. * .ORDER Must set initial predecessor to NULL */ switch (specType) { case ExPath: Lst_AtEnd(&paths, &dirSearchPath); break; case Main: if (!Lst_IsEmpty(&create)) { specType = Not; } break; case Begin: case End: case Interrupt: gn = Targ_FindNode(line, TARG_CREATE); gn->type |= OP_NOTMAIN; Lst_AtEnd(&targets, gn); break; case Default: gn = Targ_NewGN(".DEFAULT"); gn->type |= (OP_NOTMAIN|OP_TRANSFORM); Lst_AtEnd(&targets, gn); DEFAULT = gn; break; case NotParallel: { maxJobs = 1; break; } case SingleShell: compatMake = 1; break; case Order: predecessor = NULL; break; default: break; } } else if (strncmp(line, ".PATH", 5) == 0) { /* * .PATH has to be handled specially. * Call on the suffix module to give us a path to * modify. */ Lst *path; specType = ExPath; path = Suff_GetPath(&line[5]); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", &line[5]); return; } else Lst_AtEnd(&paths, path); } } /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ if ((specType == Not) && (*line != '\0')) { /* target names to be found and added to targets list */ Lst curTargs = Lst_Initializer(curTargs); if (Dir_HasWildcards(line)) { /* * Targets are to be sought only in the current directory, * so create an empty path for the thing. Note we need to * use Dir_Destroy in the destruction of the path as the * Dir module could have added a directory to the path... */ Lst emptyPath = Lst_Initializer(emptyPath); Dir_Expand(line, &emptyPath, &curTargs); Lst_Destroy(&emptyPath, Dir_Destroy); } else { /* * No wildcards, but we want to avoid code duplication, * so create a list with the word on it. */ Lst_AtEnd(&curTargs, line); } while (!Lst_IsEmpty(&curTargs)) { char *targName = Lst_DeQueue(&curTargs); if (!Suff_IsTransform (targName)) { gn = Targ_FindNode(targName, TARG_CREATE); } else { gn = Suff_AddTransform(targName); } Lst_AtEnd(&targets, gn); } } else if (specType == ExPath && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } *cp = savec; /* * If it is a special type and not .PATH, it's the only target we * allow on this line... */ if (specType != Not && specType != ExPath) { Boolean warn = FALSE; while ((*cp != '!') && (*cp != ':') && *cp) { if (*cp != ' ' && *cp != '\t') { warn = TRUE; } cp++; } if (warn) { Parse_Error(PARSE_WARNING, "Extra target ignored"); } } else { while (*cp && isspace((unsigned char)*cp)) { cp++; } } line = cp; } while ((*line != '!') && (*line != ':') && *line); if (!Lst_IsEmpty(&targets)) { switch (specType) { default: Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); break; case Default: case Begin: case End: case Interrupt: /* * These four create nodes on which to hang commands, so * targets shouldn't be empty... */ case Not: /* * Nothing special here -- targets can be empty if it wants. */ break; } } /* * Have now parsed all the target names. Must parse the operator next. The * result is left in op . */ if (*cp == '!') { op = OP_FORCE; } else if (*cp == ':') { if (cp[1] == ':') { op = OP_DOUBLEDEP; cp++; } else { op = OP_DEPENDS; } } else { Parse_Error(PARSE_FATAL, "Missing dependency operator"); return; } cp++; /* Advance beyond operator */ Lst_ForEach(&targets, ParseDoOp, &op); /* * Get to the first source */ while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; /* * Several special targets take different actions if present with no * sources: * a .SUFFIXES line with no sources clears out all old suffixes * a .PRECIOUS line makes all targets precious * a .IGNORE line ignores errors for all targets * a .SILENT line creates silence when making all targets * a .PATH removes all directories from the search path(s). */ if (!*line) { switch (specType) { case Suffixes: Suff_ClearSuffixes(); break; case Precious: allPrecious = TRUE; break; case Ignore: ignoreErrors = TRUE; break; case Silent: beSilent = TRUE; break; case ExPath: Lst_ForEach(&paths, ParseClearPath, NULL); break; case Posix: Var_Set("%POSIX", "1003.2", VAR_GLOBAL); break; default: break; } } else if (specType == MFlags) { /* * Call on functions in main.c to deal with these arguments and * set the initial character to a null-character so the loop to * get sources won't get anything */ Main_ParseArgLine(line); *line = '\0'; } else if (specType == ExShell) { if (Job_ParseShell(line) != SUCCESS) { Parse_Error(PARSE_FATAL, "improper shell specification"); return; } *line = '\0'; } else if ((specType == NotParallel) || (specType == SingleShell)) { *line = '\0'; } /* * NOW GO FOR THE SOURCES */ if ((specType == Suffixes) || (specType == ExPath) || (specType == Includes) || (specType == Libs) || (specType == Null)) { while (*line) { /* * If the target was one that doesn't take files as its sources * but takes something like suffixes, we take each * space-separated word on the line as a something and deal * with it accordingly. * * If the target was .SUFFIXES, we take each source as a * suffix and add it to the list of suffixes maintained by the * Suff module. * * If the target was a .PATH, we add the source as a directory * to search on the search path. * * If it was .INCLUDES, the source is taken to be the suffix of * files which will be #included and whose search path should * be present in the .INCLUDES variable. * * If it was .LIBS, the source is taken to be the suffix of * files which are considered libraries and whose search path * should be present in the .LIBS variable. * * If it was .NULL, the source is the suffix to use when a file * has no valid suffix. */ char savech; while (*cp && !isspace((unsigned char)*cp)) { cp++; } savech = *cp; *cp = '\0'; switch (specType) { case Suffixes: Suff_AddSuffix(line); break; case ExPath: Lst_ForEach(&paths, ParseAddDir, line); break; case Includes: Suff_AddInclude(line); break; case Libs: Suff_AddLib(line); break; case Null: Suff_SetNull(line); break; default: break; } *cp = savech; if (savech != '\0') { cp++; } while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; } Lst_Destroy(&paths, NOFREE); } else { Lst curSrcs = Lst_Initializer(curSrc); /* list of sources in order */ while (*line) { /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) * and handle them accordingly. */ while (*cp && !isspace((unsigned char)*cp)) { if ((*cp == '(') && (cp > line) && (cp[-1] != '$')) { /* * Only stop for a left parenthesis if it isn't at the * start of a word (that'll be for variable changes * later) and isn't preceded by a dollar sign (a dynamic * source). */ break; } else { cp++; } } if (*cp == '(') { GNode *gnp; /* list of archive source names after expansion */ Lst sources = Lst_Initializer(sources); if (Arch_ParseArchive(&line, &sources, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", line); return; } while (!Lst_IsEmpty(&sources)) { gnp = Lst_DeQueue(&sources); ParseDoSrc(tOp, gnp->name, &curSrcs); } cp = line; } else { if (*cp) { *cp = '\0'; cp += 1; } ParseDoSrc(tOp, line, &curSrcs); } while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; } Lst_Destroy(&curSrcs, NOFREE); } if (mainNode == NULL) { /* * If we have yet to decide on a main target to make, in the * absence of any user input, we want the first target on * the first dependency line that is actually a real target * (i.e. isn't a .USE or .EXEC rule) to be made. */ Lst_ForEach(&targets, ParseFindMain, NULL); } } /*- *--------------------------------------------------------------------- * Parse_IsVar -- * Return TRUE if the passed line is a variable assignment. A variable * assignment consists of a single word followed by optional whitespace * followed by either a += or an = operator. * This function is used both by the Parse_File function and main when * parsing the command-line arguments. * * Results: * TRUE if it is. FALSE if it ain't * * Side Effects: * none *--------------------------------------------------------------------- */ Boolean Parse_IsVar(char *line) { Boolean wasSpace = FALSE; /* set TRUE if found a space */ Boolean haveName = FALSE; /* Set TRUE if have a variable name */ int level = 0; #define ISEQOPERATOR(c) \ (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) /* * Skip to variable name */ for (; (*line == ' ') || (*line == '\t'); line++) continue; for (; *line != '=' || level != 0; line++) switch (*line) { case '\0': /* * end-of-line -- can't be a variable assignment. */ return (FALSE); case ' ': case '\t': /* * there can be as much white space as desired so long as there is * only one word before the operator */ wasSpace = TRUE; break; case '(': case '{': level++; break; case '}': case ')': level--; break; default: if (wasSpace && haveName) { if (ISEQOPERATOR(*line)) { /* * We must have a finished word */ if (level != 0) return (FALSE); /* * When an = operator [+?!:] is found, the next * character must be an = or it ain't a valid * assignment. */ if (line[1] == '=') return (haveName); #ifdef SUNSHCMD /* * This is a shell command */ if (strncmp(line, ":sh", 3) == 0) return (haveName); #endif } /* * This is the start of another word, so not assignment. */ return (FALSE); } else { haveName = TRUE; wasSpace = FALSE; } break; } return (haveName); } /*- *--------------------------------------------------------------------- * Parse_DoVar -- * Take the variable assignment in the passed line and do it in the * global context. * * Note: There is a lexical ambiguity with assignment modifier characters * in variable names. This routine interprets the character before the = * as a modifier. Therefore, an assignment like * C++=/usr/bin/CC * is interpreted as "C+ +=" instead of "C++ =". * * Results: * none * * Side Effects: * the variable structure of the given variable name is altered in the * global context. *--------------------------------------------------------------------- */ void Parse_DoVar(char *line, GNode *ctxt) { char *cp; /* pointer into line */ enum { VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL } type; /* Type of assignment */ char *opc; /* ptr to operator character to * null-terminate the variable name */ /* * Avoid clobbered variable warnings by forcing the compiler * to ``unregister'' variables */ #if __GNUC__ (void)&cp; (void)&line; #endif /* * Skip to variable name */ while ((*line == ' ') || (*line == '\t')) { line++; } /* * Skip to operator character, nulling out whitespace as we go */ for (cp = line + 1; *cp != '='; cp++) { if (isspace((unsigned char)*cp)) { *cp = '\0'; } } opc = cp - 1; /* operator is the previous character */ *cp++ = '\0'; /* nuke the = */ /* * Check operator type */ switch (*opc) { case '+': type = VAR_APPEND; *opc = '\0'; break; case '?': /* * If the variable already has a value, we don't do anything. */ *opc = '\0'; if (Var_Exists(line, ctxt)) { return; } else { type = VAR_NORMAL; } break; case ':': type = VAR_SUBST; *opc = '\0'; break; case '!': type = VAR_SHELL; *opc = '\0'; break; default: #ifdef SUNSHCMD while (*opc != ':') if (opc == line) break; else --opc; if (strncmp(opc, ":sh", 3) == 0) { type = VAR_SHELL; *opc = '\0'; break; } #endif type = VAR_NORMAL; break; } while (isspace((unsigned char)*cp)) { cp++; } if (type == VAR_APPEND) { Var_Append(line, cp, ctxt); } else if (type == VAR_SUBST) { /* * Allow variables in the old value to be undefined, but leave their * invocation alone -- this is done by forcing oldVars to be false. * XXX: This can cause recursive variables, but that's not hard to do, * and this allows someone to do something like * * CFLAGS = $(.INCLUDES) * CFLAGS := -I.. $(CFLAGS) * * And not get an error. */ Boolean oldOldVars = oldVars; oldVars = FALSE; /* * make sure that we set the variable the first time to nothing * so that it gets substituted! */ if (!Var_Exists(line, ctxt)) Var_Set(line, "", ctxt); cp = Var_Subst(NULL, cp, ctxt, FALSE); oldVars = oldOldVars; Var_Set(line, cp, ctxt); free(cp); } else if (type == VAR_SHELL) { Boolean freeCmd = FALSE; /* TRUE if the command needs to be freed, i.e. * if any variable expansion was performed */ char *res, *error; if (strchr(cp, '$') != NULL) { /* * There's a dollar sign in the command, so perform variable * expansion on the whole thing. The resulting string will need * freeing when we're done, so set freeCmd to TRUE. */ cp = Var_Subst(NULL, cp, VAR_CMD, TRUE); freeCmd = TRUE; } res = Cmd_Exec(cp, &error); Var_Set(line, res, ctxt); free(res); if (error) Parse_Error(PARSE_WARNING, error, cp); if (freeCmd) free(cp); } else { /* * Normal assignment -- just do it. */ Var_Set(line, cp, ctxt); } } /*- * ParseAddCmd -- * Lst_ForEach function to add a command line to all targets * * Results: * Always 0 * * Side Effects: * A new element is added to the commands list of the node. */ static int ParseAddCmd(void *gnp, void *cmd) { GNode *gn = gnp; /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) Lst_AtEnd(&gn->commands, cmd); else Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); return (0); } /*- *----------------------------------------------------------------------- * ParseHasCommands -- * Callback procedure for Parse_File when destroying the list of * targets on the last dependency line. Marks a target as already * having commands if it does, to keep from having shell commands * on multiple dependency lines. * * Results: * None * * Side Effects: * OP_HAS_COMMANDS may be set for the target. * *----------------------------------------------------------------------- */ static void ParseHasCommands(void *gnp) { GNode *gn = gnp; if (!Lst_IsEmpty(&gn->commands)) { gn->type |= OP_HAS_COMMANDS; } } /*- *----------------------------------------------------------------------- * Parse_AddIncludeDir -- * Add a directory to the path searched for included makefiles * bracketed by double-quotes. Used by functions in main.c * * Results: * None. * * Side Effects: * The directory is appended to the list. * *----------------------------------------------------------------------- */ void Parse_AddIncludeDir(char *dir) { Dir_AddDir(&parseIncPath, dir); } /*--------------------------------------------------------------------- * ParseDoError -- * Handle error directive * * The input is the line minus the ".error". We substitute variables, * print the message and exit(1) or just print a warning if the ".error" * directive is malformed. * *--------------------------------------------------------------------- */ static void ParseDoError(char *errmsg) { if (!isspace((unsigned char)*errmsg)) { Parse_Error(PARSE_WARNING, "invalid syntax: .error%s", errmsg); return; } while (isspace((unsigned char)*errmsg)) errmsg++; errmsg = Var_Subst(NULL, errmsg, VAR_GLOBAL, FALSE); Parse_Error(PARSE_FATAL, "%s", errmsg); /* Terminate immediately. */ exit(1); } /*--------------------------------------------------------------------- * ParseDoWarning -- * Handle warning directive * * The input is the line minus the ".warning". We substitute variables * and print the message or just print a warning if the ".warning" * directive is malformed. * *--------------------------------------------------------------------- */ static void ParseDoWarning(char *warnmsg) { if (!isspace((unsigned char)*warnmsg)) { Parse_Error(PARSE_WARNING, "invalid syntax: .warning%s", warnmsg); return; } while (isspace((unsigned char)*warnmsg)) warnmsg++; warnmsg = Var_Subst(NULL, warnmsg, VAR_GLOBAL, FALSE); Parse_Error(PARSE_WARNING, "%s", warnmsg); } /*- *--------------------------------------------------------------------- * ParseDoInclude -- * Push to another file. * * The input is the line minus the #include. A file spec is a string * enclosed in <> or "". The former is looked for only in sysIncPath. * The latter in . and the directories specified by -I command line * options * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ static void ParseDoInclude (char *file) { char *fullname; /* full pathname of file */ IFile *oldFile; /* state associated with current file */ char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ Boolean isSystem; /* TRUE if makefile is a system makefile */ /* * Skip to delimiter character so we know where to look */ while ((*file == ' ') || (*file == '\t')) { file++; } if ((*file != '"') && (*file != '<')) { Parse_Error(PARSE_FATAL, ".include filename must be delimited by '\"' or '<'"); return; } /* * Set the search path on which to find the include file based on the * characters which bracket its name. Angle-brackets imply it's * a system Makefile while double-quotes imply it's a user makefile */ if (*file == '<') { isSystem = TRUE; endc = '>'; } else { isSystem = FALSE; endc = '"'; } /* * Skip to matching delimiter */ for (cp = ++file; *cp && *cp != endc; cp++) { continue; } if (*cp != endc) { Parse_Error(PARSE_FATAL, "Unclosed %cinclude filename. '%c' expected", '.', endc); return; } *cp = '\0'; /* * Substitute for any variables in the file name before trying to * find the thing. */ file = Var_Subst(NULL, file, VAR_CMD, FALSE); /* * Now we know the file's name and its search path, we attempt to * find the durn thing. A return of NULL indicates the file don't * exist. */ if (!isSystem) { /* * Include files contained in double-quotes are first searched for * relative to the including file's location. We don't want to * cd there, of course, so we just tack on the old file's * leading path components and call Dir_FindFile to see if * we can locate the beast. */ char *prefEnd, *Fname; /* Make a temporary copy of this, to be safe. */ Fname = estrdup(curFile.fname); prefEnd = strrchr(Fname, '/'); if (prefEnd != (char *)NULL) { char *newName; *prefEnd = '\0'; if (file[0] == '/') newName = estrdup(file); else newName = str_concat(Fname, file, STR_ADDSLASH); fullname = Dir_FindFile(newName, &parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(newName, &dirSearchPath); } free(newName); *prefEnd = '/'; } else { fullname = NULL; } free(Fname); } else { fullname = NULL; } if (fullname == NULL) { /* * System makefile or makefile wasn't found in same directory as * included makefile. Search for it first on the -I search path, * then on the .PATH search path, if not found in a -I directory. * XXX: Suffix specific? */ fullname = Dir_FindFile(file, &parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(file, &dirSearchPath); } } if (fullname == NULL) { /* * Still haven't found the makefile. Look for it on the system * path as a last resort. */ fullname = Dir_FindFile(file, &sysIncPath); } if (fullname == NULL) { *cp = endc; Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } free(file); /* * Once we find the absolute path to the file, we get to save all the * state from the current file before we can start reading this * include file. The state is stored in an IFile structure which * is placed on a list with other IFile structures. The list makes * a very nice stack to track how we got here... */ oldFile = emalloc(sizeof (IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(&includes, oldFile); /* * Once the previous state has been saved, we can get down to reading * the new file. We set up the name of the file to be the absolute * name of the include file so error messages refer to the right * place. Naturally enough, we start reading at line number 0. */ curFile.fname = fullname; curFile.lineno = 0; curFile.F = fopen(fullname, "r"); curFile.p = NULL; if (curFile.F == NULL) { Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); /* * Pop to previous file */ ParseEOF(0); } else { Var_Append(".MAKEFILE_LIST", fullname, VAR_GLOBAL); } } /*- *--------------------------------------------------------------------- * Parse_FromString -- * Start Parsing from the given string * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ void Parse_FromString(char *str, int lineno) { IFile *oldFile; /* state associated with this file */ DEBUGF(FOR, ("%s\n---- at line %d\n", str, lineno)); oldFile = emalloc(sizeof(IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(&includes, oldFile); curFile.F = NULL; curFile.p = emalloc(sizeof (PTR)); curFile.p->str = curFile.p->ptr = str; curFile.lineno = lineno; curFile.fname = estrdup(curFile.fname); } #ifdef SYSVINCLUDE /*- *--------------------------------------------------------------------- * ParseTraditionalInclude -- * Push to another file. * * The input is the line minus the "include". The file name is * the string following the "include". * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ static void ParseTraditionalInclude (char *file) { char *fullname; /* full pathname of file */ IFile *oldFile; /* state associated with current file */ char *cp; /* current position in file spec */ /* * Skip over whitespace */ while ((*file == ' ') || (*file == '\t')) { file++; } if (*file == '\0') { Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); return; } /* * Skip to end of line or next whitespace */ for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) { continue; } *cp = '\0'; /* * Substitute for any variables in the file name before trying to * find the thing. */ file = Var_Subst(NULL, file, VAR_CMD, FALSE); /* * Now we know the file's name, we attempt to find the durn thing. * Search for it first on the -I search path, then on the .PATH * search path, if not found in a -I directory. */ fullname = Dir_FindFile(file, &parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(file, &dirSearchPath); } if (fullname == NULL) { /* * Still haven't found the makefile. Look for it on the system * path as a last resort. */ fullname = Dir_FindFile(file, &sysIncPath); } if (fullname == NULL) { Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } /* * Once we find the absolute path to the file, we get to save all the * state from the current file before we can start reading this * include file. The state is stored in an IFile structure which * is placed on a list with other IFile structures. The list makes * a very nice stack to track how we got here... */ oldFile = emalloc(sizeof(IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(&includes, oldFile); /* * Once the previous state has been saved, we can get down to reading * the new file. We set up the name of the file to be the absolute * name of the include file so error messages refer to the right * place. Naturally enough, we start reading at line number 0. */ curFile.fname = fullname; curFile.lineno = 0; curFile.F = fopen(fullname, "r"); curFile.p = NULL; if (curFile.F == NULL) { Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); /* * Pop to previous file */ ParseEOF(1); } else { Var_Append(".MAKEFILE_LIST", fullname, VAR_GLOBAL); } } #endif /*- *--------------------------------------------------------------------- * ParseEOF -- * Called when EOF is reached in the current file. If we were reading * an include file, the includes stack is popped and things set up * to go back to reading the previous file at the previous location. * * Results: * CONTINUE if there's more to do. DONE if not. * * Side Effects: * The old curFile.F is closed. The includes list is shortened. * curFile.lineno, curFile.F, and curFile.fname are changed if * CONTINUE is returned. *--------------------------------------------------------------------- */ static int ParseEOF(int opened) { IFile *ifile; /* the state on the top of the includes stack */ if (Lst_IsEmpty(&includes)) { Var_Append(".MAKEFILE_LIST", "..", VAR_GLOBAL); return (DONE); } ifile = Lst_DeQueue(&includes); free(curFile.fname); if (opened && curFile.F) { fclose(curFile.F); Var_Append(".MAKEFILE_LIST", "..", VAR_GLOBAL); } if (curFile.p) { free(curFile.p->str); free(curFile.p); } memcpy(&curFile, ifile, sizeof(IFile)); free(ifile); return (CONTINUE); } /*- *--------------------------------------------------------------------- * ParseReadc -- * Read a character from the current file * * Results: * The character that was read * * Side Effects: *--------------------------------------------------------------------- */ static int ParseReadc(void) { if (curFile.F) return (fgetc(curFile.F)); if (curFile.p && *curFile.p->ptr) return (*curFile.p->ptr++); return (EOF); } /*- *--------------------------------------------------------------------- * ParseUnreadc -- * Put back a character to the current file * * Results: * None. * * Side Effects: *--------------------------------------------------------------------- */ static void ParseUnreadc(int c) { if (curFile.F) { ungetc(c, curFile.F); return; } if (curFile.p) { *--(curFile.p->ptr) = c; return; } } /* ParseSkipLine(): * Grab the next line unless it begins with a dot (`.') and we're told to * ignore such lines. */ static char * ParseSkipLine(int skip, int keep_newline) { char *line; int c, lastc; size_t lineLength = 0; Buffer buf; buf = Buf_Init(MAKE_BSIZE); do { Buf_Discard(buf, lineLength); lastc = '\0'; while (((c = ParseReadc()) != '\n' || lastc == '\\') && c != EOF) { if (skip && c == '#' && lastc != '\\') { /* let a comment be terminated even by an escaped \n. * This is consistent to comment handling in ParseReadLine */ while ((c = ParseReadc()) != '\n' && c != EOF) ; break; } if (c == '\n') { if (keep_newline) Buf_AddByte(buf, (Byte)c); else Buf_ReplaceLastByte(buf, (Byte)' '); curFile.lineno++; while ((c = ParseReadc()) == ' ' || c == '\t') continue; if (c == EOF) break; } Buf_AddByte(buf, (Byte)c); lastc = c; } if (c == EOF) { Parse_Error(PARSE_FATAL, "Unclosed conditional/for loop"); Buf_Destroy(buf, TRUE); return (NULL); } curFile.lineno++; Buf_AddByte(buf, (Byte)'\0'); line = (char *)Buf_GetAll(buf, &lineLength); } while (skip == 1 && line[0] != '.'); Buf_Destroy(buf, FALSE); return (line); } /*- *--------------------------------------------------------------------- * ParseReadLine -- * Read an entire line from the input file. Called only by Parse_File. * To facilitate escaped newlines and what have you, a character is * buffered in 'lastc', which is '\0' when no characters have been * read. When we break out of the loop, c holds the terminating * character and lastc holds a character that should be added to * the line (unless we don't read anything but a terminator). * * Results: * A line w/o its newline * * Side Effects: * Only those associated with reading a character *--------------------------------------------------------------------- */ static char * ParseReadLine(void) { Buffer buf; /* Buffer for current line */ int c; /* the current character */ int lastc; /* The most-recent character */ Boolean semiNL; /* treat semi-colons as newlines */ Boolean ignDepOp; /* TRUE if should ignore dependency operators * for the purposes of setting semiNL */ Boolean ignComment; /* TRUE if should ignore comments (in a * shell command */ char *line; /* Result */ char *ep; /* to strip trailing blanks */ size_t lineLength; /* Length of result */ int lineno; /* Saved line # */ semiNL = FALSE; ignDepOp = FALSE; ignComment = FALSE; /* * Handle special-characters at the beginning of the line. Either a * leading tab (shell command) or pound-sign (possible conditional) * forces us to ignore comments and dependency operators and treat * semi-colons as semi-colons (by leaving semiNL FALSE). This also * discards completely blank lines. */ for (;;) { c = ParseReadc(); if (c == '\t') { ignComment = ignDepOp = TRUE; break; } else if (c == '\n') { curFile.lineno++; } else if (c == '#') { ParseUnreadc(c); break; } else { /* * Anything else breaks out without doing anything */ break; } } if (c != EOF) { lastc = c; buf = Buf_Init(MAKE_BSIZE); while (((c = ParseReadc()) != '\n' || (lastc == '\\')) && (c != EOF)) { test_char: switch (c) { case '\n': /* * Escaped newline: read characters until a non-space or an * unescaped newline and replace them all by a single space. * This is done by storing the space over the backslash and * dropping through with the next nonspace. If it is a * semi-colon and semiNL is TRUE, it will be recognized as a * newline in the code below this... */ curFile.lineno++; lastc = ' '; while ((c = ParseReadc()) == ' ' || c == '\t') { continue; } if (c == EOF || c == '\n') { goto line_read; } else { /* * Check for comments, semiNL's, etc. -- easier than * ParseUnreadc(c); continue; */ goto test_char; } /*NOTREACHED*/ break; case ';': /* * Semi-colon: Need to see if it should be interpreted as a * newline */ if (semiNL) { /* * To make sure the command that may be following this * semi-colon begins with a tab, we push one back into the * input stream. This will overwrite the semi-colon in the * buffer. If there is no command following, this does no * harm, since the newline remains in the buffer and the * whole line is ignored. */ ParseUnreadc('\t'); goto line_read; } break; case '=': if (!semiNL) { /* * Haven't seen a dependency operator before this, so this * must be a variable assignment -- don't pay attention to * dependency operators after this. */ ignDepOp = TRUE; } else if (lastc == ':' || lastc == '!') { /* * Well, we've seen a dependency operator already, but it * was the previous character, so this is really just an * expanded variable assignment. Revert semi-colons to * being just semi-colons again and ignore any more * dependency operators. * * XXX: Note that a line like "foo : a:=b" will blow up, * but who'd write a line like that anyway? */ ignDepOp = TRUE; semiNL = FALSE; } break; case '#': if (!ignComment) { if (lastc != '\\') { /* * If the character is a hash mark and it isn't escaped * (or we're being compatible), the thing is a comment. * Skip to the end of the line. */ do { c = ParseReadc(); } while ((c != '\n') && (c != EOF)); goto line_read; } else { /* * Don't add the backslash. Just let the # get copied * over. */ lastc = c; continue; } } break; case ':': case '!': if (!ignDepOp && (c == ':' || c == '!')) { /* * A semi-colon is recognized as a newline only on * dependency lines. Dependency lines are lines with a * colon or an exclamation point. Ergo... */ semiNL = TRUE; } break; default: break; } /* * Copy in the previous character and save this one in lastc. */ Buf_AddByte(buf, (Byte)lastc); lastc = c; } line_read: curFile.lineno++; if (lastc != '\0') { Buf_AddByte(buf, (Byte)lastc); } Buf_AddByte(buf, (Byte)'\0'); line = (char *)Buf_GetAll(buf, &lineLength); Buf_Destroy(buf, FALSE); /* * Strip trailing blanks and tabs from the line. * Do not strip a blank or tab that is preceded by * a '\' */ ep = line; while (*ep) ++ep; while (ep > line + 1 && (ep[-1] == ' ' || ep[-1] == '\t')) { if (ep > line + 1 && ep[-2] == '\\') break; --ep; } *ep = 0; if (line[0] == '.') { /* * The line might be a conditional. Ask the conditional module * about it and act accordingly */ switch (Cond_Eval(line)) { case COND_SKIP: /* * Skip to next conditional that evaluates to COND_PARSE. */ do { free(line); line = ParseSkipLine(1, 0); } while (line && Cond_Eval(line) != COND_PARSE); if (line == NULL) break; /*FALLTHRU*/ case COND_PARSE: free(line); line = ParseReadLine(); break; case COND_INVALID: if (For_Eval(line)) { int ok; free(line); lineno = curFile.lineno; do { /* * Skip after the matching end */ line = ParseSkipLine(0, 1); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in for loop.\n"); break; } ok = For_Eval(line); free(line); } while (ok); if (line != NULL) For_Run(lineno); line = ParseReadLine(); } break; default: break; } } return (line); } else { /* * Hit end-of-file, so return a NULL line to indicate this. */ return (NULL); } } /*- *----------------------------------------------------------------------- * ParseFinishLine -- * Handle the end of a dependency group. * * Results: * Nothing. * * Side Effects: * inLine set FALSE. 'targets' list destroyed. * *----------------------------------------------------------------------- */ static void ParseFinishLine(void) { if (inLine) { Lst_ForEach(&targets, Suff_EndTransform, NULL); Lst_Destroy(&targets, ParseHasCommands); inLine = FALSE; } } /*- *--------------------------------------------------------------------- * Parse_File -- * Parse a file into its component parts, incorporating it into the * current dependency graph. This is the main function and controls * almost every other function in this module * * Results: * None * * Side Effects: * Loads. Nodes are added to the list of all targets, nodes and links * are added to the dependency graph. etc. etc. etc. *--------------------------------------------------------------------- */ void Parse_File(char *name, FILE *stream) { char *cp, /* pointer into the line */ *line; /* the line we're working on */ inLine = FALSE; curFile.fname = name; curFile.F = stream; curFile.lineno = 0; fatals = 0; Var_Append(".MAKEFILE_LIST", name, VAR_GLOBAL); do { while ((line = ParseReadLine()) != NULL) { if (*line == '.') { /* * Lines that begin with the special character are either * include or undef directives. */ for (cp = line + 1; isspace((unsigned char)*cp); cp++) { continue; } if (strncmp(cp, "include", 7) == 0) { ParseDoInclude (cp + 7); goto nextLine; } else if (strncmp(cp, "error", 5) == 0) { ParseDoError(cp + 5); goto nextLine; } else if (strncmp(cp, "warning", 7) == 0) { ParseDoWarning(cp + 7); goto nextLine; } else if (strncmp(cp, "undef", 5) == 0) { char *cp2; for (cp += 5; isspace((unsigned char)*cp); cp++) { continue; } for (cp2 = cp; !isspace((unsigned char)*cp2) && (*cp2 != '\0'); cp2++) { continue; } *cp2 = '\0'; cp = Var_Subst(NULL, cp, VAR_CMD, FALSE); Var_Delete(cp, VAR_GLOBAL); goto nextLine; } } if (*line == '#') { /* If we're this far, the line must be a comment. */ goto nextLine; } if (*line == '\t') { /* * If a line starts with a tab, it can only hope to be * a creation command. */ for (cp = line + 1; isspace((unsigned char)*cp); cp++) { continue; } if (*cp) { if (inLine) { /* * So long as it's not a blank line and we're actually * in a dependency spec, add the command to the list of * commands of all targets in the dependency spec */ Lst_ForEach(&targets, ParseAddCmd, cp); continue; } else { Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", cp); } } #ifdef SYSVINCLUDE } else if (strncmp(line, "include", 7) == 0 && isspace((unsigned char)line[7]) && strchr(line, ':') == NULL) { /* * It's an S3/S5-style "include". */ ParseTraditionalInclude(line + 7); goto nextLine; #endif } else if (Parse_IsVar(line)) { ParseFinishLine(); Parse_DoVar(line, VAR_GLOBAL); } else { /* * We now know it's a dependency line so it needs to have all * variables expanded before being parsed. Tell the variable * module to complain if some variable is undefined... * To make life easier on novices, if the line is indented we * first make sure the line has a dependency operator in it. * If it doesn't have an operator and we're in a dependency * line's script, we assume it's actually a shell command * and add it to the current list of targets. */ cp = line; if (isspace((unsigned char)line[0])) { while ((*cp != '\0') && isspace((unsigned char)*cp)) { cp++; } if (*cp == '\0') { goto nextLine; } } ParseFinishLine(); cp = Var_Subst(NULL, line, VAR_CMD, TRUE); free(line); line = cp; /* * Need a non-circular list for the target nodes */ Lst_Destroy(&targets, NOFREE); inLine = TRUE; ParseDoDependency (line); } nextLine: free(line); } /* * Reached EOF, but it may be just EOF of an include file... */ } while (ParseEOF(1) == CONTINUE); /* * Make sure conditionals are clean */ Cond_End(); if (fatals) errx(1, "fatal errors encountered -- cannot continue"); } /*- *--------------------------------------------------------------------- * Parse_Init -- * initialize the parsing module * * Results: * none * * Side Effects: * the parseIncPath list is initialized... *--------------------------------------------------------------------- */ void Parse_Init(void) { mainNode = NULL; } -void -Parse_End(void) -{ - - Lst_Destroy(&targets, NOFREE); - Lst_Destroy(&sysIncPath, Dir_Destroy); - Lst_Destroy(&parseIncPath, Dir_Destroy); - Lst_Destroy(&includes, NOFREE); /* Should be empty now */ -} - - /*- *----------------------------------------------------------------------- * Parse_MainName -- * Return a Lst of the main target to create for main()'s sake. If * no such target exists, we Punt with an obnoxious error message. * * Results: * A Lst of the single node to create. * * Side Effects: * None. * *----------------------------------------------------------------------- */ void Parse_MainName(Lst *listmain) { if (mainNode == NULL) { Punt("no target to make."); /*NOTREACHED*/ } else if (mainNode->type & OP_DOUBLEDEP) { Lst_AtEnd(listmain, mainNode); Lst_Concat(listmain, &mainNode->cohorts, LST_CONCNEW); } else Lst_AtEnd(listmain, mainNode); } diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c index 4cbd488633be..297990632b2c 100644 --- a/usr.bin/make/str.c +++ b/usr.bin/make/str.c @@ -1,438 +1,420 @@ /*- * 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. * * @(#)str.c 5.8 (Berkeley) 6/1/90 */ #include __FBSDID("$FreeBSD$"); #include "make.h" static char **argv, *buffer; static int argmax, curlen; /* * str_init -- * Initialize the strings package * */ void str_init(void) { char *p1; argv = emalloc(((argmax = 50) + 1) * sizeof(char *)); argv[0] = Var_Value(".MAKE", VAR_GLOBAL, &p1); } - -/* - * str_end -- - * Cleanup the strings package - * - */ -void -str_end(void) -{ - if (argv) { - if (argv[0]) - free(argv[0]); - free(argv); - } - if (buffer) - free(buffer); -} - /*- * str_concat -- * concatenate the two strings, inserting a space or slash between them. * * returns -- * the resulting string in allocated space. */ char * str_concat(const char *s1, const char *s2, int flags) { int len1, len2; char *result; /* get the length of both strings */ len1 = strlen(s1); len2 = strlen(s2); /* allocate length plus separator plus EOS */ result = emalloc(len1 + len2 + 2); /* copy first string into place */ memcpy(result, s1, len1); /* add separator character */ if (flags & STR_ADDSPACE) { result[len1] = ' '; ++len1; } else if (flags & STR_ADDSLASH) { result[len1] = '/'; ++len1; } /* copy second string plus EOS into place */ memcpy(result + len1, s2, len2 + 1); return (result); } /*- * brk_string -- * Fracture a string into an array of words (as delineated by tabs or * spaces) taking quotation marks into account. Leading tabs/spaces * are ignored. * * returns -- * Pointer to the array of pointers to the words. To make life easier, * the first word is always the value of the .MAKE variable. */ char ** brk_string(char *str, int *store_argc, Boolean expand) { int argc, ch; char inquote, *p, *start, *t; int len; /* skip leading space chars. */ for (; *str == ' ' || *str == '\t'; ++str) continue; /* allocate room for a copy of the string */ if ((len = strlen(str) + 1) > curlen) { if (buffer) free(buffer); buffer = emalloc(curlen = len); } /* * copy the string; at the same time, parse backslashes, * quotes and build the argument list. */ argc = 1; inquote = '\0'; for (p = str, start = t = buffer;; ++p) { switch(ch = *p) { case '"': case '\'': if (inquote) { if (ch != inquote) break; inquote = '\0'; /* Don't miss "" or '' */ if (!start) start = t; } else inquote = (char)ch; if (expand) continue; break; case ' ': case '\t': case '\n': if (inquote) break; if (!start) continue; /* FALLTHROUGH */ case '\0': /* * end of a token -- make sure there's enough argv * space and save off a pointer. */ if (!start) goto done; *t++ = '\0'; if (argc == argmax) { argmax *= 2; /* ramp up fast */ argv = erealloc(argv, (argmax + 1) * sizeof(char *)); } argv[argc++] = start; start = NULL; if (ch == '\n' || ch == '\0') goto done; continue; case '\\': if (!expand) { if (!start) start = t; *t++ = '\\'; ch = *++p; break; } switch (ch = *++p) { case '\0': case '\n': /* hmmm; fix it up as best we can */ ch = '\\'; --p; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; default: break; } break; default: break; } if (!start) start = t; *t++ = (char)ch; } done: argv[argc] = NULL; *store_argc = argc; return (argv); } /* * Str_Match -- * * See if a particular string matches a particular pattern. * * Results: Non-zero is returned if string matches pattern, 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the man page for details on what these mean). * * Side effects: None. */ int Str_Match(const char *string, const char *pattern) { char c2; for (;;) { /* * See if we're at the end of both the pattern and the * string. If, we succeeded. If we're at the end of the * pattern but not at the end of the string, we failed. */ if (*pattern == 0) return (!*string); if (*string == 0 && *pattern != '*') return (0); /* * Check for a "*" as the next pattern character. It matches * any substring. We handle this by calling ourselves * recursively for each postfix of string, until either we * match or we reach the end of the string. */ if (*pattern == '*') { pattern += 1; if (*pattern == 0) return (1); while (*string != 0) { if (Str_Match(string, pattern)) return (1); ++string; } return (0); } /* * Check for a "?" as the next pattern character. It matches * any single character. */ if (*pattern == '?') goto thisCharOK; /* * Check for a "[" as the next pattern character. It is * followed by a list of characters that are acceptable, or * by a range (two characters separated by "-"). */ if (*pattern == '[') { ++pattern; for (;;) { if ((*pattern == ']') || (*pattern == 0)) return (0); if (*pattern == *string) break; if (pattern[1] == '-') { c2 = pattern[2]; if (c2 == 0) return (0); if ((*pattern <= *string) && (c2 >= *string)) break; if ((*pattern >= *string) && (c2 <= *string)) break; pattern += 2; } ++pattern; } while ((*pattern != ']') && (*pattern != 0)) ++pattern; goto thisCharOK; } /* * If the next pattern character is '/', just strip off the * '/' so we do exact matching on the character that follows. */ if (*pattern == '\\') { ++pattern; if (*pattern == 0) return (0); } /* * There's no special character. Just make sure that the * next characters of each string match. */ if (*pattern != *string) return (0); thisCharOK: ++pattern; ++string; } } /*- *----------------------------------------------------------------------- * Str_SYSVMatch -- * Check word against pattern for a match (% is wild), * * Results: * Returns the beginning position of a match or null. The number * of characters matched is returned in len. * * Side Effects: * None * *----------------------------------------------------------------------- */ const char * Str_SYSVMatch(const char *word, const char *pattern, int *len) { const char *m, *p, *w; p = pattern; w = word; if (*w == '\0') { /* Zero-length word cannot be matched against */ *len = 0; return (NULL); } if (*p == '\0') { /* Null pattern is the whole string */ *len = strlen(w); return (w); } if ((m = strchr(p, '%')) != NULL) { /* check that the prefix matches */ for (; p != m && *w && *w == *p; w++, p++) continue; if (p != m) return (NULL); /* No match */ if (*++p == '\0') { /* No more pattern, return the rest of the string */ *len = strlen(w); return (w); } } m = w; /* Find a matching tail */ do if (strcmp(p, w) == 0) { *len = w - m; return (m); } while (*w++ != '\0'); return (NULL); } /*- *----------------------------------------------------------------------- * Str_SYSVSubst -- * Substitute '%' on the pattern with len characters from src. * If the pattern does not contain a '%' prepend len characters * from src. * * Results: * None * * Side Effects: * Places result on buf * *----------------------------------------------------------------------- */ void Str_SYSVSubst(Buffer buf, const char *pat, const char *src, int len) { const char *m; if ((m = strchr(pat, '%')) != NULL) { /* Copy the prefix */ Buf_AddBytes(buf, m - pat, (const Byte *)pat); /* skip the % */ pat = m + 1; } /* Copy the pattern */ Buf_AddBytes(buf, len, (const Byte *)src); /* append the rest */ Buf_AddBytes(buf, strlen(pat), (const Byte *)pat); } diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c index 25b6dbf30bce..dce4bcada98a 100644 --- a/usr.bin/make/suff.c +++ b/usr.bin/make/suff.c @@ -1,2348 +1,2326 @@ /* * 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. * * @(#)suff.c 8.4 (Berkeley) 3/21/94 */ #include __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" /* Lst of suffixes */ static Lst sufflist = Lst_Initializer(sufflist); /* Lst of suffixes to be cleaned */ static Lst suffClean = Lst_Initializer(suffClean); /* Lst of sources */ static Lst srclist = Lst_Initializer(srclist); /* Lst of transformation rules */ static Lst transforms = Lst_Initializer(transforms); 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 ************/ +#if 0 +/* + * Keep this function for now until it is clear why a .SUFFIXES: doesn't + * actually delete the suffixes but just puts them on the suffClean list. + */ /*- *----------------------------------------------------------------------- * 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); } +#endif /*- *----------------------------------------------------------------------- * 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; /* the suffix descriptor in this element */ s2 = NULL; for (ln = Lst_First(l); ln != NULL; ln = Lst_Succ(ln)) { s2 = Lst_Datum(ln); if (s2->sNum >= s->sNum) break; } if (s2 == NULL) { DEBUGF(SUFF, ("inserting an empty list?...")); } 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); 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); } /*- *----------------------------------------------------------------------- * 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->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); Lst_Init(&s->searchPath); Lst_Init(&s->children); Lst_Init(&s->parents); Lst_Init(&s->ref); 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 */ Lst_Init(&inIncludes); Lst_Init(&inLibs); 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); Lst_Duplicate(&s->searchPath, &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); } /*- *----------------------------------------------------------------------- * 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 Lst_Init(&s2->cp); Lst_AtEnd(&targ->cp, s2); printf("1 add %p %p to %p:", 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 Lst_Init(&s2->cp); Lst_AtEnd(&targ->cp, s2); printf("2 add %p %p to %p:", 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, *ln1; Src *s; int t = 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); 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=%p] p=%p %d\n", l, s, s->children); Lst_Destroy(&s->cp, NOFREE); #endif Lst_Remove(l, ln); free(s); t |= 1; return (TRUE); } #ifdef DEBUG_SRC else { printf("keep: [l=%p] p=%p %d: ", l, s, s->children); Lst_ForEach(&s->cp, PrintAddr, (void *)NULL); printf("\n"); } #endif } 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 %p from %p\n", s, srcs); #endif rs = s; break; } if ((ptr = Dir_FindFile(s->file, &s->suff->searchPath)) != NULL) { rs = s; #ifdef DEBUG_SRC printf("remove %p from %p\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; prefLen = strlen(targ->pref); 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 Lst_Init(&ret->cp); printf("3 add %p %p\n", &targ, ret); Lst_AtEnd(&targ->cp, ret); #endif Lst_AtEnd(slst, ret); DEBUGF(SUFF, ("\tusing existing source %s\n", s->name)); return (ret); } } } } 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_Initializer(members); 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++; } } /* * 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 */ Lst_Init(&exp); 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++; } } /* * 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); Lst_Init(&srcs); Lst_Init(&targs); /* * 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 Lst_Init(&target->cp); #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 Lst_Init(&targ->cp); #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) { 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; Lst_Init(&suffNull->searchPath); Dir_Concat(&suffNull->searchPath, &dirSearchPath); Lst_Init(&suffNull->children); Lst_Init(&suffNull->parents); Lst_Init(&suffNull->ref); 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); } diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c index eff08ee5e39f..cce8bccc6a9b 100644 --- a/usr.bin/make/targ.c +++ b/usr.bin/make/targ.c @@ -1,589 +1,567 @@ /* * 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" /* the list of all targets found so far */ static Lst allTargets = Lst_Initializer(allTargets); 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 *); /*- *----------------------------------------------------------------------- * Targ_Init -- * Initialize this module * * Results: * None * * Side Effects: * The allTargets list and the targets hash table are initialized *----------------------------------------------------------------------- */ void Targ_Init(void) { 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); - 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; Lst_Init(&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); gn->suffix = NULL; return (gn); } /*- *----------------------------------------------------------------------- * Targ_FindNode -- * Find a node in the list using the given name for matching * * Results: * The node in the list if it was. If it wasn't, return 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. * ----------------------------------------------------------------------- */ void Targ_FindList(Lst *nodes, Lst *names, int flags) { LstNode *ln; /* name list element */ GNode *gn; /* node in tLn */ char *name; 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); } } } /*- *----------------------------------------------------------------------- * 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(); } diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c index d0eb74a0cd7d..a136d01d8491 100644 --- a/usr.bin/make/var.c +++ b/usr.bin/make/var.c @@ -1,1953 +1,1946 @@ /* * 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. * * @(#)var.c 8.3 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * var.c -- * Variable-handling functions * * Interface: * Var_Set Set the value of a variable in the given * context. The variable is created if it doesn't * yet exist. The value and variable name need not * be preserved. * * Var_Append Append more characters to an existing variable * in the given context. The variable needn't * exist already -- it will be created if it doesn't. * A space is placed between the old value and the * new one. * * Var_Exists See if a variable exists. * * Var_Value Return the value of a variable in a context or * NULL if the variable is undefined. * * Var_Subst Substitute named variable, or all variables if * NULL in a string using * the given context as the top-most one. If the * third argument is non-zero, Parse_Error is * called if any variables are undefined. * * Var_Parse Parse a variable expansion from a string and * return the result and the number of characters * consumed. * * Var_Delete Delete a variable in a context. * * Var_Init Initialize this module. * * Debugging: * Var_Dump Print out all variables defined in the given * context. * * XXX: There's a lot of duplication in these functions. */ #include #include #include #include #include "make.h" #include "buf.h" #include "var.h" /* * This is a harmless return value for Var_Parse that can be used by Var_Subst * to determine if there was an error in parsing -- easier than returning * a flag, as things outside this module don't give a hoot. */ char var_Error[] = ""; /* * Similar to var_Error, but returned when the 'err' flag for Var_Parse is * set false. Why not just use a constant? Well, gcc likes to condense * identical string instances... */ static char varNoError[] = ""; /* * Internally, variables are contained in four different contexts. * 1) the environment. They may not be changed. If an environment * variable is appended-to, the result is placed in the global * context. * 2) the global context. Variables set in the Makefile are located in * the global context. It is the penultimate context searched when * substituting. * 3) the command-line context. All variables set on the command line * are placed in this context. They are UNALTERABLE once placed here. * 4) the local context. Each target has associated with it a context * list. On this list are located the structures describing such * local variables as $(@) and $(*) * The four contexts are searched in the reverse order from which they are * listed. */ GNode *VAR_GLOBAL; /* variables from the makefile */ GNode *VAR_CMD; /* variables defined on the command-line */ #define FIND_CMD 0x1 /* look in VAR_CMD when searching */ #define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ #define FIND_ENV 0x4 /* look in the environment also */ static void VarPossiblyExpand(char **, GNode *); static Var *VarFind(char *, GNode *, int); static void VarAdd(char *, char *, GNode *); static void VarDelete(void *); static char *VarGetPattern(GNode *, int, char **, int, int *, size_t *, VarPattern *); static char *VarModify(char *, Boolean (*)(const char *, Boolean, Buffer, void *), void *); static int VarPrintVar(void *, void *); /*- *----------------------------------------------------------------------- * VarCmp -- * See if the given variable matches the named one. Called from * Lst_Find when searching for a variable of a given name. * * Results: * 0 if they match. non-zero otherwise. * * Side Effects: * none *----------------------------------------------------------------------- */ static int VarCmp(const void *v, const void *name) { return (strcmp(name, ((const Var *)v)->name)); } /*- *----------------------------------------------------------------------- * VarPossiblyExpand -- * Expand a variable name's embedded variables in the given context. * * Results: * The contents of name, possibly expanded. * * Side Effects: * The caller must free the new contents or old contents of name. *----------------------------------------------------------------------- */ static void VarPossiblyExpand(char **name, GNode *ctxt) { if (strchr(*name, '$') != NULL) *name = Var_Subst(NULL, *name, ctxt, 0); else *name = estrdup(*name); } /*- *----------------------------------------------------------------------- * VarFind -- * Find the given variable in the given context and any other contexts * indicated. * * Flags: * FIND_GLOBAL set means look in the VAR_GLOBAL context too * FIND_CMD set means to look in the VAR_CMD context too * FIND_ENV set means to look in the environment * * Results: * A pointer to the structure describing the desired variable or * NULL if the variable does not exist. * * Side Effects: * None *----------------------------------------------------------------------- */ static Var * VarFind(char *name, GNode *ctxt, int flags) { Boolean localCheckEnvFirst; LstNode *var; Var *v; /* * If the variable name begins with a '.', it could very well be one of * the local ones. We check the name against all the local variables * and substitute the short version in for 'name' if it matches one of * them. */ if (*name == '.' && isupper((unsigned char)name[1])) switch (name[1]) { case 'A': if (!strcmp(name, ".ALLSRC")) name = ALLSRC; if (!strcmp(name, ".ARCHIVE")) name = ARCHIVE; break; case 'I': if (!strcmp(name, ".IMPSRC")) name = IMPSRC; break; case 'M': if (!strcmp(name, ".MEMBER")) name = MEMBER; break; case 'O': if (!strcmp(name, ".OODATE")) name = OODATE; break; case 'P': if (!strcmp(name, ".PREFIX")) name = PREFIX; break; case 'T': if (!strcmp(name, ".TARGET")) name = TARGET; break; } /* * Note whether this is one of the specific variables we were told through * the -E flag to use environment-variable-override for. */ if (Lst_Find(&envFirstVars, name, (CompareProc *)strcmp) != NULL) { localCheckEnvFirst = TRUE; } else { localCheckEnvFirst = FALSE; } /* * First look for the variable in the given context. If it's not there, * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, * depending on the FIND_* flags in 'flags' */ var = Lst_Find(&ctxt->context, name, VarCmp); if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { var = Lst_Find(&VAR_CMD->context, name, VarCmp); } if ((var == NULL) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL) && !checkEnvFirst && !localCheckEnvFirst) { var = Lst_Find(&VAR_GLOBAL->context, name, VarCmp); } if ((var == NULL) && (flags & FIND_ENV)) { char *env; if ((env = getenv(name)) != NULL) { int len; v = emalloc(sizeof(Var)); v->name = estrdup(name); len = strlen(env); v->val = Buf_Init(len); Buf_AddBytes(v->val, len, (Byte *)env); v->flags = VAR_FROM_ENV; return (v); } else if ((checkEnvFirst || localCheckEnvFirst) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL)) { var = Lst_Find(&VAR_GLOBAL->context, name, VarCmp); if (var == NULL) { return (NULL); } else { return (Lst_Datum(var)); } } else { return (NULL); } } else if (var == NULL) { return (NULL); } else { return (Lst_Datum(var)); } } /*- *----------------------------------------------------------------------- * VarAdd -- * Add a new variable of name name and value val to the given context. * * Results: * None * * Side Effects: * The new variable is placed at the front of the given context * The name and val arguments are duplicated so they may * safely be freed. *----------------------------------------------------------------------- */ static void VarAdd(char *name, char *val, GNode *ctxt) { Var *v; int len; v = emalloc(sizeof(Var)); v->name = estrdup(name); len = val ? strlen(val) : 0; v->val = Buf_Init(len+1); Buf_AddBytes(v->val, len, (Byte *)val); v->flags = 0; Lst_AtFront(&ctxt->context, v); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val)); } /*- *----------------------------------------------------------------------- * VarDelete -- * Delete a variable and all the space associated with it. * * Results: * None * * Side Effects: * None *----------------------------------------------------------------------- */ static void VarDelete(void *vp) { Var *v = vp; free(v->name); Buf_Destroy(v->val, TRUE); free(v); } /*- *----------------------------------------------------------------------- * Var_Delete -- * Remove a variable from a context. * * Results: * None. * * Side Effects: * The Var structure is removed and freed. * *----------------------------------------------------------------------- */ void Var_Delete(char *name, GNode *ctxt) { LstNode *ln; DEBUGF(VAR, ("%s:delete %s\n", ctxt->name, name)); ln = Lst_Find(&ctxt->context, name, VarCmp); if (ln != NULL) { VarDelete(Lst_Datum(ln)); Lst_Remove(&ctxt->context, ln); } } /*- *----------------------------------------------------------------------- * Var_Set -- * Set the variable name to the value val in the given context. * * Results: * None. * * Side Effects: * If the variable doesn't yet exist, a new record is created for it. * Else the old value is freed and the new one stuck in its place * * Notes: * The variable is searched for only in its context before being * created in that context. I.e. if the context is VAR_GLOBAL, * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only * VAR_CMD->context is searched. This is done to avoid the literally * thousands of unnecessary strcmp's that used to be done to * set, say, $(@) or $(<). *----------------------------------------------------------------------- */ void Var_Set(char *name, char *val, GNode *ctxt) { Var *v; /* * We only look for a variable in the given context since anything set * here will override anything in a lower context, so there's not much * point in searching them all just to save a bit of memory... */ VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, 0); if (v == NULL) { VarAdd(name, val, ctxt); } else { Buf_Discard(v->val, Buf_Size(v->val)); Buf_AddBytes(v->val, strlen(val), (Byte *)val); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val)); } /* * Any variables given on the command line are automatically exported * to the environment (as per POSIX standard) */ if (ctxt == VAR_CMD) { setenv(name, val, 1); } free(name); } /*- *----------------------------------------------------------------------- * Var_Append -- * The variable of the given name has the given value appended to it in * the given context. * * Results: * None * * Side Effects: * If the variable doesn't exist, it is created. Else the strings * are concatenated (with a space in between). * * Notes: * Only if the variable is being sought in the global context is the * environment searched. * XXX: Knows its calling circumstances in that if called with ctxt * an actual target, it will only search that context since only * a local variable could be being appended to. This is actually * a big win and must be tolerated. *----------------------------------------------------------------------- */ void Var_Append(char *name, char *val, GNode *ctxt) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); if (v == NULL) { VarAdd(name, val, ctxt); } else { Buf_AddByte(v->val, (Byte)' '); Buf_AddBytes(v->val, strlen(val), (Byte *)val); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, (char *)Buf_GetAll(v->val, (size_t *)NULL))); if (v->flags & VAR_FROM_ENV) { /* * If the original variable came from the environment, we * have to install it in the global context (we could place * it in the environment, but then we should provide a way to * export other variables...) */ v->flags &= ~VAR_FROM_ENV; Lst_AtFront(&ctxt->context, v); } } free(name); } /*- *----------------------------------------------------------------------- * Var_Exists -- * See if the given variable exists. * * Results: * TRUE if it does, FALSE if it doesn't * * Side Effects: * None. * *----------------------------------------------------------------------- */ Boolean Var_Exists(char *name, GNode *ctxt) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); free(name); if (v == NULL) { return (FALSE); } else if (v->flags & VAR_FROM_ENV) { free(v->name); Buf_Destroy(v->val, TRUE); free(v); } return (TRUE); } /*- *----------------------------------------------------------------------- * Var_Value -- * Return the value of the named variable in the given context * * Results: * The value if the variable exists, NULL if it doesn't * * Side Effects: * None *----------------------------------------------------------------------- */ char * Var_Value(char *name, GNode *ctxt, char **frp) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); free(name); *frp = NULL; if (v != NULL) { char *p = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (v->flags & VAR_FROM_ENV) { Buf_Destroy(v->val, FALSE); free(v->name); free(v); *frp = p; } return (p); } else { return (NULL); } } /*- *----------------------------------------------------------------------- * VarModify -- * Modify each of the words of the passed string using the given * function. Used to implement all modifiers. * * Results: * A string of all the words modified appropriately. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static char * VarModify(char *str, Boolean (*modProc)(const char *, Boolean, Buffer, void *), void *datum) { Buffer buf; /* Buffer for the new string */ Boolean addSpace; /* TRUE if need to add a space to the * buffer before adding the trimmed * word */ char **av; /* word list [first word does not count] */ int ac, i; buf = Buf_Init(0); addSpace = FALSE; av = brk_string(str, &ac, FALSE); for (i = 1; i < ac; i++) addSpace = (*modProc)(av[i], addSpace, buf, datum); Buf_AddByte (buf, '\0'); str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } /*- *----------------------------------------------------------------------- * VarSortWords -- * Sort the words in the string. * * Input: * str String whose words should be sorted * cmp A comparison function to control the ordering * * Results: * A string containing the words sorted * * Side Effects: * None. * *----------------------------------------------------------------------- */ static char * VarSortWords(char *str, int (*cmp)(const void *, const void *)) { Buffer buf; char **av; int ac, i; buf = Buf_Init(0); av = brk_string(str, &ac, FALSE); qsort(av + 1, ac - 1, sizeof(char *), cmp); for (i = 1; i < ac; i++) { Buf_AddBytes(buf, strlen(av[i]), (Byte *)av[i]); Buf_AddByte(buf, (Byte)((i < ac - 1) ? ' ' : '\0')); } str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } static int SortIncreasing(const void *l, const void *r) { return (strcmp(*(const char* const*)l, *(const char* const*)r)); } /*- *----------------------------------------------------------------------- * VarGetPattern -- * Pass through the tstr looking for 1) escaped delimiters, * '$'s and backslashes (place the escaped character in * uninterpreted) and 2) unescaped $'s that aren't before * the delimiter (expand the variable substitution unless flags * has VAR_NOSUBST set). * Return the expanded string or NULL if the delimiter was missing * If pattern is specified, handle escaped ampersands, and replace * unescaped ampersands with the lhs of the pattern. * * Results: * A string of all the words modified appropriately. * If length is specified, return the string length of the buffer * If flags is specified and the last character of the pattern is a * $ set the VAR_MATCH_END bit of flags. * * Side Effects: * None. *----------------------------------------------------------------------- */ static char * VarGetPattern(GNode *ctxt, int err, char **tstr, int delim, int *flags, size_t *length, VarPattern *pattern) { char *cp; Buffer buf = Buf_Init(0); size_t junk; if (length == NULL) length = &junk; #define IS_A_MATCH(cp, delim) \ ((cp[0] == '\\') && ((cp[1] == delim) || \ (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&')))) /* * Skim through until the matching delimiter is found; * pick up variable substitutions on the way. Also allow * backslashes to quote the delimiter, $, and \, but don't * touch other backslashes. */ for (cp = *tstr; *cp && (*cp != delim); cp++) { if (IS_A_MATCH(cp, delim)) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if (*cp == '$') { if (cp[1] == delim) { if (flags == NULL) Buf_AddByte(buf, (Byte)*cp); else /* * Unescaped $ at end of pattern => anchor * pattern at end. */ *flags |= VAR_MATCH_END; } else { if (flags == NULL || (*flags & VAR_NOSUBST) == 0) { char *cp2; size_t len; Boolean freeIt; /* * If unescaped dollar sign not before the * delimiter, assume it's a variable * substitution and recurse. */ cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (freeIt) free(cp2); cp += len - 1; } else { char *cp2 = &cp[1]; if (*cp2 == '(' || *cp2 == '{') { /* * Find the end of this variable reference * and suck it in without further ado. * It will be interperated later. */ int have = *cp2; int want = (*cp2 == '(') ? ')' : '}'; int depth = 1; for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) { if (cp2[-1] != '\\') { if (*cp2 == have) ++depth; if (*cp2 == want) --depth; } } Buf_AddBytes(buf, cp2 - cp, (Byte *)cp); cp = --cp2; } else Buf_AddByte(buf, (Byte)*cp); } } } else if (pattern && *cp == '&') Buf_AddBytes(buf, pattern->leftLen, (Byte *)pattern->lhs); else Buf_AddByte(buf, (Byte)*cp); } Buf_AddByte(buf, (Byte)'\0'); if (*cp != delim) { *tstr = cp; *length = 0; return (NULL); } else { *tstr = ++cp; cp = (char *)Buf_GetAll(buf, length); *length -= 1; /* Don't count the NULL */ Buf_Destroy(buf, FALSE); return (cp); } } /*- *----------------------------------------------------------------------- * Var_Quote -- * Quote shell meta-characters in the string * * Results: * The quoted string * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_Quote(const char *str) { Buffer buf; /* This should cover most shells :-( */ static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~"; char *ret; buf = Buf_Init(MAKE_BSIZE); for (; *str; str++) { if (strchr(meta, *str) != NULL) Buf_AddByte(buf, (Byte)'\\'); Buf_AddByte(buf, (Byte)*str); } Buf_AddByte(buf, (Byte)'\0'); ret = Buf_GetAll(buf, NULL); Buf_Destroy(buf, FALSE); return (ret); } /*- *----------------------------------------------------------------------- * VarREError -- * Print the error caused by a regcomp or regexec call. * * Results: * None. * * Side Effects: * An error gets printed. * *----------------------------------------------------------------------- */ void VarREError(int err, regex_t *pat, const char *str) { char *errbuf; int errlen; errlen = regerror(err, pat, 0, 0); errbuf = emalloc(errlen); regerror(err, pat, errbuf, errlen); Error("%s: %s", str, errbuf); free(errbuf); } /*- *----------------------------------------------------------------------- * Var_Parse -- * Given the start of a variable invocation, extract the variable * name and find its value, then modify it according to the * specification. * * Results: * The (possibly-modified) value of the variable or var_Error if the * specification is invalid. The length of the specification is * placed in *lengthPtr (for invalid specifications, this is just * 2 to skip the '$' and the following letter, or 1 if '$' was the * last character in the string). * A Boolean in *freePtr telling whether the returned string should * be freed by the caller. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_Parse(char *str, GNode *ctxt, Boolean err, size_t *lengthPtr, Boolean *freePtr) { char *tstr; /* Pointer into str */ Var *v; /* Variable in invocation */ char *cp; /* Secondary pointer into str (place marker * for tstr) */ Boolean haveModifier;/* TRUE if have modifiers for the variable */ char endc; /* Ending character when variable in parens * or braces */ char startc=0; /* Starting character when variable in parens * or braces */ int cnt; /* Used to count brace pairs when variable in * in parens or braces */ char *start; char delim; Boolean dynamic; /* TRUE if the variable is local and we're * expanding it in a non-local context. This * is done to support dynamic sources. The * result is just the invocation, unaltered */ int vlen; /* length of variable name, after embedded variable * expansion */ *freePtr = FALSE; dynamic = FALSE; start = str; if (str[1] != '(' && str[1] != '{') { /* * If it's not bounded by braces of some sort, life is much simpler. * We just need to check for the first character and return the * value if it exists. */ char name[2]; name[0] = str[1]; name[1] = '\0'; v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); if (v == (Var *)NULL) { if (str[1] != '\0') *lengthPtr = 2; else *lengthPtr = 1; if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ /* XXX: It looks like $% and $! are reversed here */ switch (str[1]) { case '@': return ("$(.TARGET)"); case '%': return ("$(.ARCHIVE)"); case '*': return ("$(.PREFIX)"); case '!': return ("$(.MEMBER)"); default: break; } } /* * Error */ return (err ? var_Error : varNoError); } else { haveModifier = FALSE; tstr = &str[1]; endc = str[1]; } } else { /* build up expanded variable name in this buffer */ Buffer buf = Buf_Init(MAKE_BSIZE); startc = str[1]; endc = startc == '(' ? ')' : '}'; /* * Skip to the end character or a colon, whichever comes first, * replacing embedded variables as we go. */ for (tstr = str + 2; *tstr != '\0' && *tstr != endc && *tstr != ':'; tstr++) if (*tstr == '$') { size_t rlen; Boolean rfree; char* rval = Var_Parse(tstr, ctxt, err, &rlen, &rfree); if (rval == var_Error) { Fatal("Error expanding embedded variable."); } else if (rval != NULL) { Buf_AddBytes(buf, strlen(rval), (Byte *)rval); if (rfree) free(rval); } tstr += rlen - 1; } else Buf_AddByte(buf, (Byte)*tstr); if (*tstr == '\0') { /* * If we never did find the end character, return NULL * right now, setting the length to be the distance to * the end of the string, since that's what make does. */ *lengthPtr = tstr - str; return (var_Error); } haveModifier = (*tstr == ':'); *tstr = '\0'; Buf_AddByte(buf, (Byte)'\0'); str = Buf_GetAll(buf, (size_t *)NULL); vlen = strlen(str); v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); if ((v == (Var *)NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && (vlen == 2) && (str[1] == 'F' || str[1] == 'D')) { /* * Check for bogus D and F forms of local variables since we're * in a local context and the name is the right length. */ switch (str[0]) { case '@': case '%': case '*': case '!': case '>': case '<': { char vname[2]; char *val; /* * Well, it's local -- go look for it. */ vname[0] = str[0]; vname[1] = '\0'; v = VarFind(vname, ctxt, 0); if (v != NULL && !haveModifier) { /* * No need for nested expansion or anything, as we're * the only one who sets these things and we sure don't * put nested invocations in them... */ val = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (str[1] == 'D') { val = VarModify(val, VarHead, (void *)NULL); } else { val = VarModify(val, VarTail, (void *)NULL); } /* * Resulting string is dynamically allocated, so * tell caller to free it. */ *freePtr = TRUE; *lengthPtr = tstr-start+1; *tstr = endc; Buf_Destroy(buf, TRUE); return (val); } break; default: break; } } } if (v == (Var *)NULL) { if (((vlen == 1) || (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ switch (str[0]) { case '@': case '%': case '*': case '!': dynamic = TRUE; break; default: break; } } else if ((vlen > 2) && (str[0] == '.') && isupper((unsigned char)str[1]) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { int len; len = vlen - 1; if ((strncmp(str, ".TARGET", len) == 0) || (strncmp(str, ".ARCHIVE", len) == 0) || (strncmp(str, ".PREFIX", len) == 0) || (strncmp(str, ".MEMBER", len) == 0)) { dynamic = TRUE; } } if (!haveModifier) { /* * No modifiers -- have specification length so we can return * now. */ *lengthPtr = tstr - start + 1; *tstr = endc; if (dynamic) { str = emalloc(*lengthPtr + 1); strncpy(str, start, *lengthPtr); str[*lengthPtr] = '\0'; *freePtr = TRUE; Buf_Destroy(buf, TRUE); return (str); } else { Buf_Destroy(buf, TRUE); return (err ? var_Error : varNoError); } } else { /* * Still need to get to the end of the variable specification, * so kludge up a Var structure for the modifications */ v = emalloc(sizeof(Var)); v->name = estrdup(str); v->val = Buf_Init(1); v->flags = VAR_JUNK; } } Buf_Destroy(buf, TRUE); } if (v->flags & VAR_IN_USE) { Fatal("Variable %s is recursive.", v->name); /*NOTREACHED*/ } else { v->flags |= VAR_IN_USE; } /* * Before doing any modification, we have to make sure the value * has been fully expanded. If it looks like recursion might be * necessary (there's a dollar sign somewhere in the variable's value) * we just call Var_Subst to do any other substitutions that are * necessary. Note that the value returned by Var_Subst will have * been dynamically-allocated, so it will need freeing when we * return. */ str = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (strchr(str, '$') != (char *)NULL) { str = Var_Subst(NULL, str, ctxt, err); *freePtr = TRUE; } v->flags &= ~VAR_IN_USE; /* * Now we need to apply any modifiers the user wants applied. * These are: * :M words which match the given . * is of the standard file * wildcarding form. * :S[g] * Substitute for in the value * :C[g] * Substitute for regex in the value * :H Substitute the head of each word * :T Substitute the tail of each word * :E Substitute the extension (minus '.') of * each word * :R Substitute the root of each word * (pathname minus the suffix). * :lhs=rhs Like :S, but the rhs goes to the end of * the invocation. * :U Converts variable to upper-case. * :L Converts variable to lower-case. */ if ((str != NULL) && haveModifier) { /* * Skip initial colon while putting it back. */ *tstr++ = ':'; while (*tstr != endc) { char *newStr; /* New value to return */ char termc; /* Character which terminated scan */ DEBUGF(VAR, ("Applying :%c to \"%s\"\n", *tstr, str)); switch (*tstr) { case 'N': case 'M': { char *pattern; char *cp2; Boolean copy; copy = FALSE; for (cp = tstr + 1; *cp != '\0' && *cp != ':' && *cp != endc; cp++) { if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)) { copy = TRUE; cp++; } } termc = *cp; *cp = '\0'; if (copy) { /* * Need to compress the \:'s out of the pattern, so * allocate enough room to hold the uncompressed * pattern (note that cp started at tstr+1, so * cp - tstr takes the null byte into account) and * compress the pattern into the space. */ pattern = emalloc(cp - tstr); for (cp2 = pattern, cp = tstr + 1; *cp != '\0'; cp++, cp2++) { if ((*cp == '\\') && (cp[1] == ':' || cp[1] == endc)) { cp++; } *cp2 = *cp; } *cp2 = '\0'; } else { pattern = &tstr[1]; } if (*tstr == 'M' || *tstr == 'm') { newStr = VarModify(str, VarMatch, pattern); } else { newStr = VarModify(str, VarNoMatch, pattern); } if (copy) { free(pattern); } break; } case 'S': { VarPattern pattern; char del; Buffer buf; /* Buffer for patterns */ pattern.flags = 0; del = tstr[1]; tstr += 2; /* * If pattern begins with '^', it is anchored to the * start of the word -- skip over it and flag pattern. */ if (*tstr == '^') { pattern.flags |= VAR_MATCH_START; tstr += 1; } buf = Buf_Init(0); /* * Pass through the lhs looking for 1) escaped delimiters, * '$'s and backslashes (place the escaped character in * uninterpreted) and 2) unescaped $'s that aren't before * the delimiter (expand the variable substitution). * The result is left in the Buffer buf. */ for (cp = tstr; *cp != '\0' && *cp != del; cp++) { if ((*cp == '\\') && ((cp[1] == del) || (cp[1] == '$') || (cp[1] == '\\'))) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if (*cp == '$') { if (cp[1] != del) { /* * If unescaped dollar sign not before the * delimiter, assume it's a variable * substitution and recurse. */ char *cp2; size_t len; Boolean freeIt; cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (freeIt) { free(cp2); } cp += len - 1; } else { /* * Unescaped $ at end of pattern => anchor * pattern at end. */ pattern.flags |= VAR_MATCH_END; } } else { Buf_AddByte(buf, (Byte)*cp); } } Buf_AddByte(buf, (Byte)'\0'); /* * If lhs didn't end with the delimiter, complain and * exit. */ if (*cp != del) { Fatal("Unclosed substitution for %s (%c missing)", v->name, del); } /* * Fetch pattern and destroy buffer, but preserve the data * in it, since that's our lhs. Note that Buf_GetAll * will return the actual number of bytes, which includes * the null byte, so we have to decrement the length by * one. */ pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen); pattern.leftLen--; Buf_Destroy(buf, FALSE); /* * Now comes the replacement string. Three things need to * be done here: 1) need to compress escaped delimiters and * ampersands and 2) need to replace unescaped ampersands * with the l.h.s. (since this isn't regexp, we can do * it right here) and 3) expand any variable substitutions. */ buf = Buf_Init(0); tstr = cp + 1; for (cp = tstr; *cp != '\0' && *cp != del; cp++) { if ((*cp == '\\') && ((cp[1] == del) || (cp[1] == '&') || (cp[1] == '\\') || (cp[1] == '$'))) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if ((*cp == '$') && (cp[1] != del)) { char *cp2; size_t len; Boolean freeIt; cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); cp += len - 1; if (freeIt) { free(cp2); } } else if (*cp == '&') { Buf_AddBytes(buf, pattern.leftLen, (Byte *)pattern.lhs); } else { Buf_AddByte(buf, (Byte)*cp); } } Buf_AddByte(buf, (Byte)'\0'); /* * If didn't end in delimiter character, complain */ if (*cp != del) { Fatal("Unclosed substitution for %s (%c missing)", v->name, del); } pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen); pattern.rightLen--; Buf_Destroy(buf, FALSE); /* * Check for global substitution. If 'g' after the final * delimiter, substitution is global and is marked that * way. */ cp++; if (*cp == 'g') { pattern.flags |= VAR_SUB_GLOBAL; cp++; } /* * Global substitution of the empty string causes an * infinite number of matches, unless anchored by '^' * (start of string) or '$' (end of string). Catch the * infinite substitution here. * Note that flags can only contain the 3 bits we're * interested in so we don't have to mask unrelated * bits. We can test for equality. */ if (!pattern.leftLen && pattern.flags == VAR_SUB_GLOBAL) Fatal("Global substitution of the empty string"); termc = *cp; newStr = VarModify(str, VarSubstitute, &pattern); /* * Free the two strings. */ free(pattern.lhs); free(pattern.rhs); break; } case 'C': { VarREPattern pattern; char *re; int error; pattern.flags = 0; delim = tstr[1]; tstr += 2; cp = tstr; if ((re = VarGetPattern(ctxt, err, &cp, delim, NULL, NULL, NULL)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } if ((pattern.replace = VarGetPattern(ctxt, err, &cp, delim, NULL, NULL, NULL)) == NULL){ free(re); /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } for (;; cp++) { switch (*cp) { case 'g': pattern.flags |= VAR_SUB_GLOBAL; continue; case '1': pattern.flags |= VAR_SUB_ONE; continue; default: break; } break; } termc = *cp; error = regcomp(&pattern.re, re, REG_EXTENDED); free(re); if (error) { *lengthPtr = cp - start + 1; VarREError(error, &pattern.re, "RE substitution error"); free(pattern.replace); return (var_Error); } pattern.nsub = pattern.re.re_nsub + 1; if (pattern.nsub < 1) pattern.nsub = 1; if (pattern.nsub > 10) pattern.nsub = 10; pattern.matches = emalloc(pattern.nsub * sizeof(regmatch_t)); newStr = VarModify(str, VarRESubstitute, &pattern); regfree(&pattern.re); free(pattern.replace); free(pattern.matches); break; } case 'L': if (tstr[1] == endc || tstr[1] == ':') { Buffer buf; buf = Buf_Init(MAKE_BSIZE); for (cp = str; *cp ; cp++) Buf_AddByte(buf, (Byte)tolower(*cp)); Buf_AddByte(buf, (Byte)'\0'); newStr = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'O': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarSortWords(str, SortIncreasing); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'Q': if (tstr[1] == endc || tstr[1] == ':') { newStr = Var_Quote(str); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'T': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarTail, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'U': if (tstr[1] == endc || tstr[1] == ':') { Buffer buf; buf = Buf_Init(MAKE_BSIZE); for (cp = str; *cp ; cp++) Buf_AddByte(buf, (Byte)toupper(*cp)); Buf_AddByte(buf, (Byte)'\0'); newStr = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'H': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarHead, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'E': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarSuffix, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'R': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarRoot, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ #ifdef SUNSHCMD case 's': if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { char *error; newStr = Cmd_Exec(str, &error); if (error) Error(error, str); cp = tstr + 2; termc = *cp; break; } /*FALLTHRU*/ #endif default: { #ifdef SYSVVARSUB /* * This can either be a bogus modifier or a System-V * substitution command. */ VarPattern pattern; Boolean eqFound; pattern.flags = 0; eqFound = FALSE; /* * First we make a pass through the string trying * to verify it is a SYSV-make-style translation: * it must be: =) */ cp = tstr; cnt = 1; while (*cp != '\0' && cnt) { if (*cp == '=') { eqFound = TRUE; /* continue looking for endc */ } else if (*cp == endc) cnt--; else if (*cp == startc) cnt++; if (cnt) cp++; } if (*cp == endc && eqFound) { /* * Now we break this sucker into the lhs and * rhs. We must null terminate them of course. */ cp = tstr; delim = '='; if ((pattern.lhs = VarGetPattern(ctxt, err, &cp, delim, &pattern.flags, &pattern.leftLen, NULL)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } delim = endc; if ((pattern.rhs = VarGetPattern(ctxt, err, &cp, delim, NULL, &pattern.rightLen, &pattern)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } /* * SYSV modifications happen through the whole * string. Note the pattern is anchored at the end. */ termc = *--cp; delim = '\0'; newStr = VarModify(str, VarSYSVMatch, &pattern); free(pattern.lhs); free(pattern.rhs); termc = endc; } else #endif { Error("Unknown modifier '%c'\n", *tstr); for (cp = tstr+1; *cp != ':' && *cp != endc && *cp != '\0'; cp++) continue; termc = *cp; newStr = var_Error; } } } DEBUGF(VAR, ("Result is \"%s\"\n", newStr)); if (*freePtr) { free(str); } str = newStr; if (str != var_Error) { *freePtr = TRUE; } else { *freePtr = FALSE; } if (termc == '\0') { Error("Unclosed variable specification for %s", v->name); } else if (termc == ':') { *cp++ = termc; } else { *cp = termc; } tstr = cp; } *lengthPtr = tstr - start + 1; } else { *lengthPtr = tstr - start + 1; *tstr = endc; } if (v->flags & VAR_FROM_ENV) { Boolean destroy = FALSE; if (str != (char *)Buf_GetAll(v->val, (size_t *)NULL)) { destroy = TRUE; } else { /* * Returning the value unmodified, so tell the caller to free * the thing. */ *freePtr = TRUE; } free(v->name); Buf_Destroy(v->val, destroy); free(v); } else if (v->flags & VAR_JUNK) { /* * Perform any free'ing needed and set *freePtr to FALSE so the caller * doesn't try to free a static pointer. */ if (*freePtr) { free(str); } *freePtr = FALSE; free(v->name); Buf_Destroy(v->val, TRUE); free(v); if (dynamic) { str = emalloc(*lengthPtr + 1); strncpy(str, start, *lengthPtr); str[*lengthPtr] = '\0'; *freePtr = TRUE; } else { str = err ? var_Error : varNoError; } } return (str); } /*- *----------------------------------------------------------------------- * Var_Subst -- * Substitute for all variables in the given string in the given context * If undefErr is TRUE, Parse_Error will be called when an undefined * variable is encountered. * * Results: * The resulting string. * * Side Effects: * None. The old string must be freed by the caller *----------------------------------------------------------------------- */ char * Var_Subst(char *var, char *str, GNode *ctxt, Boolean undefErr) { Buffer buf; /* Buffer for forming things */ char *val; /* Value to substitute for a variable */ size_t length; /* Length of the variable invocation */ Boolean doFree; /* Set true if val should be freed */ static Boolean errorReported; /* Set true if an error has already * been reported to prevent a plethora * of messages when recursing */ buf = Buf_Init(MAKE_BSIZE); errorReported = FALSE; while (*str) { if (var == NULL && (*str == '$') && (str[1] == '$')) { /* * A dollar sign may be escaped either with another dollar sign. * In such a case, we skip over the escape character and store the * dollar sign into the buffer directly. */ str++; Buf_AddByte(buf, (Byte)*str); str++; } else if (*str != '$') { /* * Skip as many characters as possible -- either to the end of * the string or to the next dollar sign (variable invocation). */ char *cp; for (cp = str++; *str != '$' && *str != '\0'; str++) continue; Buf_AddBytes(buf, str - cp, (Byte *)cp); } else { if (var != NULL) { int expand; for (;;) { if (str[1] != '(' && str[1] != '{') { if (str[1] != *var || var[1] != '\0') { Buf_AddBytes(buf, 2, (Byte *)str); str += 2; expand = FALSE; } else expand = TRUE; break; } else { char *p; /* * Scan up to the end of the variable name. */ for (p = &str[2]; *p && *p != ':' && *p != ')' && *p != '}'; p++) if (*p == '$') break; /* * A variable inside the variable. We cannot expand * the external variable yet, so we try again with * the nested one */ if (*p == '$') { Buf_AddBytes(buf, p - str, (Byte *)str); str = p; continue; } if (strncmp(var, str + 2, p - str - 2) != 0 || var[p - str - 2] != '\0') { /* * Not the variable we want to expand, scan * until the next variable */ for (;*p != '$' && *p != '\0'; p++) continue; Buf_AddBytes(buf, p - str, (Byte *)str); str = p; expand = FALSE; } else expand = TRUE; break; } } if (!expand) continue; } val = Var_Parse(str, ctxt, undefErr, &length, &doFree); /* * When we come down here, val should either point to the * value of this variable, suitably modified, or be NULL. * Length should be the total length of the potential * variable invocation (from $ to end character...) */ if (val == var_Error || val == varNoError) { /* * If performing old-time variable substitution, skip over * the variable and continue with the substitution. Otherwise, * store the dollar sign and advance str so we continue with * the string... */ if (oldVars) { str += length; } else if (undefErr) { /* * If variable is undefined, complain and skip the * variable. The complaint will stop us from doing anything * when the file is parsed. */ if (!errorReported) { Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",length,str); } str += length; errorReported = TRUE; } else { Buf_AddByte (buf, (Byte)*str); str += 1; } } else { /* * We've now got a variable structure to store in. But first, * advance the string pointer. */ str += length; /* * Copy all the characters from the variable value straight * into the new string. */ Buf_AddBytes(buf, strlen(val), (Byte *)val); if (doFree) { free(val); } } } } Buf_AddByte(buf, '\0'); str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } /*- *----------------------------------------------------------------------- * Var_GetTail -- * Return the tail from each of a list of words. Used to set the * System V local variables. * * Results: * The resulting string. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_GetTail(char *file) { return (VarModify(file, VarTail, (void *)NULL)); } /*- *----------------------------------------------------------------------- * Var_GetHead -- * Find the leading components of a (list of) filename(s). * XXX: VarHead does not replace foo by ., as (sun) System V make * does. * * Results: * The leading components. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_GetHead(char *file) { return (VarModify(file, VarHead, (void *)NULL)); } /*- *----------------------------------------------------------------------- * Var_Init -- * Initialize the module * * Results: * None * * Side Effects: * The VAR_CMD and VAR_GLOBAL contexts are created *----------------------------------------------------------------------- */ void Var_Init(void) { VAR_GLOBAL = Targ_NewGN("Global"); VAR_CMD = Targ_NewGN("Command"); - -} - -void -Var_End(void) -{ } - /****************** PRINT DEBUGGING INFO *****************/ static int VarPrintVar(void *vp, void *dummy __unused) { Var *v = (Var *) vp; printf("%-16s = %s\n", v->name, (char *)Buf_GetAll(v->val, (size_t *)NULL)); return (0); } /*- *----------------------------------------------------------------------- * Var_Dump -- * print all variables in a context *----------------------------------------------------------------------- */ void Var_Dump(GNode *ctxt) { Lst_ForEach(&ctxt->context, VarPrintVar, (void *)NULL); }