diff options
author | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-11-20 03:54:37 +0000 |
---|---|---|
committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-11-20 03:54:37 +0000 |
commit | 1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d (patch) | |
tree | 90c374b8513ec5109e1ec4e2228e2edf648e8756 | |
parent | 302da1a3d35c15cb29d76e0a939f8bcb13f7ad80 (diff) | |
download | src-test2-1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d.tar.gz src-test2-1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d.zip |
Notes
247 files changed, 6143 insertions, 4176 deletions
diff --git a/ChangeLog b/ChangeLog index 82995f735b2d..ac723511d75a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,78 @@ +2020-11-17 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20201117 + Merge with NetBSD make, pick up + o fix some unit-tests when dash is .SHELL + o rename Targ_NewGN to GNode_New + o make some GNode functions const + o main.c: call Targ_Init before Var_Init + cleanup PrintOnError, getTmpdir and ParseBoolean + o var.c: fix error message of failed :!cmd! modifier + +2020-11-14 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20201114 + Merge with NetBSD make, pick up + o replace a few HashTable_CreateEntry with HashTable_Set + o clean up cached_stats + o rename DEFAULT to defaultNode + o remove redundant struct make_stat + o cond.c: in lint mode, check for ".else <cond>" + use bitset for IfState + replace large switch with if-else in Cond_EvalLine + o job.c: clean up JobExec, JobStart, JobDoOutput + use stderr for error message about failed touch + clean up Job_Touch + replace macro DBPRINTF with JobPrintln + rename JobState to JobStatus + main.c: switch cache for realpath from GNode to HashTable + clean up Fatal + clean up InitDefSysIncPath + use progname instead of hard-coded 'make' in warning + rename Main_SetVarObjdir to SetVarObjdir + make.1: document the -S option + make.c: fix debug output for GNode details + use symbolic names in debug output of GNodes + +2020-11-12 Simon J Gerraty <sjg@beast.crufty.net> + + * configure.in: fix --with-force-machine-arch + + * VERSION (_MAKE_VERSION): 20201112 + Merge with NetBSD make, pick up + o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable + checks in InitObjdir. Explicit .OBJDIR target always allows + read-only directory. + o cond.c: clean up Cond_EvalLine + +2020-11-11 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20201111 + Merge with NetBSD make, pick up + o more unit-tests + o style cleanup + remove redundant parentheses from sizeof operator + replace character literal 0 with '\0'. + replace pointer literal 0 with NULL. + remove redundant parentheses. + replace (expr & mask) == 0 with !(expr & mask). + use strict typing in conditions of the form !var + o rename Make_OODate to GNode_IsOODate + o rename Make_TimeStamp to GNode_UpdateYoungestChild + o rename Var_Set_with_flags to Var_SetWithFlags + o rename dieQuietly to shouldDieQuietly + o buf.c: make API of Buf_Init simpler + o compat.c: clean up Compat_Make, Compat_RunCommand, + CompatDeleteTarget and CompatInterrupt + o cond.c: in lint mode, only allow '&&' and '||', not '&' and '|' + clean up CondParser_Comparison + o main.c: rename getBoolean and s2Boolean + rename MAKEFILE_PREFERENCE for consistency + o parse.c: replace strstr in ParseMaybeSubMake with optimized code + o var.c: rename VARE_ASSIGN to VARE_KEEP_DOLLAR + replace emptyString with allocated empty string + error out on unclosed expressions after the colon + 2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> * VERSION (_MAKE_VERSION): 20201101 @@ -75,8 +75,14 @@ unit-tests/archive-suffix.exp unit-tests/archive-suffix.mk unit-tests/archive.exp unit-tests/archive.mk +unit-tests/cmd-errors-lint.exp +unit-tests/cmd-errors-lint.mk +unit-tests/cmd-errors.exp +unit-tests/cmd-errors.mk unit-tests/cmd-interrupt.exp unit-tests/cmd-interrupt.mk +unit-tests/cmdline-undefined.exp +unit-tests/cmdline-undefined.mk unit-tests/cmdline.exp unit-tests/cmdline.mk unit-tests/comment.exp @@ -115,10 +121,14 @@ unit-tests/cond-func.exp unit-tests/cond-func.mk unit-tests/cond-late.exp unit-tests/cond-late.mk +unit-tests/cond-op-and-lint.exp +unit-tests/cond-op-and-lint.mk unit-tests/cond-op-and.exp unit-tests/cond-op-and.mk unit-tests/cond-op-not.exp unit-tests/cond-op-not.mk +unit-tests/cond-op-or-lint.exp +unit-tests/cond-op-or-lint.mk unit-tests/cond-op-or.exp unit-tests/cond-op-or.mk unit-tests/cond-op-parentheses.exp @@ -287,6 +297,8 @@ unit-tests/directive-for.exp unit-tests/directive-for.mk unit-tests/directive-hyphen-include.exp unit-tests/directive-hyphen-include.mk +unit-tests/directive-if-nested.exp +unit-tests/directive-if-nested.mk unit-tests/directive-if.exp unit-tests/directive-if.mk unit-tests/directive-ifdef.exp @@ -315,8 +327,6 @@ unit-tests/directive-warning.exp unit-tests/directive-warning.mk unit-tests/directive.exp unit-tests/directive.mk -unit-tests/directives.exp -unit-tests/directives.mk unit-tests/dollar.exp unit-tests/dollar.mk unit-tests/doterror.exp @@ -341,6 +351,8 @@ unit-tests/forloop.exp unit-tests/forloop.mk unit-tests/forsubst.exp unit-tests/forsubst.mk +unit-tests/gnode-submake.exp +unit-tests/gnode-submake.mk unit-tests/hanoi-include.exp unit-tests/hanoi-include.mk unit-tests/impsrc.exp @@ -349,6 +361,8 @@ unit-tests/include-main.exp unit-tests/include-main.mk unit-tests/include-sub.mk unit-tests/include-subsub.mk +unit-tests/job-flags.exp +unit-tests/job-flags.mk unit-tests/job-output-long-lines.exp unit-tests/job-output-long-lines.mk unit-tests/lint.exp @@ -365,6 +379,8 @@ unit-tests/modts.exp unit-tests/modts.mk unit-tests/modword.exp unit-tests/modword.mk +unit-tests/objdir-writable.exp +unit-tests/objdir-writable.mk unit-tests/opt-backwards.exp unit-tests/opt-backwards.mk unit-tests/opt-chdir.exp @@ -447,6 +463,8 @@ unit-tests/opt-raw.exp unit-tests/opt-raw.mk unit-tests/opt-silent.exp unit-tests/opt-silent.mk +unit-tests/opt-touch-jobs.exp +unit-tests/opt-touch-jobs.mk unit-tests/opt-touch.exp unit-tests/opt-touch.mk unit-tests/opt-tracefile.exp @@ -517,6 +535,8 @@ unit-tests/suff-main.exp unit-tests/suff-main.mk unit-tests/suff-rebuild.exp unit-tests/suff-rebuild.mk +unit-tests/suff-self.exp +unit-tests/suff-self.mk unit-tests/suff-transform-endless.exp unit-tests/suff-transform-endless.mk unit-tests/suff-transform-expand.exp @@ -737,14 +757,14 @@ unit-tests/varname.exp unit-tests/varname.mk unit-tests/varparse-dynamic.exp unit-tests/varparse-dynamic.mk +unit-tests/varparse-errors.exp +unit-tests/varparse-errors.mk unit-tests/varparse-mod.exp unit-tests/varparse-mod.mk unit-tests/varparse-undef-partial.exp unit-tests/varparse-undef-partial.mk unit-tests/varquote.exp unit-tests/varquote.mk -unit-tests/varshell.exp -unit-tests/varshell.mk util.c var.c wait.h @@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $ +# $Id: Makefile,v 1.114 2020/11/13 21:47:25 sjg Exp $ PROG= bmake @@ -49,6 +49,12 @@ CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\"" +.for x in FORCE_MACHINE FORCE_MACHINE_ARCH +.ifdef $x +COPTS.main.c+= "-D$x=\"${$x}\"" +.endif +.endfor + # meta mode can be useful even without filemon # should be set by now USE_FILEMON ?= no diff --git a/Makefile.config.in b/Makefile.config.in index 38de8f5dd3c9..55cd60ca80ba 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -5,8 +5,8 @@ _MAKE_VERSION?=@_MAKE_VERSION@ prefix?= @prefix@ srcdir= @srcdir@ CC?= @CC@ -MACHINE?= @machine@ -MACHINE_ARCH?= @machine_arch@ +@force_machine@MACHINE?= @machine@ +@force_machine_arch@MACHINE_ARCH?= @machine_arch@ DEFAULT_SYS_PATH?= @default_sys_path@ CPPFLAGS+= @CPPFLAGS@ @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20201101 +_MAKE_VERSION=20201117 @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,38 +68,38 @@ * SUCH DAMAGE. */ -/*- - * arch.c -- - * Functions to manipulate libraries, archives and their members. +/* 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 first time an archive is referenced, all of its members' headers are + * read and cached and the archive closed again. All cached archives are kept + * on a list which is searched each time an archive member is referenced. * * The interface to this module is: + * + * Arch_Init Initialize this module. + * + * Arch_End Clean up this module. + * * Arch_ParseArchive - * Given an archive specification, return a list - * of GNode's, one for each member in the spec. - * FALSE is returned if the specification is - * invalid for some reason. + * Parse an archive specification such as + * "archive.a(member1 member2)". * * Arch_Touch Alter the modification time of the archive * member described by the given node to be - * the current time. + * the time when make was started. * * 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_UpdateMTime + * Find the modification time of a member of + * an archive *in the archive* and place it in the + * member's GNode. * - * Arch_MemTime Find the modification time of a member of + * Arch_UpdateMemberMTime + * 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 @@ -109,12 +109,7 @@ * library name in the GNode should be in * -l<name> format. * - * Arch_LibOODate Special function to decide if a library node - * is out-of-date. - * - * Arch_Init Initialize this module. - * - * Arch_End Clean up this module. + * Arch_LibOODate Decide if a library node is out-of-date. */ #ifdef HAVE_CONFIG_H @@ -151,16 +146,7 @@ struct ar_hdr { #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $"); - -#ifdef TARGET_MACHINE -#undef MAKE_MACHINE -#define MAKE_MACHINE TARGET_MACHINE -#endif -#ifdef TARGET_MACHINE_ARCH -#undef MAKE_MACHINE_ARCH -#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH -#endif +MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; @@ -230,38 +216,36 @@ ArchFree(void *ap) #endif -/*- - *----------------------------------------------------------------------- - * 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. +/* + * Parse an archive specification such as "archive.a(member1 member2.${EXT})", + * adding nodes for the expanded members to nodeLst. Nodes are created as + * necessary. * * Input: - * linePtr Pointer to start of specification - * nodeLst Lst on which to place the nodes - * ctxt Context in which to expand variables + * pp The start of the specification. + * nodeLst The list on which to place the nodes. + * ctxt The context in which to expand variables. * - * Results: - * TRUE 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. - *----------------------------------------------------------------------- + * Output: + * return TRUE if it was a valid specification. + * *pp Points to the first non-space after the archive spec. + * *nodeLst Nodes for the members have been added. */ Boolean -Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) +Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt) { char *cp; /* Pointer into line */ GNode *gn; /* New node */ char *libName; /* Library-part of specification */ + char *libName_freeIt = NULL; char *memName; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ - Boolean subLibName; /* TRUE if libName should have/had - * variable substitution performed on it */ + Boolean expandLibName; /* Whether the parsed libName contains + * variable expressions that need to be + * expanded */ - libName = *linePtr; - - subLibName = FALSE; + libName = *pp; + expandLibName = FALSE; for (cp = libName; *cp != '(' && *cp != '\0';) { if (*cp == '$') { @@ -274,7 +258,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) const char *result; Boolean isError; - (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + /* XXX: is expanded twice: once here and once below */ + (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR, &result, &result_freeIt); /* TODO: handle errors */ isError = result == var_Error; @@ -282,16 +267,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) if (isError) return FALSE; - subLibName = TRUE; + expandLibName = TRUE; cp += nested_p - cp; } else cp++; } *cp++ = '\0'; - if (subLibName) { - (void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName); + if (expandLibName) { + (void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName); /* TODO: handle errors */ + libName_freeIt = libName; } @@ -317,7 +303,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) Boolean isError; const char *nested_p = cp; - (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, + (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR, &result, &freeIt); /* TODO: handle errors */ isError = result == var_Error; @@ -339,7 +325,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) * so it's better to return failure than allow such things to happen */ if (*cp == '\0') { - printf("No closing parenthesis in archive specification\n"); + Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification"); return FALSE; } @@ -370,7 +356,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) char *sacrifice; char *oldMemName = memName; - (void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES, + (void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &memName); /* TODO: handle errors */ @@ -381,7 +367,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) */ buf = sacrifice = str_concat4(libName, "(", memName, ")"); - if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { + if (strchr(memName, '$') != NULL && + 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 @@ -437,17 +424,12 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) *cp = saveChar; } - /* - * If substituted libName, free it now, since we need it no longer. - */ - if (subLibName) { - free(libName); - } + free(libName_freeIt); cp++; /* skip the ')' */ - /* We promised that linePtr would be set up at the next non-space. */ + /* We promised that pp would be set up at the next non-space. */ pp_skip_whitespace(&cp); - *linePtr = cp; + *pp = cp; return TRUE; } @@ -457,15 +439,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) * Input: * archive Path to the archive * member Name of member; only its basename is used. - * hash TRUE if archive should be hashed if not already so. + * addToCache TRUE if archive should be cached if not already so. * * Results: - * The ar_hdr for the member. + * The ar_hdr for the member, or NULL. + * + * See ArchFindMember for an almost identical copy of this code. */ static struct ar_hdr * -ArchStatMember(const char *archive, const char *member, Boolean hash) +ArchStatMember(const char *archive, const char *member, Boolean addToCache) { -#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME) - 1) +#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1) FILE *arch; /* Stream to archive */ size_t size; /* Size of archive member */ char magic[SARMAG]; @@ -484,8 +468,8 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) member = lastSlash + 1; for (ln = archives->first; ln != NULL; ln = ln->next) { - const Arch *archPtr = ln->datum; - if (strcmp(archPtr->name, archive) == 0) + const Arch *a = ln->datum; + if (strcmp(a->name, archive) == 0) break; } @@ -505,17 +489,17 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) if (len > AR_MAX_NAME_LEN) { len = AR_MAX_NAME_LEN; snprintf(copy, sizeof copy, "%s", member); + hdr = HashTable_FindValue(&ar->members, copy); } - hdr = HashTable_FindValue(&ar->members, copy); return hdr; } } - if (!hash) { + if (!addToCache) { /* - * Caller doesn't want the thing hashed, just use ArchFindMember + * Caller doesn't want the thing cached, 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 + * again. Since the archive is not to be cached, we assume there's * no need to allocate extra room for the header we're returning, * so just declare it static. */ @@ -541,98 +525,92 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) * 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); + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { + (void)fclose(arch); return NULL; } - ar = bmake_malloc(sizeof(Arch)); + ar = bmake_malloc(sizeof *ar); ar->name = bmake_strdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; HashTable_Init(&ar->members); memName[AR_MAX_NAME_LEN] = '\0'; - while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp(arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) { - /* - * The header is bogus, so the archive is bad - * and there's no way we can recover... - */ + while (fread(&arh, sizeof arh, 1, arch) == 1) { + char *nameend; + + /* If the header is bogus, there's no way we can recover. */ + if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0) goto badarch; - } else { - char *nameend; - /* - * 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 = (size_t)strtol(arh.ar_size, NULL, 10); + /* + * 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 = (size_t)strtol(arh.AR_SIZE, NULL, 10); - memcpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME)); - nameend = memName + AR_MAX_NAME_LEN; - while (*nameend == ' ') { - nameend--; - } - nameend[1] = '\0'; + memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME); + nameend = memName + AR_MAX_NAME_LEN; + while (nameend > memName && *nameend == ' ') + nameend--; + nameend[1] = '\0'; #ifdef SVR4ARCHIVES + /* + * svr4 names are slash terminated. Also svr4 extended AR format. + */ + if (memName[0] == '/') { /* - * svr4 names are slash terminated. Also svr4 extended AR format. + * svr4 magic mode; handle it */ - 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 (nameend[0] == '/') - nameend[0] = '\0'; + 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 (nameend[0] == '/') + nameend[0] = '\0'; + } #endif #ifdef AR_EFMT1 - /* - * BSD 4.4 extended AR format: #1/<namelen>, with name as the - * first <namelen> bytes of the file - */ - if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) { - - int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]); - - if ((unsigned int)elen > MAXPATHLEN) - goto badarch; - if (fread(memName, (size_t)elen, 1, arch) != 1) - goto badarch; - memName[elen] = '\0'; - if (fseek(arch, -elen, SEEK_CUR) != 0) - goto badarch; - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("ArchStat: Extended format entry for %s\n", - memName); - } - } + /* + * BSD 4.4 extended AR format: #1/<namelen>, with name as the + * first <namelen> bytes of the file + */ + if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && + ch_isdigit(memName[sizeof AR_EFMT1 - 1])) { + + int elen = atoi(memName + sizeof AR_EFMT1 - 1); + + if ((unsigned int)elen > MAXPATHLEN) + goto badarch; + if (fread(memName, (size_t)elen, 1, arch) != 1) + goto badarch; + memName[elen] = '\0'; + if (fseek(arch, -elen, SEEK_CUR) != 0) + goto badarch; + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("ArchStatMember: Extended format entry for %s\n", + memName); + } #endif - { - HashEntry *he; - he = HashTable_CreateEntry(&ar->members, memName, NULL); - HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr))); - memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr)); - } + { + struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr); + memcpy(cached_hdr, &arh, sizeof arh); + HashTable_Set(&ar->members, memName, cached_hdr); } + if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) goto badarch; } @@ -643,7 +621,7 @@ ArchStatMember(const char *archive, const char *member, Boolean hash) /* * Now that the archive has been read and cached, we can look into - * the hash table to find the desired member's header. + * the addToCache table to find the desired member's header. */ return HashTable_FindValue(&ar->members, member); @@ -674,15 +652,15 @@ badarch: *----------------------------------------------------------------------- */ static int -ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) +ArchSVR4Entry(Arch *ar, char *inout_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 (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 || + strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) { if (ar->fnametab != NULL) { DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n"); @@ -711,51 +689,74 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) return 0; } - if (name[1] == ' ' || name[1] == '\0') + if (inout_name[1] == ' ' || inout_name[1] == '\0') return 2; - entry = (size_t)strtol(&name[1], &eptr, 0); - if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { - DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name); + entry = (size_t)strtol(&inout_name[1], &eptr, 0); + if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) { + DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name); return 2; } if (entry >= ar->fnamesize) { DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", - name, (unsigned long)ar->fnamesize); + inout_name, (unsigned long)ar->fnamesize); return 2; } - DEBUG2(ARCH, "Replaced %s with %s\n", name, &ar->fnametab[entry]); + DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]); - snprintf(name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); + snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); 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". - * The passed struct ar_hdr structure is filled in. +static Boolean +ArchiveMember_HasName(const struct ar_hdr *hdr, + const char *name, size_t namelen) +{ + const size_t ar_name_len = sizeof hdr->AR_NAME; + const char *ar_name = hdr->AR_NAME; + + if (strncmp(ar_name, name, namelen) != 0) + return FALSE; + + if (namelen >= ar_name_len) + return namelen == ar_name_len; + + /* hdr->AR_NAME is space-padded to the right. */ + if (ar_name[namelen] == ' ') + return TRUE; + + /* In archives created by GNU binutils 2.27, the member names end with + * a slash. */ + if (ar_name[namelen] == '/' && + (namelen == ar_name_len || ar_name[namelen + 1] == ' ')) + return TRUE; + + return FALSE; +} + +/* Locate a member of an archive, given the path of the archive and the path + * of the desired member. * * Input: * archive Path to the archive * member Name of member. If it is a path, only the last * component is used. - * arhPtr Pointer to header structure to be filled in - * mode The mode for opening the stream + * out_arh Archive header to be filled in + * mode "r" for read-only access, "r+" for read-write access * - * 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. - *----------------------------------------------------------------------- + * Output: + * return The archive file, positioned at the start of the + * member's struct ar_hdr, or NULL if the member doesn't + * exist. + * *out_arh The current struct ar_hdr for member. + * + * See ArchStatMember for an almost identical copy of this code. */ static FILE * -ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, +ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh, const char *mode) { FILE *arch; /* Stream to archive */ @@ -772,8 +773,8 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, * 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)) { + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { fclose(arch); return NULL; } @@ -787,13 +788,13 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, member = lastSlash + 1; len = tlen = strlen(member); - if (len > sizeof(arhPtr->AR_NAME)) { - tlen = sizeof(arhPtr->AR_NAME); + if (len > sizeof out_arh->AR_NAME) { + tlen = sizeof out_arh->AR_NAME; } - while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { + while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) { - if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG)) != 0) { + if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) { /* * The header is bogus, so the archive is bad * and there's no way we can recover... @@ -802,25 +803,21 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, return NULL; } - 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; + DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n", + archive, + (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME, + (int)sizeof out_arh->ar_date, out_arh->ar_date); + if (ArchiveMember_HasName(out_arh, member, len)) { /* - * To make life easier, we reposition the file at the start + * To make life easier for callers that want to update the + * archive, 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... + * not here. */ - if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) { + if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) { fclose(arch); return NULL; } @@ -832,10 +829,10 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, * BSD 4.4 extended AR format: #1/<namelen>, with name as the * first <namelen> bytes of the file */ - if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) + if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && + ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1])) { - int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]); + int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]); char ename[MAXPATHLEN + 1]; if ((unsigned int)elen > MAXPATHLEN) { @@ -847,9 +844,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, return NULL; } ename[elen] = '\0'; - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("ArchFind: Extended format entry for %s\n", ename); - } + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("ArchFindMember: Extended format entry for %s\n", + ename); if (strncmp(ename, member, len) == 0) { /* Found as extended name */ if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen, @@ -866,7 +863,6 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, } #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 @@ -874,113 +870,89 @@ skip: * 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); + out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0'; + size = (int)strtol(out_arh->AR_SIZE, NULL, 10); if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { fclose(arch); return NULL; } } - /* - * 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. - * The modification time of the entire archive is also changed. - * For a library, this could necessitate the re-ranlib'ing of the - * whole thing. +/* Touch a member of an archive, on disk. + * The GNode's modification time is left as-is. + * + * The st_mtime of the entire archive is also changed. + * For a library, it may be required to run ranlib after this. * * Input: * gn Node of member to touch * * Results: * The 'time' field of the member's header is updated. - *----------------------------------------------------------------------- */ void Arch_Touch(GNode *gn) { - FILE *arch; /* Stream open to archive, positioned properly */ - struct ar_hdr arh; /* Current header describing member */ - - arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), - &arh, "r+"); + FILE *f; + struct ar_hdr arh; - snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now); + f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+"); + if (f == NULL) + return; - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); - } + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ } /* Given a node which represents a library, touch the thing, making sure that - * the table of contents also is touched. + * the table of contents is also touched. * * Both the modification time of the library and of the RANLIBMAG member are - * set to 'now'. - * - * Input: - * gn The node of the library to touch - */ + * set to 'now'. */ void -Arch_TouchLib(GNode *gn) +Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) { #ifdef RANLIBMAG - FILE * arch; /* Stream open to archive */ - struct ar_hdr arh; /* Header describing table of contents */ - struct utimbuf times; /* Times for utime() call */ + FILE *f; + struct ar_hdr arh; /* Header describing table of contents */ + struct utimbuf times; - arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); - snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now); + f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); + if (f == NULL) + return; - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ - times.actime = times.modtime = now; - utime(gn->path, ×); - } -#else - (void)gn; + times.actime = times.modtime = now; + utime(gn->path, ×); /* TODO: handle errors */ #endif } -/* Return the modification time of a member of an archive. The mtime field - * of the given node is filled in with the value returned by the function. - * - * Input: - * gn Node describing archive member - */ -time_t -Arch_MTime(GNode *gn) +/* Update the mtime of the GNode with the mtime from the archive member on + * disk (or in the cache). */ +void +Arch_UpdateMTime(GNode *gn) { - struct ar_hdr *arhPtr; /* Header of desired member */ - time_t modTime; /* Modification time as an integer */ - - arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); - if (arhPtr != NULL) { - modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10); - } else { - modTime = 0; - } + struct ar_hdr *arh; - gn->mtime = modTime; - return modTime; + arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); + if (arh != NULL) + gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10); + else + gn->mtime = 0; } -/* Given a non-existent archive member's node, get its modification time from - * its archived form, if it exists. gn->mtime is filled in as well. */ -time_t -Arch_MemMTime(GNode *gn) +/* Given a non-existent archive member's node, update gn->mtime from its + * archived form, if it exists. */ +void +Arch_UpdateMemberMTime(GNode *gn) { GNodeListNode *ln; @@ -1001,7 +973,8 @@ Arch_MemMTime(GNode *gn) if ((pgn->flags & REMAKE) && strncmp(nameStart, gn->name, nameLen) == 0) { - gn->mtime = Arch_MTime(pgn); + Arch_UpdateMTime(pgn); + gn->mtime = pgn->mtime; } } else if (pgn->flags & REMAKE) { /* @@ -1012,8 +985,6 @@ Arch_MemMTime(GNode *gn) break; } } - - return gn->mtime; } /* Search for a library along the given search path. @@ -1045,13 +1016,15 @@ Arch_FindLib(GNode *gn, SearchPath *path) } /* Decide if a node with the OP_LIB attribute is out-of-date. Called from - * Make_OODate to make its life easier. - * The library will be hashed if it hasn't been already. + * GNode_IsOODate to make its life easier. + * The library is cached if it hasn't been already. * * 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 + * 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, @@ -1066,16 +1039,10 @@ Arch_FindLib(GNode *gn, SearchPath *path) * * 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 + * We don't compare 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. - * - * Input: - * gn The library's graph node - * - * Results: - * TRUE if the library is out-of-date. FALSE otherwise. */ Boolean Arch_LibOODate(GNode *gn) @@ -1093,25 +1060,23 @@ Arch_LibOODate(GNode *gn) oodate = TRUE; } else { #ifdef RANLIBMAG - struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ + struct ar_hdr *arh; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents's mod time */ - arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); + arh = ArchStatMember(gn->path, RANLIBMAG, FALSE); - if (arhPtr != NULL) { - modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10); + if (arh != NULL) { + modTimeTOC = (int)strtol(arh->ar_date, NULL, 10); - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); - } - oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC); + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("%s modified %s...", + RANLIBMAG, Targ_FmtTime(modTimeTOC)); + oodate = gn->youngestChild == NULL || + gn->youngestChild->mtime > modTimeTOC; } else { - /* - * A library w/o a table of contents is out-of-date - */ - if (DEBUG(ARCH) || DEBUG(MAKE)) { - debug_printf("No t.o.c...."); - } + /* A library without a table of contents is out-of-date. */ + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("no toc..."); oodate = TRUE; } #else @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ +.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd November 1, 2020 +.Dd November 14, 2020 .Dt BMAKE 1 .Os .Sh NAME @@ -37,7 +37,7 @@ .Nd maintain program dependencies .Sh SYNOPSIS .Nm -.Op Fl BeikNnqrstWwX +.Op Fl BeikNnqrSstWwX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags @@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. +.It Fl S +Stop processing if an error is encountered. +This is the default behavior and the opposite of +.Fl k . .It Fl s Do not echo any commands as they are executed. Equivalent to specifying @@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set and .Ql Ev PWD to that directory before executing any targets. +.Pp +Except in the case of an explicit +.Ql Ic .OBJDIR +target, +.Nm +will check that the specified directory is writable and ignore it if not. +This check can be skipped by setting the environment variable +.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE +to "no". . .It Va .PARSEDIR A path to the directory of the current diff --git a/bmake.cat1 b/bmake.cat1 index 5e78b65c13c5..46e07ba9cc4f 100644 --- a/bmake.cat1 +++ b/bmake.cat1 @@ -4,7 +4,7 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) [1mbmake [22m-- maintain program dependencies [1mSYNOPSIS[0m - [1mbmake [22m[[1m-BeikNnqrstWwX[22m] [[1m-C [4m[22mdirectory[24m] [[1m-D [4m[22mvariable[24m] [[1m-d [4m[22mflags[24m] + [1mbmake [22m[[1m-BeikNnqrSstWwX[22m] [[1m-C [4m[22mdirectory[24m] [[1m-D [4m[22mvariable[24m] [[1m-d [4m[22mflags[24m] [[1m-f [4m[22mmakefile[24m] [[1m-I [4m[22mdirectory[24m] [[1m-J [4m[22mprivate[24m] [[1m-j [4m[22mmax_jobs[24m] [[1m-m [4m[22mdirectory[24m] [[1m-T [4m[22mfile[24m] [[1m-V [4m[22mvariable[24m] [[1m-v [4m[22mvariable[24m] [[4mvariable=value[24m] [[4mtarget[24m [4m...[24m] @@ -205,6 +205,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) [1m-r [22mDo not use the built-in rules specified in the system makefile. + [1m-S [22mStop processing if an error is encountered. This is the default + behavior and the opposite of [1m-k[22m. + [1m-s [22mDo not echo any commands as they are executed. Equivalent to specifying `[1m@[22m' before each command line in the makefile. @@ -718,6 +721,12 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) the specified directory if it exists, and set `[4m.OBJDIR[24m' and `PWD' to that directory before executing any targets. + Except in the case of an explicit `[1m.OBJDIR[22m' target, [1mbmake[0m + will check that the specified directory is writable and + ignore it if not. This check can be skipped by setting + the environment variable `MAKE_OBJDIR_CHECK_WRITABLE' to + "no". + [4m.PARSEDIR[24m A path to the directory of the current `[4mMakefile[24m' being parsed. @@ -1568,4 +1577,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) There is no way of escaping a space character in a filename. -FreeBSD 11.3 November 1, 2020 FreeBSD 11.3 +FreeBSD 11.3 November 14, 2020 FreeBSD 11.3 diff --git a/boot-strap b/boot-strap index f3b98f2a7b23..d251649db670 100755 --- a/boot-strap +++ b/boot-strap @@ -107,6 +107,10 @@ # set "machine_arch" to override that determined by # machine.sh # +# --with-force_machine_arch="machine_arch" +# force "machine_arch" to override that determined by +# machine.sh +# # --with-default-sys-path="syspath" # set an explicit default "syspath" which is where bmake # will look for sys.mk and friends. @@ -115,7 +119,7 @@ # Simon J. Gerraty <sjg@crufty.net> # RCSid: -# $Id: boot-strap,v 1.53 2020/09/16 02:12:01 sjg Exp $ +# $Id: boot-strap,v 1.54 2020/11/13 21:47:25 sjg Exp $ # # @(#) Copyright (c) 2001 Simon J. Gerraty # @@ -1,4 +1,4 @@ -/* $NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $ */ +/* $NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -75,7 +75,7 @@ #include "make.h" /* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $"); /* Make space in the buffer for adding a single byte. */ void @@ -155,19 +155,22 @@ Buf_Empty(Buffer *buf) buf->data[0] = '\0'; } -/* Initialize a buffer. - * If the given initial capacity is 0, a reasonable default is used. */ +/* Initialize a buffer. */ void -Buf_Init(Buffer *buf, size_t cap) +Buf_InitSize(Buffer *buf, size_t cap) { - if (cap <= 0) - cap = 256; buf->cap = cap; buf->len = 0; buf->data = bmake_malloc(cap); buf->data[0] = '\0'; } +void +Buf_Init(Buffer *buf) +{ + Buf_InitSize(buf, 256); +} + /* Reset the buffer. * If freeData is TRUE, the data from the buffer is freed as well. * Otherwise it is kept and returned. */ @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.34 2020/09/27 16:59:02 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.36 2020/11/10 00:32:12 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -94,7 +94,7 @@ typedef struct Buffer { void Buf_Expand_1(Buffer *); /* Buf_AddByte adds a single byte to a buffer. */ -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void Buf_AddByte(Buffer *buf, char byte) { size_t old_len = buf->len++; @@ -106,13 +106,13 @@ Buf_AddByte(Buffer *buf, char byte) end[1] = '\0'; } -static inline MAKE_ATTR_UNUSED size_t +MAKE_INLINE size_t Buf_Len(const Buffer *buf) { return buf->len; } -static inline MAKE_ATTR_UNUSED Boolean +MAKE_INLINE Boolean Buf_EndsWith(const Buffer *buf, char ch) { return buf->len > 0 && buf->data[buf->len - 1] == ch; @@ -124,7 +124,8 @@ void Buf_AddStr(Buffer *, const char *); void Buf_AddInt(Buffer *, int); char *Buf_GetAll(Buffer *, size_t *); void Buf_Empty(Buffer *); -void Buf_Init(Buffer *, size_t); +void Buf_Init(Buffer *); +void Buf_InitSize(Buffer *, size_t); char *Buf_Destroy(Buffer *, Boolean); char *Buf_DestroyCompact(Buffer *); @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -99,15 +99,15 @@ #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; static int compatSigno; /* - * CompatDeleteTarget -- delete a failed, interrupted, or otherwise - * duffed target if not inhibited by .PRECIOUS. + * CompatDeleteTarget -- delete the file of a failed, interrupted, or + * otherwise duffed target if not inhibited by .PRECIOUS. */ static void CompatDeleteTarget(GNode *gn) @@ -132,8 +132,6 @@ CompatDeleteTarget(GNode *gn) static void CompatInterrupt(int signo) { - GNode *gn; - CompatDeleteTarget(curTarg); if (curTarg != NULL && !Targ_Precious(curTarg)) { @@ -141,7 +139,7 @@ CompatInterrupt(int signo) * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { - gn = Targ_FindNode(".INTERRUPT"); + GNode *gn = Targ_FindNode(".INTERRUPT"); if (gn != NULL) { Compat_Make(gn, gn); } @@ -206,7 +204,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn) (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); /* TODO: handle errors */ - if (*cmdStart == '\0') { + if (cmdStart[0] == '\0') { free(cmdStart); return 0; } @@ -225,20 +223,17 @@ Compat_RunCommand(const char *cmdp, GNode *gn) return 0; } - while (*cmd == '@' || *cmd == '-' || *cmd == '+') { - switch (*cmd) { - case '@': + for (;;) { + if (*cmd == '@') silent = !DEBUG(LOUD); - break; - case '-': + else if (*cmd == '-') errCheck = FALSE; - break; - case '+': + else if (*cmd == '+') { doIt = TRUE; - if (!shellName) /* we came here from jobs */ + if (!shellName) /* we came here from jobs */ Shell_Init(); + } else break; - } cmd++; } @@ -248,7 +243,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn) /* * If we did not end up with a command, just skip it. */ - if (!*cmd) + if (cmd[0] == '\0') return 0; #if !defined(MAKE_NATIVE) @@ -286,9 +281,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn) * If we're not supposed to execute any commands, this is as far as * we go... */ - if (!doIt && !GNode_ShouldExecute(gn)) { + if (!doIt && !GNode_ShouldExecute(gn)) return 0; - } + DEBUG1(JOB, "Execute: '%s'\n", cmd); if (useShell) { @@ -297,20 +292,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn) * because the command contains a "meta" character. */ static const char *shargv[5]; - int shargc; - shargc = 0; + /* The following work for any of the builtin shell specs. */ + int shargc = 0; shargv[shargc++] = shellPath; - /* - * The following work for any of the builtin shell specs. - */ - if (errCheck && shellErrFlag) { + if (errCheck && shellErrFlag) shargv[shargc++] = shellErrFlag; - } - if (DEBUG(SHELL)) - shargv[shargc++] = "-xc"; - else - shargv[shargc++] = "-c"; + shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c"; shargv[shargc++] = cmd; shargv[shargc] = NULL; av = shargv; @@ -389,17 +377,19 @@ Compat_RunCommand(const char *cmdp, GNode *gn) #endif if (status != 0) { if (DEBUG(ERROR)) { - const char *cp; + const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", gn->name); - for (cp = cmd; *cp; ) { - if (ch_isspace(*cp)) { + + /* Replace runs of whitespace with a single space, to reduce + * the amount of whitespace for multi-line command lines. */ + while (*p != '\0') { + if (ch_isspace(*p)) { debug_printf(" "); - while (ch_isspace(*cp)) - cp++; + cpp_skip_whitespace(&p); } else { - debug_printf("%c", *cp); - cp++; + debug_printf("%c", *p); + p++; } } debug_printf("\n"); @@ -480,16 +470,17 @@ MakeNodes(GNodeList *gnodes, GNode *pgn) void Compat_Make(GNode *gn, GNode *pgn) { - if (!shellName) /* we came here from jobs */ + if (shellName == NULL) /* we came here from jobs */ Shell_Init(); + if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can * descend and make all our children. If any of them has an error - * but the -k flag was given, our 'make' field will be set FALSE again. - * This is our signal to not attempt to do anything but abort our - * parent as well. + * but the -k flag was given, our 'make' field will be set to FALSE + * again. This is our signal to not attempt to do anything but abort + * our parent as well. */ gn->flags |= REMAKE; gn->made = BEINGMADE; @@ -509,10 +500,10 @@ Compat_Make(GNode *gn, GNode *pgn) * All the children were made ok. Now youngestChild->mtime contains the * modification time of the newest child, we need to find out if we * exist and when we were modified last. The criteria for datedness - * are defined by the Make_OODate function. + * are defined by GNode_IsOODate. */ DEBUG1(MAKE, "Examining %s...", gn->name); - if (!Make_OODate(gn)) { + if (!GNode_IsOODate(gn)) { gn->made = UPTODATE; DEBUG0(MAKE, "up-to-date.\n"); goto cohorts; @@ -523,9 +514,8 @@ Compat_Make(GNode *gn, GNode *pgn) * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ - if (opts.queryFlag) { + if (opts.queryFlag) exit(1); - } /* * We need to be re-made. We also have to make sure we've got a $? @@ -573,15 +563,15 @@ Compat_Make(GNode *gn, GNode *pgn) if (gn->made != ERROR) { /* * If the node was made successfully, mark it so, update - * its modification time and timestamp all its parents. Note - * that for .ZEROTIME targets, the timestamping isn't done. + * its modification time and timestamp all its parents. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; - pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; + if (Make_Recheck(gn) == 0) + pgn->flags |= FORCE; if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); + GNode_UpdateYoungestChild(pgn, gn); } } else if (opts.keepgoing) { pgn->flags &= ~(unsigned)REMAKE; @@ -604,15 +594,14 @@ Compat_Make(GNode *gn, GNode *pgn) pgn->flags &= ~(unsigned)REMAKE; break; case MADE: - if ((gn->type & OP_EXEC) == 0) { + if (!(gn->type & OP_EXEC)) { pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); + GNode_UpdateYoungestChild(pgn, gn); } break; case UPTODATE: - if ((gn->type & OP_EXEC) == 0) { - Make_TimeStamp(pgn, gn); - } + if (!(gn->type & OP_EXEC)) + GNode_UpdateYoungestChild(pgn, gn); break; default: break; @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,7 +72,8 @@ /* Handling of conditionals in a makefile. * * Interface: - * Cond_EvalLine Evaluate the conditional. + * Cond_EvalLine Evaluate the conditional directive, such as + * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'. * * Cond_EvalCondition * Evaluate the conditional, which is either the argument @@ -93,7 +94,7 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $"); /* * The parsing of conditional expressions is based on this grammar: @@ -162,7 +163,7 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */ * * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) * FALSE when CondEvalExpression is called from ApplyModifier_IfElse - * since lhs is already expanded and we cannot tell if + * since lhs is already expanded, and at that point we cannot tell if * it was a variable reference or not. */ static Boolean lhsStrict; @@ -173,6 +174,12 @@ is_token(const char *str, const char *tok, size_t len) return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); } +static Token +ToToken(Boolean cond) +{ + return cond ? TOK_TRUE : TOK_FALSE; +} + /* Push back the most recent token read. We only need one level of this. */ static void CondParser_PushBack(CondParser *par, Token t) @@ -200,7 +207,7 @@ CondParser_SkipWhitespace(CondParser *par) * func says whether the argument belongs to an actual function, or * whether the parsed argument is passed to the default function. * - * Return the length of the argument. */ + * Return the length of the argument, or 0 on error. */ static size_t ParseFuncArg(const char **pp, Boolean doEval, const char *func, char **out_arg) { @@ -213,26 +220,18 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func, p++; /* Skip opening '(' - verified by caller */ if (*p == '\0') { - /* - * No arguments whatsoever. Because 'make' and 'defined' aren't really - * "reserved words", we don't print a message. I think this is better - * than hitting the user with a warning message every time s/he uses - * the word 'make' or 'defined' at the beginning of a symbol... - */ - *out_arg = NULL; - return 0; + *out_arg = NULL; /* Missing closing parenthesis: */ + return 0; /* .if defined( */ } - while (*p == ' ' || *p == '\t') { - p++; - } + cpp_skip_hspace(&p); - Buf_Init(&argBuf, 16); + Buf_InitSize(&argBuf, 16); paren_depth = 0; for (;;) { char ch = *p; - if (ch == 0 || ch == ' ' || ch == '\t') + if (ch == '\0' || ch == ' ' || ch == '\t') break; if ((ch == '&' || ch == '|') && paren_depth == 0) break; @@ -244,7 +243,8 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func, * though perhaps we should... */ void *nestedVal_freeIt; - VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0); + VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR + : VARE_NONE; const char *nestedVal; (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, &nestedVal_freeIt); @@ -264,9 +264,7 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func, *out_arg = Buf_GetAll(&argBuf, &argLen); Buf_Destroy(&argBuf, FALSE); - while (*p == ' ' || *p == '\t') { - p++; - } + cpp_skip_hspace(&p); if (func != NULL && *p++ != ')') { Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", @@ -309,13 +307,10 @@ FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) char *path; path = Dir_FindFile(arg, dirSearchPath); - DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : ""); - if (path != NULL) { - result = TRUE; - free(path); - } else { - result = FALSE; - } + DEBUG2(COND, "exists(%s) result is \"%s\"\n", + arg, path != NULL ? path : ""); + result = path != NULL; + free(path); return result; } @@ -336,40 +331,41 @@ FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); } -/*- +/* * Convert the given number into a double. * We try a base 10 or 16 integer conversion first, if that fails * then we try a floating point conversion instead. * * Results: - * Sets 'value' to double value of string. * Returns TRUE if the conversion succeeded. + * Sets 'out_value' to the converted number. */ static Boolean -TryParseNumber(const char *str, double *value) +TryParseNumber(const char *str, double *out_value) { - char *eptr, ech; - unsigned long l_val; - double d_val; + char *end; + unsigned long ul_val; + double dbl_val; errno = 0; - if (!*str) { - *value = 0.0; + if (str[0] == '\0') { /* XXX: why is an empty string a number? */ + *out_value = 0.0; return TRUE; } - l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); - ech = *eptr; - if (ech == '\0' && errno != ERANGE) { - d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; - } else { - if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E') - return FALSE; - d_val = strtod(str, &eptr); - if (*eptr) - return FALSE; + + ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); + if (*end == '\0' && errno != ERANGE) { + *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; + return TRUE; } - *value = d_val; + if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') + return FALSE; /* skip the expensive strtod call */ + dbl_val = strtod(str, &end); + if (*end != '\0') + return FALSE; + + *out_value = dbl_val; return TRUE; } @@ -385,31 +381,31 @@ is_separator(char ch) * * Results: * Returns the string, absent any quotes, or NULL on error. - * Sets quoted if the string was quoted. - * Sets freeIt if needed. + * Sets out_quoted if the string was quoted. + * Sets out_freeIt. */ /* coverity:[+alloc : arg-*4] */ static const char * CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, - Boolean *quoted, void **freeIt) + Boolean *out_quoted, void **out_freeIt) { Buffer buf; const char *str; Boolean atStart; const char *nested_p; - Boolean qt; + Boolean quoted; const char *start; VarEvalFlags eflags; VarParseResult parseResult; - Buf_Init(&buf, 0); + Buf_Init(&buf); str = NULL; - *freeIt = NULL; - *quoted = qt = par->p[0] == '"' ? 1 : 0; + *out_freeIt = NULL; + *out_quoted = quoted = par->p[0] == '"'; start = par->p; - if (qt) + if (quoted) par->p++; - while (par->p[0] && str == NULL) { + while (par->p[0] != '\0' && str == NULL) { switch (par->p[0]) { case '\\': par->p++; @@ -419,40 +415,44 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, } continue; case '"': - if (qt) { - par->p++; /* we don't want the quotes */ + if (quoted) { + par->p++; /* skip the closing quote */ goto got_str; } Buf_AddByte(&buf, par->p[0]); /* likely? */ par->p++; continue; - case ')': + case ')': /* see is_separator */ case '!': case '=': case '>': case '<': case ' ': case '\t': - if (!qt) + if (!quoted) goto got_str; Buf_AddByte(&buf, par->p[0]); par->p++; continue; case '$': /* if we are in quotes, an undefined variable is ok */ - eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) | - (doEval ? VARE_WANTRES : 0); + eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR : + doEval ? VARE_WANTRES : + VARE_NONE; + nested_p = par->p; atStart = nested_p == start; parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, - freeIt); + out_freeIt); /* TODO: handle errors */ if (str == var_Error) { if (parseResult & VPR_ANY_MSG) par->printedError = TRUE; - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; + if (*out_freeIt != NULL) { + /* XXX: Can there be any situation in which a returned + * var_Error requires freeIt? */ + free(*out_freeIt); + *out_freeIt = NULL; } /* * Even if !doEval, we still report syntax errors, which @@ -473,19 +473,15 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, goto cleanup; Buf_AddStr(&buf, str); - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; + if (*out_freeIt) { + free(*out_freeIt); + *out_freeIt = NULL; } str = NULL; /* not finished yet */ continue; default: - if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) { + if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) { /* lhs must be quoted, a variable reference or number */ - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; - } str = NULL; goto cleanup; } @@ -495,20 +491,22 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, } } got_str: - *freeIt = Buf_GetAll(&buf, NULL); - str = *freeIt; + *out_freeIt = Buf_GetAll(&buf, NULL); + str = *out_freeIt; cleanup: Buf_Destroy(&buf, FALSE); return str; } -/* The different forms of .if directives. */ -static const struct If { +struct If { const char *form; /* Form of if */ size_t formlen; /* Length of form */ Boolean doNot; /* TRUE if default function should be negated */ Boolean (*defProc)(size_t, const char *); /* Default function to apply */ -} ifs[] = { +}; + +/* The different forms of .if directives. */ +static const struct If ifs[] = { { "def", 3, FALSE, FuncDefined }, { "ndef", 4, TRUE, FuncDefined }, { "make", 4, FALSE, FuncMake }, @@ -516,28 +514,39 @@ static const struct If { { "", 0, FALSE, FuncDefined }, { NULL, 0, FALSE, NULL } }; +enum { PLAIN_IF_INDEX = 4 }; + +static Boolean +If_Eval(const struct If *if_info, const char *arg, size_t arglen) +{ + Boolean res = if_info->defProc(arglen, arg); + return if_info->doNot ? !res : res; +} /* Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". */ -static Token -EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted) +static Boolean +EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) { - double left; + double num; - /* For .ifxxx "..." check for non-empty string. */ - if (lhsQuoted) - return lhs[0] != '\0'; + /* For .ifxxx "...", check for non-empty string. */ + if (quoted) + return value[0] != '\0'; - /* For .ifxxx <number> compare against zero */ - if (TryParseNumber(lhs, &left)) - return left != 0.0; + /* For .ifxxx <number>, compare against zero */ + if (TryParseNumber(value, &num)) + return num != 0.0; - /* For .if ${...} check for non-empty string (defProc is ifdef). */ + /* For .if ${...}, check for non-empty string. This is different from + * the evaluation function from that .if variant, which would test + * whether a variable of the given name were defined. */ + /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */ if (par->if_info->form[0] == '\0') - return lhs[0] != 0; + return value[0] != '\0'; - /* Otherwise action default test ... */ - return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot; + /* For the other variants of .ifxxx ${...}, use its default function. */ + return If_Eval(par->if_info, value, strlen(value)); } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ @@ -553,18 +562,18 @@ EvalCompareNum(double lhs, const char *op, double rhs) /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return TOK_ERROR; } - return lhs != rhs; + return ToToken(lhs != rhs); case '=': if (op[1] != '=') { Parse_Error(PARSE_WARNING, "Unknown operator"); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ return TOK_ERROR; } - return lhs == rhs; + return ToToken(lhs == rhs); case '<': - return op[1] == '=' ? lhs <= rhs : lhs < rhs; + return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs); case '>': - return op[1] == '=' ? lhs >= rhs : lhs > rhs; + return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs); } return TOK_ERROR; } @@ -580,7 +589,7 @@ EvalCompareStr(const char *lhs, const char *op, const char *rhs) } DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); - return (*op == '=') == (strcmp(lhs, rhs) == 0); + return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0)); } /* Evaluate a comparison, such as "${VAR} == 12345". */ @@ -609,43 +618,34 @@ CondParser_Comparison(CondParser *par, Boolean doEval) { Token t = TOK_ERROR; const char *lhs, *op, *rhs; - void *lhsFree, *rhsFree; + void *lhs_freeIt, *rhs_freeIt; Boolean lhsQuoted, rhsQuoted; - rhs = NULL; - lhsFree = rhsFree = NULL; - lhsQuoted = rhsQuoted = FALSE; - /* * Parse the variable spec and skip over it, saving its * value in lhs. */ - lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree); - if (!lhs) - goto done; + lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt); + if (lhs == NULL) + goto done_lhs; CondParser_SkipWhitespace(par); - /* - * Make sure the operator is a valid one. If it isn't a - * known relational operator, pretend we got a - * != 0 comparison. - */ op = par->p; switch (par->p[0]) { case '!': case '=': case '<': case '>': - if (par->p[1] == '=') { + if (par->p[1] == '=') par->p += 2; - } else { + else par->p++; - } break; default: - t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE; - goto done; + /* Unknown operator, compare against an empty string or 0. */ + t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted)); + goto done_lhs; } CondParser_SkipWhitespace(par); @@ -653,42 +653,45 @@ CondParser_Comparison(CondParser *par, Boolean doEval) if (par->p[0] == '\0') { Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ - goto done; + goto done_lhs; } - rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree); + rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt); if (rhs == NULL) - goto done; + goto done_rhs; if (!doEval) { t = TOK_FALSE; - goto done; + goto done_rhs; } t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); -done: - free(lhsFree); - free(rhsFree); +done_rhs: + free(rhs_freeIt); +done_lhs: + free(lhs_freeIt); return t; } +/* The argument to empty() is a variable name, optionally followed by + * variable modifiers. */ static size_t -ParseEmptyArg(const char **linePtr, Boolean doEval, - const char *func MAKE_ATTR_UNUSED, char **argPtr) +ParseEmptyArg(const char **pp, Boolean doEval, + const char *func MAKE_ATTR_UNUSED, char **out_arg) { void *val_freeIt; const char *val; size_t magic_res; /* We do all the work here and return the result as the length */ - *argPtr = NULL; + *out_arg = NULL; - (*linePtr)--; /* Make (*linePtr)[1] point to the '('. */ - (void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0, + (*pp)--; /* Make (*pp)[1] point to the '('. */ + (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, &val, &val_freeIt); /* TODO: handle errors */ - /* If successful, *linePtr points beyond the closing ')' now. */ + /* If successful, *pp points beyond the closing ')' now. */ if (val == var_Error) { free(val_freeIt); @@ -714,54 +717,71 @@ FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) return arglen == 1; } -static Token -CondParser_Func(CondParser *par, Boolean doEval) +static Boolean +CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) { static const struct fn_def { const char *fn_name; size_t fn_name_len; size_t (*fn_parse)(const char **, Boolean, const char *, char **); Boolean (*fn_eval)(size_t, const char *); - } fn_defs[] = { + } fns[] = { { "defined", 7, ParseFuncArg, FuncDefined }, { "make", 4, ParseFuncArg, FuncMake }, { "exists", 6, ParseFuncArg, FuncExists }, { "empty", 5, ParseEmptyArg, FuncEmpty }, { "target", 6, ParseFuncArg, FuncTarget }, - { "commands", 8, ParseFuncArg, FuncCommands }, - { NULL, 0, NULL, NULL }, + { "commands", 8, ParseFuncArg, FuncCommands } }; - const struct fn_def *fn_def; - Token t; + const struct fn_def *fn; char *arg = NULL; size_t arglen; const char *cp = par->p; - const char *cp1; + const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; - for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { - if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len)) + for (fn = fns; fn != fns_end; fn++) { + if (!is_token(cp, fn->fn_name, fn->fn_name_len)) continue; - cp += fn_def->fn_name_len; - /* There can only be whitespace before the '(' */ + + cp += fn->fn_name_len; cpp_skip_whitespace(&cp); if (*cp != '(') break; - arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg); + arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg); if (arglen == 0 || arglen == (size_t)-1) { par->p = cp; - return arglen == 0 ? TOK_FALSE : TOK_ERROR; + *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; + return TRUE; } + /* Evaluate the argument using the required function. */ - t = !doEval || fn_def->fn_eval(arglen, arg); + *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); free(arg); par->p = cp; - return t; + return TRUE; } + return FALSE; +} + +/* Parse a function call, a number, a variable expression or a string + * literal. */ +static Token +CondParser_LeafToken(CondParser *par, Boolean doEval) +{ + Token t; + char *arg = NULL; + size_t arglen; + const char *cp = par->p; + const char *cp1; + + if (CondParser_Func(par, doEval, &t)) + return t; + /* Push anything numeric through the compare expression */ cp = par->p; - if (ch_isdigit(cp[0]) || strchr("+-", cp[0])) + if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') return CondParser_Comparison(par, doEval); /* @@ -785,7 +805,7 @@ CondParser_Func(CondParser *par, Boolean doEval) * after .if must have been taken literally, so the argument cannot * be empty - even if it contained a variable expansion. */ - t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot; + t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen)); free(arg); return t; } @@ -802,9 +822,7 @@ CondParser_Token(CondParser *par, Boolean doEval) return t; } - while (par->p[0] == ' ' || par->p[0] == '\t') { - par->p++; - } + cpp_skip_hspace(&par->p); switch (par->p[0]) { @@ -818,15 +836,23 @@ CondParser_Token(CondParser *par, Boolean doEval) case '|': par->p++; - if (par->p[0] == '|') { + if (par->p[0] == '|') par->p++; + else if (opts.lint) { + Parse_Error(PARSE_FATAL, "Unknown operator '|'"); + par->printedError = TRUE; + return TOK_ERROR; } return TOK_OR; case '&': par->p++; - if (par->p[0] == '&') { + if (par->p[0] == '&') par->p++; + else if (opts.lint) { + Parse_Error(PARSE_FATAL, "Unknown operator '&'"); + par->printedError = TRUE; + return TOK_ERROR; } return TOK_AND; @@ -834,8 +860,9 @@ CondParser_Token(CondParser *par, Boolean doEval) par->p++; return TOK_NOT; - case '#': - case '\n': + case '#': /* XXX: see unit-tests/cond-token-plain.mk */ + case '\n': /* XXX: why should this end the condition? */ + /* Probably obsolete now, from 1993-03-21. */ case '\0': return TOK_EOF; @@ -844,7 +871,7 @@ CondParser_Token(CondParser *par, Boolean doEval) return CondParser_Comparison(par, doEval); default: - return CondParser_Func(par, doEval); + return CondParser_LeafToken(par, doEval); } } @@ -1003,25 +1030,14 @@ static CondEvalResult CondEvalExpression(const struct If *info, const char *cond, Boolean *value, Boolean eprint, Boolean strictLHS) { - static const struct If *dflt_info; CondParser par; - int rval; + CondEvalResult rval; lhsStrict = strictLHS; - while (*cond == ' ' || *cond == '\t') - cond++; - - if (info == NULL && (info = dflt_info) == NULL) { - /* Scan for the entry for .if - it can't be first */ - for (info = ifs;; info++) - if (info->form[0] == 0) - break; - dflt_info = info; - } - assert(info != NULL); + cpp_skip_hspace(&cond); - par.if_info = info; + par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX; par.p = cond; par.curr = TOK_NONE; par.printedError = FALSE; @@ -1034,123 +1050,154 @@ CondEvalExpression(const struct If *info, const char *cond, Boolean *value, return rval; } +/* Evaluate a condition in a :? modifier, such as + * ${"${VAR}" == value:?yes:no}. */ CondEvalResult Cond_EvalCondition(const char *cond, Boolean *out_value) { return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); } -/* Evaluate the conditional in the passed line. The line looks like this: - * .<cond-type> <expr> - * In this line, <cond-type> is any of if, ifmake, ifnmake, ifdef, ifndef, - * elif, elifmake, elifnmake, elifdef, elifndef. - * In this line, <expr> consists of &&, ||, !, function(arg), comparisons - * and parenthetical groupings thereof. +/* Evaluate the conditional directive in the line, which is one of: * - * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order - * to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF), - * otherwise .else could be treated as '.elif 1'. + * .if <cond> + * .ifmake <cond> + * .ifnmake <cond> + * .ifdef <cond> + * .ifndef <cond> + * .elif <cond> + * .elifmake <cond> + * .elifnmake <cond> + * .elifdef <cond> + * .elifndef <cond> + * .else + * .endif + * + * In these directives, <cond> consists of &&, ||, !, function(arg), + * comparisons, expressions, bare words, numbers and strings, and + * parenthetical groupings thereof. * * Results: - * COND_PARSE to continue parsing the lines after the conditional - * (when .if or .else returns TRUE) + * COND_PARSE to continue parsing the lines that follow the + * conditional (when <cond> evaluates to TRUE) * COND_SKIP to skip the lines after the conditional - * (when .if or .elif returns FALSE, or when a previous + * (when <cond> evaluates to FALSE, or when a previous * branch has already been taken) * COND_INVALID if the conditional was not valid, either because of * a syntax error or because some variable was undefined * or because the condition could not be evaluated */ CondEvalResult -Cond_EvalLine(const char *line) +Cond_EvalLine(const char *const line) { - enum { MAXIF = 128 }; /* maximum depth of .if'ing */ - enum { MAXIF_BUMP = 32 }; /* how much to grow by */ - enum if_states { - IF_ACTIVE, /* .if or .elif part active */ - ELSE_ACTIVE, /* .else part active */ - SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ - SKIP_TO_ELSE, /* has been true, but not seen '.else' */ - SKIP_TO_ENDIF /* nothing else to execute */ - }; - static enum if_states *cond_state = NULL; - static unsigned int max_if_depth = MAXIF; + typedef enum IfState { + + /* None of the previous <cond> evaluated to TRUE. */ + IFS_INITIAL = 0, + + /* The previous <cond> evaluated to TRUE. + * The lines following this condition are interpreted. */ + IFS_ACTIVE = 1 << 0, + + /* The previous directive was an '.else'. */ + IFS_SEEN_ELSE = 1 << 1, + + /* One of the previous <cond> evaluated to TRUE. */ + IFS_WAS_ACTIVE = 1 << 2 + + } IfState; + + static enum IfState *cond_states = NULL; + static unsigned int cond_states_cap = 128; const struct If *ifp; Boolean isElif; Boolean value; - enum if_states state; + IfState state; + const char *p = line; - if (!cond_state) { - cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); - cond_state[0] = IF_ACTIVE; + if (cond_states == NULL) { + cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states); + cond_states[0] = IFS_ACTIVE; } - /* skip leading character (the '.') and any whitespace */ - for (line++; *line == ' ' || *line == '\t'; line++) - continue; - - /* Find what type of if we're dealing with. */ - if (line[0] == 'e') { - if (line[1] != 'l') { - if (!is_token(line + 1, "ndif", 4)) + + p++; /* skip the leading '.' */ + cpp_skip_hspace(&p); + + /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ + if (p[0] == 'e') { + if (p[1] != 'l') { + if (!is_token(p + 1, "ndif", 4)) { + /* Unknown directive. It might still be a transformation + * rule like '.elisp.scm', therefore no error message here. */ return COND_INVALID; - /* End of conditional section */ + } + + /* It is an '.endif'. */ + /* TODO: check for extraneous <cond> */ + if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less endif"); return COND_PARSE; } + /* Return state for previous conditional */ cond_depth--; - return cond_state[cond_depth] <= ELSE_ACTIVE + return cond_states[cond_depth] & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Quite likely this is 'else' or 'elif' */ - line += 2; - if (is_token(line, "se", 2)) { - /* It is else... */ + p += 2; + if (is_token(p, "se", 2)) { /* It is an 'else'. */ + + if (opts.lint && p[2] != '\0') + Parse_Error(PARSE_FATAL, + "The .else directive does not take arguments."); + if (cond_depth == cond_min_depth) { Parse_Error(PARSE_FATAL, "if-less else"); return COND_PARSE; } - state = cond_state[cond_depth]; - switch (state) { - case SEARCH_FOR_ELIF: - state = ELSE_ACTIVE; - break; - case ELSE_ACTIVE: - case SKIP_TO_ENDIF: - Parse_Error(PARSE_WARNING, "extra else"); - /* FALLTHROUGH */ - default: - case IF_ACTIVE: - case SKIP_TO_ELSE: - state = SKIP_TO_ENDIF; - break; + state = cond_states[cond_depth]; + if (state == IFS_INITIAL) { + state = IFS_ACTIVE | IFS_SEEN_ELSE; + } else { + if (state & IFS_SEEN_ELSE) + Parse_Error(PARSE_WARNING, "extra else"); + state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; } - cond_state[cond_depth] = state; - return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + cond_states[cond_depth] = state; + + return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Assume for now it is an elif */ isElif = TRUE; } else isElif = FALSE; - if (line[0] != 'i' || line[1] != 'f') - /* Not an ifxxx or elifxxx line */ - return COND_INVALID; + if (p[0] != 'i' || p[1] != 'f') { + /* Unknown directive. It might still be a transformation rule like + * '.elisp.scm', therefore no error message here. */ + return COND_INVALID; /* Not an ifxxx or elifxxx line */ + } /* * Figure out what sort of conditional it is -- what its default * function is, etc. -- by looking in the table of valid "ifs" */ - line += 2; + p += 2; for (ifp = ifs;; ifp++) { - if (ifp->form == NULL) + if (ifp->form == NULL) { + /* TODO: Add error message about unknown directive, + * since there is no other known directive that starts with 'el' + * or 'if'. + * Example: .elifx 123 */ return COND_INVALID; - if (is_token(ifp->form, line, ifp->formlen)) { - line += ifp->formlen; + } + if (is_token(p, ifp->form, ifp->formlen)) { + p += ifp->formlen; break; } } @@ -1162,51 +1209,51 @@ Cond_EvalLine(const char *line) Parse_Error(PARSE_FATAL, "if-less elif"); return COND_PARSE; } - state = cond_state[cond_depth]; - if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { + state = cond_states[cond_depth]; + if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); - cond_state[cond_depth] = SKIP_TO_ENDIF; + cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; return COND_SKIP; } - if (state != SEARCH_FOR_ELIF) { - /* Either just finished the 'true' block, or already SKIP_TO_ELSE */ - cond_state[cond_depth] = SKIP_TO_ELSE; + if (state != IFS_INITIAL) { + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } else { /* Normal .if */ - if (cond_depth + 1 >= max_if_depth) { + if (cond_depth + 1 >= cond_states_cap) { /* * This is rare, but not impossible. * In meta mode, dirdeps.mk (only runs at level 0) * can need more than the default. */ - max_if_depth += MAXIF_BUMP; - cond_state = bmake_realloc(cond_state, - max_if_depth * sizeof(*cond_state)); + cond_states_cap += 32; + cond_states = bmake_realloc(cond_states, + cond_states_cap * sizeof *cond_states); } - state = cond_state[cond_depth]; + state = cond_states[cond_depth]; cond_depth++; - if (state > ELSE_ACTIVE) { + if (!(state & IFS_ACTIVE)) { /* If we aren't parsing the data, treat as always false */ - cond_state[cond_depth] = SKIP_TO_ELSE; + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } } /* And evaluate the conditional expression */ - if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) { + if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) { /* Syntax error in conditional, error message already output. */ /* Skip everything to matching .endif */ - cond_state[cond_depth] = SKIP_TO_ELSE; + /* XXX: An extra '.else' is not detected in this case. */ + cond_states[cond_depth] = IFS_WAS_ACTIVE; return COND_SKIP; } if (!value) { - cond_state[cond_depth] = SEARCH_FOR_ELIF; + cond_states[cond_depth] = IFS_INITIAL; return COND_SKIP; } - cond_state[cond_depth] = IF_ACTIVE; + cond_states[cond_depth] = IFS_ACTIVE; return COND_PARSE; } diff --git a/configure b/configure index 814157f4e75f..bf729ee239ae 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for bmake 20201018. +# Generated by GNU Autoconf 2.69 for bmake 20201112. # # Report bugs to <sjg@NetBSD.org>. # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='bmake' PACKAGE_TARNAME='bmake' -PACKAGE_VERSION='20201018' -PACKAGE_STRING='bmake 20201018' +PACKAGE_VERSION='20201112' +PACKAGE_STRING='bmake 20201112' PACKAGE_BUGREPORT='sjg@NetBSD.org' PACKAGE_URL='' @@ -631,6 +631,7 @@ GCC INSTALL default_sys_path mksrc +force_machine_arch machine_arch force_machine machine @@ -1254,7 +1255,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bmake 20201018 to adapt to many kinds of systems. +\`configure' configures bmake 20201112 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1315,7 +1316,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bmake 20201018:";; + short | recursive ) echo "Configuration of bmake 20201112:";; esac cat <<\_ACEOF @@ -1331,9 +1332,9 @@ Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-defshell=SHELL use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions - --without-makefile disable use of generated makefile - --without-meta disable use of meta-mode - --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev + --without-makefile disable use of generated makefile + --without-meta disable use of meta-mode + --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev --with-machine=MACHINE explicitly set MACHINE --with-force-machine=MACHINE set FORCE_MACHINE --with-force-machine-arch=MACHINE set FORCE_MACHINE_ARCH @@ -1421,7 +1422,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bmake configure 20201018 +bmake configure 20201112 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2001,7 +2002,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bmake $as_me 20201018, which was +It was created by bmake $as_me 20201112, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -6045,7 +6046,7 @@ if test "${with_force_machine_arch+set}" = set; then : withval=$with_force_machine_arch; case "${withval}" in yes) force_machine_arch=FORCE_;; no) ;; -*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;; +*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;; esac fi @@ -6059,7 +6060,7 @@ no) ;; esac fi -echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 +echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6 default_sys_path=\${prefix}/share/mk # Check whether --with-default-sys-path was given. @@ -6151,6 +6152,7 @@ fi + bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh" if test $use_makefile = yes; then bm_outfiles="makefile $bm_outfiles" @@ -6664,7 +6666,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bmake $as_me 20201018, which was +This file was extended by bmake $as_me 20201112, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -6726,7 +6728,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -bmake config.status 20201018 +bmake config.status 20201112 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.in b/configure.in index 384c403e544a..c52546068198 100644 --- a/configure.in +++ b/configure.in @@ -1,11 +1,11 @@ dnl dnl RCSid: -dnl $Id: configure.in,v 1.67 2020/10/19 19:47:50 sjg Exp $ +dnl $Id: configure.in,v 1.69 2020/11/14 07:40:43 sjg Exp $ dnl dnl Process this file with autoconf to produce a configure script dnl AC_PREREQ(2.50) -AC_INIT([bmake], [20201018], [sjg@NetBSD.org]) +AC_INIT([bmake], [20201112], [sjg@NetBSD.org]) AC_CONFIG_HEADERS(config.h) dnl make srcdir absolute @@ -38,7 +38,7 @@ CYGWIN*|MINGW*) use_makefile=no;; *) use_makefile=yes;; esac AC_ARG_WITH(makefile, -[ --without-makefile disable use of generated makefile], +[ --without-makefile disable use of generated makefile], [case "${withval}" in yes|no) use_makefile=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for makefile) ;; @@ -46,14 +46,14 @@ esac]) dnl use_meta=yes AC_ARG_WITH(meta, -[ --without-meta disable use of meta-mode], +[ --without-meta disable use of meta-mode], [case "${withval}" in yes|no) use_meta=${withval};; *) AC_MSG_ERROR(bad value ${withval} given for meta) ;; esac]) dnl AC_ARG_WITH(filemon, -[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev], +[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev], [ case "/${withval}" in /no) use_filemon=no;; /*trace) filemon_h=no use_filemon="${withval}";; @@ -308,7 +308,7 @@ AC_ARG_WITH(force_machine_arch, [case "${withval}" in yes) force_machine_arch=FORCE_;; no) ;; -*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;; +*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;; esac]) dnl AC_ARG_WITH(machine_arch, @@ -321,7 +321,7 @@ esac]) dnl dnl Tell them what we ended up with dnl -echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 +echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6 dnl dnl Allow folk to control _PATH_DEFSYSPATH dnl @@ -407,6 +407,7 @@ dnl AC_SUBST(machine) AC_SUBST(force_machine) AC_SUBST(machine_arch) +AC_SUBST(force_machine_arch) AC_SUBST(mksrc) AC_SUBST(default_sys_path) AC_SUBST(INSTALL) @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -100,9 +100,9 @@ * then all the directories above it in turn until * the path is found or we reach the root ("/"). * - * 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_UpdateMTime + * Update the modification time and path of a node with + * data from the file corresponding to the node. * * Dir_AddDir Add a directory to a search path. * @@ -134,7 +134,7 @@ #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $"); #define DIR_DEBUG0(text) DEBUG0(DIR, text) #define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1) @@ -168,9 +168,9 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); * 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. + * it is only recently (as of 1993 or earlier) 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 CachedDir structure and * verify the directory hasn't changed since the contents were cached. @@ -184,11 +184,11 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); * 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. + * C programs and until recently (as of 1993 or earlier) 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 @@ -204,7 +204,7 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); * 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. + * the mtime in a cache for when Dir_UpdateMTime was actually called. */ typedef List CachedDirList; @@ -253,12 +253,10 @@ OpenDirs_Find(OpenDirs *odirs, const char *name) static void OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir) { - HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name); - if (he != NULL) + if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL) return; - he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL); Lst_Append(odirs->list, cdir); - HashEntry_Set(he, odirs->list->last); + HashTable_Set(&odirs->table, cdir->name, odirs->list->last); } static void @@ -273,10 +271,10 @@ OpenDirs_Remove(OpenDirs *odirs, const char *name) Lst_Remove(odirs->list, ln); } -static OpenDirs openDirs; /* the list of all open directories */ +static OpenDirs openDirs; /* all cached directories */ /* - * Variables for gathering statistics on the efficiency of the cashing + * Variables for gathering statistics on the efficiency of the caching * mechanism. */ static int hits; /* Found in directory cache */ @@ -300,74 +298,50 @@ static HashTable mtimes; static HashTable lmtimes; /* same as mtimes but for lstat */ -/* - * We use stat(2) a lot, cache the results. - * mtime and mode are all we care about. - */ -struct cache_st { - time_t lmtime; /* lstat */ - time_t mtime; /* stat */ - mode_t mode; -}; - -/* minimize changes below */ typedef enum CachedStatsFlags { - CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */ - CST_UPDATE = 0x02 /* ignore existing cached entry */ + CST_NONE = 0, + CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */ + CST_UPDATE = 1 << 1 /* ignore existing cached entry */ } CachedStatsFlags; -/* Returns 0 and the result of stat(2) or lstat(2) in *mst, or -1 on error. */ +/* Returns 0 and the result of stat(2) or lstat(2) in *out_cst, + * or -1 on error. */ static int -cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst, +cached_stats(const char *pathname, struct cached_stat *out_cst, CachedStatsFlags flags) { - HashEntry *entry; + HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes; struct stat sys_st; - struct cache_st *cst; + struct cached_stat *cst; int rc; - if (!pathname || !pathname[0]) - return -1; + if (pathname == NULL || pathname[0] == '\0') + return -1; /* This can happen in meta mode. */ - entry = HashTable_FindEntry(htp, pathname); - - if (entry && !(flags & CST_UPDATE)) { - cst = HashEntry_Get(entry); - - mst->mst_mode = cst->mode; - mst->mst_mtime = (flags & CST_LSTAT) ? cst->lmtime : cst->mtime; - if (mst->mst_mtime) { - DIR_DEBUG2("Using cached time %s for %s\n", - Targ_FmtTime(mst->mst_mtime), pathname); - return 0; - } + cst = HashTable_FindValue(tbl, pathname); + if (cst != NULL && !(flags & CST_UPDATE)) { + *out_cst = *cst; + DIR_DEBUG2("Using cached time %s for %s\n", + Targ_FmtTime(cst->cst_mtime), pathname); + return 0; } - rc = (flags & CST_LSTAT) - ? lstat(pathname, &sys_st) - : stat(pathname, &sys_st); + rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st); if (rc == -1) - return -1; + return -1; /* don't cache negative lookups */ if (sys_st.st_mtime == 0) sys_st.st_mtime = 1; /* avoid confusion with missing file */ - mst->mst_mode = sys_st.st_mode; - mst->mst_mtime = sys_st.st_mtime; - - if (entry == NULL) - entry = HashTable_CreateEntry(htp, pathname, NULL); - if (HashEntry_Get(entry) == NULL) { - HashEntry_Set(entry, bmake_malloc(sizeof(*cst))); - memset(HashEntry_Get(entry), 0, sizeof(*cst)); - } - cst = HashEntry_Get(entry); - if (flags & CST_LSTAT) { - cst->lmtime = sys_st.st_mtime; - } else { - cst->mtime = sys_st.st_mtime; + if (cst == NULL) { + cst = bmake_malloc(sizeof *cst); + HashTable_Set(tbl, pathname, cst); } - cst->mode = sys_st.st_mode; + + cst->cst_mtime = sys_st.st_mtime; + cst->cst_mode = sys_st.st_mode; + + *out_cst = *cst; DIR_DEBUG2(" Caching %s for %s\n", Targ_FmtTime(sys_st.st_mtime), pathname); @@ -375,15 +349,15 @@ cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst, } int -cached_stat(const char *pathname, struct make_stat *st) +cached_stat(const char *pathname, struct cached_stat *cst) { - return cached_stats(&mtimes, pathname, st, 0); + return cached_stats(pathname, cst, CST_NONE); } int -cached_lstat(const char *pathname, struct make_stat *st) +cached_lstat(const char *pathname, struct cached_stat *cst) { - return cached_stats(&lmtimes, pathname, st, CST_LSTAT); + return cached_stats(pathname, cst, CST_LSTAT); } /* Initialize the directories module. */ @@ -401,7 +375,7 @@ Dir_InitDir(const char *cdname) { Dir_InitCur(cdname); - dotLast = bmake_malloc(sizeof(CachedDir)); + dotLast = bmake_malloc(sizeof *dotLast); dotLast->refCount = 1; dotLast->hits = 0; dotLast->name = bmake_strdup(".DOTLAST"); @@ -416,23 +390,31 @@ Dir_InitCur(const char *cdname) { CachedDir *dir; - if (cdname != NULL) { + if (cdname == NULL) + return; + + /* + * Our build directory is not the same as our source directory. + * Keep this one around too. + */ + dir = Dir_AddDir(NULL, cdname); + if (dir == NULL) + return; + + /* XXX: Reference counting is wrong here. + * If this function is called repeatedly with the same directory name, + * its reference count increases each time even though the number of + * actual references stays the same. */ + + dir->refCount++; + if (cur != NULL && cur != dir) { /* - * Our build directory is not the same as our source directory. - * Keep this one around too. + * We've been here before, clean up. */ - if ((dir = Dir_AddDir(NULL, cdname))) { - dir->refCount++; - if (cur && cur != dir) { - /* - * We've been here before, clean up. - */ - cur->refCount--; - Dir_Destroy(cur); - } - cur = dir; - } + cur->refCount--; + Dir_Destroy(cur); } + cur = dir; } /* (Re)initialize "dot" (current/object directory) path hash. @@ -588,6 +570,9 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; HashIter hi; + /* XXX: Iterating over all hash entries is inefficient. If the pattern + * is a plain string without any wildcards, a direct lookup is faster. */ + HashIter_Init(&hi, &dir->files); while (HashIter_Next(&hi) != NULL) { const char *base = hi.entry->key; @@ -879,13 +864,13 @@ DirLookup(CachedDir *dir, const char *base) static char * DirLookupSubdir(CachedDir *dir, const char *name) { - struct make_stat mst; + struct cached_stat cst; char *file = dir == dot ? bmake_strdup(name) : str_concat3(dir->name, "/", name); DIR_DEBUG1("checking %s ...\n", file); - if (cached_stat(file, &mst) == 0) { + if (cached_stat(file, &cst) == 0) { nearmisses++; return file; } @@ -974,7 +959,7 @@ Dir_FindFile(const char *name, SearchPath *path) const char *base; /* Terminal name of file */ Boolean hasLastDot = FALSE; /* true if we should search dot last */ Boolean hasSlash; /* true if 'name' contains a / */ - struct make_stat mst; /* Buffer for stat, if necessary */ + struct cached_stat cst; /* Buffer for stat, if necessary */ const char *trailing_dot = "."; /* @@ -1176,7 +1161,7 @@ Dir_FindFile(const char *name, SearchPath *path) * When searching for $(FILE), we will find it in $(INSTALLDIR) * b/c we added it here. This is not good... */ -#ifdef notdef +#if 0 if (base == trailing_dot) { base = strrchr(name, '/'); base++; @@ -1198,17 +1183,17 @@ Dir_FindFile(const char *name, SearchPath *path) } else { return NULL; } -#else /* !notdef */ +#else DIR_DEBUG1(" Looking for \"%s\" ...\n", name); bigmisses++; - if (cached_stat(name, &mst) == 0) { + if (cached_stat(name, &cst) == 0) { return bmake_strdup(name); } DIR_DEBUG0(" failed. Returning NULL\n"); return NULL; -#endif /* notdef */ +#endif } @@ -1225,7 +1210,7 @@ Dir_FindFile(const char *name, SearchPath *path) char * Dir_FindHereOrAbove(const char *here, const char *search_path) { - struct make_stat mst; + struct cached_stat cst; char *dirbase, *dirbase_end; char *try, *try_end; @@ -1238,12 +1223,12 @@ Dir_FindHereOrAbove(const char *here, const char *search_path) /* try and stat(2) it ... */ try = str_concat3(dirbase, "/", search_path); - if (cached_stat(try, &mst) != -1) { + if (cached_stat(try, &cst) != -1) { /* * success! if we found a file, chop off * the filename so we return a directory. */ - if ((mst.mst_mode & S_IFMT) != S_IFDIR) { + if ((cst.cst_mode & S_IFMT) != S_IFDIR) { try_end = try + strlen(try); while (try_end > try && *try_end != '/') try_end--; @@ -1275,36 +1260,27 @@ Dir_FindHereOrAbove(const char *here, const char *search_path) return NULL; } -/*- - *----------------------------------------------------------------------- - * Dir_MTime -- - * Find the modification time of the file described by gn along the - * search path dirSearchPath. +/* Search gn along dirSearchPath and store its modification time in gn->mtime. + * If no file is found, store 0 instead. * - * Input: - * gn the file whose modification time is desired - * - * 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. - *----------------------------------------------------------------------- - */ -time_t -Dir_MTime(GNode *gn, Boolean recheck) + * The found file is stored in gn->path, unless the node already had a path. */ +void +Dir_UpdateMTime(GNode *gn, Boolean recheck) { - char *fullName; /* the full pathname of name */ - struct make_stat mst; /* buffer for finding the mod time */ + char *fullName; + struct cached_stat cst; if (gn->type & OP_ARCHV) { - return Arch_MTime(gn); - } else if (gn->type & OP_PHONY) { + Arch_UpdateMTime(gn); + return; + } + + if (gn->type & OP_PHONY) { gn->mtime = 0; - return 0; - } else if (gn->path == NULL) { + return; + } + + if (gn->path == NULL) { if (gn->type & OP_NOPATH) fullName = NULL; else { @@ -1344,25 +1320,24 @@ Dir_MTime(GNode *gn, Boolean recheck) fullName = gn->path; } - if (fullName == NULL) { + if (fullName == NULL) fullName = bmake_strdup(gn->name); - } - if (cached_stats(&mtimes, fullName, &mst, recheck ? CST_UPDATE : 0) < 0) { + if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) { if (gn->type & OP_MEMBER) { if (fullName != gn->path) free(fullName); - return Arch_MemMTime(gn); - } else { - mst.mst_mtime = 0; + Arch_UpdateMemberMTime(gn); + return; } + + cst.cst_mtime = 0; } if (fullName != NULL && gn->path == NULL) gn->path = fullName; - gn->mtime = mst.mst_mtime; - return gn->mtime; + gn->mtime = cst.cst_mtime; } /* Read the list of filenames in the directory and store the result @@ -1387,6 +1362,7 @@ Dir_AddDir(SearchPath *path, const char *name) if (path != NULL && strcmp(name, ".DOTLAST") == 0) { SearchPathNode *ln; + /* XXX: Linear search gets slow with thousands of entries. */ for (ln = path->first; ln != NULL; ln = ln->next) { CachedDir *pathDir = ln->datum; if (strcmp(pathDir->name, name) == 0) @@ -1410,7 +1386,7 @@ Dir_AddDir(SearchPath *path, const char *name) DIR_DEBUG1("Caching %s ...", name); if ((d = opendir(name)) != NULL) { - dir = bmake_malloc(sizeof(CachedDir)); + dir = bmake_malloc(sizeof *dir); dir->name = bmake_strdup(name); dir->hits = 0; dir->refCount = 1; @@ -1480,7 +1456,7 @@ Dir_MakeFlags(const char *flag, SearchPath *path) Buffer buf; SearchPathNode *ln; - Buf_Init(&buf, 0); + Buf_Init(&buf); if (path != NULL) { for (ln = path->first; ln != NULL; ln = ln->next) { @@ -1,4 +1,4 @@ -/* $NetBSD: dir.h,v 1.32 2020/10/25 10:00:20 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.34 2020/11/14 19:24:24 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -101,7 +101,7 @@ Boolean Dir_HasWildcards(const char *); void Dir_Expand(const char *, SearchPath *, StringList *); char *Dir_FindFile(const char *, SearchPath *); char *Dir_FindHereOrAbove(const char *, const char *); -time_t Dir_MTime(GNode *, Boolean); +void Dir_UpdateMTime(GNode *, Boolean); CachedDir *Dir_AddDir(SearchPath *, const char *); char *Dir_MakeFlags(const char *, SearchPath *); void Dir_ClearPath(SearchPath *); @@ -112,12 +112,12 @@ void Dir_Destroy(void *); SearchPath *Dir_CopyDirSearchPath(void); /* Stripped-down variant of struct stat. */ -struct make_stat { - time_t mst_mtime; - mode_t mst_mode; +struct cached_stat { + time_t cst_mtime; + mode_t cst_mode; }; -int cached_lstat(const char *, struct make_stat *); -int cached_stat(const char *, struct make_stat *); +int cached_lstat(const char *, struct cached_stat *); +int cached_stat(const char *, struct cached_stat *); #endif /* MAKE_DIR_H */ diff --git a/filemon/filemon_dev.c b/filemon/filemon_dev.c index 85e56689f10d..afedb3c57e58 100644 --- a/filemon/filemon_dev.c +++ b/filemon/filemon_dev.c @@ -1,4 +1,4 @@ -/* $NetBSD: filemon_dev.c,v 1.3 2020/07/10 15:53:30 sjg Exp $ */ +/* $NetBSD: filemon_dev.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */ /*- * Copyright (c) 2020 The NetBSD Foundation, Inc. @@ -65,7 +65,7 @@ filemon_open(void) int error; /* Allocate and zero a struct filemon object. */ - F = calloc(1, sizeof(*F)); + F = calloc(1, sizeof *F); if (F == NULL) return NULL; diff --git a/filemon/filemon_ktrace.c b/filemon/filemon_ktrace.c index 4d2a5450c093..857e72e78028 100644 --- a/filemon/filemon_ktrace.c +++ b/filemon/filemon_ktrace.c @@ -1,4 +1,4 @@ -/* $NetBSD: filemon_ktrace.c,v 1.3 2020/10/18 11:54:43 rillig Exp $ */ +/* $NetBSD: filemon_ktrace.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */ /*- * Copyright (c) 2019 The NetBSD Foundation, Inc. @@ -198,7 +198,7 @@ filemon_open(void) int error; /* Allocate and zero a struct filemon object. */ - F = calloc(1, sizeof(*F)); + F = calloc(1, sizeof *F); if (F == NULL) return NULL; @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */ +/* $NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -60,15 +60,7 @@ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $"); - -/* The .for loop substitutes the items as ${:U<value>...}, which means - * that characters that break this syntax must be backslash-escaped. */ -typedef enum ForEscapes { - FOR_SUB_ESCAPE_CHAR = 0x0001, - FOR_SUB_ESCAPE_BRACE = 0x0002, - FOR_SUB_ESCAPE_PAREN = 0x0004 -} ForEscapes; +MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $"); static int forLevel = 0; /* Nesting level */ @@ -120,30 +112,6 @@ For_Free(For *f) free(f); } -static ForEscapes -GetEscapes(const char *word) -{ - const char *p; - ForEscapes escapes = 0; - - for (p = word; *p != '\0'; p++) { - switch (*p) { - case ':': - case '$': - case '\\': - escapes |= FOR_SUB_ESCAPE_CHAR; - break; - case ')': - escapes |= FOR_SUB_ESCAPE_PAREN; - break; - case '}': - escapes |= FOR_SUB_ESCAPE_BRACE; - break; - } - } - return escapes; -} - static Boolean IsFor(const char *p) { @@ -191,11 +159,11 @@ For_Eval(const char *line) */ f = bmake_malloc(sizeof *f); - Buf_Init(&f->body, 0); + Buf_Init(&f->body); Vector_Init(&f->vars, sizeof(ForVar)); f->items.words = NULL; f->items.freeIt = NULL; - Buf_Init(&f->curBody, 0); + Buf_Init(&f->curBody); f->short_var = FALSE; f->sub_next = 0; @@ -302,7 +270,7 @@ for_var_len(const char *var) size_t len; var_start = *var; - if (var_start == 0) + if (var_start == '\0') /* just escape the $ */ return 0; @@ -315,7 +283,7 @@ for_var_len(const char *var) return 1; depth = 1; - for (len = 1; (ch = var[len++]) != 0;) { + for (len = 1; (ch = var[len++]) != '\0';) { if (ch == var_start) depth++; else if (ch == var_end && --depth == 0) @@ -326,18 +294,30 @@ for_var_len(const char *var) return 0; } +/* The .for loop substitutes the items as ${:U<value>...}, which means + * that characters that break this syntax must be backslash-escaped. */ +static Boolean +NeedsEscapes(const char *word, char endc) +{ + const char *p; + + for (p = word; *p != '\0'; p++) { + if (*p == ':' || *p == '$' || *p == '\\' || *p == endc) + return TRUE; + } + return FALSE; +} + /* While expanding the body of a .for loop, write the item in the ${:U...} - * expression, escaping characters as needed. See ApplyModifier_Defined. */ + * expression, escaping characters as needed. + * + * The result is later unescaped by ApplyModifier_Defined. */ static void Buf_AddEscaped(Buffer *cmds, const char *item, char ech) { - ForEscapes escapes = GetEscapes(item); char ch; - /* If there were no escapes, or the only escape is the other variable - * terminator, then just substitute the full string */ - if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE - : ~(unsigned)FOR_SUB_ESCAPE_PAREN))) { + if (!NeedsEscapes(item, ech)) { Buf_AddStr(cmds, item); return; } @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -74,7 +74,7 @@ #include "make.h" /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to @@ -128,7 +128,7 @@ void HashTable_Init(HashTable *t) { unsigned int n = 16, i; - HashEntry **buckets = bmake_malloc(sizeof(*buckets) * n); + HashEntry **buckets = bmake_malloc(sizeof *buckets * n); for (i = 0; i < n; i++) buckets[i] = NULL; @@ -195,7 +195,7 @@ HashTable_Enlarge(HashTable *t) HashEntry **oldBuckets = t->buckets; unsigned int newSize = 2 * oldSize; unsigned int newMask = newSize - 1; - HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize); + HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize); size_t i; for (i = 0; i < newSize; i++) @@ -239,7 +239,7 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) if (t->numEntries >= rebuildLimit * t->bucketsSize) HashTable_Enlarge(t); - he = bmake_malloc(sizeof(*he) + keylen); + he = bmake_malloc(sizeof *he + keylen); he->value = NULL; he->key_hash = h; memcpy(he->key, key, keylen + 1); @@ -253,6 +253,14 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) return he; } +HashEntry * +HashTable_Set(HashTable *t, const char *key, void *value) +{ + HashEntry *he = HashTable_CreateEntry(t, key, NULL); + HashEntry_Set(he, value); + return he; +} + /* Delete the entry from the table and free the associated memory. */ void HashTable_DeleteEntry(HashTable *t, HashEntry *he) @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.31 2020/10/25 19:19:07 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.33 2020/11/14 21:29:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -103,13 +103,13 @@ typedef struct HashIter { HashEntry *entry; /* Next entry to check in current bucket. */ } HashIter; -static inline MAKE_ATTR_UNUSED void * +MAKE_INLINE void * HashEntry_Get(HashEntry *h) { return h->value; } -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void HashEntry_Set(HashEntry *h, void *datum) { h->value = datum; @@ -122,6 +122,7 @@ void *HashTable_FindValue(HashTable *, const char *); unsigned int Hash_Hash(const char *); void *HashTable_FindValueHash(HashTable *, const char *, unsigned int); HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *); +HashEntry *HashTable_Set(HashTable *, const char *, void *); void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DebugStats(HashTable *, const char *); @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $ */ +/* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -156,7 +156,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $"); /* A shell defines how the commands are run. All commands for a target are * written into a single file, which is then given to the shell to execute @@ -182,7 +182,7 @@ MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $"); * status. Finally errExit is a printf template for running the command and * causing the shell to exit on error. If any of these strings are empty when * hasErrCtl is FALSE, the command will be executed anyway as is, and if it - * causes an error, so be it. Any templates setup to echo the command will + * causes an error, so be it. Any templates set up to echo the command will * escape any '$ ` \ "' characters in the command string to avoid common * problems with echo "%s\n" as a template. * @@ -388,8 +388,8 @@ static char *shellArgv = NULL; /* Custom shell args */ static Job *job_table; /* The structures that describe them */ static Job *job_table_end; /* job_table + maxJobs */ static unsigned int wantToken; /* we want a token */ -static int lurking_children = 0; -static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ +static Boolean lurking_children = FALSE; +static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to @@ -443,7 +443,7 @@ job_table_dump(const char *where) debug_printf("job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { debug_printf("job %d, status %d, flags %d, pid %d\n", - (int)(job - job_table), job->job_state, job->flags, job->pid); + (int)(job - job_table), job->status, job->flags, job->pid); } } @@ -539,7 +539,7 @@ JobCondPassSig(int signo) DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) + if (job->status != JOB_ST_RUNNING) continue; DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", signo, job->pid); @@ -596,7 +596,7 @@ JobPassSig_suspend(int signo) struct sigaction act; /* Suppress job started/continued messages */ - make_suspended = 1; + make_suspended = TRUE; /* Pass the signal onto every job */ JobCondPassSig(signo); @@ -646,12 +646,12 @@ JobPassSig_suspend(int signo) } static Job * -JobFindPid(int pid, JobState status, Boolean isJobs) +JobFindPid(int pid, JobStatus status, Boolean isJobs) { Job *job; for (job = job_table; job < job_table_end; job++) { - if ((job->job_state == status) && job->pid == pid) + if (job->status == status && job->pid == pid) return job; } if (DEBUG(JOB) && isJobs) @@ -705,6 +705,22 @@ EscapeShellDblQuot(const char *cmd) return esc; } +static void +JobPrintf(Job *job, const char *fmt, const char *arg) +{ + if (DEBUG(JOB)) + debug_printf(fmt, arg); + + (void)fprintf(job->cmdFILE, fmt, arg); + (void)fflush(job->cmdFILE); +} + +static void +JobPrintln(Job *job, const char *line) +{ + JobPrintf(job, "%s\n", line); +} + /*- *----------------------------------------------------------------------- * JobPrintCommand -- @@ -717,7 +733,7 @@ EscapeShellDblQuot(const char *cmd) * If the command is just "..." we take all future commands for this * job to be commands to be executed once the entire graph has been * made and return non-zero to signal that the end of the commands - * was reached. These commands are later attached to the postCommands + * was reached. These commands are later attached to the .END * node and executed by Job_End when all things are done. * * Side Effects: @@ -746,12 +762,6 @@ JobPrintCommand(Job *job, char *cmd) noSpecials = !GNode_ShouldExecute(job->node); -#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ - debug_printf(fmt, arg); \ - } \ - (void)fprintf(job->cmdFILE, fmt, arg); \ - (void)fflush(job->cmdFILE); - numCommands++; Var_Subst(cmd, job->node, VARE_WANTRES, &cmd); @@ -783,8 +793,8 @@ JobPrintCommand(Job *job, char *cmd) if (shutUp) { if (!(job->flags & JOB_SILENT) && !noSpecials && - commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); + (commandShell->hasEchoCtl)) { + JobPrintln(job, commandShell->echoOff); } else { if (commandShell->hasErrCtl) shutUp = FALSE; @@ -803,16 +813,15 @@ JobPrintCommand(Job *job, char *cmd) * it already is? */ if (!(job->flags & JOB_SILENT) && !shutUp && - commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); - DBPRINTF("%s\n", commandShell->echoOn); + (commandShell->hasEchoCtl)) { + JobPrintln(job, commandShell->echoOff); + JobPrintln(job, commandShell->errOffOrExecIgnore); + JobPrintln(job, commandShell->echoOn); } else { - DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); + JobPrintln(job, commandShell->errOffOrExecIgnore); } } else if (commandShell->errOffOrExecIgnore && - commandShell->errOffOrExecIgnore[0] != '\0') - { + commandShell->errOffOrExecIgnore[0] != '\0') { /* * The shell has no error control, so we need to be * weird to get it to ignore any errors from the command. @@ -824,15 +833,14 @@ JobPrintCommand(Job *job, char *cmd) */ job->flags |= JOB_IGNERR; if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } - DBPRINTF(commandShell->errOnOrEcho, escCmd); - shutUp = TRUE; + if (commandShell->hasEchoCtl) { + JobPrintln(job, commandShell->echoOff); + } + JobPrintf(job, commandShell->errOnOrEcho, escCmd); + shutUp = TRUE; } else { - if (!shutUp) { - DBPRINTF(commandShell->errOnOrEcho, escCmd); - } + if (!shutUp) + JobPrintf(job, commandShell->errOnOrEcho, escCmd); } cmdTemplate = commandShell->errOffOrExecIgnore; /* @@ -851,36 +859,35 @@ JobPrintCommand(Job *job, char *cmd) /* * If errors are being checked and the shell doesn't have error control - * but does supply an errExit template, then setup commands to run + * but does supply an errExit template, then set up commands to run * through it. */ if (!commandShell->hasErrCtl && commandShell->errExit && commandShell->errExit[0] != '\0') { - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } - DBPRINTF(commandShell->errOnOrEcho, escCmd); - shutUp = TRUE; - } - /* If it's a comment line or blank, treat as an ignored error */ - if ((escCmd[0] == commandShell->commentChar) || - (escCmd[0] == 0)) - cmdTemplate = commandShell->errOffOrExecIgnore; - else - cmdTemplate = commandShell->errExit; - errOff = FALSE; + if (!(job->flags & JOB_SILENT) && !shutUp) { + if (commandShell->hasEchoCtl) + JobPrintln(job, commandShell->echoOff); + JobPrintf(job, commandShell->errOnOrEcho, escCmd); + shutUp = TRUE; + } + /* If it's a comment line or blank, treat as an ignored error */ + if (escCmd[0] == commandShell->commentChar || + (escCmd[0] == '\0')) + cmdTemplate = commandShell->errOffOrExecIgnore; + else + cmdTemplate = commandShell->errExit; + errOff = FALSE; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && - (job->flags & JOB_TRACED) == 0) { - DBPRINTF("set -%s\n", "x"); - job->flags |= JOB_TRACED; + !(job->flags & JOB_TRACED)) { + JobPrintln(job, "set -x"); + job->flags |= JOB_TRACED; } - DBPRINTF(cmdTemplate, cmd); + JobPrintf(job, cmdTemplate, cmd); free(cmdStart); free(escCmd); if (errOff) { @@ -889,15 +896,14 @@ JobPrintCommand(Job *job, char *cmd) * echoOff command. Otherwise we issue it and pretend it was on * for the whole command... */ - if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){ - DBPRINTF("%s\n", commandShell->echoOff); + if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) { + JobPrintln(job, commandShell->echoOff); shutUp = TRUE; } - DBPRINTF("%s\n", commandShell->errOnOrEcho); - } - if (shutUp && commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOn); + JobPrintln(job, commandShell->errOnOrEcho); } + if (shutUp && commandShell->hasEchoCtl) + JobPrintln(job, commandShell->echoOn); } /* Print all commands to the shell file that is later executed. @@ -914,12 +920,11 @@ JobPrintCommands(Job *job) if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; - if ((job->flags & JOB_IGNDOTS) == 0) { - job->tailCmds = ln->next; - break; - } - } else - JobPrintCommand(job, ln->datum); + job->tailCmds = ln->next; + break; + } + + JobPrintCommand(job, ln->datum); } } @@ -944,7 +949,7 @@ JobSaveCommands(Job *job) /* Called to close both input and output pipes when a job is finished. */ static void -JobClose(Job *job) +JobClosePipes(Job *job) { clearfd(job); (void)close(job->outPipe); @@ -955,28 +960,17 @@ JobClose(Job *job) job->inPipe = -1; } -/*- - *----------------------------------------------------------------------- - * JobFinish -- - * Do final processing for the given job including updating - * parents and starting new jobs as available/necessary. Note - * that we pay no attention to the JOB_IGNERR flag here. - * This is because when we're called because of a noexecute flag - * or something, jstat.w_status is 0 and when called from - * Job_CatchChildren, the status is zeroed if it s/b ignored. +/* Do final processing for the given job including updating parent nodes and + * starting new jobs as available/necessary. + * + * Deferred commands for the job are placed on the .END node. + * + * If there was a serious error (errors != 0; not an ignored one), no more + * jobs will be started. * * Input: * job job to finish * status sub-why job went away - * - * Side Effects: - * Final commands for the job are placed on postCommands. - * - * If we got an error and are aborting (aborting == ABORT_ERROR) and - * the job list is now empty, we are done for the day. - * If we recognized an error (errors !=0), we set the aborting flag - * to ABORT_ERROR so no more jobs will be started. - *----------------------------------------------------------------------- */ static void JobFinish (Job *job, WAIT_T status) @@ -987,7 +981,7 @@ JobFinish (Job *job, WAIT_T status) job->pid, job->node->name, status); if ((WIFEXITED(status) && - (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || + ((WEXITSTATUS(status) != 0 && !(job->flags & JOB_IGNERR)))) || WIFSIGNALED(status)) { /* @@ -998,7 +992,7 @@ JobFinish (Job *job, WAIT_T status) * cases, finish out the job's output before printing the exit * status... */ - JobClose(job); + JobClosePipes(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; @@ -1007,19 +1001,11 @@ JobFinish (Job *job, WAIT_T status) } else if (WIFEXITED(status)) { /* * Deal with ignored errors in -B mode. We need to print a message - * telling of the ignored error as well as setting status.w_status - * to 0 so the next command gets run. To do this, we set done to be - * TRUE if in -B mode and the job exited non-zero. + * telling of the ignored error as well as to run the next command. + * */ done = WEXITSTATUS(status) != 0; - /* - * Old comment said: "Note we don't - * want to close down any of the streams until we know we're at the - * end." - * But we do. Otherwise when are we going to print the rest of the - * stuff? - */ - JobClose(job); + JobClosePipes(job); } else { /* * No need to close things down or anything. @@ -1041,7 +1027,7 @@ JobFinish (Job *job, WAIT_T status) meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); } #endif - if (!dieQuietly(job->node, -1)) + if (!shouldDieQuietly(job->node, -1)) (void)printf("*** [%s] Error code %d%s\n", job->node->name, WEXITSTATUS(status), @@ -1078,11 +1064,9 @@ JobFinish (Job *job, WAIT_T status) #ifdef USE_META if (useMeta) { - int x; - - if ((x = meta_job_finish(job)) != 0 && status == 0) { - status = x; - } + int meta_status = meta_job_finish(job); + if (meta_status != 0 && status == 0) + status = meta_status; } #endif @@ -1090,13 +1074,12 @@ JobFinish (Job *job, WAIT_T status) Trace_Log(JOBEND, job); if (!(job->flags & JOB_SPECIAL)) { - if ((WAIT_STATUS(status) != 0) || - (aborting == ABORT_ERROR) || - (aborting == ABORT_INTERRUPT)) + if (WAIT_STATUS(status) != 0 || + (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) return_job_token = TRUE; } - if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && + if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && (WAIT_STATUS(status) == 0)) { /* * As long as we aren't aborting and the job didn't return a non-zero @@ -1108,33 +1091,50 @@ JobFinish (Job *job, WAIT_T status) if (!(job->flags & JOB_SPECIAL)) return_job_token = TRUE; Make_Update(job->node); - job->job_state = JOB_ST_FREE; + job->status = JOB_ST_FREE; } else if (WAIT_STATUS(status)) { errors++; - job->job_state = JOB_ST_FREE; + job->status = JOB_ST_FREE; } - /* - * Set aborting if any error. - */ - if (errors && !opts.keepgoing && (aborting != ABORT_INTERRUPT)) { - /* - * If we found any errors in this batch of children and the -k flag - * wasn't given, we set the aborting flag so no more jobs get - * started. - */ - aborting = ABORT_ERROR; - } + if (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) + aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */ if (return_job_token) Job_TokenReturn(); - if (aborting == ABORT_ERROR && jobTokensRunning == 0) { - /* - * If we are aborting and the job table is now empty, we finish. - */ + if (aborting == ABORT_ERROR && jobTokensRunning == 0) Finish(errors); +} + +static void +TouchRegular(GNode *gn) +{ + const char *file = GNode_Path(gn); + struct utimbuf times = { now, now }; + int fd; + char c; + + if (utime(file, ×) >= 0) + return; + + fd = open(file, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + (void)fprintf(stderr, "*** couldn't touch %s: %s\n", + file, strerror(errno)); + (void)fflush(stderr); + return; /* XXX: What about propagating the error? */ } + + /* Last resort: update the file's time stamps in the traditional way. + * XXX: This doesn't work for empty files, which are sometimes used + * as marker files. */ + if (read(fd, &c, 1) == 1) { + (void)lseek(fd, 0, SEEK_SET); + while (write(fd, &c, 1) == -1 && errno == EAGAIN) + continue; + } + (void)close(fd); /* XXX: What about propagating the error? */ } /* Touch the given target. Called by JobStart when the -t flag was given. @@ -1144,15 +1144,9 @@ JobFinish (Job *job, WAIT_T status) void Job_Touch(GNode *gn, Boolean silent) { - int streamID; /* ID of stream opened to do the touch */ - struct utimbuf times; /* Times for utime() call */ - if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| OP_SPECIAL|OP_PHONY)) { - /* - * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets - * and, as such, shouldn't really be created. - */ + /* These are "virtual" targets and should not really be created. */ return; } @@ -1161,42 +1155,20 @@ Job_Touch(GNode *gn, Boolean silent) (void)fflush(stdout); } - if (!GNode_ShouldExecute(gn)) { + if (!GNode_ShouldExecute(gn)) return; - } if (gn->type & OP_ARCHV) { Arch_Touch(gn); - } else if (gn->type & OP_LIB) { - Arch_TouchLib(gn); - } else { - const char *file = GNode_Path(gn); - - times.actime = times.modtime = now; - if (utime(file, ×) < 0){ - streamID = open(file, O_RDWR | O_CREAT, 0666); - - if (streamID >= 0) { - char c; - - /* - * Read and write a byte to the file to change the - * modification time, then close the file. - */ - if (read(streamID, &c, 1) == 1) { - (void)lseek(streamID, (off_t)0, SEEK_SET); - while (write(streamID, &c, 1) == -1 && errno == EAGAIN) - continue; - } + return; + } - (void)close(streamID); - } else { - (void)fprintf(stdout, "*** couldn't touch %s: %s", - file, strerror(errno)); - (void)fflush(stdout); - } - } + if (gn->type & OP_LIB) { + Arch_TouchLib(gn); + return; } + + TouchRegular(gn); } /* Make sure the given node has all the commands it needs. @@ -1223,25 +1195,25 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) /* * No commands. Look for .DEFAULT rule from which we might infer - * commands + * commands. */ - if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && - (gn->type & OP_SPECIAL) == 0) { + if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) && + !(gn->type & OP_SPECIAL)) { /* - * Make only looks for a .DEFAULT if the node was never the - * target of an operator, so that's what we do too. If - * a .DEFAULT was given, we substitute its commands for gn's - * commands and set the IMPSRC variable to be the target's name - * The DEFAULT node acts like a transformation rule, in that + * The traditional Make only looks for a .DEFAULT if the node was + * never the target of an operator, so that's what we do too. + * + * The .DEFAULT node acts like a transformation rule, in that * gn also inherits any attributes or sources attached to * .DEFAULT itself. */ - Make_HandleUse(DEFAULT, gn); + Make_HandleUse(defaultNode, gn); Var_Set(IMPSRC, GNode_VarTarget(gn), gn); return TRUE; } - if (Dir_MTime(gn, 0) != 0 || (gn->type & OP_SPECIAL)) + Dir_UpdateMTime(gn, FALSE); + if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) return TRUE; /* @@ -1280,21 +1252,19 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) /* Execute the shell for the given job. * - * A shell is executed, its output is altered and the Job structure added - * to the job table. - */ + * See Job_CatchOutput for handling the output of the shell. */ static void JobExec(Job *job, char **argv) { int cpid; /* ID of new child */ - sigset_t mask; + sigset_t mask; job->flags &= ~JOB_TRACED; if (DEBUG(JOB)) { int i; - debug_printf("Running %s %sly\n", job->node->name, "local"); + debug_printf("Running %s\n", job->node->name); debug_printf("\tCommand: "); for (i = 0; argv[i] != NULL; i++) { debug_printf("%s ", argv[i]); @@ -1317,7 +1287,7 @@ JobExec(Job *job, char **argv) JobSigLock(&mask); /* Pre-emptively mark job running, pid still zero though */ - job->job_state = JOB_ST_RUNNING; + job->status = JOB_ST_RUNNING; cpid = vFork(); if (cpid == -1) @@ -1351,17 +1321,17 @@ JobExec(Job *job, char **argv) execDie("dup2", "job->cmdFILE"); if (fcntl(0, F_SETFD, 0) == -1) execDie("fcntl clear close-on-exec", "stdin"); - if (lseek(0, (off_t)0, SEEK_SET) == -1) + if (lseek(0, 0, SEEK_SET) == -1) execDie("lseek to 0", "stdin"); if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { - /* - * Pass job token pipe to submakes. - */ - if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.inPipe"); - if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.outPipe"); + /* + * Pass job token pipe to submakes. + */ + if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "tokenWaitJob.inPipe"); + if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "tokenWaitJob.outPipe"); } /* @@ -1458,7 +1428,7 @@ JobMakeArgv(Job *job, char **argv) * Bourne shell thinks its second argument is a file to source. * Grrrr. Note the ten-character limitation on the combined arguments. */ - (void)snprintf(args, sizeof(args), "-%s%s", + (void)snprintf(args, sizeof args, "-%s%s", ((job->flags & JOB_IGNERR) ? "" : (commandShell->exit ? commandShell->exit : "")), ((job->flags & JOB_SILENT) ? "" : @@ -1490,7 +1460,6 @@ JobMakeArgv(Job *job, char **argv) * Input: * gn target to create * flags flags for the job to override normal ones. - * e.g. JOB_SPECIAL or JOB_IGNDOTS * previous The previous Job structure for this node, if any. * * Results: @@ -1502,13 +1471,11 @@ JobMakeArgv(Job *job, char **argv) * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. * - * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set - * JOB_IGNDOTS is never set (dsl) - * Also the return value is ignored by everyone. + * NB: The return value is ignored by everyone. *----------------------------------------------------------------------- */ static JobStartResult -JobStart(GNode *gn, int flags) +JobStart(GNode *gn, JobFlags flags) { Job *job; /* new job descriptor */ char *argv[10]; /* Argument vector to shell */ @@ -1517,33 +1484,24 @@ JobStart(GNode *gn, int flags) int tfd; /* File descriptor to the temp file */ for (job = job_table; job < job_table_end; job++) { - if (job->job_state == JOB_ST_FREE) + if (job->status == JOB_ST_FREE) break; } if (job >= job_table_end) Punt("JobStart no job slots vacant"); memset(job, 0, sizeof *job); - job->job_state = JOB_ST_SETUP; - if (gn->type & OP_SPECIAL) - flags |= JOB_SPECIAL; - job->node = gn; job->tailCmds = NULL; + job->status = JOB_ST_SET_UP; - /* - * Set the initial value of the flags for this job based on the global - * ones and the node's attributes... Any flags supplied by the caller - * are also added to the field. - */ - job->flags = 0; - if (Targ_Ignore(gn)) { - job->flags |= JOB_IGNERR; - } - if (Targ_Silent(gn)) { - job->flags |= JOB_SILENT; - } - job->flags |= flags; + if (gn->type & OP_SPECIAL) + flags |= JOB_SPECIAL; + if (Targ_Ignore(gn)) + flags |= JOB_IGNERR; + if (Targ_Silent(gn)) + flags |= JOB_SILENT; + job->flags = flags; /* * Check the commands now so any attributes from .DEFAULT have a chance @@ -1559,7 +1517,7 @@ JobStart(GNode *gn, int flags) * we just set the file to be stdout. Cute, huh? */ if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || - (!opts.noExecute && !opts.touchFlag)) { + (!opts.noExecute && !opts.touchFlag)) { /* * tfile is the name of a file into which all shell commands are * put. It is removed before the child shell is executed, unless @@ -1579,13 +1537,13 @@ JobStart(GNode *gn, int flags) JobSigLock(&mask); tfd = mkTempFile(TMPPAT, &tfile); if (!DEBUG(SCRIPT)) - (void)eunlink(tfile); + (void)eunlink(tfile); JobSigUnlock(&mask); job->cmdFILE = fdopen(tfd, "w+"); - if (job->cmdFILE == NULL) { + if (job->cmdFILE == NULL) Punt("Could not fdopen %s", tfile); - } + (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); /* * Send the commands to the command file, flush all its buffers then @@ -1596,9 +1554,8 @@ JobStart(GNode *gn, int flags) #ifdef USE_META if (useMeta) { meta_job_start(job, gn); - if (Targ_Silent(gn)) { /* might have changed */ + if (Targ_Silent(gn)) /* might have changed */ job->flags |= JOB_SILENT; - } } #endif /* @@ -1645,7 +1602,7 @@ JobStart(GNode *gn, int flags) * up the graph. */ job->cmdFILE = stdout; - Job_Touch(gn, job->flags&JOB_SILENT); + Job_Touch(gn, job->flags & JOB_SILENT); noExec = TRUE; } /* Just in case it isn't already... */ @@ -1660,11 +1617,9 @@ JobStart(GNode *gn, int flags) /* * Unlink and close the command file if we opened one */ - if (job->cmdFILE != stdout) { - if (job->cmdFILE != NULL) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; } /* @@ -1676,7 +1631,7 @@ JobStart(GNode *gn, int flags) job->node->made = MADE; Make_Update(job->node); } - job->job_state = JOB_ST_FREE; + job->status = JOB_ST_FREE; return cmdsOK ? JOB_FINISHED : JOB_ERROR; } @@ -1693,71 +1648,58 @@ JobStart(GNode *gn, int flags) return JOB_RUNNING; } +/* Print the output of the shell command, skipping the noPrint command of + * the shell, if any. */ static char * JobOutput(Job *job, char *cp, char *endp) { char *ecp; - if (commandShell->noPrint && commandShell->noPrint[0] != '\0') { - while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { - if (cp != ecp) { - *ecp = '\0'; - /* - * The only way there wouldn't be a newline after - * this line is if it were the last in the buffer. - * however, since the non-printable comes after it, - * there must be a newline, so we don't print one. - */ - (void)fprintf(stdout, "%s", cp); - (void)fflush(stdout); - } - cp = ecp + commandShell->noPrintLen; - if (cp != endp) { - /* - * Still more to print, look again after skipping - * the whitespace following the non-printable - * command.... - */ - cp++; - while (*cp == ' ' || *cp == '\t' || *cp == '\n') { - cp++; - } - } else { - return cp; - } + if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0') + return cp; + + while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { + if (ecp != cp) { + *ecp = '\0'; + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the non-printable comes after it, + * there must be a newline, so we don't print one. + */ + (void)fprintf(stdout, "%s", cp); + (void)fflush(stdout); + } + cp = ecp + commandShell->noPrintLen; + if (cp != endp) { + /* + * Still more to print, look again after skipping + * the whitespace following the non-printable + * command.... + */ + cp++; + pp_skip_whitespace(&cp); + } else { + return cp; } } return cp; } -/*- - *----------------------------------------------------------------------- - * JobDoOutput -- - * This function is called at different times depending on - * whether the user has specified that output is to be collected - * via pipes or temporary files. In the former case, we are called - * whenever there is something to read on the pipe. We collect more - * output from the given job and store it in the job's outBuf. If - * this makes up a line, we print it tagged by the job's identifier, - * as necessary. - * If output has been collected in a temporary file, we open the - * file and read it line by line, transferring it to our own - * output channel until the file is empty. At which point we - * remove the temporary file. - * In both cases, however, we keep our figurative eye out for the - * 'noPrint' line for the shell from which the output came. If - * we recognize a line, we don't print it. If the command is not - * alone on the line (the character after it is not \0 or \n), we - * do print whatever follows it. +/* + * This function is called whenever there is something to read on the pipe. + * We collect more output from the given job and store it in the job's + * outBuf. If this makes up a line, we print it tagged by the job's + * identifier, as necessary. + * + * In the output of the shell, the 'noPrint' lines are removed. If the + * command is not alone on the line (the character after it is not \0 or + * \n), we do print whatever follows it. * * Input: * job the job whose output needs printing * finish TRUE if this is the last time we'll be called * for this job - * - * Side Effects: - * curPos may be shifted as may the contents of outBuf. - *----------------------------------------------------------------------- */ static void JobDoOutput(Job *job, Boolean finish) @@ -1772,12 +1714,12 @@ JobDoOutput(Job *job, Boolean finish) /* * Read as many bytes as will fit in the buffer. */ -end_loop: +again: gotNL = FALSE; fbuf = FALSE; nRead = read(job->inPipe, &job->outBuf[job->curPos], - JOB_BUFSIZE - job->curPos); + JOB_BUFSIZE - job->curPos); if (nRead < 0) { if (errno == EAGAIN) return; @@ -1795,7 +1737,7 @@ end_loop: * output remaining in the buffer. * Also clear the 'finish' flag so we stop looping. */ - if ((nr == 0) && (job->curPos != 0)) { + if (nr == 0 && job->curPos != 0) { job->outBuf[job->curPos] = '\n'; nr = 1; finish = FALSE; @@ -1890,7 +1832,7 @@ end_loop: * we do get an EOF, finish will be set FALSE and we'll fall * through and out. */ - goto end_loop; + goto again; } } @@ -1988,13 +1930,13 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) (void)printf("*** [%s] Stopped -- signal %d\n", job->node->name, WSTOPSIG(status)); } - job->job_suspended = 1; + job->suspended = TRUE; } (void)fflush(stdout); return; } - job->job_state = JOB_ST_FINISHED; + job->status = JOB_ST_FINISHED; job->exit_status = WAIT_STATUS(status); JobFinish(job, status); @@ -2038,7 +1980,7 @@ Job_CatchOutput(void) default: abort(); } - --nready; + nready--; } Job_CatchChildren(); @@ -2049,7 +1991,7 @@ Job_CatchOutput(void) if (!fds[i].revents) continue; job = jobfds[i]; - if (job->job_state == JOB_ST_RUNNING) + if (job->status == JOB_ST_RUNNING) JobDoOutput(job, FALSE); #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) /* @@ -2073,7 +2015,7 @@ Job_CatchOutput(void) void Job_Make(GNode *gn) { - (void)JobStart(gn, 0); + (void)JobStart(gn, JOB_NONE); } void @@ -2094,7 +2036,7 @@ Shell_Init(void) #endif shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); } - Var_Set_with_flags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); + Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); if (commandShell->exit == NULL) { commandShell->exit = ""; } @@ -2172,7 +2114,7 @@ Job_Init(void) if (rval > 0) continue; if (rval == 0) - lurking_children = 1; + lurking_children = TRUE; break; } @@ -2181,9 +2123,9 @@ Job_Init(void) JobCreatePipe(&childExitJob, 3); /* Preallocate enough for the maximum number of jobs. */ - fds = bmake_malloc(sizeof(*fds) * + fds = bmake_malloc(sizeof *fds * (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); - jobfds = bmake_malloc(sizeof(*jobfds) * + jobfds = bmake_malloc(sizeof *jobfds * (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); /* These are permanent entries and take slots 0 and 1 */ @@ -2328,7 +2270,7 @@ Job_ParseShell(char *line) free(shellArgv); - memset(&newShell, 0, sizeof(newShell)); + memset(&newShell, 0, sizeof newShell); /* * Parse the specification by keyword @@ -2440,7 +2382,7 @@ Job_ParseShell(char *line) } commandShell = sh; } else { - commandShell = bmake_malloc(sizeof(Shell)); + commandShell = bmake_malloc(sizeof *commandShell); *commandShell = newShell; } /* this will take care of shellErrFlag */ @@ -2491,7 +2433,7 @@ JobInterrupt(int runINTERRUPT, int signo) JobSigLock(&mask); for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) + if (job->status != JOB_ST_RUNNING) continue; gn = job->node; @@ -2513,7 +2455,7 @@ JobInterrupt(int runINTERRUPT, int signo) JobRun(interrupt); } } - Trace_Log(MAKEINTR, 0); + Trace_Log(MAKEINTR, NULL); exit(signo); } @@ -2570,7 +2512,7 @@ Job_AbortAll(void) if (jobTokensRunning) { for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) + if (job->status != JOB_ST_RUNNING) continue; /* * kill the child process with increasingly drastic signals to make @@ -2596,23 +2538,23 @@ JobRestartJobs(void) Job *job; for (job = job_table; job < job_table_end; job++) { - if (job->job_state == JOB_ST_RUNNING && - (make_suspended || job->job_suspended)) { + if (job->status == JOB_ST_RUNNING && + (make_suspended || job->suspended)) { DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); - if (job->job_suspended) { + if (job->suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } - job->job_suspended = 0; + job->suspended = FALSE; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { debug_printf("Failed to send SIGCONT to %d\n", job->pid); } } - if (job->job_state == JOB_ST_FINISHED) + if (job->status == JOB_ST_FINISHED) /* Job exit deferred after calling waitpid() in a signal handler */ JobFinish(job, job->exit_status); } - make_suspended = 0; + make_suspended = FALSE; } static void @@ -2716,7 +2658,7 @@ Job_ServerStart(int max_tokens, int jp_0, int jp_1) JobCreatePipe(&tokenWaitJob, 15); - snprintf(jobarg, sizeof(jobarg), "%d,%d", + snprintf(jobarg, sizeof jobarg, "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); @@ -2784,7 +2726,7 @@ Job_TokenWithdraw(void) /* And put the stopper back */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; - if (dieQuietly(NULL, 1)) + if (shouldDieQuietly(NULL, 1)) exit(2); Fatal("A failure has been detected in another branch of the parallel make"); } @@ -2860,7 +2802,7 @@ emul_poll(struct pollfd *fd, int nfd, int timeout) tvp = &tv; } - nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); + nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp); if (nselect <= 0) return nselect; @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.58 2020/10/26 21:34:10 rillig Exp $ */ +/* $NetBSD: job.h,v 1.63 2020/11/14 13:27:01 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -117,25 +117,25 @@ struct pollfd; # include "meta.h" #endif -typedef enum JobState { +typedef enum JobStatus { JOB_ST_FREE = 0, /* Job is available */ - JOB_ST_SETUP = 1, /* Job is allocated but otherwise invalid */ + JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */ + /* XXX: What about the 2? */ JOB_ST_RUNNING = 3, /* Job is running, pid valid */ JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ -} JobState; +} JobStatus; typedef enum JobFlags { + JOB_NONE = 0, /* Ignore non-zero exits */ - JOB_IGNERR = 0x001, + JOB_IGNERR = 1 << 0, /* no output */ - JOB_SILENT = 0x002, + JOB_SILENT = 1 << 1, /* Target is a special one. i.e. run it locally * if we can't export it and maxLocal is 0 */ - JOB_SPECIAL = 0x004, - /* Ignore "..." lines when processing commands */ - JOB_IGNDOTS = 0x008, + JOB_SPECIAL = 1 << 2, /* we've sent 'set -x' */ - JOB_TRACED = 0x400 + JOB_TRACED = 1 << 10 } JobFlags; /* A Job manages the shell commands that are run to create a single target. @@ -167,9 +167,9 @@ typedef struct Job { int exit_status; /* from wait4() in signal handler */ - JobState job_state; /* status of the job entry */ + JobStatus status; - char job_suspended; + Boolean suspended; JobFlags flags; /* Flags to control treatment of job */ @@ -1,4 +1,4 @@ -/* $NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -34,7 +34,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $"); +MAKE_RCSID("$NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $"); #ifdef HAVE_INTTYPES_H #include <inttypes.h> @@ -45,11 +45,11 @@ MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $"); static ListNode * LstNodeNew(ListNode *prev, ListNode *next, void *datum) { - ListNode *node = bmake_malloc(sizeof *node); - node->prev = prev; - node->next = next; - node->datum = datum; - return node; + ListNode *ln = bmake_malloc(sizeof *ln); + ln->prev = prev; + ln->next = next; + ln->datum = datum; + return ln; } /* Create and initialize a new, empty list. */ @@ -68,12 +68,11 @@ Lst_New(void) void Lst_Free(List *list) { - ListNode *node; - ListNode *next; + ListNode *ln, *next; - for (node = list->first; node != NULL; node = next) { - next = node->next; - free(node); + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + free(ln); } free(list); @@ -84,37 +83,32 @@ Lst_Free(List *list) void Lst_Destroy(List *list, LstFreeProc freeProc) { - ListNode *node; - ListNode *next; + ListNode *ln, *next; - for (node = list->first; node != NULL; node = next) { - next = node->next; - freeProc(node->datum); - free(node); + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + freeProc(ln->datum); + free(ln); } free(list); } -/* - * Functions to modify a list - */ - /* Insert a new node with the datum before the given node. */ void -Lst_InsertBefore(List *list, ListNode *node, void *datum) +Lst_InsertBefore(List *list, ListNode *ln, void *datum) { ListNode *newNode; assert(datum != NULL); - newNode = LstNodeNew(node->prev, node, datum); + newNode = LstNodeNew(ln->prev, ln, datum); - if (node->prev != NULL) - node->prev->next = newNode; - node->prev = newNode; + if (ln->prev != NULL) + ln->prev->next = newNode; + ln->prev = newNode; - if (node == list->first) + if (ln == list->first) list->first = newNode; } @@ -122,18 +116,18 @@ Lst_InsertBefore(List *list, ListNode *node, void *datum) void Lst_Prepend(List *list, void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - node = LstNodeNew(NULL, list->first, datum); + ln = LstNodeNew(NULL, list->first, datum); if (list->first == NULL) { - list->first = node; - list->last = node; + list->first = ln; + list->last = ln; } else { - list->first->prev = node; - list->first = node; + list->first->prev = ln; + list->first = ln; } } @@ -141,71 +135,69 @@ Lst_Prepend(List *list, void *datum) void Lst_Append(List *list, void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - node = LstNodeNew(list->last, NULL, datum); + ln = LstNodeNew(list->last, NULL, datum); if (list->last == NULL) { - list->first = node; - list->last = node; + list->first = ln; + list->last = ln; } else { - list->last->next = node; - list->last = node; + list->last->next = ln; + list->last = ln; } } /* Remove the given node from the given list. * The datum stored in the node must be freed by the caller, if necessary. */ void -Lst_Remove(List *list, ListNode *node) +Lst_Remove(List *list, ListNode *ln) { /* unlink it from its neighbors */ - if (node->next != NULL) - node->next->prev = node->prev; - if (node->prev != NULL) - node->prev->next = node->next; + if (ln->next != NULL) + ln->next->prev = ln->prev; + if (ln->prev != NULL) + ln->prev->next = ln->next; /* unlink it from the list */ - if (list->first == node) - list->first = node->next; - if (list->last == node) - list->last = node->prev; + if (list->first == ln) + list->first = ln->next; + if (list->last == ln) + list->last = ln->prev; } /* Replace the datum in the given node with the new datum. */ void -LstNode_Set(ListNode *node, void *datum) +LstNode_Set(ListNode *ln, void *datum) { assert(datum != NULL); - node->datum = datum; + ln->datum = datum; } -/* Replace the datum in the given node to NULL. +/* Replace the datum in the given node with NULL. * Having NULL values in a list is unusual though. */ void -LstNode_SetNull(ListNode *node) +LstNode_SetNull(ListNode *ln) { - node->datum = NULL; + ln->datum = NULL; } -/* - * Functions for entire lists - */ - -/* Return the first node that contains the given datum, or NULL. */ +/* Return the first node that contains the given datum, or NULL. + * + * Time complexity: O(length(list)) */ ListNode * Lst_FindDatum(List *list, const void *datum) { - ListNode *node; + ListNode *ln; assert(datum != NULL); - for (node = list->first; node != NULL; node = node->next) - if (node->datum == datum) - return node; + for (ln = list->first; ln != NULL; ln = ln->next) + if (ln->datum == datum) + return ln; return NULL; } @@ -213,32 +205,32 @@ Lst_FindDatum(List *list, const void *datum) int Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData) { - ListNode *node; + ListNode *ln; int result = 0; - for (node = list->first; node != NULL; node = node->next) { - result = proc(node->datum, procData); + for (ln = list->first; ln != NULL; ln = ln->next) { + result = proc(ln->datum, procData); if (result != 0) break; } return result; } -/* Move all nodes from list2 to the end of list1. - * List2 is destroyed and freed. */ +/* Move all nodes from src to the end of dst. + * The source list is destroyed and freed. */ void -Lst_MoveAll(List *list1, List *list2) +Lst_MoveAll(List *dst, List *src) { - if (list2->first != NULL) { - list2->first->prev = list1->last; - if (list1->last != NULL) - list1->last->next = list2->first; + if (src->first != NULL) { + src->first->prev = dst->last; + if (dst->last != NULL) + dst->last->next = src->first; else - list1->first = list2->first; + dst->first = src->first; - list1->last = list2->last; + dst->last = src->last; } - free(list2); + free(src); } /* Copy the element data from src to the start of dst. */ @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.84 2020/10/28 02:43:16 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.85 2020/11/10 00:32:12 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -118,7 +118,7 @@ void Lst_Destroy(List *, LstFreeProc); /* Get information about a list */ -static inline MAKE_ATTR_UNUSED Boolean +MAKE_INLINE Boolean Lst_IsEmpty(List *list) { return list->first == NULL; } /* Find the first node that contains the given datum, or NULL. */ @@ -173,7 +173,7 @@ void Vector_Init(Vector *, size_t); /* Return the pointer to the given item in the vector. * The returned data is valid until the next modifying operation. */ -static inline MAKE_ATTR_UNUSED void * +MAKE_INLINE void * Vector_Get(Vector *v, size_t i) { unsigned char *items = v->items; @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,31 +68,22 @@ * SUCH DAMAGE. */ -/*- - * main.c -- - * The main file for this entire program. Exit routines etc - * reside here. +/* The main file for this entire program. Exit routines etc. reside here. * * Utility functions defined in this file: - * Main_ParseArgLine Takes a line of arguments, breaks them and - * treats them as if they were given when first - * invoked. Used by the parse module to implement - * the .MFLAGS target. * - * Error Print a tagged error message. The global - * MAKE variable must have been defined. This - * takes a format string and optional arguments - * for it. + * Main_ParseArgLine Parse and process command line arguments from + * a single string. Used to implement the + * special targets .MFLAGS and .MAKEFLAGS. + * + * Error Print a tagged error message. * - * Fatal Print an error message and exit. Also takes - * a format string and arguments for it. + * Fatal Print an error message and exit. * - * Punt Aborts all jobs and exits with a message. Also - * takes a format string and arguments for it. + * Punt Abort all jobs and exit with a message. * * Finish Finish things up by printing the number of - * errors which occurred, as passed to it, and - * exiting. + * errors which occurred, and exit. */ #include <sys/types.h> @@ -118,15 +109,15 @@ #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $"); #if defined(MAKE_NATIVE) && !defined(lint) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " "All rights reserved."); #endif -#ifndef DEFMAXLOCAL -#define DEFMAXLOCAL DEFMAXJOBS +#ifndef DEFMAXLOCAL +#define DEFMAXLOCAL DEFMAXJOBS #endif #ifndef __arraycount @@ -134,202 +125,208 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " #endif CmdOpts opts; -time_t now; /* Time at start of make */ -GNode *DEFAULT; /* .DEFAULT node */ -Boolean allPrecious; /* .PRECIOUS given on line by itself */ -Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ - -static int maxJobTokens; /* -j argument */ -Boolean enterFlagObj; /* -w and objdir != srcdir */ - -Boolean oldVars; /* variable substitution style */ -static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ -Boolean doing_depend; /* Set while reading .depend */ -static Boolean jobsRunning; /* TRUE if the jobs might be running */ -static const char * tracefile; -static int ReadMakefile(const char *); -static void usage(void) MAKE_ATTR_DEAD; -static void purge_cached_realpaths(void); - -static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ -static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ -char curdir[MAXPATHLEN + 1]; /* Startup directory */ -char *progname; /* the program name */ +time_t now; /* Time at start of make */ +GNode *defaultNode; /* .DEFAULT node */ +Boolean allPrecious; /* .PRECIOUS given on line by itself */ +Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ + +static int maxJobTokens; /* -j argument */ +Boolean enterFlagObj; /* -w and objdir != srcdir */ + +Boolean preserveUndefined; +static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ +Boolean doing_depend; /* Set while reading .depend */ +static Boolean jobsRunning; /* TRUE if the jobs might be running */ +static const char *tracefile; +static int ReadMakefile(const char *); +static void purge_relative_cached_realpaths(void); + +static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ +static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ +char curdir[MAXPATHLEN + 1]; /* Startup directory */ +char *progname; /* the program name */ char *makeDependfile; pid_t myPid; int makelevel; Boolean forceJobs = FALSE; static int errors = 0; - -/* - * On some systems MACHINE is defined as something other than - * what we want. - */ -#ifdef FORCE_MACHINE -# undef MACHINE -# define MACHINE FORCE_MACHINE -#endif - -extern SearchPath *parseIncPath; +static HashTable cached_realpaths; /* * For compatibility with the POSIX version of MAKEFLAGS that includes - * all the options with out -, convert flags to -f -l -a -g -s. + * all the options without '-', convert 'flags' to '-f -l -a -g -s'. */ static char * explode(const char *flags) { - size_t len; - char *nf, *st; - const char *f; + size_t len; + char *nf, *st; + const char *f; - if (flags == NULL) - return NULL; + if (flags == NULL) + return NULL; + + for (f = flags; *f; f++) + if (!ch_isalpha(*f)) + break; + + if (*f) + return bmake_strdup(flags); - for (f = flags; *f; f++) - if (!ch_isalpha(*f)) - break; - - if (*f) - return bmake_strdup(flags); - - len = strlen(flags); - st = nf = bmake_malloc(len * 3 + 1); - while (*flags) { - *nf++ = '-'; - *nf++ = *flags++; - *nf++ = ' '; - } - *nf = '\0'; - return st; + len = strlen(flags); + st = nf = bmake_malloc(len * 3 + 1); + while (*flags) { + *nf++ = '-'; + *nf++ = *flags++; + *nf++ = ' '; + } + *nf = '\0'; + return st; +} + +/* + * usage -- + * exit with usage message + */ +MAKE_ATTR_DEAD static void +usage(void) +{ + size_t prognameLen = strcspn(progname, "["); + + (void)fprintf(stderr, +"usage: %.*s [-BeikNnqrSstWwX]\n" +" [-C directory] [-D variable] [-d flags] [-f makefile]\n" +" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n" +" [-V variable] [-v variable] [variable=value] [target ...]\n", + (int)prognameLen, progname); + exit(2); } static void parse_debug_option_F(const char *modules) { - const char *mode; - size_t len; - char *fname; + const char *mode; + size_t len; + char *fname; - if (opts.debug_file != stdout && opts.debug_file != stderr) - fclose(opts.debug_file); + if (opts.debug_file != stdout && opts.debug_file != stderr) + fclose(opts.debug_file); - if (*modules == '+') { - modules++; - mode = "a"; - } else - mode = "w"; + if (*modules == '+') { + modules++; + mode = "a"; + } else + mode = "w"; - if (strcmp(modules, "stdout") == 0) { - opts.debug_file = stdout; - return; - } - if (strcmp(modules, "stderr") == 0) { - opts.debug_file = stderr; - return; - } + if (strcmp(modules, "stdout") == 0) { + opts.debug_file = stdout; + return; + } + if (strcmp(modules, "stderr") == 0) { + opts.debug_file = stderr; + return; + } - len = strlen(modules); - fname = bmake_malloc(len + 20); - memcpy(fname, modules, len + 1); + len = strlen(modules); + fname = bmake_malloc(len + 20); + memcpy(fname, modules, len + 1); - /* Let the filename be modified by the pid */ - if (strcmp(fname + len - 3, ".%d") == 0) - snprintf(fname + len - 2, 20, "%d", getpid()); + /* Let the filename be modified by the pid */ + if (strcmp(fname + len - 3, ".%d") == 0) + snprintf(fname + len - 2, 20, "%d", getpid()); - opts.debug_file = fopen(fname, mode); - if (!opts.debug_file) { - fprintf(stderr, "Cannot open debug file %s\n", - fname); - usage(); - } - free(fname); + opts.debug_file = fopen(fname, mode); + if (opts.debug_file == NULL) { + fprintf(stderr, "Cannot open debug file %s\n", + fname); + usage(); + } + free(fname); } static void parse_debug_options(const char *argvalue) { const char *modules; + DebugFlags debug = opts.debug; for (modules = argvalue; *modules; ++modules) { switch (*modules) { case '0': /* undocumented, only intended for tests */ - opts.debug &= DEBUG_LINT; + debug = DEBUG_NONE; break; case 'A': - opts.debug = ~(0|DEBUG_LINT); + debug = DEBUG_ALL; break; case 'a': - opts.debug |= DEBUG_ARCH; + debug |= DEBUG_ARCH; break; case 'C': - opts.debug |= DEBUG_CWD; + debug |= DEBUG_CWD; break; case 'c': - opts.debug |= DEBUG_COND; + debug |= DEBUG_COND; break; case 'd': - opts.debug |= DEBUG_DIR; + debug |= DEBUG_DIR; break; case 'e': - opts.debug |= DEBUG_ERROR; + debug |= DEBUG_ERROR; break; case 'f': - opts.debug |= DEBUG_FOR; + debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { - opts.debug |= DEBUG_GRAPH1; - ++modules; - } - else if (modules[1] == '2') { - opts.debug |= DEBUG_GRAPH2; - ++modules; - } - else if (modules[1] == '3') { - opts.debug |= DEBUG_GRAPH3; - ++modules; + debug |= DEBUG_GRAPH1; + modules++; + } else if (modules[1] == '2') { + debug |= DEBUG_GRAPH2; + modules++; + } else if (modules[1] == '3') { + debug |= DEBUG_GRAPH3; + modules++; } break; case 'h': - opts.debug |= DEBUG_HASH; + debug |= DEBUG_HASH; break; case 'j': - opts.debug |= DEBUG_JOB; + debug |= DEBUG_JOB; break; case 'L': - opts.debug |= DEBUG_LINT; + opts.lint = TRUE; break; case 'l': - opts.debug |= DEBUG_LOUD; + debug |= DEBUG_LOUD; break; case 'M': - opts.debug |= DEBUG_META; + debug |= DEBUG_META; break; case 'm': - opts.debug |= DEBUG_MAKE; + debug |= DEBUG_MAKE; break; case 'n': - opts.debug |= DEBUG_SCRIPT; + debug |= DEBUG_SCRIPT; break; case 'p': - opts.debug |= DEBUG_PARSE; + debug |= DEBUG_PARSE; break; case 's': - opts.debug |= DEBUG_SUFF; + debug |= DEBUG_SUFF; break; case 't': - opts.debug |= DEBUG_TARG; + debug |= DEBUG_TARG; break; case 'V': opts.debugVflag = TRUE; break; case 'v': - opts.debug |= DEBUG_VAR; + debug |= DEBUG_VAR; break; case 'x': - opts.debug |= DEBUG_SHELL; + debug |= DEBUG_SHELL; break; case 'F': parse_debug_option_F(modules + 1); @@ -341,7 +338,10 @@ parse_debug_options(const char *argvalue) usage(); } } + debug_setbuf: + opts.debug = debug; + /* * Make the debug_file unbuffered, and make * stdout line buffered (unless debugfile == stdout). @@ -365,12 +365,10 @@ is_relpath(const char *path) cp = path; while ((cp = strstr(cp, "/.")) != NULL) { cp += 2; + if (*cp == '.') + cp++; if (cp[0] == '/' || cp[0] == '\0') return TRUE; - else if (cp[0] == '.') { - if (cp[1] == '/' || cp[1] == '\0') - return TRUE; - } } return FALSE; } @@ -401,10 +399,11 @@ MainParseArgChdir(const char *argvalue) static void MainParseArgJobsInternal(const char *argvalue) { - if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { + char end; + if (sscanf(argvalue, "%d,%d%c", &jp_0, &jp_1, &end) != 2) { (void)fprintf(stderr, - "%s: internal error -- J option malformed (%s)\n", - progname, argvalue); + "%s: internal error -- J option malformed (%s)\n", + progname, argvalue); usage(); } if ((fcntl(jp_0, F_GETFD, 0) < 0) || @@ -504,7 +503,7 @@ MainParseArg(char c, const char *argvalue) break; case 'V': case 'v': - opts.printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS; + opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED; Lst_Append(opts.variables, bmake_strdup(argvalue)); /* XXX: Why always -V? */ Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); @@ -512,6 +511,7 @@ MainParseArg(char c, const char *argvalue) break; case 'W': opts.parseWarnFatal = TRUE; + /* XXX: why no Var_Append? */ break; case 'X': opts.varNoExportEnv = TRUE; @@ -547,6 +547,7 @@ MainParseArg(char c, const char *argvalue) break; case 'm': MainParseArgSysInc(argvalue); + /* XXX: why no Var_Append? */ break; case 'n': opts.noExecute = TRUE; @@ -610,8 +611,8 @@ rearg: arginc = 0; if (inOption) { if (c == '\0') { - ++argv; - --argc; + argv++; + argc--; inOption = FALSE; continue; } @@ -653,8 +654,6 @@ rearg: argc -= arginc; } - 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 @@ -665,9 +664,9 @@ rearg: if (Parse_IsVar(argv[1], &var)) { Parse_DoVar(&var, VAR_CMDLINE); } else { - if (!*argv[1]) + if (argv[1][0] == '\0') Punt("illegal (null) argument."); - if (*argv[1] == '-' && !dashDash) + if (argv[1][0] == '-' && !dashDash) goto rearg; Lst_Append(opts.create, bmake_strdup(argv[1])); } @@ -688,15 +687,13 @@ void Main_ParseArgLine(const char *line) { Words words; - void *p1; - const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); char *buf; if (line == NULL) return; for (; *line == ' '; ++line) continue; - if (!*line) + if (line[0] == '\0') return; #ifndef POSIX @@ -713,8 +710,12 @@ Main_ParseArgLine(const char *line) return; } #endif - buf = str_concat3(argv0, " ", line); - free(p1); + { + void *freeIt; + const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &freeIt); + buf = str_concat3(argv0, " ", line); + free(freeIt); + } words = Str_Words(buf, TRUE); if (words.words == NULL) { @@ -729,7 +730,7 @@ Main_ParseArgLine(const char *line) } Boolean -Main_SetObjdir(const char *fmt, ...) +Main_SetObjdir(Boolean writable, const char *fmt, ...) { struct stat sb; char *path; @@ -749,17 +750,16 @@ Main_SetObjdir(const char *fmt, ...) /* look for the directory and try to chdir there */ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { - /* if not .CURDIR it must be writable */ - if ((strcmp(path, curdir) != 0 && access(path, W_OK) != 0) || - chdir(path)) { - (void)fprintf(stderr, "make warning: %s: %s.\n", - path, strerror(errno)); + if ((writable && access(path, W_OK) != 0) || + (chdir(path) != 0)) { + (void)fprintf(stderr, "%s warning: %s: %s.\n", + progname, path, strerror(errno)); } else { snprintf(objdir, sizeof objdir, "%s", path); Var_Set(".OBJDIR", objdir, VAR_GLOBAL); setenv("PWD", objdir, 1); Dir_InitDot(); - purge_cached_realpaths(); + purge_relative_cached_realpaths(); rc = TRUE; if (opts.enterFlag && strcmp(objdir, curdir) != 0) enterFlagObj = TRUE; @@ -770,7 +770,7 @@ Main_SetObjdir(const char *fmt, ...) } static Boolean -Main_SetVarObjdir(const char *var, const char *suffix) +SetVarObjdir(Boolean writable, const char *var, const char *suffix) { void *path_freeIt; const char *path = Var_Value(var, VAR_CMDLINE, &path_freeIt); @@ -791,35 +791,28 @@ Main_SetVarObjdir(const char *var, const char *suffix) xpath = xpath_freeIt; } - (void)Main_SetObjdir("%s%s", xpath, suffix); + (void)Main_SetObjdir(writable, "%s%s", xpath, suffix); bmake_free(xpath_freeIt); bmake_free(path_freeIt); return TRUE; } -/* Read and parse the makefile. - * Return TRUE if reading the makefile succeeded. */ -static int -ReadMakefileSucceeded(void *fname, void *unused) -{ - return ReadMakefile(fname) == 0; -} - +/* Splits str into words, adding them to the list. + * The string must be kept alive as long as the list. */ int -str2Lst_Append(StringList *lp, char *str, const char *sep) +str2Lst_Append(StringList *lp, char *str) { - char *cp; - int n; + char *cp; + int n; - if (!sep) - sep = " \t"; + const char *sep = " \t"; - for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { - Lst_Append(lp, cp); - n++; - } - return n; + for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { + Lst_Append(lp, cp); + n++; + } + return n; } #ifdef SIGINFO @@ -830,9 +823,9 @@ siginfo(int signo MAKE_ATTR_UNUSED) char dir[MAXPATHLEN]; char str[2 * MAXPATHLEN]; int len; - if (getcwd(dir, sizeof(dir)) == NULL) + if (getcwd(dir, sizeof dir) == NULL) return; - len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir); + len = snprintf(str, sizeof str, "%s: Working in: %s\n", progname, dir); if (len > 0) (void)write(STDERR_FILENO, str, (size_t)len); } @@ -844,27 +837,27 @@ siginfo(int signo MAKE_ATTR_UNUSED) void MakeMode(const char *mode) { - char *mode_freeIt = NULL; - - if (mode == NULL) { - (void)Var_Subst("${" MAKE_MODE ":tl}", - VAR_GLOBAL, VARE_WANTRES, &mode_freeIt); - /* TODO: handle errors */ - mode = mode_freeIt; - } + char *mode_freeIt = NULL; - if (mode[0] != '\0') { - if (strstr(mode, "compat")) { - opts.compatMake = TRUE; - forceJobs = FALSE; + if (mode == NULL) { + (void)Var_Subst("${" MAKE_MODE ":tl}", + VAR_GLOBAL, VARE_WANTRES, &mode_freeIt); + /* TODO: handle errors */ + mode = mode_freeIt; } + + if (mode[0] != '\0') { + if (strstr(mode, "compat")) { + opts.compatMake = TRUE; + forceJobs = FALSE; + } #if USE_META - if (strstr(mode, "meta")) - meta_mode_init(mode); + if (strstr(mode, "meta")) + meta_mode_init(mode); #endif - } + } - free(mode_freeIt); + free(mode_freeIt); } static void @@ -894,18 +887,40 @@ PrintVar(const char *varname, Boolean expandVars) } } +/* + * Return a Boolean based on a variable. + * + * If the knob is not set, return the fallback. + * If set, anything that looks or smells like "No", "False", "Off", "0", etc. + * is FALSE, otherwise TRUE. + */ +Boolean +GetBooleanVar(const char *varname, Boolean fallback) +{ + char *expr = str_concat3("${", varname, ":U}"); + char *value; + Boolean res; + + (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + res = ParseBoolean(value, fallback); + free(value); + free(expr); + return res; +} + static void doPrintVars(void) { StringListNode *ln; Boolean expandVars; - if (opts.printVars == EXPAND_VARS) + if (opts.printVars == PVM_EXPANDED) expandVars = TRUE; else if (opts.debugVflag) expandVars = FALSE; else - expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); + expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE); for (ln = opts.variables->first; ln != NULL; ln = ln->next) { const char *varname = ln->datum; @@ -916,7 +931,7 @@ doPrintVars(void) static Boolean runTargets(void) { - GNodeList *targs; /* target nodes to create -- passed to Make_Init */ + GNodeList *targs; /* target nodes to create */ Boolean outOfDate; /* FALSE if all targets up to date */ /* @@ -988,20 +1003,19 @@ InitRandom(void) } static const char * -init_machine(const struct utsname *utsname) +InitVarMachine(const struct utsname *utsname) { #ifdef FORCE_MACHINE - const char *machine = FORCE_MACHINE; + return FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); -#endif + if (machine != NULL) return machine; -#ifdef MAKE_NATIVE +#if defined(MAKE_NATIVE) return utsname->machine; -#else -#ifdef MAKE_MACHINE +#elif defined(MAKE_MACHINE) return MAKE_MACHINE; #else return "unknown"; @@ -1010,8 +1024,11 @@ init_machine(const struct utsname *utsname) } static const char * -init_machine_arch(void) +InitVarMachineArch(void) { +#ifdef FORCE_MACHINE_ARCH + return FORCE_MACHINE_ARCH; +#else const char *env = getenv("MACHINE_ARCH"); if (env != NULL) return env; @@ -1019,9 +1036,9 @@ init_machine_arch(void) #if defined(MAKE_NATIVE) && defined(CTL_HW) { struct utsname utsname; - static char machine_arch_buf[sizeof(utsname.machine)]; + static char machine_arch_buf[sizeof utsname.machine]; const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; - size_t len = sizeof(machine_arch_buf); + size_t len = sizeof machine_arch_buf; if (sysctl(mib, __arraycount(mib), machine_arch_buf, &len, NULL, 0) < 0) { @@ -1032,16 +1049,13 @@ init_machine_arch(void) return machine_arch_buf; } -#else -#ifndef MACHINE_ARCH -#ifdef MAKE_MACHINE_ARCH +#elif defined(MACHINE_ARCH) + return MACHINE_ARCH; +#elif defined(MAKE_MACHINE_ARCH) return MAKE_MACHINE_ARCH; #else return "unknown"; #endif -#else - return MACHINE_ARCH; -#endif #endif } @@ -1070,7 +1084,8 @@ HandlePWD(const struct stat *curdir_st) if (ignorePWD || (pwd = getenv("PWD")) == NULL) return; - if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != NULL) { + if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != + NULL) { bmake_free(prefix_freeIt); return; } @@ -1101,15 +1116,18 @@ ignore_pwd: static void InitObjdir(const char *machine, const char *machine_arch) { + Boolean writable; + Dir_InitDir(curdir); - (void)Main_SetObjdir("%s", curdir); - - if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) && - !Main_SetVarObjdir("MAKEOBJDIR", "") && - !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && - !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) && - !Main_SetObjdir("%s", _PATH_OBJDIR)) - (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir); + writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE); + (void)Main_SetObjdir(FALSE, "%s", curdir); + + if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) && + !SetVarObjdir(writable, "MAKEOBJDIR", "") && + !Main_SetObjdir(writable, "%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && + !Main_SetObjdir(writable, "%s.%s", _PATH_OBJDIR, machine) && + !Main_SetObjdir(writable, "%s", _PATH_OBJDIR)) + (void)Main_SetObjdir(writable, "%s%s", _PATH_OBJDIRPREFIX, curdir); } /* get rid of resource limit on file descriptors */ @@ -1132,6 +1150,7 @@ CmdOpts_Init(void) opts.compatMake = FALSE; /* No compat mode */ opts.debug = 0; /* No debug verbosity, please. */ /* opts.debug_file has been initialized earlier */ + opts.lint = FALSE; opts.debugVflag = FALSE; opts.checkEnvFirst = FALSE; opts.makefiles = Lst_New(); @@ -1144,7 +1163,7 @@ CmdOpts_Init(void) opts.noBuiltins = FALSE; /* Read the built-in rules */ opts.beSilent = FALSE; /* Print commands as executed */ opts.touchFlag = FALSE; /* Actually update targets */ - opts.printVars = 0; + opts.printVars = PVM_NONE; opts.variables = Lst_New(); opts.parseWarnFatal = FALSE; opts.enterFlag = FALSE; @@ -1174,6 +1193,8 @@ InitVarMake(const char *argv0) Var_Set(".MAKE", make, VAR_GLOBAL); } +/* Add the directories from the colon-separated syspath to defSysIncPath. + * After returning, the contents of syspath is unspecified. */ static void InitDefSysIncPath(char *syspath) { @@ -1185,7 +1206,6 @@ InitDefSysIncPath(char *syspath) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ - /* XXX: mismatch: the -m option sets sysIncPath, not syspath */ if (syspath == NULL || syspath[0] == '\0') syspath = defsyspath; else @@ -1194,18 +1214,18 @@ InitDefSysIncPath(char *syspath) for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; - if (*cp == ':') { + if (*cp == ':') *cp++ = '\0'; - } + /* look for magic parent directory search string */ - if (strncmp(".../", start, 4) != 0) { - (void)Dir_AddDir(defSysIncPath, start); - } else { + if (strncmp(start, ".../", 4) == 0) { char *dir = Dir_FindHereOrAbove(curdir, start + 4); if (dir != NULL) { (void)Dir_AddDir(defSysIncPath, dir); free(dir); } + } else { + (void)Dir_AddDir(defSysIncPath, start); } } @@ -1216,16 +1236,26 @@ InitDefSysIncPath(char *syspath) static void ReadBuiltinRules(void) { + StringListNode *ln; StringList *sysMkPath = Lst_New(); + Dir_Expand(_PATH_DEFSYSMK, - Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath, - sysMkPath); + Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath, + sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); - if (!Lst_ForEachUntil(sysMkPath, ReadMakefileSucceeded, NULL)) - Fatal("%s: cannot open %s.", progname, - (char *)sysMkPath->first->datum); - /* XXX: sysMkPath is not freed */ + + for (ln = sysMkPath->first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; + + if (ln == NULL) + Fatal("%s: cannot open %s.", + progname, (const char *)sysMkPath->first->datum); + + /* Free the list but not the actual filenames since these may still + * be used in GNodes. */ + Lst_Free(sysMkPath); } static void @@ -1243,9 +1273,9 @@ InitMaxJobs(void) n = (int)strtol(value, NULL, 0); if (n < 1) { (void)fprintf(stderr, - "%s: illegal value for .MAKE.JOBS " - "-- must be positive integer!\n", - progname); + "%s: illegal value for .MAKE.JOBS " + "-- must be positive integer!\n", + progname); exit(1); } @@ -1293,82 +1323,46 @@ InitVpath(void) } static void -ReadMakefiles(void) +ReadAllMakefiles(StringList *makefiles) { - if (opts.makefiles->first != NULL) { - StringListNode *ln; + StringListNode *ln; - for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) { - if (ReadMakefile(ln->datum) != 0) - Fatal("%s: cannot open %s.", - progname, (char *)ln->datum); - } - } else { - char *p1; - (void)Var_Subst("${" MAKEFILE_PREFERENCE "}", - VAR_CMDLINE, VARE_WANTRES, &p1); - /* TODO: handle errors */ - (void)str2Lst_Append(opts.makefiles, p1, NULL); - (void)Lst_ForEachUntil(opts.makefiles, - ReadMakefileSucceeded, NULL); - free(p1); + for (ln = makefiles->first; ln != NULL; ln = ln->next) { + const char *fname = ln->datum; + if (ReadMakefile(fname) != 0) + Fatal("%s: cannot open %s.", progname, fname); } } static void -CleanUp(void) +ReadFirstDefaultMakefile(void) { -#ifdef CLEANUP - Lst_Destroy(opts.variables, free); - Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */ - Lst_Destroy(opts.create, free); -#endif + StringListNode *ln; + char *prefs; - /* print the graph now it's been processed if the user requested it */ - if (DEBUG(GRAPH2)) - Targ_PrintGraph(2); + (void)Var_Subst("${" MAKE_MAKEFILE_PREFERENCE "}", + VAR_CMDLINE, VARE_WANTRES, &prefs); + /* TODO: handle errors */ - Trace_Log(MAKEEND, 0); + /* XXX: This should use a local list instead of opts.makefiles + * since these makefiles do not come from the command line. They + * also have different semantics in that only the first file that + * is found is processed. See ReadAllMakefiles. */ + (void)str2Lst_Append(opts.makefiles, prefs); - if (enterFlagObj) - printf("%s: Leaving directory `%s'\n", progname, objdir); - if (opts.enterFlag) - printf("%s: Leaving directory `%s'\n", progname, curdir); + for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; -#ifdef USE_META - meta_finish(); -#endif - Suff_End(); - Targ_End(); - Arch_End(); - Var_End(); - Parse_End(); - Dir_End(); - Job_End(); - Trace_End(); + free(prefs); } -/*- - * 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) +/* Initialize variables such as MAKE, MACHINE, .MAKEFLAGS. + * Initialize a few modules. + * Parse the arguments from MAKEFLAGS and the command line. */ +static void +main_Init(int argc, char **argv) { - Boolean outOfDate; /* FALSE if all targets up to date */ struct stat sa; const char *machine; const char *machine_arch; @@ -1378,6 +1372,8 @@ main(int argc, char **argv) /* default to writing debug to stderr */ opts.debug_file = stderr; + HashTable_Init(&cached_realpaths); + #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif @@ -1392,9 +1388,9 @@ main(int argc, char **argv) UnlimitFiles(); if (uname(&utsname) == -1) { - (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, - strerror(errno)); - exit(2); + (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, + strerror(errno)); + exit(2); } /* @@ -1405,16 +1401,16 @@ main(int argc, char **argv) * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ - machine = init_machine(&utsname); - machine_arch = init_machine_arch(); + machine = InitVarMachine(&utsname); + machine_arch = InitVarMachineArch(); - myPid = getpid(); /* remember this for vFork() */ + myPid = getpid(); /* remember this for vFork() */ /* * Just in case MAKEOBJDIR wants us to do something tricky. */ - Var_Init(); /* Initialize the lists of variables for - * parsing arguments */ + Targ_Init(); + Var_Init(); Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL); Var_Set("MACHINE", machine, VAR_GLOBAL); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL); @@ -1428,13 +1424,12 @@ main(int argc, char **argv) #ifndef MAKEFILE_PREFERENCE_LIST # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif - Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, - VAR_GLOBAL); + Var_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, VAR_GLOBAL); Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL); CmdOpts_Init(); - allPrecious = FALSE; /* Remove targets when interrupted */ - deleteOnError = FALSE; /* Historical default behavior */ + allPrecious = FALSE; /* Remove targets when interrupted */ + deleteOnError = FALSE; /* Historical default behavior */ jobsRunning = FALSE; maxJobTokens = opts.maxJobs; @@ -1461,25 +1456,23 @@ main(int argc, char **argv) /* some makefiles need to know this */ Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMDLINE); - /* - * Set some other useful macros - */ + /* Set some other useful variables. */ { - char tmp[64], *ep; - - makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0; - if (makelevel < 0) - makelevel = 0; - snprintf(tmp, sizeof(tmp), "%d", makelevel); - Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL); - snprintf(tmp, sizeof(tmp), "%u", myPid); - Var_Set(".MAKE.PID", tmp, VAR_GLOBAL); - snprintf(tmp, sizeof(tmp), "%u", getppid()); - Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL); + char tmp[64], *ep = getenv(MAKE_LEVEL_ENV); + + makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; + if (makelevel < 0) + makelevel = 0; + snprintf(tmp, sizeof tmp, "%d", makelevel); + Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL); + snprintf(tmp, sizeof tmp, "%u", myPid); + Var_Set(".MAKE.PID", tmp, VAR_GLOBAL); + snprintf(tmp, sizeof tmp, "%u", getppid()); + Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL); } if (makelevel > 0) { char pn[1024]; - snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); + snprintf(pn, sizeof pn, "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } @@ -1495,9 +1488,9 @@ main(int argc, char **argv) */ #ifdef POSIX { - char *p1 = explode(getenv("MAKEFLAGS")); - Main_ParseArgLine(p1); - free(p1); + char *p1 = explode(getenv("MAKEFLAGS")); + Main_ParseArgLine(p1); + free(p1); } #else Main_ParseArgLine(getenv("MAKE")); @@ -1522,9 +1515,9 @@ main(int argc, char **argv) * Verify that cwd is sane. */ if (stat(curdir, &sa) == -1) { - (void)fprintf(stderr, "%s: %s: %s.\n", - progname, curdir, strerror(errno)); - exit(2); + (void)fprintf(stderr, "%s: %s: %s.\n", + progname, curdir, strerror(errno)); + exit(2); } #ifndef NO_PWD_OVERRIDE @@ -1539,11 +1532,10 @@ main(int argc, char **argv) * parsing the makefile(s) */ Arch_Init(); - Targ_Init(); Suff_Init(); Trace_Init(tracefile); - DEFAULT = NULL; + defaultNode = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); @@ -1551,27 +1543,38 @@ main(int argc, char **argv) InitVarTargets(); InitDefSysIncPath(syspath); +} + +/* Read the system makefile followed by either makefile, Makefile or the + * files given by the -f option. Exit on parse errors. */ +static void +main_ReadFiles(void) +{ - /* - * Read in the built-in rules first, followed by the specified - * makefiles, or the default makefile and Makefile, in that order, - * if no makefiles were given on the command line. - */ if (!opts.noBuiltins) ReadBuiltinRules(); - ReadMakefiles(); - + + if (!Lst_IsEmpty(opts.makefiles)) + ReadAllMakefiles(opts.makefiles); + else + ReadFirstDefaultMakefile(); +} + +/* Compute the dependency graph. */ +static void +main_PrepareMaking(void) +{ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ - if (!opts.noBuiltins || !opts.printVars) { - /* ignore /dev/null and anything starting with "no" */ - (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}", - VAR_CMDLINE, VARE_WANTRES, &makeDependfile); - if (makeDependfile[0] != '\0') { - /* TODO: handle errors */ - doing_depend = TRUE; - (void)ReadMakefile(makeDependfile); - doing_depend = FALSE; - } + if (!opts.noBuiltins || opts.printVars == PVM_NONE) { + /* ignore /dev/null and anything starting with "no" */ + (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}", + VAR_CMDLINE, VARE_WANTRES, &makeDependfile); + if (makeDependfile[0] != '\0') { + /* TODO: handle errors */ + doing_depend = TRUE; + (void)ReadMakefile(makeDependfile); + doing_depend = FALSE; + } } if (enterFlagObj) @@ -1580,30 +1583,28 @@ main(int argc, char **argv) MakeMode(NULL); { - void *freeIt; - Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt), - VAR_GLOBAL); - bmake_free(freeIt); - + void *freeIt; + Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt), + VAR_GLOBAL); + bmake_free(freeIt); } InitMaxJobs(); /* - * Be compatible if user did not specify -j and did not explicitly - * turned compatibility on + * Be compatible if the user did not specify -j and did not explicitly + * turn compatibility on. */ - if (!opts.compatMake && !forceJobs) { - opts.compatMake = TRUE; - } + if (!opts.compatMake && !forceJobs) + opts.compatMake = TRUE; if (!opts.compatMake) - Job_ServerStart(maxJobTokens, jp_0, jp_1); + Job_ServerStart(maxJobTokens, jp_0, jp_1); DEBUG5(JOB, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", - jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); + jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); - if (!opts.printVars) - Main_ExportMAKEFLAGS(TRUE); /* initial export */ + if (opts.printVars == PVM_NONE) + Main_ExportMAKEFLAGS(TRUE); /* initial export */ InitVpath(); @@ -1621,22 +1622,79 @@ main(int argc, char **argv) /* 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 (opts.printVars) { +/* Make the targets. + * If the -v or -V options are given, print variables instead. + * Return whether any of the targets is out-of-date. */ +static Boolean +main_Run(void) +{ + if (opts.printVars != PVM_NONE) { + /* print the values of any variables requested by the user */ doPrintVars(); - outOfDate = FALSE; + return FALSE; } else { - outOfDate = runTargets(); + return runTargets(); } +} - CleanUp(); +/* Clean up after making the targets. */ +static void +main_CleanUp(void) +{ +#ifdef CLEANUP + Lst_Destroy(opts.variables, free); + Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */ + Lst_Destroy(opts.create, free); +#endif + + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + Trace_Log(MAKEEND, NULL); + + if (enterFlagObj) + printf("%s: Leaving directory `%s'\n", progname, objdir); + if (opts.enterFlag) + printf("%s: Leaving directory `%s'\n", progname, curdir); + +#ifdef USE_META + meta_finish(); +#endif + Suff_End(); + Targ_End(); + Arch_End(); + Var_End(); + Parse_End(); + Dir_End(); + Job_End(); + Trace_End(); +} - if (DEBUG(LINT) && (errors > 0 || Parse_GetFatals() > 0)) - return 2; /* Not 1 so -q can distinguish error */ +/* Determine the exit code. */ +static int +main_Exit(Boolean outOfDate) +{ + if (opts.lint && (errors > 0 || Parse_GetFatals() > 0)) + return 2; /* Not 1 so -q can distinguish error */ return outOfDate ? 1 : 0; } +int +main(int argc, char **argv) +{ + Boolean outOfDate; + + main_Init(argc, argv); + main_ReadFiles(); + main_PrepareMaking(); + outOfDate = main_Run(); + main_CleanUp(); + return main_Exit(outOfDate); +} + /* Open and parse the given makefile, with all its side effects. * * Results: @@ -1648,12 +1706,12 @@ ReadMakefile(const char *fname) int fd; char *name, *path = NULL; - if (!strcmp(fname, "-")) { + if (strcmp(fname, "-") == 0) { Parse_File(NULL /*stdin*/, -1); Var_Set("MAKEFILE", "", VAR_INTERNAL); } else { /* if we've chdir'd, rebuild the path name */ - if (strcmp(curdir, objdir) && *fname != '/') { + if (strcmp(curdir, objdir) != 0 && *fname != '/') { path = str_concat3(curdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { @@ -1676,12 +1734,12 @@ ReadMakefile(const char *fname) } /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); - if (!name) { + if (name == NULL) { SearchPath *sysInc = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath; name = Dir_FindFile(fname, sysInc); } - if (!name || (fd = open(name, O_RDONLY)) == -1) { + if (name == NULL || (fd = open(name, O_RDONLY)) == -1) { free(name); free(path); return -1; @@ -1701,8 +1759,6 @@ found: return 0; } - - /*- * Cmd_Exec -- * Execute the command in cmd, and return the output of that command @@ -1719,125 +1775,114 @@ found: char * Cmd_Exec(const char *cmd, const char **errfmt) { - const char *args[4]; /* Args for invoking the shell */ - int fds[2]; /* Pipe streams */ - int cpid; /* Child PID */ - int pid; /* PID from wait() */ - WAIT_T status; /* command exit status */ - Buffer buf; /* buffer to store the result */ - ssize_t bytes_read; - char *res; /* result */ - size_t res_len; - char *cp; - int savederr; /* saved errno */ - - *errfmt = NULL; - - if (!shellName) - 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) { - *errfmt = "Couldn't create pipe for \"%s\""; - goto bad; - } - - /* - * Fork - */ - switch (cpid = vFork()) { - case 0: + const char *args[4]; /* Args for invoking the shell */ + int fds[2]; /* Pipe streams */ + int cpid; /* Child PID */ + int pid; /* PID from wait() */ + int status; /* command exit status */ + Buffer buf; /* buffer to store the result */ + ssize_t bytes_read; + char *res; /* result */ + size_t res_len; + char *cp; + int savederr; /* saved errno */ + + *errfmt = NULL; + + if (!shellName) + Shell_Init(); /* - * Close input side of pipe + * Set up arguments for shell */ - (void)close(fds[0]); + args[0] = shellName; + args[1] = "-c"; + args[2] = cmd; + args[3] = NULL; /* - * 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? + * Open a pipe for fetching its output */ - (void)dup2(fds[1], 1); - (void)close(fds[1]); - - Var_ExportVars(); - - (void)execv(shellPath, UNCONST(args)); - _exit(1); - /*NOTREACHED*/ - - case -1: - *errfmt = "Couldn't exec \"%s\""; - goto bad; + if (pipe(fds) == -1) { + *errfmt = "Couldn't create pipe for \"%s\""; + goto bad; + } - default: /* - * No need for the writing half + * Fork */ - (void)close(fds[1]); + switch (cpid = vFork()) { + case 0: + (void)close(fds[0]); /* Close input side of pipe */ - savederr = 0; - Buf_Init(&buf, 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? + */ + (void)dup2(fds[1], 1); + (void)close(fds[1]); - do { - char result[BUFSIZ]; - bytes_read = read(fds[0], result, sizeof(result)); - if (bytes_read > 0) - Buf_AddBytes(&buf, result, (size_t)bytes_read); - } - while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR)); - if (bytes_read == -1) - savederr = errno; + Var_ExportVars(); - /* - * Close the input side of the pipe. - */ - (void)close(fds[0]); + (void)execv(shellPath, UNCONST(args)); + _exit(1); + /*NOTREACHED*/ - /* - * Wait for the process to exit. - */ - while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) { - JobReapChild(pid, status, FALSE); - continue; - } - res_len = Buf_Len(&buf); - res = Buf_Destroy(&buf, FALSE); - - if (savederr != 0) - *errfmt = "Couldn't read shell's output for \"%s\""; - - if (WIFSIGNALED(status)) - *errfmt = "\"%s\" exited on a signal"; - else if (WEXITSTATUS(status) != 0) - *errfmt = "\"%s\" returned non-zero status"; - - /* Convert newlines to spaces. A final newline is just stripped */ - if (res_len > 0 && res[res_len - 1] == '\n') - res[res_len - 1] = '\0'; - for (cp = res; *cp != '\0'; cp++) - if (*cp == '\n') - *cp = ' '; - break; - } - return res; + case -1: + *errfmt = "Couldn't exec \"%s\""; + goto bad; + + default: + (void)close(fds[1]); /* No need for the writing half */ + + savederr = 0; + Buf_Init(&buf); + + do { + char result[BUFSIZ]; + bytes_read = read(fds[0], result, sizeof result); + if (bytes_read > 0) + Buf_AddBytes(&buf, result, (size_t)bytes_read); + } while (bytes_read > 0 || + (bytes_read == -1 && errno == EINTR)); + if (bytes_read == -1) + savederr = errno; + + (void)close( + fds[0]); /* Close the input side of the pipe. */ + + /* Wait for the process to exit. */ + while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) + JobReapChild(pid, status, FALSE); + + res_len = Buf_Len(&buf); + res = Buf_Destroy(&buf, FALSE); + + if (savederr != 0) + *errfmt = "Couldn't read shell's output for \"%s\""; + + if (WIFSIGNALED(status)) + *errfmt = "\"%s\" exited on a signal"; + else if (WEXITSTATUS(status) != 0) + *errfmt = "\"%s\" returned non-zero status"; + + /* Convert newlines to spaces. A final newline is just stripped */ + if (res_len > 0 && res[res_len - 1] == '\n') + res[res_len - 1] = '\0'; + for (cp = res; *cp != '\0'; cp++) + if (*cp == '\n') + *cp = ' '; + break; + } + return res; bad: - return bmake_strdup(""); + return bmake_strdup(""); } /* Print a printf-style error message. * - * This error message has no consequences, in particular it does not affect - * the exit status. */ + * In default mode, this error message has no consequences, in particular it + * does not affect the exit status. Only in lint mode (-dL) it does. */ void Error(const char *fmt, ...) { @@ -1862,19 +1907,21 @@ Error(const char *fmt, ...) errors++; } -/* Produce a Fatal error message, then exit immediately. +/* Wait for any running jobs to finish, then produce an error message, + * finally exit immediately. * - * If jobs are running, wait for them to finish. */ + * Exiting immediately differs from Parse_Error, which exits only after the + * current top-level makefile has been parsed completely. */ void Fatal(const char *fmt, ...) { va_list ap; - va_start(ap, fmt); if (jobsRunning) Job_Wait(); (void)fflush(stdout); + va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -1884,7 +1931,7 @@ Fatal(const char *fmt, ...) if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) Targ_PrintGraph(2); - Trace_Log(MAKEERROR, 0); + Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1 so -q can distinguish error */ } @@ -1916,7 +1963,7 @@ DieHorribly(void) Job_AbortAll(); if (DEBUG(GRAPH2)) Targ_PrintGraph(2); - Trace_Log(MAKEERROR, 0); + Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1, so -q can distinguish error */ } @@ -1926,7 +1973,7 @@ DieHorribly(void) void Finish(int errs) { - if (dieQuietly(NULL, -1)) + if (shouldDieQuietly(NULL, -1)) exit(2); Fatal("%d error%s", errs, errs == 1 ? "" : "s"); } @@ -1975,7 +2022,7 @@ execDie(const char *af, const char *av) { Buffer buf; - Buf_Init(&buf, 0); + Buf_Init(&buf); Buf_AddStr(&buf, progname); Buf_AddStr(&buf, ": "); Buf_AddStr(&buf, af); @@ -1991,216 +2038,183 @@ execDie(const char *af, const char *av) _exit(1); } -/* - * usage -- - * exit with usage message - */ -static void -usage(void) -{ - char *p; - if ((p = strchr(progname, '[')) != NULL) - *p = '\0'; - - (void)fprintf(stderr, -"usage: %s [-BeikNnqrstWwX] \n" -" [-C directory] [-D variable] [-d flags] [-f makefile]\n" -" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n" -" [-V variable] [-v variable] [variable=value] [target ...]\n", - progname); - exit(2); -} - -/* - * realpath(3) can get expensive, cache results... - */ -static GNode *cached_realpaths = NULL; - -static GNode * -get_cached_realpaths(void) -{ - - if (!cached_realpaths) { - cached_realpaths = Targ_NewGN("Realpath"); -#ifndef DEBUG_REALPATH_CACHE - cached_realpaths->flags = INTERNAL; -#endif - } - - return cached_realpaths; -} - /* purge any relative paths */ static void -purge_cached_realpaths(void) +purge_relative_cached_realpaths(void) { - GNode *cache = get_cached_realpaths(); - HashEntry *he, *nhe; - HashIter hi; - - HashIter_Init(&hi, &cache->context); - he = HashIter_Next(&hi); - while (he != NULL) { - nhe = HashIter_Next(&hi); - if (he->key[0] != '/') { - if (DEBUG(DIR)) - fprintf(stderr, "cached_realpath: purging %s\n", he->key); - HashTable_DeleteEntry(&cache->context, he); - } - he = nhe; - } + HashEntry *he, *nhe; + HashIter hi; + + HashIter_Init(&hi, &cached_realpaths); + he = HashIter_Next(&hi); + while (he != NULL) { + nhe = HashIter_Next(&hi); + if (he->key[0] != '/') { + DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); + HashTable_DeleteEntry(&cached_realpaths, he); + /* XXX: What about the allocated he->value? Either + * free them or document why they cannot be freed. */ + } + he = nhe; + } } char * cached_realpath(const char *pathname, char *resolved) { - GNode *cache; - const char *rp; - void *freeIt; + const char *rp; - if (!pathname || !pathname[0]) - return NULL; + if (pathname == NULL || pathname[0] == '\0') + return NULL; - cache = get_cached_realpaths(); + rp = HashTable_FindValue(&cached_realpaths, pathname); + if (rp != NULL) { + /* a hit */ + strncpy(resolved, rp, MAXPATHLEN); + resolved[MAXPATHLEN - 1] = '\0'; + return resolved; + } - if ((rp = Var_Value(pathname, cache, &freeIt)) != NULL) { - /* a hit */ - strlcpy(resolved, rp, MAXPATHLEN); - } else if ((rp = realpath(pathname, resolved)) != NULL) { - Var_Set(pathname, rp, cache); - } /* else should we negative-cache? */ + rp = realpath(pathname, resolved); + if (rp != NULL) { + HashTable_Set(&cached_realpaths, pathname, bmake_strdup(rp)); + DEBUG2(DIR, "cached_realpath: %s -> %s\n", pathname, rp); + return resolved; + } - bmake_free(freeIt); - return rp ? resolved : NULL; + /* should we negative-cache? */ + return NULL; } /* * Return true if we should die without noise. - * For example our failing child was a sub-make - * or failure happend elsewhere. + * For example our failing child was a sub-make or failure happened elsewhere. */ -int -dieQuietly(GNode *gn, int bf) +Boolean +shouldDieQuietly(GNode *gn, int bf) { - static int quietly = -1; - - if (quietly < 0) { - if (DEBUG(JOB) || !getBoolean(".MAKE.DIE_QUIETLY", TRUE)) - quietly = 0; - else if (bf >= 0) - quietly = bf; - else - quietly = gn != NULL ? ((gn->type & (OP_MAKE)) != 0) : 0; - } - return quietly; + static int quietly = -1; + + if (quietly < 0) { + if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE)) + quietly = 0; + else if (bf >= 0) + quietly = bf; + else + quietly = gn != NULL && (gn->type & OP_MAKE); + } + return quietly; } static void SetErrorVars(GNode *gn) { - StringListNode *ln; + StringListNode *ln; - /* - * We can print this even if there is no .ERROR target. - */ - Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); - Var_Delete(".ERROR_CMD", VAR_GLOBAL); + /* + * We can print this even if there is no .ERROR target. + */ + Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL); + Var_Delete(".ERROR_CMD", VAR_GLOBAL); - for (ln = gn->commands->first; ln != NULL; ln = ln->next) { - const char *cmd = ln->datum; + for (ln = gn->commands->first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; - if (cmd == NULL) - break; - Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL); - } + if (cmd == NULL) + break; + Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL); + } } +/* Print some helpful information in case of an error. + * The caller should exit soon after calling this function. */ void -PrintOnError(GNode *gn, const char *s) +PrintOnError(GNode *gn, const char *msg) { - static GNode *en = NULL; - const char *expr; - char *cp; + static GNode *errorNode = NULL; - if (DEBUG(HASH)) { - Targ_Stats(); - Var_Stats(); - } + if (DEBUG(HASH)) { + Targ_Stats(); + Var_Stats(); + } - /* we generally want to keep quiet if a sub-make died */ - if (dieQuietly(gn, -1)) - return; + /* we generally want to keep quiet if a sub-make died */ + if (shouldDieQuietly(gn, -1)) + return; + + if (msg != NULL) + printf("%s", msg); + printf("\n%s: stopped in %s\n", progname, curdir); - if (s) - printf("%s", s); - - printf("\n%s: stopped in %s\n", progname, curdir); - - if (en) - return; /* we've been here! */ - if (gn) - SetErrorVars(gn); - expr = "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}"; - (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp); - /* TODO: handle errors */ - printf("%s", cp); - free(cp); - fflush(stdout); - - /* - * Finally, see if there is a .ERROR target, and run it if so. - */ - en = Targ_FindNode(".ERROR"); - if (en) { - en->type |= OP_SPECIAL; - Compat_Make(en, en); - } + if (errorNode != NULL) + return; /* we've been here! */ + + if (gn != NULL) + SetErrorVars(gn); + + { + char *errorVarsValues; + (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", + VAR_GLOBAL, VARE_WANTRES, &errorVarsValues); + /* TODO: handle errors */ + printf("%s", errorVarsValues); + free(errorVarsValues); + } + + fflush(stdout); + + /* + * Finally, see if there is a .ERROR target, and run it if so. + */ + errorNode = Targ_FindNode(".ERROR"); + if (errorNode != NULL) { + errorNode->type |= OP_SPECIAL; + Compat_Make(errorNode, errorNode); + } } void Main_ExportMAKEFLAGS(Boolean first) { - static Boolean once = TRUE; - const char *expr; - char *s; + static Boolean once = TRUE; + const char *expr; + char *s; - if (once != first) - return; - once = FALSE; + if (once != first) + return; + once = FALSE; - expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; - (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s); - /* TODO: handle errors */ - if (s[0] != '\0') { + expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; + (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s); + /* TODO: handle errors */ + if (s[0] != '\0') { #ifdef POSIX - setenv("MAKEFLAGS", s, 1); + setenv("MAKEFLAGS", s, 1); #else - setenv("MAKE", s, 1); + setenv("MAKE", s, 1); #endif - } + } } char * getTmpdir(void) { - static char *tmpdir = NULL; - - if (!tmpdir) { + static char *tmpdir = NULL; struct stat st; - /* - * Honor $TMPDIR but only if it is valid. - * Ensure it ends with /. - */ - (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, - VARE_WANTRES, &tmpdir); + if (tmpdir != NULL) + return tmpdir; + + /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */ + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", + VAR_GLOBAL, VARE_WANTRES, &tmpdir); /* TODO: handle errors */ + if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { - free(tmpdir); - tmpdir = bmake_strdup(_PATH_TMP); + free(tmpdir); + tmpdir = bmake_strdup(_PATH_TMP); } - } - return tmpdir; + return tmpdir; } /* @@ -2211,73 +2225,45 @@ getTmpdir(void) int mkTempFile(const char *pattern, char **out_fname) { - static char *tmpdir = NULL; - char tfile[MAXPATHLEN]; - int fd; - - if (pattern != NULL) - pattern = TMPPAT; - if (tmpdir == NULL) - tmpdir = getTmpdir(); - if (pattern[0] == '/') { - snprintf(tfile, sizeof(tfile), "%s", pattern); - } else { - snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern); - } - if ((fd = mkstemp(tfile)) < 0) - Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); - if (out_fname) { - *out_fname = bmake_strdup(tfile); - } else { - unlink(tfile); /* we just want the descriptor */ - } - return fd; -} + static char *tmpdir = NULL; + char tfile[MAXPATHLEN]; + int fd; -/* - * Convert a string representation of a boolean. - * Anything that looks like "No", "False", "Off", "0" etc, - * is FALSE, otherwise TRUE. - */ -Boolean -s2Boolean(const char *s, Boolean bf) -{ - switch(s[0]) { - case '\0': /* not set - the default wins */ - break; - case '0': - case 'F': - case 'f': - case 'N': - case 'n': - return FALSE; - case 'O': - case 'o': - return s[1] != 'F' && s[1] != 'f'; - default: - return TRUE; - } - return bf; + if (pattern == NULL) + pattern = TMPPAT; + if (tmpdir == NULL) + tmpdir = getTmpdir(); + if (pattern[0] == '/') { + snprintf(tfile, sizeof tfile, "%s", pattern); + } else { + snprintf(tfile, sizeof tfile, "%s%s", tmpdir, pattern); + } + if ((fd = mkstemp(tfile)) < 0) + Punt("Could not create temporary file %s: %s", tfile, + strerror(errno)); + if (out_fname) { + *out_fname = bmake_strdup(tfile); + } else { + unlink( + tfile); /* we just want the descriptor */ + } + return fd; } /* - * Return a Boolean based on a variable. - * - * If the knob is not set, return the fallback. - * If set, anything that looks or smells like "No", "False", "Off", "0", etc. - * is FALSE, otherwise TRUE. + * Convert a string representation of a boolean into a boolean value. + * Anything that looks like "No", "False", "Off", "0" etc. is FALSE, + * the empty string is the fallback, everything else is TRUE. */ Boolean -getBoolean(const char *varname, Boolean fallback) +ParseBoolean(const char *s, Boolean fallback) { - char *expr = str_concat3("${", varname, ":U}"); - char *value; - Boolean res; - - (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value); - /* TODO: handle errors */ - res = s2Boolean(value, fallback); - free(value); - free(expr); - return res; + char ch = ch_tolower(s[0]); + if (ch == '\0') + return fallback; + if (ch == '0' || ch == 'f' || ch == 'n') + return FALSE; + if (ch == 'o') + return ch_tolower(s[1]) != 'f'; + return TRUE; } diff --git a/make-bootstrap.sh.in b/make-bootstrap.sh.in index a75634b3dae6..0ecce455da74 100755 --- a/make-bootstrap.sh.in +++ b/make-bootstrap.sh.in @@ -16,7 +16,8 @@ CFLAGS="@CFLAGS@ -I. -I${srcdir} @DEFS@ @CPPFLAGS@ -DMAKE_NATIVE ${XDEFS} -DBMAK MAKE_VERSION=@_MAKE_VERSION@ MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \ --D@force_machine@MACHINE=\"@machine@\" -DMACHINE_ARCH=\"@machine_arch@\" \ +-D@force_machine@MACHINE=\"@machine@\" \ +-D@force_machine_arch@MACHINE_ARCH=\"@machine_arch@\" \ -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\"" @@ -59,7 +60,7 @@ do_link() { } BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \ -lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \ +lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o \ suff.o targ.o trace.o var.o util.o" LIB_OBJECTS="@LIBOBJS@" @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ +.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd November 1, 2020 +.Dd November 14, 2020 .Dt MAKE 1 .Os .Sh NAME @@ -37,7 +37,7 @@ .Nd maintain program dependencies .Sh SYNOPSIS .Nm -.Op Fl BeikNnqrstWwX +.Op Fl BeikNnqrSstWwX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags @@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. +.It Fl S +Stop processing if an error is encountered. +This is the default behavior and the opposite of +.Fl k . .It Fl s Do not echo any commands as they are executed. Equivalent to specifying @@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set and .Ql Ev PWD to that directory before executing any targets. +.Pp +Except in the case of an explicit +.Ql Ic .OBJDIR +target, +.Nm +will check that the specified directory is writable and ignore it if not. +This check can be skipped by setting the environment variable +.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE +to "no". . .It Va .PARSEDIR A path to the directory of the current @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,33 +68,28 @@ * SUCH DAMAGE. */ -/*- - * make.c -- - * The functions which perform the examination of targets and - * their suitability for creation +/* Examination of targets and their suitability for creation. * * Interface: - * Make_Run Initialize things for the module and recreate - * whatever needs recreating. Returns TRUE if - * work was (or would have been) done and FALSE - * otherwise. + * Make_Run Initialize things for the module. Returns TRUE if + * work was (or would have been) done. * - * Make_Update Update all parents of a given child. Performs - * various bookkeeping chores like the updating + * Make_Update After a target is made, update all its parents. + * Perform various bookkeeping chores like the updating * of the youngestChild field of the parent, filling - * of the IMPSRC context variable, etc. It will - * place the parent on the toBeMade queue if it - * should be. + * of the IMPSRC context variable, etc. Place the parent + * on the toBeMade queue if it should be. * - * Make_TimeStamp Function to set the parent's youngestChild field - * based on a child's modification time. + * GNode_UpdateYoungestChild + * Update the node's youngestChild field based on the + * child's modification time. * * Make_DoAllVar Set up the various local variables for a * target, including the .ALLSRC variable, making * sure that any variable that needs to exist * at the very least has the empty value. * - * Make_OODate Determine if a target is out-of-date. + * GNode_IsOODate Determine if a target is out-of-date. * * Make_HandleUse See if a child is a .USE node for a parent * and perform the .USE actions if so. @@ -107,17 +102,16 @@ #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $"); /* Sequence # to detect recursion. */ -static unsigned int checked = 1; +static unsigned int checked_seqno = 1; /* The current fringe of the graph. * These are nodes which await examination by MakeOODate. * It is added to by Make_Update and subtracted from by MakeStartJobs */ static GNodeList *toBeMade; -static int MakeCheckOrder(void *, void *); static int MakeBuildParent(void *, void *); void @@ -185,11 +179,37 @@ GNode_ShouldExecute(GNode *gn) /* Update the youngest child of the node, according to the given child. */ void -Make_TimeStamp(GNode *pgn, GNode *cgn) +GNode_UpdateYoungestChild(GNode *gn, GNode *cgn) { - if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) { - pgn->youngestChild = cgn; + if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime) + gn->youngestChild = cgn; +} + +static Boolean +IsOODateRegular(GNode *gn) +{ + /* These rules are inherited from the original Make. */ + + if (gn->youngestChild != NULL) { + if (gn->mtime < gn->youngestChild->mtime) { + DEBUG1(MAKE, "modified before source \"%s\"...", + GNode_Path(gn->youngestChild)); + return TRUE; + } + return FALSE; + } + + if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) { + DEBUG0(MAKE, "non-existent and no sources..."); + return TRUE; } + + if (gn->type & OP_DOUBLEDEP) { + DEBUG0(MAKE, ":: operator and no sources..."); + return TRUE; + } + + return FALSE; } /* See if the node is out of date with respect to its sources. @@ -204,7 +224,7 @@ Make_TimeStamp(GNode *pgn, GNode *cgn) * may be changed. */ Boolean -Make_OODate(GNode *gn) +GNode_IsOODate(GNode *gn) { Boolean oodate; @@ -212,14 +232,13 @@ Make_OODate(GNode *gn) * Certain types of targets needn't even be sought as their datedness * doesn't depend on their modification time... */ - if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) { - (void)Dir_MTime(gn, 1); + if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { + Dir_UpdateMTime(gn, TRUE); if (DEBUG(MAKE)) { - if (gn->mtime != 0) { + if (gn->mtime != 0) debug_printf("modified %s...", Targ_FmtTime(gn->mtime)); - } else { + else debug_printf("non-existent..."); - } } } @@ -244,8 +263,7 @@ Make_OODate(GNode *gn) */ DEBUG0(MAKE, ".USE node..."); oodate = FALSE; - } else if ((gn->type & OP_LIB) && - ((gn->mtime==0) || Arch_IsLib(gn))) { + } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) { DEBUG0(MAKE, "library..."); /* @@ -261,7 +279,7 @@ Make_OODate(GNode *gn) */ DEBUG0(MAKE, ".JOIN node..."); DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); - oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; + oodate = (gn->flags & CHILDMADE) != 0; } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { /* * A node which is the object of the force (!) operator or which has @@ -277,30 +295,7 @@ Make_OODate(GNode *gn) } } oodate = TRUE; - } else if ((gn->youngestChild != NULL && - gn->mtime < gn->youngestChild->mtime) || - (gn->youngestChild == NULL && - ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) - || gn->type & OP_DOUBLEDEP))) - { - /* - * A node whose modification time is less than that of its - * youngest child or that has no children (youngestChild == NULL) and - * either doesn't exist (mtime == 0) and it isn't optional - * or was the object of a * :: operator is out-of-date. - * Why? Because that's the way Make does it. - */ - if (DEBUG(MAKE)) { - if (gn->youngestChild != NULL && - gn->mtime < gn->youngestChild->mtime) { - debug_printf("modified before source %s...", - GNode_Path(gn->youngestChild)); - } else if (gn->mtime == 0) { - debug_printf("non-existent and no sources..."); - } else { - debug_printf(":: operator and no sources..."); - } - } + } else if (IsOODateRegular(gn)) { oodate = TRUE; } else { /* @@ -314,7 +309,7 @@ Make_OODate(GNode *gn) if (gn->flags & FORCE) debug_printf("non existing child..."); } - oodate = (gn->flags & FORCE) ? TRUE : FALSE; + oodate = (gn->flags & FORCE) != 0; } #ifdef USE_META @@ -333,46 +328,24 @@ Make_OODate(GNode *gn) if (!oodate) { GNodeListNode *ln; for (ln = gn->parents->first; ln != NULL; ln = ln->next) - Make_TimeStamp(ln->datum, gn); + GNode_UpdateYoungestChild(ln->datum, gn); } return oodate; } -/* Add the node to the list if it needs to be examined. */ -static int -MakeAddChild(void *gnp, void *lp) -{ - GNode *gn = gnp; - GNodeList *l = lp; - - if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { - DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n", - gn->name, gn->cohort_num); - Lst_Enqueue(l, gn); - } - return 0; -} - -/* Find the pathname of a child that was already made. - * - * The path and mtime of the node and the youngestChild of the parent are - * updated; the unmade children count of the parent is decremented. - * - * Input: - * gnp the node to find - */ -static int -MakeFindChild(void *gnp, void *pgnp) +static void +PretendAllChildrenAreMade(GNode *pgn) { - GNode *gn = gnp; - GNode *pgn = pgnp; + GNodeListNode *ln; - (void)Dir_MTime(gn, 0); - Make_TimeStamp(pgn, gn); - pgn->unmade--; + for (ln = pgn->children->first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; - return 0; + Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */ + GNode_UpdateYoungestChild(pgn, cgn); + pgn->unmade--; + } } /* Called by Make_Run and SuffApplyTransform on the downward pass to handle @@ -394,9 +367,9 @@ Make_HandleUse(GNode *cgn, GNode *pgn) GNodeListNode *ln; /* An element in the children list */ #ifdef DEBUG_SRC - if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { + if (!(cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM))) { debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name); - return; + return; /* XXX: debug mode should not affect control flow */ } #endif @@ -457,10 +430,10 @@ MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln) { Boolean unmarked; - unmarked = ((cgn->type & OP_MARK) == 0); + unmarked = !(cgn->type & OP_MARK); cgn->type |= OP_MARK; - if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) + if (!(cgn->type & (OP_USE|OP_USEBEFORE))) return; if (unmarked) @@ -493,7 +466,10 @@ HandleUseNodes(GNode *gn) time_t Make_Recheck(GNode *gn) { - time_t mtime = Dir_MTime(gn, 1); + time_t mtime; + + Dir_UpdateMTime(gn, TRUE); + mtime = gn->mtime; #ifndef RECHECK /* @@ -512,13 +488,11 @@ Make_Recheck(GNode *gn) * In this case, if the definitions produced by yacc haven't changed * from before, parse.h won't have been updated and gn->mtime will * reflect the current modification time for parse.h. This is - * something of a kludge, I admit, but it's a useful one.. - * XXX: People like to use a rule like + * something of a kludge, I admit, but it's a useful one. * - * FRC: - * - * To force things that depend on FRC to be made, so we have to - * check for gn->children being empty as well... + * XXX: People like to use a rule like "FRC:" to force things that + * depend on FRC to be made, so we have to check for gn->children + * being empty as well. */ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { gn->mtime = now; @@ -535,7 +509,7 @@ Make_Recheck(GNode *gn) * using the same file from a common server), there are times * when the modification time of a file created on a remote * machine will not be modified before the local stat() implied by - * the Dir_MTime occurs, thus leading us to believe that the file + * the Dir_UpdateMTime occurs, thus leading us to believe that the file * is unchanged, wreaking havoc with files that depend on this one. * * I have decided it is better to make too much than to make too @@ -543,8 +517,8 @@ Make_Recheck(GNode *gn) * -- ardeb 1/12/88 */ /* - * Christos, 4/9/92: If we are saving commands pretend that - * the target is made now. Otherwise archives with ... rules + * Christos, 4/9/92: If we are saving commands, pretend that + * the target is made now. Otherwise archives with '...' rules * don't work! */ if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) || @@ -552,12 +526,14 @@ Make_Recheck(GNode *gn) DEBUG2(MAKE, " recheck(%s): update time from %s to now\n", gn->name, Targ_FmtTime(gn->mtime)); gn->mtime = now; - } - else { + } else { DEBUG2(MAKE, " recheck(%s): current update time: %s\n", gn->name, Targ_FmtTime(gn->mtime)); } #endif + + /* XXX: The returned mtime may differ from gn->mtime. + * Intentionally? */ return mtime; } @@ -581,6 +557,25 @@ UpdateImplicitParentsVars(GNode *cgn, const char *cname) } } +/* See if a .ORDER rule stops us from building this node. */ +static Boolean +IsWaitingForOrder(GNode *gn) +{ + GNodeListNode *ln; + + for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) { + GNode *ogn = ln->datum; + + if (ogn->made >= MADE || !(ogn->flags & REMAKE)) + continue; + + DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n", + ogn->name, ogn->cohort_num); + return TRUE; + } + return FALSE; +} + /* Perform update on the parents of a node. Used by JobFinish once * a node has been dealt with and by MakeStartJobs if it finds an * up-to-date node. @@ -610,7 +605,7 @@ Make_Update(GNode *cgn) GNode *centurion; /* It is save to re-examine any nodes again */ - checked++; + checked_seqno++; cname = GNode_VarTarget(cgn); @@ -647,11 +642,11 @@ Make_Update(GNode *cgn) for (ln = parents->first; ln != NULL; ln = ln->next) { GNode *pgn = ln->datum; - if (DEBUG(MAKE)) - debug_printf("inspect parent %s%s: flags %x, " - "type %x, made %d, unmade %d ", - pgn->name, pgn->cohort_num, pgn->flags, - pgn->type, pgn->made, pgn->unmade - 1); + if (DEBUG(MAKE)) { + debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", pgn, ""); + debug_printf(", unmade %d ", pgn->unmade - 1); + } if (!(pgn->flags & REMAKE)) { /* This parent isn't needed */ @@ -674,10 +669,10 @@ Make_Update(GNode *cgn) continue; } - if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) { + if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) { if (cgn->made == MADE) pgn->flags |= CHILDMADE; - (void)Make_TimeStamp(pgn, cgn); + GNode_UpdateYoungestChild(pgn, cgn); } /* @@ -716,11 +711,10 @@ Make_Update(GNode *cgn) DEBUG0(MAKE, "- not deferred\n"); continue; } - assert(pgn->order_pred != NULL); - if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) { - /* A .ORDER rule stops us building this */ + + if (IsWaitingForOrder(pgn)) continue; - } + if (DEBUG(MAKE)) { debug_printf("- %s%s made, schedule %s%s (made %d)\n", cgn->name, cgn->cohort_num, @@ -771,7 +765,7 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn) return; cgn->type |= OP_MARK; - if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { + if (!(cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE))) { const char *child, *allsrc; if (cgn->type & OP_ARCHV) @@ -798,9 +792,9 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn) * the start of the make. This is to keep pmake from getting * confused if something else updates the parent after the * make starts (shouldn't happen, I know, but sometimes it - * does). In such a case, if we've updated the kid, the parent + * does). In such a case, if we've updated the child, the parent * is likely to have a modification time later than that of - * the kid and anything that relies on the OODATE variable will + * the child and anything that relies on the OODATE variable will * be hosed. * * XXX: This will cause all made children to go in the OODATE @@ -838,12 +832,10 @@ Make_DoAllVar(GNode *gn) for (ln = gn->children->first; ln != NULL; ln = ln->next) MakeAddAllSrc(ln->datum, gn); - if (!Var_Exists(OODATE, gn)) { + if (!Var_Exists(OODATE, gn)) Var_Set(OODATE, "", gn); - } - if (!Var_Exists(ALLSRC, gn)) { + if (!Var_Exists(ALLSRC, gn)) Var_Set(ALLSRC, "", gn); - } if (gn->type & OP_JOIN) Var_Set(TARGET, GNode_VarAllsrc(gn), gn); @@ -851,31 +843,20 @@ Make_DoAllVar(GNode *gn) } static int -MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) -{ - GNode *bn = v_bn; - - if (bn->made >= MADE || !(bn->flags & REMAKE)) - return 0; - - DEBUG2(MAKE, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", - bn->name, bn->cohort_num); - return 1; -} - -static int MakeBuildChild(void *v_cn, void *toBeMade_next) { GNode *cn = v_cn; - DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n", - cn->name, cn->cohort_num, cn->made, cn->type); + if (DEBUG(MAKE)) { + debug_printf("MakeBuildChild: inspect %s%s, ", + cn->name, cn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", cn, "\n"); + } if (cn->made > DEFERRED) return 0; /* If this node is on the RHS of a .ORDER, check LHSs. */ - assert(cn->order_pred); - if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) { + if (IsWaitingForOrder(cn)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; return 0; /* but keep looking */ @@ -899,7 +880,7 @@ MakeBuildChild(void *v_cn, void *toBeMade_next) return cn->type & OP_WAIT && cn->unmade > 0; } -/* When a .ORDER LHS node completes we do this on each RHS */ +/* When a .ORDER LHS node completes, we do this on each RHS. */ static int MakeBuildParent(void *v_pn, void *toBeMade_next) { @@ -918,21 +899,21 @@ MakeBuildParent(void *v_pn, void *toBeMade_next) /* Start as many jobs as possible, taking them from the toBeMade queue. * - * If the query flag was given to pmake, no job will be started, + * If the -q option was given, no job will be started, * but as soon as an out-of-date target is found, this function - * returns TRUE. At all other times, this function returns FALSE. + * returns TRUE. In all other cases, this function returns FALSE. */ static Boolean MakeStartJobs(void) { - GNode *gn; - int have_token = 0; + GNode *gn; + Boolean have_token = FALSE; while (!Lst_IsEmpty(toBeMade)) { /* Get token now to avoid cycling job-list when we only have 1 token */ if (!have_token && !Job_TokenWithdraw()) break; - have_token = 1; + have_token = TRUE; gn = Lst_Dequeue(toBeMade); DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); @@ -943,13 +924,13 @@ MakeStartJobs(void) make_abort(gn, __LINE__); } - if (gn->checked_seqno == checked) { + if (gn->checked_seqno == checked_seqno) { /* We've already looked at this node since a job finished... */ DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num); gn->made = DEFERRED; continue; } - gn->checked_seqno = checked; + gn->checked_seqno = checked_seqno; if (gn->unmade != 0) { /* @@ -964,14 +945,13 @@ MakeStartJobs(void) } gn->made = BEINGMADE; - if (Make_OODate(gn)) { + if (GNode_IsOODate(gn)) { DEBUG0(MAKE, "out-of-date\n"); - if (opts.queryFlag) { + if (opts.queryFlag) return TRUE; - } Make_DoAllVar(gn); Job_Make(gn); - have_token = 0; + have_token = FALSE; } else { DEBUG0(MAKE, "up-to-date\n"); gn->made = UPTODATE; @@ -994,6 +974,7 @@ MakeStartJobs(void) return FALSE; } +/* Print the status of a .ORDER node. */ static void MakePrintStatusOrderNode(GNode *ogn, GNode *gn) { @@ -1074,7 +1055,7 @@ MakePrintStatus(GNode *gn, int *errors) * print out the cycle by recursing on its children. */ if (!(gn->flags & CYCLE)) { - /* Fist time we've seen this node, check all children */ + /* First time we've seen this node, check all children */ gn->flags |= CYCLE; MakePrintStatusList(gn->children, errors); /* Mark that this node needn't be processed again */ @@ -1103,6 +1084,25 @@ MakePrintStatusList(GNodeList *gnodes, int *errors) break; } +static void +ExamineLater(GNodeList *examine, GNodeList *toBeExamined) +{ + ListNode *ln; + + for (ln = toBeExamined->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + if (gn->flags & REMAKE) + continue; + if (gn->type & (OP_USE | OP_USEBEFORE)) + continue; + + DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n", + gn->name, gn->cohort_num); + Lst_Enqueue(examine, gn); + } +} + /* Expand .USE nodes and create a new targets list. * * Input: @@ -1111,17 +1111,8 @@ MakePrintStatusList(GNodeList *gnodes, int *errors) void Make_ExpandUse(GNodeList *targs) { - GNodeList *examine; /* List of targets to examine */ - - { - /* XXX: Why is it necessary to copy the list? There shouldn't be - * any modifications to the list, at least the function name - * ExpandUse doesn't suggest that. */ - GNodeListNode *ln; - examine = Lst_New(); - for (ln = targs->first; ln != NULL; ln = ln->next) - Lst_Append(examine, ln->datum); - } + GNodeList *examine = Lst_New(); /* Queue of targets to examine */ + Lst_AppendAll(examine, targs); /* * Make an initial downward pass over the graph, marking nodes to be made @@ -1151,9 +1142,8 @@ Make_ExpandUse(GNodeList *targs) * expansions. */ if (gn->type & OP_ARCHV) { - char *eoa, *eon; - eoa = strchr(gn->name, '('); - eon = strchr(gn->name, ')'); + char *eoa = strchr(gn->name, '('); + char *eon = strchr(gn->name, ')'); if (eoa == NULL || eon == NULL) continue; *eoa = '\0'; @@ -1164,23 +1154,22 @@ Make_ExpandUse(GNodeList *targs) *eon = ')'; } - (void)Dir_MTime(gn, 0); + Dir_UpdateMTime(gn, FALSE); Var_Set(TARGET, GNode_Path(gn), gn); UnmarkChildren(gn); HandleUseNodes(gn); - if ((gn->type & OP_MADE) == 0) + if (!(gn->type & OP_MADE)) Suff_FindDeps(gn); else { - /* Pretend we made all this node's children */ - Lst_ForEachUntil(gn->children, MakeFindChild, gn); + PretendAllChildrenAreMade(gn); if (gn->unmade != 0) - printf("Warning: %s%s still has %d unmade children\n", - gn->name, gn->cohort_num, gn->unmade); + printf("Warning: %s%s still has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); } if (gn->unmade != 0) - Lst_ForEachUntil(gn->children, MakeAddChild, examine); + ExamineLater(examine, gn->children); } Lst_Free(examine); @@ -1218,7 +1207,7 @@ Make_ProcessWait(GNodeList *targs) * Perhaps this should be done earlier... */ - pgn = Targ_NewGN(".MAIN"); + pgn = GNode_New(".MAIN"); pgn->flags = REMAKE; pgn->type = OP_PHONY | OP_DEPENDS; /* Get it displayed in the diag dumps */ @@ -1353,9 +1342,9 @@ Make_Run(GNodeList *targs) MakePrintStatusList(targs, &errors); if (DEBUG(MAKE)) { debug_printf("done: errors %d\n", errors); - if (errors) + if (errors > 0) Targ_PrintGraph(4); } } - return errors != 0; + return errors > 0; } @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.179 2020/11/01 17:47:26 rillig Exp $ */ +/* $NetBSD: make.h,v 1.210 2020/11/16 21:53:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -135,6 +135,8 @@ #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #endif +#define MAKE_INLINE static inline MAKE_ATTR_UNUSED + /* * A boolean type is defined as an integer, not an enum, for historic reasons. * The only allowed values are the constants TRUE and FALSE (1 and 0). @@ -187,7 +189,7 @@ typedef int Boolean; #define POSIX_SIGNALS #endif -typedef enum { +typedef enum GNodeMade { UNMADE, /* Not examined yet */ DEFERRED, /* Examined once (building child) */ REQUESTED, /* on toBeMade list */ @@ -207,6 +209,8 @@ typedef enum { * * Some of the OP_ constants can be combined, others cannot. */ typedef enum GNodeType { + OP_NONE = 0, + /* The dependency operator ':' is the most common one. The commands of * this node are executed if any child is out-of-date. */ OP_DEPENDS = 1 << 0, @@ -215,7 +219,8 @@ typedef enum GNodeType { OP_FORCE = 1 << 1, /* The dependency operator '::' behaves like ':', except that it allows * multiple dependency groups to be defined. Each of these groups is - * executed on its own, independently from the others. */ + * executed on its own, independently from the others. Each individual + * dependency group is called a cohort. */ OP_DOUBLEDEP = 1 << 2, /* Matches the dependency operators ':', '!' and '::'. */ @@ -246,7 +251,7 @@ typedef enum GNodeType { /* Like .USE, only prepend commands */ OP_USEBEFORE = 1 << 13, /* The node is invisible to its parents. I.e. it doesn't show up in the - * parents' local variables. */ + * parents' local variables (.IMPSRC, .ALLSRC). */ OP_INVISIBLE = 1 << 14, /* The node is exempt from normal 'main target' processing in parse.c */ OP_NOTMAIN = 1 << 15, @@ -254,7 +259,10 @@ typedef enum GNodeType { OP_PHONY = 1 << 16, /* Don't search for file in the path */ OP_NOPATH = 1 << 17, - /* .WAIT phony node */ + /* In a dependency line "target: source1 .WAIT source2", source1 is made + * first, including its children. Once that is finished, source2 is made, + * including its children. The .WAIT keyword may appear more than once in + * a single dependency declaration. */ OP_WAIT = 1 << 18, /* .NOMETA do not create a .meta file */ OP_NOMETA = 1 << 19, @@ -267,7 +275,7 @@ typedef enum GNodeType { /* Attributes applied by PMake */ - /* The node is a transformation rule */ + /* The node is a transformation rule, such as ".c.o". */ OP_TRANSFORM = 1 << 31, /* Target is a member of an archive */ /* XXX: How does this differ from OP_ARCHV? */ @@ -337,7 +345,7 @@ typedef struct GNode { int unmade; /* The number of unmade children */ /* The modification time; 0 means the node does not have a corresponding - * file; see Make_OODate. */ + * file; see GNode_IsOODate. */ time_t mtime; struct GNode *youngestChild; @@ -346,9 +354,6 @@ typedef struct GNode { * file.c has the node for file.o in this list. */ GNodeList *implicitParents; - /* Other nodes of the same name, for the '::' operator. */ - GNodeList *cohorts; - /* The nodes that depend on this one, or in other words, the nodes for * which this is a source. */ GNodeList *parents; @@ -364,6 +369,8 @@ typedef struct GNode { * in the normal sense. */ GNodeList *order_succ; + /* Other nodes of the same name, for the '::' dependency operator. */ + GNodeList *cohorts; /* The "#n" suffix for this cohort, or "" for other nodes */ char cohort_num[8]; /* The number of unmade instances on the cohorts list */ @@ -389,20 +396,20 @@ typedef struct GNode { * but the Suff module) */ struct Suff *suffix; - /* filename where the GNode got defined */ + /* Filename where the GNode got defined */ + /* XXX: What is the lifetime of this string? */ const char *fname; - /* line number where the GNode got defined */ + /* Line number where the GNode got defined */ int lineno; } GNode; -/* - * Error levels for parsing. PARSE_FATAL means the process cannot continue - * once the top-level makefile has been parsed. PARSE_WARNING and PARSE_INFO - * mean it can. - */ +/* Error levels for diagnostics during parsing. */ typedef enum ParseErrorLevel { + /* Exit when the current top-level makefile has been parsed completely. */ PARSE_FATAL = 1, + /* Print "warning"; may be upgraded to fatal by the -w option. */ PARSE_WARNING, + /* Informational, mainly used during development of makefiles. */ PARSE_INFO } ParseErrorLevel; @@ -415,9 +422,7 @@ typedef enum CondEvalResult { COND_INVALID /* Not a conditional statement */ } CondEvalResult; -/* - * Definitions for the "local" variables. Used only for clarity. - */ +/* Names of the variables that are "local" to a specific target. */ #define TARGET "@" /* Target of dependency */ #define OODATE "?" /* All out-of-date sources */ #define ALLSRC ">" /* All sources */ @@ -426,58 +431,77 @@ typedef enum CondEvalResult { #define ARCHIVE "!" /* Archive in "archive(member)" syntax */ #define MEMBER "%" /* Member in "archive(member)" syntax */ -#define FTARGET "@F" /* file part of TARGET */ -#define DTARGET "@D" /* directory part of TARGET */ -#define FIMPSRC "<F" /* file part of IMPSRC */ -#define DIMPSRC "<D" /* directory part of IMPSRC */ -#define FPREFIX "*F" /* file part of PREFIX */ -#define DPREFIX "*D" /* directory part of PREFIX */ - /* * Global Variables */ -extern SearchPath *dirSearchPath; - /* The list of directories to search when - * looking for targets */ -extern Boolean allPrecious; /* True if every target is precious */ -extern Boolean deleteOnError; /* True if failed targets should be deleted */ -extern Boolean doing_depend; /* TRUE if processing .depend */ - -extern GNode *DEFAULT; /* .DEFAULT rule */ - -extern GNode *VAR_INTERNAL; /* Variables defined internally by make - * which should not override those set by - * makefiles. - */ -extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g - * in the Makefile itself */ -extern GNode *VAR_CMDLINE; /* Variables defined on the command line */ -extern char var_Error[]; /* Value returned by Var_Parse when an error - * is encountered. It actually points to - * an empty string, so naive callers needn't - * worry about it. */ - -extern time_t now; /* The time at the start of this whole - * process */ - -extern Boolean oldVars; /* Do old-style variable substitution */ -extern SearchPath *sysIncPath; /* The system include path. */ -extern SearchPath *defSysIncPath; /* The default system include path. */ +/* True if every target is precious */ +extern Boolean allPrecious; +/* True if failed targets should be deleted */ +extern Boolean deleteOnError; +/* TRUE while processing .depend */ +extern Boolean doing_depend; +/* .DEFAULT rule */ +extern GNode *defaultNode; + +/* Variables defined internally by make which should not override those set + * by makefiles. */ +extern GNode *VAR_INTERNAL; +/* Variables defined in a global context, e.g in the Makefile itself. */ +extern GNode *VAR_GLOBAL; +/* Variables defined on the command line. */ +extern GNode *VAR_CMDLINE; + +/* Value returned by Var_Parse when an error is encountered. It actually + * points to an empty string, so naive callers needn't worry about it. */ +extern char var_Error[]; + +/* The time at the start of this whole process */ +extern time_t now; -extern char curdir[]; /* Startup directory */ -extern char *progname; /* The program name */ -extern char *makeDependfile; /* .depend */ -extern char **savedEnv; /* if we replaced environ this will be non-NULL */ +/* + * If FALSE (the default behavior), undefined subexpressions in a variable + * expression are discarded. If TRUE (only during variable assignments using + * the ':=' assignment operator, no matter how deeply nested), they are + * preserved and possibly expanded later when the variable from the + * subexpression has been defined. + * + * Example for a ':=' assignment: + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * # If .INCLUDES (an undocumented special variable, by the way) is + * # still undefined, the updated CFLAGS becomes "-I.. $(.INCLUDES)". + */ +extern Boolean preserveUndefined; -extern int makelevel; +/* The list of directories to search when looking for targets (set by the + * special target .PATH). */ +extern SearchPath *dirSearchPath; +/* Used for .include "...". */ +extern SearchPath *parseIncPath; +/* Used for .include <...>, for the built-in sys.mk and makefiles from the + * command line arguments. */ +extern SearchPath *sysIncPath; +/* The default for sysIncPath. */ +extern SearchPath *defSysIncPath; + +/* Startup directory */ +extern char curdir[]; +/* The basename of the program name, suffixed with [n] for sub-makes. */ +extern char *progname; +/* Name of the .depend makefile */ +extern char *makeDependfile; +/* If we replaced environ, this will be non-NULL. */ +extern char **savedEnv; + +extern int makelevel; /* * We cannot vfork() in a child of vfork(). * Most systems do not enforce this but some do. */ #define vFork() ((getpid() == myPid) ? vfork() : fork()) -extern pid_t myPid; +extern pid_t myPid; #define MAKEFLAGS ".MAKEFLAGS" #define MAKEOVERRIDES ".MAKEOVERRIDES" @@ -485,7 +509,7 @@ extern pid_t myPid; #define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */ #define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */ #define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ -#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" +#define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" #define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ #define MAKE_MODE ".MAKE.MODE" #ifndef MAKE_LEVEL_ENV @@ -493,29 +517,28 @@ extern pid_t myPid; #endif typedef enum DebugFlags { + DEBUG_NONE = 0, DEBUG_ARCH = 1 << 0, DEBUG_COND = 1 << 1, - DEBUG_DIR = 1 << 2, - DEBUG_GRAPH1 = 1 << 3, - DEBUG_GRAPH2 = 1 << 4, - DEBUG_JOB = 1 << 5, - DEBUG_MAKE = 1 << 6, - DEBUG_SUFF = 1 << 7, - DEBUG_TARG = 1 << 8, - DEBUG_VAR = 1 << 9, - DEBUG_FOR = 1 << 10, - DEBUG_SHELL = 1 << 11, - DEBUG_ERROR = 1 << 12, - DEBUG_LOUD = 1 << 13, - DEBUG_META = 1 << 14, - DEBUG_HASH = 1 << 15, - - DEBUG_GRAPH3 = 1 << 16, - DEBUG_SCRIPT = 1 << 17, - DEBUG_PARSE = 1 << 18, - DEBUG_CWD = 1 << 19, - - DEBUG_LINT = 1 << 20 + DEBUG_CWD = 1 << 2, + DEBUG_DIR = 1 << 3, + DEBUG_ERROR = 1 << 4, + DEBUG_FOR = 1 << 5, + DEBUG_GRAPH1 = 1 << 6, + DEBUG_GRAPH2 = 1 << 7, + DEBUG_GRAPH3 = 1 << 8, + DEBUG_HASH = 1 << 9, + DEBUG_JOB = 1 << 10, + DEBUG_LOUD = 1 << 11, + DEBUG_MAKE = 1 << 12, + DEBUG_META = 1 << 13, + DEBUG_PARSE = 1 << 14, + DEBUG_SCRIPT = 1 << 15, + DEBUG_SHELL = 1 << 16, + DEBUG_SUFF = 1 << 17, + DEBUG_TARG = 1 << 18, + DEBUG_VAR = 1 << 19, + DEBUG_ALL = (1 << 20) - 1 } DebugFlags; #define CONCAT(a,b) a##b @@ -549,8 +572,9 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5) typedef enum PrintVarsMode { - COMPAT_VARS = 1, - EXPAND_VARS + PVM_NONE, + PVM_UNEXPANDED, + PVM_EXPANDED } PrintVarsMode; /* Command line options */ @@ -565,6 +589,12 @@ typedef struct CmdOpts { /* -df: debug output is written here - default stderr */ FILE *debug_file; + /* -dL: lint mode + * + * Runs make in strict mode, with additional checks and better error + * handling. */ + Boolean lint; + /* -dV: for the -V option, print unexpanded variable values */ Boolean debugVflag; @@ -630,49 +660,49 @@ extern CmdOpts opts; #include "nonints.h" -void Make_TimeStamp(GNode *, GNode *); -Boolean Make_OODate(GNode *); +void GNode_UpdateYoungestChild(GNode *, GNode *); +Boolean GNode_IsOODate(GNode *); void Make_ExpandUse(GNodeList *); time_t Make_Recheck(GNode *); void Make_HandleUse(GNode *, GNode *); void Make_Update(GNode *); void Make_DoAllVar(GNode *); Boolean Make_Run(GNodeList *); -int dieQuietly(GNode *, int); +Boolean shouldDieQuietly(GNode *, int); void PrintOnError(GNode *, const char *); void Main_ExportMAKEFLAGS(Boolean); -Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); +Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); int mkTempFile(const char *, char **); -int str2Lst_Append(StringList *, char *, const char *); +int str2Lst_Append(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); Boolean GNode_ShouldExecute(GNode *gn); /* See if the node was seen on the left-hand side of a dependency operator. */ -static MAKE_ATTR_UNUSED Boolean +MAKE_INLINE Boolean GNode_IsTarget(const GNode *gn) { return (gn->type & OP_OPMASK) != 0; } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_Path(const GNode *gn) { return gn->path != NULL ? gn->path : gn->name; } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); } -static MAKE_ATTR_UNUSED const char * +MAKE_INLINE const char * GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); } #ifdef __GNUC__ @@ -703,35 +733,49 @@ GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); } #define KILLPG(pid, sig) killpg((pid), (sig)) #endif -static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch) -{ return isalnum((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch) -{ return isalpha((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch) -{ return isdigit((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch) -{ return isspace((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch) -{ return isupper((unsigned char)ch) != 0; } -static inline MAKE_ATTR_UNUSED char ch_tolower(char ch) -{ return (char)tolower((unsigned char)ch); } -static inline MAKE_ATTR_UNUSED char ch_toupper(char ch) -{ return (char)toupper((unsigned char)ch); } - -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE Boolean +ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } +MAKE_INLINE char +ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } +MAKE_INLINE char +ch_toupper(char ch) { return (char)toupper((unsigned char)ch); } + +MAKE_INLINE void cpp_skip_whitespace(const char **pp) { while (ch_isspace(**pp)) (*pp)++; } -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void +cpp_skip_hspace(const char **pp) +{ + while (**pp == ' ' || **pp == '\t') + (*pp)++; +} + +MAKE_INLINE void pp_skip_whitespace(char **pp) { while (ch_isspace(**pp)) (*pp)++; } +MAKE_INLINE void +pp_skip_hspace(char **pp) +{ + while (**pp == ' ' || **pp == '\t') + (*pp)++; +} + #ifdef MAKE_NATIVE # include <sys/cdefs.h> # ifndef lint diff --git a/make_malloc.h b/make_malloc.h index aa03f380070e..551495fb6266 100644 --- a/make_malloc.h +++ b/make_malloc.h @@ -1,4 +1,4 @@ -/* $NetBSD: make_malloc.h,v 1.12 2020/10/19 23:43:55 rillig Exp $ */ +/* $NetBSD: make_malloc.h,v 1.13 2020/11/10 00:32:12 rillig Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -46,7 +46,7 @@ char *bmake_strsedup(const char *, const char *); * * The case of a NULL pointer happens especially often after Var_Value, * since only environment variables need to be freed, but not others. */ -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void bmake_free(void *p) { if (p != NULL) @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.136 2020/10/31 12:04:24 rillig Exp $ */ +/* $NetBSD: meta.c,v 1.144 2020/11/15 12:02:44 rillig Exp $ */ /* * Implement 'meta' mode. @@ -84,7 +84,6 @@ static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ extern Boolean forceJobs; -extern Boolean comatMake; extern char **environ; #define MAKE_META_PREFIX ".MAKE.META.PREFIX" @@ -97,7 +96,7 @@ extern char **environ; #endif #if !defined(HAVE_STRSEP) -# define strsep(s, d) stresep((s), (d), 0) +# define strsep(s, d) stresep((s), (d), '\0') #endif /* @@ -184,7 +183,7 @@ filemon_read(FILE *mfp, int fd) error = 0; fprintf(mfp, "\n-- filemon acquired metadata --\n"); - while ((n = read(fd, buf, sizeof(buf))) > 0) { + while ((n = read(fd, buf, sizeof buf)) > 0) { if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n) error = EIO; } @@ -276,12 +275,12 @@ meta_name(char *mname, size_t mnamelen, * next time through. */ if (tname[0] == '/') { - strlcpy(buf, tname, sizeof(buf)); + strlcpy(buf, tname, sizeof buf); } else { - snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); + snprintf(buf, sizeof buf, "%s/%s", cwd, tname); } - eat_dots(buf, sizeof(buf), 1); /* ./ */ - eat_dots(buf, sizeof(buf), 2); /* ../ */ + eat_dots(buf, sizeof buf, 1); /* ./ */ + eat_dots(buf, sizeof buf, 2); /* ../ */ tname = buf; } } @@ -329,7 +328,7 @@ is_submake(void *cmdp, void *gnp) char *cp2; int rc = 0; /* keep looking */ - if (!p_make) { + if (p_make == NULL) { void *dontFreeIt; p_make = Var_Value(".MAKE", gn, &dontFreeIt); p_len = strlen(p_make); @@ -341,7 +340,7 @@ is_submake(void *cmdp, void *gnp) cmd = mp; } cp2 = strstr(cmd, p_make); - if ((cp2)) { + if (cp2 != NULL) { switch (cp2[p_len]) { case '\0': case ' ': @@ -415,7 +414,7 @@ static Boolean meta_needed(GNode *gn, const char *dname, char *objdir, int verbose) { - struct make_stat mst; + struct cached_stat cst; if (verbose) verbose = DEBUG(META); @@ -446,7 +445,7 @@ meta_needed(GNode *gn, const char *dname, } /* The object directory may not exist. Check it.. */ - if (cached_stat(dname, &mst) != 0) { + if (cached_stat(dname, &cst) != 0) { if (verbose) debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); return FALSE; @@ -513,7 +512,7 @@ meta_create(BuildMon *pbm, GNode *gn) /* Don't create meta data. */ goto out; - fname = meta_name(pbm->meta_fname, sizeof(pbm->meta_fname), + fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname, dname, tname, objdir); #ifdef DEBUG_META_MODE @@ -529,7 +528,7 @@ meta_create(BuildMon *pbm, GNode *gn) printCMDs(gn, &mf); - fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); + fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof buf)); fprintf(mf.fp, "TARGET %s\n", tname); cp = GNode_VarOodate(gn); if (cp && *cp) { @@ -585,7 +584,7 @@ meta_init(void) #define get_mode_bf(bf, token) \ if ((cp = strstr(make_mode, token))) \ - bf = boolValue(&cp[sizeof(token) - 1]) + bf = boolValue(cp + sizeof (token) - 1) /* * Initialization we need after reading makefiles. @@ -630,7 +629,7 @@ meta_mode_init(const char *make_mode) if (once) return; once = 1; - memset(&Mybm, 0, sizeof(Mybm)); + memset(&Mybm, 0, sizeof Mybm); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ @@ -638,7 +637,7 @@ meta_mode_init(const char *make_mode) (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr); /* TODO: handle errors */ - str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); + str2Lst_Append(metaBailiwick, metaBailiwickStr); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ @@ -648,7 +647,7 @@ meta_mode_init(const char *make_mode) (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); /* TODO: handle errors */ - str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); + str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr); /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} @@ -784,7 +783,7 @@ meta_job_error(Job *job, GNode *gn, int flags, int status) if (job != NULL) { pbm = &job->bm; - if (!gn) + if (gn == NULL) gn = job->node; } else { pbm = &Mybm; @@ -798,9 +797,9 @@ meta_job_error(Job *job, GNode *gn, int flags, int status) if (gn) { Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL); } - getcwd(cwd, sizeof(cwd)); + getcwd(cwd, sizeof cwd); Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL); - if (pbm->meta_fname[0]) { + if (pbm->meta_fname[0] != '\0') { Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL); } meta_job_finish(job); @@ -821,7 +820,7 @@ meta_job_output(Job *job, char *cp, const char *nl) static char *meta_prefix = NULL; static size_t meta_prefix_len; - if (!meta_prefix) { + if (meta_prefix == NULL) { char *cp2; (void)Var_Subst("${" MAKE_META_PREFIX "}", @@ -851,7 +850,7 @@ meta_cmd_finish(void *pbmp) int x; #endif - if (!pbm) + if (pbm == NULL) pbm = &Mybm; #ifdef USE_FILEMON @@ -865,9 +864,11 @@ meta_cmd_finish(void *pbmp) error = x; pbm->mon_fd = -1; pbm->filemon = NULL; - } else + return error; + } #endif - fprintf(pbm->mfp, "\n"); /* ensure end with newline */ + + fprintf(pbm->mfp, "\n"); /* ensure end with newline */ return error; } @@ -941,7 +942,7 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) *bufp = buf = p; *szp = bufsz = newsz; /* fetch the rest */ - if (!fgets(&buf[x], (int)bufsz - x, fp)) + if (fgets(&buf[x], (int)bufsz - x, fp) == NULL) return x; /* truncated! */ goto check_newline; } @@ -1012,7 +1013,7 @@ meta_ignore(GNode *gn, const char *p) char *fm; /* skip if filter result is empty */ - snprintf(fname, sizeof(fname), + snprintf(fname, sizeof fname, "${%s:L:${%s:ts:}}", p, MAKE_META_IGNORE_FILTER); (void)Var_Subst(fname, gn, VARE_WANTRES, &fm); @@ -1090,7 +1091,7 @@ meta_oodate(GNode *gn, Boolean oodate) FILE *fp; Boolean needOODATE = FALSE; StringList *missingFiles; - int have_filemon = FALSE; + Boolean have_filemon = FALSE; void *objdir_freeIt; if (oodate) @@ -1114,7 +1115,7 @@ meta_oodate(GNode *gn, Boolean oodate) */ Make_DoAllVar(gn); - meta_name(fname, sizeof(fname), dname, tname, dname); + meta_name(fname, sizeof fname, dname, tname, dname); #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: %s\n", fname); @@ -1128,22 +1129,22 @@ meta_oodate(GNode *gn, Boolean oodate) int pid; int x; StringListNode *cmdNode; - struct make_stat mst; + struct cached_stat cst; - if (!buf) { + if (buf == NULL) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } - if (!cwdlen) { - if (getcwd(cwd, sizeof(cwd)) == NULL) + if (cwdlen == 0) { + if (getcwd(cwd, sizeof cwd) == NULL) err(1, "Could not get current working directory"); cwdlen = strlen(cwd); } - strlcpy(lcwd, cwd, sizeof(lcwd)); - strlcpy(latestdir, cwd, sizeof(latestdir)); + strlcpy(lcwd, cwd, sizeof lcwd); + strlcpy(latestdir, cwd, sizeof latestdir); - if (!tmpdir) { + if (tmpdir == NULL) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } @@ -1228,17 +1229,17 @@ meta_oodate(GNode *gn, Boolean oodate) Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, latestdir, VAR_GLOBAL); } - snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); - snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); + snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid); + snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid); lastpid = pid; ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); if (ldir) { - strlcpy(latestdir, ldir, sizeof(latestdir)); + strlcpy(latestdir, ldir, sizeof latestdir); bmake_free(tp); } ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); if (ldir) { - strlcpy(lcwd, ldir, sizeof(lcwd)); + strlcpy(lcwd, ldir, sizeof lcwd); bmake_free(tp); } } @@ -1271,9 +1272,9 @@ meta_oodate(GNode *gn, Boolean oodate) child = atoi(p); if (child > 0) { - snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); + snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child); Var_Set(cldir, lcwd, VAR_GLOBAL); - snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); + snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child); Var_Set(cldir, latestdir, VAR_GLOBAL); #ifdef DEBUG_META_MODE if (DEBUG(META)) @@ -1288,8 +1289,8 @@ meta_oodate(GNode *gn, Boolean oodate) case 'C': /* Chdir */ /* Update lcwd and latest directory. */ - strlcpy(latestdir, p, sizeof(latestdir)); - strlcpy(lcwd, p, sizeof(lcwd)); + strlcpy(latestdir, p, sizeof latestdir); + strlcpy(lcwd, p, sizeof lcwd); Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, lcwd, VAR_GLOBAL); #ifdef DEBUG_META_MODE @@ -1382,11 +1383,12 @@ meta_oodate(GNode *gn, Boolean oodate) break; /* ignore anything containing the string "tmp" */ + /* XXX: The arguments to strstr must be swapped. */ if ((strstr("tmp", p))) break; - if ((link_src != NULL && cached_lstat(p, &mst) < 0) || - (link_src == NULL && cached_stat(p, &mst) < 0)) { + if ((link_src != NULL && cached_lstat(p, &cst) < 0) || + (link_src == NULL && cached_stat(p, &cst) < 0)) { if (!meta_ignore(gn, p)) append_if_new(missingFiles, p); } @@ -1425,17 +1427,17 @@ meta_oodate(GNode *gn, Boolean oodate) continue; /* no point */ /* Check vs latestdir */ - snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); + snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, lcwd) != 0) { /* Check vs lcwd */ - snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); + snprintf(fname2, sizeof fname2, "%s/%s", lcwd, p); sdirs[sdx++] = fname2; } if (strcmp(lcwd, cwd) != 0) { /* Check vs cwd */ - snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); + snprintf(fname3, sizeof fname3, "%s/%s", cwd, p); sdirs[sdx++] = fname3; } } @@ -1446,7 +1448,7 @@ meta_oodate(GNode *gn, Boolean oodate) DEBUG3(META, "%s: %d: looking for: %s\n", fname, lineno, *sdp); #endif - if (cached_stat(*sdp, &mst) == 0) { + if (cached_stat(*sdp, &cst) == 0) { found = 1; p = *sdp; } @@ -1456,12 +1458,12 @@ meta_oodate(GNode *gn, Boolean oodate) DEBUG3(META, "%s: %d: found: %s\n", fname, lineno, p); #endif - if (!S_ISDIR(mst.mst_mode) && - mst.mst_mtime > gn->mtime) { + if (!S_ISDIR(cst.cst_mode) && + cst.cst_mtime > gn->mtime) { DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); oodate = TRUE; - } else if (S_ISDIR(mst.mst_mode)) { + } else if (S_ISDIR(cst.cst_mode)) { /* Update the latest directory. */ cached_realpath(p, latestdir); } @@ -1476,7 +1478,7 @@ meta_oodate(GNode *gn, Boolean oodate) } if (buf[0] == 'E') { /* previous latestdir is no longer relevant */ - strlcpy(latestdir, lcwd, sizeof(latestdir)); + strlcpy(latestdir, lcwd, sizeof latestdir); } break; default: @@ -1537,10 +1539,10 @@ meta_oodate(GNode *gn, Boolean oodate) if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } - if (p && + if (p != NULL && !hasOODATE && !(gn->type & OP_NOMETA_CMP) && - strcmp(p, cmd) != 0) { + (strcmp(p, cmd) != 0)) { DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) @@ -1588,7 +1590,7 @@ meta_oodate(GNode *gn, Boolean oodate) cp = NULL; /* not in .CURDIR */ } } - if (!cp) { + if (cp == NULL) { DEBUG1(META, "%s: required but missing\n", fname); oodate = TRUE; needOODATE = TRUE; /* assume the worst */ @@ -1686,7 +1688,7 @@ meta_compat_parent(pid_t child) if (outfd != -1 && FD_ISSET(outfd, &readfds)) do { /* XXX this is not line-buffered */ - ssize_t nread = read(outfd, buf, sizeof(buf) - 1); + ssize_t nread = read(outfd, buf, sizeof buf - 1); if (nread == -1) err(1, "read"); if (nread == 0) { diff --git a/metachar.h b/metachar.h index f180b483f8c3..ced0648fa19e 100644 --- a/metachar.h +++ b/metachar.h @@ -1,4 +1,4 @@ -/* $NetBSD: metachar.h,v 1.11 2020/10/31 18:20:00 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.12 2020/11/10 00:32:12 rillig Exp $ */ /*- * Copyright (c) 2015 The NetBSD Foundation, Inc. @@ -37,7 +37,7 @@ extern unsigned char _metachar[]; #define is_shell_metachar(c) _metachar[(c) & 0x7f] -static inline MAKE_ATTR_UNUSED int +MAKE_INLINE int needshell(const char *cmd) { while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') diff --git a/missing/sys/cdefs.h b/missing/sys/cdefs.h new file mode 100644 index 000000000000..56acb3fb2cfc --- /dev/null +++ b/missing/sys/cdefs.h @@ -0,0 +1,186 @@ +/* $NetBSD: cdefs.h,v 1.18 1997/06/18 19:09:50 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Berkeley Software Design, Inc. + * + * 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. + * + * @(#)cdefs.h 8.7 (Berkeley) 1/21/94 + */ + +#ifndef _SYS_CDEFS_H_ + +#if defined(NEED_HOST_CDEFS_H) +/* + * make sure we don't come past here again. + */ +#undef NEED_HOST_CDEFS_H +/* + * Some systems - notably linux, have sys/cdefs.h + * which is not really compatible with our's. + */ +#ifdef __GNUC__ +# include_next <sys/cdefs.h> +#else +/* + * It sucks that we have to hard code a path like this. + * But systems that have a sys/cdefs.h that don't use gcc + * should be few. + */ +# include "/usr/include/sys/cdefs.h" +#endif +/* + * We are about to [re]define these + */ +#undef __P +#undef _SYS_CDEFS_H_ +#endif + +#define _SYS_CDEFS_H_ + +#ifdef NetBSD +#include <machine/cdefs.h> +#endif + +#if defined(__cplusplus) +# ifndef __BEGIN_DECLS +# define __BEGIN_DECLS extern "C" { +# endif +# ifndef __END_DECLS +# define __END_DECLS }; +# endif +#else +# ifndef __BEGIN_DECLS +# define __BEGIN_DECLS +# endif +# ifndef __END_DECLS +# define __END_DECLS +# endif +#endif + +/* + * The __CONCAT macro is used to concatenate parts of symbol names, e.g. + * with "#define OLD(foo) __CONCAT(old,foo)", OLD(foo) produces oldfoo. + * The __CONCAT macro is a bit tricky -- make sure you don't put spaces + * in between its arguments. __CONCAT can also concatenate double-quoted + * strings produced by the __STRING macro, but this only works with ANSI C. + */ +#if defined(__STDC__) || defined(__cplusplus) +#define __P(protos) protos /* full-blown ANSI C */ +#ifndef __CONCAT +#define __CONCAT(x,y) x ## y +#endif +#define __STRING(x) #x + +#define __const const /* define reserved names to standard */ +#define __signed signed +#define __volatile volatile +#if defined(__cplusplus) +#define __inline inline /* convert to C++ keyword */ +#else +#ifndef __GNUC__ +#define __inline /* delete GCC keyword */ +#endif /* !__GNUC__ */ +#endif /* !__cplusplus */ + +#else /* !(__STDC__ || __cplusplus) */ +#define __P(protos) () /* traditional C preprocessor */ +#define __CONCAT(x,y) x/**/y +#define __STRING(x) "x" + +#ifndef __GNUC__ +#define __const /* delete pseudo-ANSI C keywords */ +#define __inline +#define __signed +#define __volatile +#endif /* !__GNUC__ */ + +/* + * In non-ANSI C environments, new programs will want ANSI-only C keywords + * deleted from the program and old programs will want them left alone. + * Programs using the ANSI C keywords const, inline etc. as normal + * identifiers should define -DNO_ANSI_KEYWORDS. + */ +#ifndef NO_ANSI_KEYWORDS +#define const __const /* convert ANSI C keywords */ +#define inline __inline +#define signed __signed +#define volatile __volatile +#endif /* !NO_ANSI_KEYWORDS */ +#endif /* !(__STDC__ || __cplusplus) */ + +/* + * GCC1 and some versions of GCC2 declare dead (non-returning) and + * pure (no side effects) functions using "volatile" and "const"; + * unfortunately, these then cause warnings under "-ansi -pedantic". + * GCC2 uses a new, peculiar __attribute__((attrs)) style. All of + * these work for GNU C++ (modulo a slight glitch in the C++ grammar + * in the distribution version of 2.5.5). + */ +#if !defined(__GNUC__) || __GNUC__ < 2 || \ + (__GNUC__ == 2 && __GNUC_MINOR__ < 5) +#define __attribute__(x) /* delete __attribute__ if non-gcc or gcc1 */ +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) +#define __dead __volatile +#define __pure __const +#endif +#endif + +#ifdef sun386 +# define __attribute__(x) +#endif + +#ifdef __KPRINTF_ATTRIBUTE__ +#define __kprintf_attribute__(a) __attribute__(a) +#else +#define __kprintf_attribute__(a) +#endif + +/* Delete pseudo-keywords wherever they are not available or needed. */ +#ifndef __dead +#define __dead +#define __pure +#endif + +#define __IDSTRING(name,string) \ + static const char name[] __attribute__((__unused__)) = string + +#ifndef __RCSID +#define __RCSID(s) __IDSTRING(rcsid,s) +#endif + +#ifndef __COPYRIGHT +#define __COPYRIGHT(s) __IDSTRING(copyright,s) +#endif + +#endif /* !_SYS_CDEFS_H_ */ diff --git a/mk/ChangeLog b/mk/ChangeLog index d72f9ff2bb6f..80abde7ea93f 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,10 @@ +2020-11-06 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20201106 + + * meta.autodep.mk: use OBJ_EXTENSIONS rather than hardcode sed + args to tweak extensions for local deps. + 2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> * install-mk (MK_VERSION): 20201101 diff --git a/mk/install-mk b/mk/install-mk index 01680f401745..cd396f7d5331 100644 --- a/mk/install-mk +++ b/mk/install-mk @@ -55,7 +55,7 @@ # Simon J. Gerraty <sjg@crufty.net> # RCSid: -# $Id: install-mk,v 1.183 2020/11/02 16:34:12 sjg Exp $ +# $Id: install-mk,v 1.184 2020/11/08 05:47:56 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # @@ -70,7 +70,7 @@ # sjg@crufty.net # -MK_VERSION=20201101 +MK_VERSION=20201106 OWNER= GROUP= MODE=444 diff --git a/mk/meta.autodep.mk b/mk/meta.autodep.mk index 5e18c35fa560..b5cb39a30855 100644 --- a/mk/meta.autodep.mk +++ b/mk/meta.autodep.mk @@ -1,4 +1,4 @@ -# $Id: meta.autodep.mk,v 1.52 2020/07/18 05:57:57 sjg Exp $ +# $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty @@ -178,7 +178,8 @@ DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh @case "${.MAKE.META.FILES:T:M*.po.*}" in \ *.po.*) mv $@.${.MAKE.PID} $@;; \ *) { cat $@.${.MAKE.PID}; \ - sed 's,\${PICO}:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \ + sed ${OBJ_EXTENSIONS:N.o:N.po:@o@-e 's,\$o:,.o:,'@} \ + -e 's,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \ rm -f $@.${.MAKE.PID};; \ esac .else diff --git a/mk/meta2deps.sh b/mk/meta2deps.sh index f01d54774a04..64add8542571 100755 --- a/mk/meta2deps.sh +++ b/mk/meta2deps.sh @@ -77,7 +77,7 @@ # RCSid: -# $Id: meta2deps.sh,v 1.14 2020/10/02 03:11:17 sjg Exp $ +# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $ # Copyright (c) 2010-2013, Juniper Networks, Inc. # All rights reserved. @@ -259,7 +259,7 @@ meta2deps() { 0) error "no filemon data";; *) ;; esac - version=0 + version=0 continue ;; $pid,$pid) ;; diff --git a/nonints.h b/nonints.h index ba2e3bbbe4ed..f089b39ca50e 100644 --- a/nonints.h +++ b/nonints.h @@ -1,4 +1,4 @@ -/* $NetBSD: nonints.h,v 1.149 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: nonints.h,v 1.162 2020/11/16 21:48:18 rillig Exp $ */ /*- * Copyright (c) 1988, 1989, 1990, 1993 @@ -79,8 +79,8 @@ void Arch_End(void); Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); -time_t Arch_MTime(GNode *); -time_t Arch_MemMTime(GNode *); +void Arch_UpdateMTime(GNode *gn); +void Arch_UpdateMemberMTime(GNode *gn); void Arch_FindLib(GNode *, SearchPath *); Boolean Arch_LibOODate(GNode *); Boolean Arch_IsLib(GNode *); @@ -107,6 +107,7 @@ void JobReapChild(pid_t, WAIT_T, Boolean); #endif /* main.c */ +Boolean GetBooleanVar(const char *, Boolean); void Main_ParseArgLine(const char *); void MakeMode(const char *); char *Cmd_Exec(const char *, const char **); @@ -118,8 +119,7 @@ void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); void execDie(const char *, const char *); char *getTmpdir(void); -Boolean s2Boolean(const char *, Boolean); -Boolean getBoolean(const char *, Boolean); +Boolean ParseBoolean(const char *, Boolean); char *cached_realpath(const char *, char *); /* parse.c */ @@ -159,7 +159,7 @@ typedef struct Words { } Words; Words Str_Words(const char *, Boolean); -static inline MAKE_ATTR_UNUSED void +MAKE_INLINE void Words_Free(Words w) { free(w.words); free(w.freeIt); @@ -199,15 +199,15 @@ void Targ_End(void); void Targ_Stats(void); GNodeList *Targ_List(void); -GNode *Targ_NewGN(const char *); +GNode *GNode_New(const char *); GNode *Targ_FindNode(const char *); GNode *Targ_GetNode(const char *); GNode *Targ_NewInternalNode(const char *); GNode *Targ_GetEndNode(void); GNodeList *Targ_FindList(StringList *); -Boolean Targ_Ignore(GNode *); -Boolean Targ_Silent(GNode *); -Boolean Targ_Precious(GNode *); +Boolean Targ_Ignore(const GNode *); +Boolean Targ_Silent(const GNode *); +Boolean Targ_Precious(const GNode *); void Targ_SetMain(GNode *); void Targ_PrintCmds(GNode *); void Targ_PrintNode(GNode *, int); @@ -223,21 +223,40 @@ void Var_End(void); typedef enum VarEvalFlags { VARE_NONE = 0, - /* Treat undefined variables as errors. */ - VARE_UNDEFERR = 0x01, - /* Expand and evaluate variables during parsing. */ - VARE_WANTRES = 0x02, - /* In an assignment using the ':=' operator, keep '$$' as '$$' instead - * of reducing it to a single '$'. */ - VARE_ASSIGN = 0x04 + + /* Expand and evaluate variables during parsing. + * + * TODO: Document what Var_Parse and Var_Subst return when this flag + * is not set. */ + VARE_WANTRES = 1 << 0, + + /* Treat undefined variables as errors. + * Must only be used in combination with VARE_WANTRES. */ + VARE_UNDEFERR = 1 << 1, + + /* Keep '$$' as '$$' instead of reducing it to a single '$'. + * + * Used in variable assignments using the ':=' operator. It allows + * multiple such assignments to be chained without accidentally expanding + * '$$file' to '$file' in the first assignment and interpreting it as + * '${f}' followed by 'ile' in the next assignment. + * + * See also preserveUndefined, which preserves subexpressions that are + * based on undefined variables; maybe that can be converted to a flag + * as well. */ + VARE_KEEP_DOLLAR = 1 << 2 } VarEvalFlags; -typedef enum VarSet_Flags { - VAR_NO_EXPORT = 0x01, /* do not export */ +typedef enum VarSetFlags { + VAR_SET_NONE = 0, + + /* do not export */ + VAR_SET_NO_EXPORT = 1 << 0, + /* Make the variable read-only. No further modification is possible, * except for another call to Var_Set with the same flag. */ - VAR_SET_READONLY = 0x02 -} VarSet_Flags; + VAR_SET_READONLY = 1 << 1 +} VarSetFlags; /* The state of error handling returned by Var_Parse. * @@ -296,7 +315,7 @@ typedef enum VarParseResult { void Var_Delete(const char *, GNode *); void Var_Set(const char *, const char *, GNode *); -void Var_Set_with_flags(const char *, const char *, GNode *, VarSet_Flags); +void Var_SetWithFlags(const char *, const char *, GNode *, VarSetFlags); void Var_Append(const char *, const char *, GNode *); Boolean Var_Exists(const char *, GNode *); const char *Var_Value(const char *, GNode *, void **); @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -124,7 +124,7 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $"); /* types and constants */ @@ -132,7 +132,7 @@ MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $"); * Structure for a file being read ("included file") */ typedef struct IFile { - char *fname; /* name of file */ + char *fname; /* name of file (relative? absolute?) */ Boolean fromForLoop; /* simulated .include by the .for loop */ int lineno; /* current line number in file */ int first_lineno; /* line number of start of text */ @@ -355,6 +355,7 @@ static const struct { /* file loader */ struct loadedfile { + /* XXX: What is the lifetime of this path? Who manages the memory? */ const char *path; /* name, for error reports */ char *buf; /* contents buffer */ size_t len; /* length of contents */ @@ -362,12 +363,13 @@ struct loadedfile { Boolean used; /* XXX: have we used the data yet */ }; +/* XXX: What is the lifetime of the path? Who manages the memory? */ static struct loadedfile * loadedfile_create(const char *path) { struct loadedfile *lf; - lf = bmake_malloc(sizeof(*lf)); + lf = bmake_malloc(sizeof *lf); lf->path = path == NULL ? "(stdin)" : path; lf->buf = NULL; lf->len = 0; @@ -380,13 +382,12 @@ static void loadedfile_destroy(struct loadedfile *lf) { if (lf->buf != NULL) { - if (lf->maplen > 0) { #ifdef HAVE_MMAP + if (lf->maplen > 0) munmap(lf->buf, lf->maplen); + else #endif - } else { free(lf->buf); - } } free(lf); } @@ -400,9 +401,9 @@ loadedfile_nextbuf(void *x, size_t *len) { struct loadedfile *lf = x; - if (lf->used) { + if (lf->used) return NULL; - } + lf->used = TRUE; *len = lf->len; return lf->buf; @@ -416,13 +417,11 @@ load_getsize(int fd, size_t *ret) { struct stat st; - if (fstat(fd, &st) < 0) { + if (fstat(fd, &st) < 0) return FALSE; - } - if (!S_ISREG(st.st_mode)) { + if (!S_ISREG(st.st_mode)) return FALSE; - } /* * st_size is an off_t, which is 64 bits signed; *ret is @@ -433,9 +432,8 @@ load_getsize(int fd, size_t *ret) * * While we're at it reject negative sizes too, just in case. */ - if (st.st_size < 0 || st.st_size > 0x7fffffff) { + if (st.st_size < 0 || st.st_size > 0x7fffffff) return FALSE; - } *ret = (size_t)st.st_size; return TRUE; @@ -447,48 +445,47 @@ loadedfile_mmap(struct loadedfile *lf, int fd) { static unsigned long pagesize = 0; - if (load_getsize(fd, &lf->len)) { + if (!load_getsize(fd, &lf->len)) + return FALSE; - /* found a size, try mmap */ + /* found a size, try mmap */ #ifdef _SC_PAGESIZE - if (pagesize == 0) - pagesize = (unsigned long)sysconf(_SC_PAGESIZE); + if (pagesize == 0) + pagesize = (unsigned long)sysconf(_SC_PAGESIZE); #endif - if (pagesize == 0 || pagesize == (unsigned long)-1) { - pagesize = 0x1000; - } - /* round size up to a page */ - lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize); + if (pagesize == 0 || pagesize == (unsigned long)-1) + pagesize = 0x1000; - /* - * XXX hack for dealing with empty files; remove when - * we're no longer limited by interfacing to the old - * logic elsewhere in this file. - */ - if (lf->maplen == 0) { - lf->maplen = pagesize; - } + /* round size up to a page */ + lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize); - /* - * FUTURE: remove PROT_WRITE when the parser no longer - * needs to scribble on the input. - */ - lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE, - MAP_FILE|MAP_COPY, fd, 0); - if (lf->buf != MAP_FAILED) { - /* succeeded */ - if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { - char *b = bmake_malloc(lf->len + 1); - b[lf->len] = '\n'; - memcpy(b, lf->buf, lf->len++); - munmap(lf->buf, lf->maplen); - lf->maplen = 0; - lf->buf = b; - } - return TRUE; - } + /* + * XXX hack for dealing with empty files; remove when + * we're no longer limited by interfacing to the old + * logic elsewhere in this file. + */ + if (lf->maplen == 0) + lf->maplen = pagesize; + + /* + * FUTURE: remove PROT_WRITE when the parser no longer + * needs to scribble on the input. + */ + lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE, + MAP_FILE|MAP_COPY, fd, 0); + if (lf->buf == MAP_FAILED) + return FALSE; + + if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { + char *b = bmake_malloc(lf->len + 1); + b[lf->len] = '\n'; + memcpy(b, lf->buf, lf->len++); + munmap(lf->buf, lf->maplen); + lf->maplen = 0; + lf->buf = b; } - return FALSE; + + return TRUE; } #endif @@ -499,8 +496,7 @@ loadedfile_mmap(struct loadedfile *lf, int fd) * being in the caller in another source file, we need to have the fd * passed in already open. Bleh. * - * If the path is NULL use stdin and (to insure against fd leaks) - * assert that the caller passed in -1. + * If the path is NULL, use stdin. */ static struct loadedfile * loadfile(const char *path, int fd) @@ -537,7 +533,7 @@ loadfile(const char *path, int fd) lf->buf = bmake_malloc(lf->len); bufpos = 0; - while (1) { + for (;;) { assert(bufpos <= lf->len); if (bufpos == lf->len) { if (lf->len > SIZE_MAX/2) { @@ -554,9 +550,9 @@ loadfile(const char *path, int fd) Error("%s: read error: %s", path, strerror(errno)); exit(1); } - if (result == 0) { + if (result == 0) break; - } + bufpos += (size_t)result; } assert(bufpos <= lf->len); @@ -574,9 +570,9 @@ loadfile(const char *path, int fd) #ifdef HAVE_MMAP done: #endif - if (path != NULL) { + if (path != NULL) close(fd); - } + return lf; } @@ -611,36 +607,33 @@ ParseMark(GNode *gn) static int ParseFindKeyword(const char *str) { - int start, end, cur; - int diff; - - start = 0; - end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; + int start = 0; + int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; do { - cur = start + (end - start) / 2; - diff = strcmp(str, parseKeywords[cur].name); + int cur = start + (end - start) / 2; + int diff = strcmp(str, parseKeywords[cur].name); - if (diff == 0) { + if (diff == 0) return cur; - } else if (diff < 0) { + if (diff < 0) end = cur - 1; - } else { + else start = cur + 1; - } } while (start <= end); + return -1; } static void -PrintLocation(FILE *f, const char *filename, size_t lineno) +PrintLocation(FILE *f, const char *fname, size_t lineno) { char dirbuf[MAXPATHLEN+1]; const char *dir, *base; void *dir_freeIt, *base_freeIt; - if (*filename == '/' || strcmp(filename, "(stdin)") == 0) { - (void)fprintf(f, "\"%s\" line %zu: ", filename, lineno); + if (*fname == '/' || strcmp(fname, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %zu: ", fname, lineno); return; } @@ -655,8 +648,8 @@ PrintLocation(FILE *f, const char *filename, size_t lineno) base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt); if (base == NULL) { - const char *slash = strrchr(filename, '/'); - base = slash != NULL ? slash + 1 : filename; + const char *slash = strrchr(fname, '/'); + base = slash != NULL ? slash + 1 : fname; } (void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno); @@ -664,21 +657,16 @@ PrintLocation(FILE *f, const char *filename, size_t lineno) bmake_free(dir_freeIt); } -/* Print a parse error message, including location information. - * - * Increment "fatals" if the level is PARSE_FATAL, and continue parsing - * until the end of the current top-level makefile, then exit (see - * Parse_File). */ static void -ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, +ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, ParseErrorLevel type, const char *fmt, va_list ap) { static Boolean fatal_warning_error_printed = FALSE; (void)fprintf(f, "%s: ", progname); - if (cfname != NULL) - PrintLocation(f, cfname, clineno); + if (fname != NULL) + PrintLocation(f, fname, lineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); @@ -696,26 +684,28 @@ ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, } static void -ParseErrorInternal(const char *cfname, size_t clineno, ParseErrorLevel type, - const char *fmt, ...) +ParseErrorInternal(const char *fname, size_t lineno, + ParseErrorLevel type, const char *fmt, ...) { va_list ap; - va_start(ap, fmt); (void)fflush(stdout); - ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); + va_start(ap, fmt); + ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, cfname, clineno, type, + ParseVErrorInternal(opts.debug_file, fname, lineno, type, fmt, ap); va_end(ap); } } -/* External interface to ParseErrorInternal; uses the default filename and - * line number. +/* Print a parse error message, including location information. + * + * If the level is PARSE_FATAL, continue parsing until the end of the + * current top-level makefile, then exit (see Parse_File). * * Fmt is given without a trailing newline. */ void @@ -748,12 +738,8 @@ Parse_Error(ParseErrorLevel type, const char *fmt, ...) } -/* Parse a .info .warning or .error directive. - * - * The input is the line minus the ".". We substitute variables, print the - * message and exit(1) (for .error) or just print a warning if the directive - * is malformed. - */ +/* Parse and handle a .info, .warning or .error directive. + * For an .error directive, immediately exit. */ static Boolean ParseMessage(const char *directive) { @@ -832,9 +818,12 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) 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 + * If the node was of the left-hand side of a '::' operator, we need + * to create a new instance of it for the children and commands on + * this dependency line since each of these dependency groups has its + * own attributes and commands, separate from the others. + * + * 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. @@ -866,7 +855,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) } else { /* * We don't want to nuke any previous flags (whatever they were) so we - * just OR the new operator into the old + * just OR the new operator into the old. */ gn->type |= op; } @@ -893,7 +882,7 @@ ParseDoSrcKeyword(const char *src, ParseSpecial specType) if (*src == '.' && ch_isupper(src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { - int op = parseKeywords[keywd].op; + GNodeType op = parseKeywords[keywd].op; if (op != 0) { ApplyDependencyOperator(op); return TRUE; @@ -902,11 +891,12 @@ ParseDoSrcKeyword(const char *src, ParseSpecial specType) /* * We add a .WAIT node in the dependency list. * After any dynamic dependencies (and filename globbing) - * have happened, it is given a dependency on the each - * previous child back to and previous .WAIT node. + * have happened, it is given a dependency on each + * previous child, back until the previous .WAIT node. * The next child won't be scheduled until the .WAIT node * is built. - * We give each .WAIT node a unique name (mainly for diag). + * We give each .WAIT node a unique name (mainly for + * diagnostics). */ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); gn = Targ_NewInternalNode(wait_src); @@ -925,12 +915,13 @@ static void ParseDoSrcMain(const char *src) { /* - * 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... + * In a line like ".MAIN: source1 source2", it means we need to add + * the sources of said target to the list of things to create. + * + * Note that this will only be invoked if the user didn't specify a + * target on the command line. This is to allow .ifmake to succeed. + * + * XXX: Double-check all of the above comment. */ Lst_Append(opts.create, bmake_strdup(src)); /* @@ -988,11 +979,10 @@ ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType) gn = Targ_GetNode(src); if (doing_depend) ParseMark(gn); - if (tOp) { + if (tOp != OP_NONE) gn->type |= tOp; - } else { + else LinkToTargets(gn, specType != SP_NOT); - } } /* Given the name of a source in a dependency line, figure out if it is an @@ -1090,8 +1080,8 @@ ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart) const char *nested_val; void *freeIt; - (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_UNDEFERR|VARE_WANTRES, - &nested_val, &freeIt); + (void)Var_Parse(&nested_p, VAR_CMDLINE, + VARE_WANTRES | VARE_UNDEFERR, &nested_val, &freeIt); /* TODO: handle errors */ free(freeIt); cp += nested_p - cp; @@ -1102,38 +1092,7 @@ ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart) *pp = cp; } -/* - * 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 - * .NOPATH Don't search for file in the path - * .STALE - * .BEGIN - * .END - * .ERROR - * .DELETE_ON_ERROR - * .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 - */ +/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */ static void ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, const char *line, @@ -1141,15 +1100,14 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, { switch (*inout_specType) { case SP_PATH: - if (*inout_paths == NULL) { + if (*inout_paths == NULL) *inout_paths = Lst_New(); - } Lst_Append(*inout_paths, dirSearchPath); break; case SP_MAIN: - if (!Lst_IsEmpty(opts.create)) { + /* Allow targets from the command line to override the .MAIN node. */ + if (!Lst_IsEmpty(opts.create)) *inout_specType = SP_NOT; - } break; case SP_BEGIN: case SP_END: @@ -1164,10 +1122,15 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, break; } case SP_DEFAULT: { - GNode *gn = Targ_NewGN(".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. 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. */ + GNode *gn = GNode_New(".DEFAULT"); gn->type |= OP_NOTMAIN|OP_TRANSFORM; Lst_Append(targets, gn); - DEFAULT = gn; + defaultNode = gn; break; } case SP_DELETE_ON_ERROR: @@ -1202,12 +1165,12 @@ ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths) "Suffix '%s' not defined (yet)", &line[5]); return FALSE; - } else { - if (*inout_paths == NULL) { - *inout_paths = Lst_New(); - } - Lst_Append(*inout_paths, path); } + + if (*inout_paths == NULL) + *inout_paths = Lst_New(); + Lst_Append(*inout_paths, path); + return TRUE; } @@ -1290,17 +1253,16 @@ ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) Boolean warning = FALSE; char *cp = *pp; - while (*cp && (ParseIsEscaped(lstart, cp) || - (*cp != '!' && *cp != ':'))) { - if (ParseIsEscaped(lstart, cp) || - (*cp != ' ' && *cp != '\t')) { + while (*cp != '\0') { + if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) + break; + if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) warning = TRUE; - } cp++; } - if (warning) { + if (warning) Parse_Error(PARSE_WARNING, "Extra target ignored"); - } + *pp = cp; } @@ -1310,7 +1272,8 @@ ParseDoDependencyCheckSpec(ParseSpecial specType) switch (specType) { default: Parse_Error(PARSE_WARNING, - "Special and mundane targets don't mix. Mundane ones ignored"); + "Special and mundane targets don't mix. " + "Mundane ones ignored"); break; case SP_DEFAULT: case SP_STALE: @@ -1319,13 +1282,11 @@ ParseDoDependencyCheckSpec(ParseSpecial specType) case SP_ERROR: case SP_INTERRUPT: /* - * These four create nodes on which to hang commands, so - * targets shouldn't be empty... + * These create nodes on which to hang commands, so targets + * shouldn't be empty... */ case SP_NOT: - /* - * Nothing special here -- targets can be empty if it wants. - */ + /* Nothing special here -- targets can be empty if it wants. */ break; } } @@ -1459,7 +1420,7 @@ ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, Suff_SetNull(word); break; case SP_OBJDIR: - Main_SetObjdir("%s", word); + Main_SetObjdir(FALSE, "%s", word); break; default: break; @@ -1532,11 +1493,10 @@ ParseDoDependencyTargets(char **inout_cp, * Have word in line. Get or create its node and stick it at * the end of the targets list */ - if (*inout_specType == SP_NOT && *line != '\0') { + if (*inout_specType == SP_NOT && *line != '\0') ParseDoDependencyTargetMundane(line, curTargs); - } else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') { + else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); - } /* Don't need the inserted null terminator any more. */ *cp = savec; @@ -1545,11 +1505,11 @@ ParseDoDependencyTargets(char **inout_cp, * If it is a special type and not .PATH, it's the only target we * allow on this line... */ - if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) { + if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) ParseDoDependencyTargetExtraWarn(&cp, lstart); - } else { + else pp_skip_whitespace(&cp); - } + line = cp; if (*line == '\0') break; @@ -1586,7 +1546,7 @@ static Boolean ParseDoDependencySourcesMundane(char *start, char *end, ParseSpecial specType, GNodeType tOp) { - while (*start) { + while (*start != '\0') { /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) @@ -1644,9 +1604,8 @@ ParseDoDependencySourcesMundane(char *start, char *end, * * The sources are parsed in much the same way as the targets, except * that 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. + * and a target is created for each expanded word. Each of the resulting + * nodes is then linked to each of the targets as one of its children. * * Certain targets and sources such as .PHONY or .PRECIOUS are handled * specially. These are the ones detailed by the specType variable. @@ -1656,6 +1615,8 @@ ParseDoDependencySourcesMundane(char *start, char *end, * 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. + * + * Upon return, the value of the line is unspecified. */ static void ParseDoDependency(char *line) @@ -1664,7 +1625,7 @@ ParseDoDependency(char *line) GNodeType op; /* the operator on the line */ SearchPathList *paths; /* search paths to alter when parsing * a list of .PATH targets */ - int tOp; /* operator from special target */ + GNodeType tOp; /* operator from special target */ StringList *curTargs; /* target names to be found and added * to the targets list */ char *lstart = line; @@ -1677,7 +1638,7 @@ ParseDoDependency(char *line) ParseSpecial specType = SP_NOT; DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); - tOp = 0; + tOp = OP_NONE; paths = NULL; @@ -1690,9 +1651,8 @@ ParseDoDependency(char *line) curTargs)) goto out; - /* - * Don't need the list of target names anymore... - */ + /* Don't need the list of target names anymore. + * The targets themselves are now in the global variable 'targets'. */ Lst_Free(curTargs); curTargs = NULL; @@ -1719,7 +1679,7 @@ ParseDoDependency(char *line) * end of the string if not. */ pp_skip_whitespace(&cp); - line = cp; + line = cp; /* XXX: 'line' is an inappropriate name */ /* * Several special targets take different actions if present with no @@ -1730,7 +1690,7 @@ ParseDoDependency(char *line) * a .SILENT line creates silence when making all targets * a .PATH removes all directories from the search path(s). */ - if (!*line) { + if (line[0] == '\0') { ParseDoDependencySourcesEmpty(specType, paths); } else if (specType == SP_MFLAGS) { /* @@ -1751,9 +1711,7 @@ ParseDoDependency(char *line) *line = '\0'; } - /* - * NOW GO FOR THE SOURCES - */ + /* Now go for the sources. */ if (specType == SP_SUFFIXES || specType == SP_PATH || specType == SP_INCLUDES || specType == SP_LIBS || specType == SP_NULL || specType == SP_OBJDIR) @@ -1849,12 +1807,9 @@ Parse_IsVar(const char *p, VarAssign *out_var) { VarAssignParsed pvar; const char *firstSpace = NULL; - char ch; int level = 0; - /* Skip to variable name */ - while (*p == ' ' || *p == '\t') - p++; + cpp_skip_hspace(&p); /* Skip to variable name */ /* During parsing, the '+' of the '+=' operator is initially parsed * as part of the variable name. It is later corrected, as is the ':sh' @@ -1867,7 +1822,8 @@ Parse_IsVar(const char *p, VarAssign *out_var) #endif /* Scan for one of the assignment operators outside a variable expansion */ - while ((ch = *p++) != 0) { + while (*p != '\0') { + char ch = *p++; if (ch == '(' || ch == '{') { level++; continue; @@ -1887,7 +1843,7 @@ Parse_IsVar(const char *p, VarAssign *out_var) ch = *p++; #ifdef SUNSHCMD - if (ch == ':' && strncmp(p, "sh", 2) == 0) { + if (ch == ':' && p[0] == 's' && p[1] == 'h') { p += 2; continue; } @@ -1917,7 +1873,7 @@ Parse_IsVar(const char *p, VarAssign *out_var) static void VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt) { - if (DEBUG(LINT)) { + if (opts.lint) { if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { /* Check for syntax errors such as unclosed expressions or * unknown modifiers. */ @@ -1936,20 +1892,18 @@ VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt, { const char *avalue = uvalue; char *evalue; - /* - * Allow variables in the old value to be undefined, but leave their - * expressions 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; + Boolean savedPreserveUndefined = preserveUndefined; - oldVars = FALSE; + /* TODO: Can this assignment to preserveUndefined be moved further down + * to the actually interesting Var_Subst call, without affecting any + * edge cases? + * + * It might affect the implicit expansion of the variable name in the + * Var_Exists and Var_Set calls, even though it's unlikely that anyone + * cared about this edge case when adding this code. In addition, + * variable assignments should not refer to any undefined variables in + * the variable name. */ + preserveUndefined = TRUE; /* * make sure that we set the variable the first time to nothing @@ -1958,9 +1912,9 @@ VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt, if (!Var_Exists(name, ctxt)) Var_Set(name, "", ctxt); - (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue); + (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_KEEP_DOLLAR, &evalue); /* TODO: handle errors */ - oldVars = oldOldVars; + preserveUndefined = savedPreserveUndefined; avalue = evalue; Var_Set(name, avalue, ctxt); @@ -1979,7 +1933,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt, cmd = uvalue; if (strchr(cmd, '$') != NULL) { char *ecmd; - (void)Var_Subst(cmd, VAR_CMDLINE, VARE_UNDEFERR | VARE_WANTRES, &ecmd); + (void)Var_Subst(cmd, VAR_CMDLINE, VARE_WANTRES | VARE_UNDEFERR, &ecmd); /* TODO: handle errors */ cmd = cmd_freeIt = ecmd; } @@ -2009,13 +1963,13 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, const char *avalue = uvalue; void *avalue_freeIt = NULL; - if (op == VAR_APPEND) { + if (op == VAR_APPEND) Var_Append(name, uvalue, ctxt); - } else if (op == VAR_SUBST) { + else if (op == VAR_SUBST) VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt); - } else if (op == VAR_SHELL) { + else if (op == VAR_SHELL) VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt); - } else { + else { if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) { *out_avalue_freeIt = NULL; return FALSE; @@ -2043,11 +1997,10 @@ VarAssignSpecial(const char *name, const char *avalue) */ Dir_InitCur(avalue); Dir_SetPATH(); - } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) { + } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) Job_SetPrefix(); - } else if (strcmp(name, MAKE_EXPORTED) == 0) { + else if (strcmp(name, MAKE_EXPORTED) == 0) Var_Export(avalue, FALSE); - } } /* Perform the variable variable assignment in the given context. */ @@ -2067,36 +2020,41 @@ Parse_DoVar(VarAssign *var, GNode *ctxt) } -/* - * ParseMaybeSubMake -- - * Scan the command string to see if it a possible submake node - * Input: - * cmd the command to scan - * Results: - * TRUE if the command is possibly a submake, FALSE if not. - */ +/* See if the command possibly calls a sub-make by using the variable + * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static Boolean -ParseMaybeSubMake(const char *cmd) +MaybeSubMake(const char *cmd) { - size_t i; - static struct { - const char *name; - size_t len; - } vals[] = { -#define MKV(A) { A, sizeof(A) - 1 } - MKV("${MAKE}"), - MKV("${.MAKE}"), - MKV("$(MAKE)"), - MKV("$(.MAKE)"), - MKV("make"), - }; - for (i = 0; i < sizeof vals / sizeof vals[0]; i++) { - char *ptr; - if ((ptr = strstr(cmd, vals[i].name)) == NULL) + const char *start; + + for (start = cmd; *start != '\0'; start++) { + const char *p = start; + char endc; + + /* XXX: What if progname != "make"? */ + if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e') + if (start == cmd || !ch_isalnum(p[-1])) + if (!ch_isalnum(p[4])) + return TRUE; + + if (*p != '$') continue; - if ((ptr == cmd || !ch_isalnum(ptr[-1])) - && !ch_isalnum(ptr[vals[i].len])) - return TRUE; + p++; + + if (*p == '{') + endc = '}'; + else if (*p == '(') + endc = ')'; + else + continue; + p++; + + if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */ + p++; + + if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E') + if (p[4] == endc) + return TRUE; } return FALSE; } @@ -2115,7 +2073,7 @@ ParseAddCmd(GNode *gn, char *cmd) /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) { Lst_Append(gn->commands, cmd); - if (ParseMaybeSubMake(cmd)) + if (MaybeSubMake(cmd)) gn->type |= OP_SUBMAKE; ParseMark(gn); } else { @@ -2145,49 +2103,46 @@ Parse_AddIncludeDir(const char *dir) (void)Dir_AddDir(parseIncPath, dir); } -/* Push to another file. +/* Handle one of the .[-ds]include directives by remembering the current file + * and pushing the included file on the stack. After the included file has + * finished, parsing continues with the including file; see Parse_SetInput + * and ParseEOF. * - * The input is the line minus the '.'. A file spec is a string enclosed in - * <> or "". The <> file is looked for only in sysIncPath. The "" file is - * first searched in the parsedir and then in the directories specified by - * the -I command line options. + * System includes are looked up in sysIncPath, any other includes are looked + * up in the parsedir and then in the directories specified by the -I command + * line options. */ static void -Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) +Parse_include_file(char *file, Boolean isSystem, Boolean depinc, Boolean silent) { struct loadedfile *lf; char *fullname; /* full pathname of file */ char *newName; - char *prefEnd, *incdir; + char *slash, *incdir; int fd; int i; - /* - * 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. - */ fullname = file[0] == '/' ? bmake_strdup(file) : NULL; if (fullname == NULL && !isSystem) { /* - * Include files contained in double-quotes are first searched for + * Include files contained in double-quotes are first searched * 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. + * we can locate the file. */ incdir = bmake_strdup(CurFile()->fname); - prefEnd = strrchr(incdir, '/'); - if (prefEnd != NULL) { - *prefEnd = '\0'; + slash = strrchr(incdir, '/'); + if (slash != NULL) { + *slash = '\0'; /* Now do lexical processing of leading "../" on the filename */ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { - prefEnd = strrchr(incdir + 1, '/'); - if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0) + slash = strrchr(incdir + 1, '/'); + if (slash == NULL || strcmp(slash, "/..") == 0) break; - *prefEnd = '\0'; + *slash = '\0'; } newName = str_concat3(incdir, "/", file + i); fullname = Dir_FindFile(newName, parseIncPath); @@ -2204,20 +2159,18 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) * then on the .PATH search path, if not found in a -I directory. * If we have a suffix specific path we should use that. */ - char *suff; + const char *suff; SearchPath *suffPath = NULL; if ((suff = strrchr(file, '.'))) { suffPath = Suff_GetPath(suff); - if (suffPath != NULL) { + if (suffPath != NULL) fullname = Dir_FindFile(file, suffPath); - } } if (fullname == NULL) { fullname = Dir_FindFile(file, parseIncPath); - if (fullname == NULL) { + if (fullname == NULL) fullname = Dir_FindFile(file, dirSearchPath); - } } } } @@ -2261,12 +2214,11 @@ ParseDoInclude(char *line) { char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ - int silent = *line != 'i'; + Boolean silent = *line != 'i'; char *file = line + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ - while (*file == ' ' || *file == '\t') - file++; + pp_skip_hspace(&file); if (*file != '"' && *file != '<') { Parse_Error(PARSE_FATAL, @@ -2279,11 +2231,10 @@ ParseDoInclude(char *line) * characters which bracket its name. Angle-brackets imply it's * a system Makefile while double-quotes imply it's a user makefile */ - if (*file == '<') { + if (*file == '<') endc = '>'; - } else { + else endc = '"'; - } /* Skip to matching delimiter */ for (cp = ++file; *cp && *cp != endc; cp++) @@ -2291,15 +2242,15 @@ ParseDoInclude(char *line) if (*cp != endc) { Parse_Error(PARSE_FATAL, - "Unclosed %cinclude filename. '%c' expected", - '.', endc); + "Unclosed .include filename. '%c' expected", endc); return; } + *cp = '\0'; /* - * Substitute for any variables in the file name before trying to - * find the thing. + * Substitute for any variables in the filename before trying to + * find the file. */ (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file); /* TODO: handle errors */ @@ -2394,6 +2345,7 @@ StrContainsWord(const char *str, const char *word) /* XXX: Searching through a set of words with this linear search is * inefficient for variables that contain thousands of words. */ +/* XXX: The paths in this list don't seem to be normalized in any way. */ static Boolean VarContainsWord(const char *varname, const char *word) { @@ -2405,7 +2357,10 @@ VarContainsWord(const char *varname, const char *word) } /* Track the makefiles we read - so makefiles can set dependencies on them. - * Avoid adding anything more than once. */ + * Avoid adding anything more than once. + * + * Time complexity: O(n) per call, in total O(n^2), where n is the number + * of makefiles that have been loaded. */ static void ParseTrackInput(const char *name) { @@ -2414,7 +2369,7 @@ ParseTrackInput(const char *name) } -/* Start Parsing from the given source. +/* Start parsing from the given source. * * The given file is added to the includes stack. */ void @@ -2442,13 +2397,6 @@ Parse_SetInput(const char *name, int line, int fd, return; curFile = Vector_Push(&includes); - - /* - * 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. - */ curFile->fname = bmake_strdup(name); curFile->fromForLoop = fromForLoop; curFile->lineno = line; @@ -2504,14 +2452,14 @@ IsSysVInclude(const char *line) /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { - if (*++p == '\0') { - /* end of line -> dependency */ + + /* end of line -> it's a dependency */ + if (*++p == '\0') return FALSE; - } - if (*p == ':' || ch_isspace(*p)) { - /* :: operator or ': ' -> dependency */ + + /* '::' operator or ': ' -> it's a dependency */ + if (*p == ':' || ch_isspace(*p)) return FALSE; - } } return TRUE; } @@ -2521,8 +2469,8 @@ static void ParseTraditionalInclude(char *line) { char *cp; /* current position in file spec */ - int done = 0; - int silent = line[0] != 'i'; + Boolean done = FALSE; + Boolean silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); char *all_files; @@ -2547,10 +2495,10 @@ ParseTraditionalInclude(char *line) for (cp = file; *cp && !ch_isspace(*cp); cp++) continue; - if (*cp) + if (*cp != '\0') *cp = '\0'; else - done = 1; + done = TRUE; Parse_include_file(file, FALSE, FALSE, silent); } @@ -2593,8 +2541,8 @@ ParseGmakeExport(char *line) #endif /* 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. + * include file or a .for loop, the includes stack is popped and things set + * up to go back to reading the previous file at the previous location. * * Results: * TRUE to continue parsing, i.e. it had only reached the end of an @@ -2614,12 +2562,10 @@ ParseEOF(void) ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); curFile->buf_ptr = ptr; curFile->buf_freeIt = ptr; - curFile->buf_end = ptr + len; + curFile->buf_end = ptr + len; /* XXX: undefined behavior if ptr == NULL */ curFile->lineno = curFile->first_lineno; - if (ptr != NULL) { - /* Iterate again */ - return TRUE; - } + if (ptr != NULL) + return TRUE; /* Iterate again */ /* Ensure the makefile (or loop) didn't have mismatched conditionals */ Cond_restore_depth(curFile->cond_depth); @@ -2651,11 +2597,14 @@ ParseEOF(void) return TRUE; } -#define PARSE_RAW 1 -#define PARSE_SKIP 2 +typedef enum GetLineMode { + PARSE_NORMAL, + PARSE_RAW, + PARSE_SKIP +} GetLineMode; static char * -ParseGetLine(int flags) +ParseGetLine(GetLineMode mode) { IFile *cf = CurFile(); char *ptr; @@ -2678,11 +2627,11 @@ ParseGetLine(int flags) /* XXX: can buf_end ever be null? */ if (cf->buf_end != NULL && ptr == cf->buf_end) { /* end of buffer */ - ch = 0; + ch = '\0'; break; } ch = *ptr; - if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { + if (ch == '\0' || (ch == '\\' && ptr[1] == '\0')) { /* XXX: can buf_end ever be null? */ if (cf->buf_end == NULL) /* End of string (aka for loop) data */ @@ -2698,6 +2647,7 @@ ParseGetLine(int flags) break; } } + /* XXX: Can cf->nextbuf ever be NULL? */ if (cf->nextbuf != NULL) { /* * End of this buffer; return EOF and outer logic @@ -2738,7 +2688,7 @@ ParseGetLine(int flags) /* Check we have a non-comment, non-blank line */ if (line_end == line || comment == line) { - if (ch == 0) + if (ch == '\0') /* At end of file */ return NULL; /* Parse another line */ @@ -2746,14 +2696,14 @@ ParseGetLine(int flags) } /* We now have a line of data */ - *line_end = 0; + *line_end = '\0'; - if (flags & PARSE_RAW) { + if (mode == PARSE_RAW) { /* Leave '\' (etc) in line buffer (eg 'for' lines) */ return line; } - if (flags & PARSE_SKIP) { + if (mode == PARSE_SKIP) { /* Completely ignore non-directives */ if (line[0] != '.') continue; @@ -2765,7 +2715,7 @@ ParseGetLine(int flags) /* Brutally ignore anything after a non-escaped '#' in non-commands */ if (comment != NULL && line[0] != '\t') { line_end = comment; - *line_end = 0; + *line_end = '\0'; } /* If we didn't see a '\\' then the in-situ data is fine */ @@ -2778,13 +2728,13 @@ ParseGetLine(int flags) for (; ; *tp++ = ch) { ch = *ptr++; if (ch != '\\') { - if (ch == 0) + if (ch == '\0') break; continue; } ch = *ptr++; - if (ch == 0) { + if (ch == '\0') { /* Delete '\\' at end of buffer */ tp--; break; @@ -2802,9 +2752,8 @@ ParseGetLine(int flags) continue; } - /* Escaped '\n' replace following whitespace with a single ' ' */ - while (ptr[0] == ' ' || ptr[0] == '\t') - ptr++; + /* Escaped '\n' -- replace following whitespace with a single ' '. */ + pp_skip_hspace(&ptr); ch = ' '; } @@ -2812,17 +2761,14 @@ ParseGetLine(int flags) while (tp > escaped && ch_isspace(tp[-1])) tp--; - *tp = 0; + *tp = '\0'; return line; } /* Read an entire line from the input file. Called only by Parse_File. * * Results: - * A line without its newline. - * - * Side Effects: - * Only those associated with reading a character + * A line without its newline and without any trailing whitespace. */ static char * ParseReadLine(void) @@ -2832,7 +2778,7 @@ ParseReadLine(void) int rval; for (;;) { - line = ParseGetLine(0); + line = ParseGetLine(PARSE_NORMAL); if (line == NULL) return NULL; @@ -2886,22 +2832,24 @@ ParseReadLine(void) static void FinishDependencyGroup(void) { - if (targets != NULL) { - GNodeListNode *ln; - for (ln = targets->first; ln != NULL; ln = ln->next) { - GNode *gn = ln->datum; + GNodeListNode *ln; - Suff_EndTransform(gn); + if (targets == NULL) + return; - /* Mark the target as already having commands if it does, to - * keep from having shell commands on multiple dependency lines. */ - if (!Lst_IsEmpty(gn->commands)) - gn->type |= OP_HAS_COMMANDS; - } + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; - Lst_Free(targets); - targets = NULL; + Suff_EndTransform(gn); + + /* Mark the target as already having commands if it does, to + * keep from having shell commands on multiple dependency lines. */ + if (!Lst_IsEmpty(gn->commands)) + gn->type |= OP_HAS_COMMANDS; } + + Lst_Free(targets); + targets = NULL; } /* Add the command to each target from the current dependency spec. */ @@ -2938,10 +2886,11 @@ ParseDirective(char *line) if (*line == '.') { /* - * Lines that begin with the special character may be - * include or undef directives. - * On the other hand they can be suffix rules (.c.o: ...) - * or just dependencies for filenames that start '.'. + * Lines that begin with '.' can be pretty much anything: + * - directives like '.include' or '.if', + * - suffix rules like '.c.o:', + * - dependencies for filenames that start with '.', + * - variable assignments like '.tmp=value'. */ cp = line + 1; pp_skip_whitespace(&cp); @@ -2983,12 +2932,13 @@ static Boolean ParseVarassign(const char *line) { VarAssign var; - if (Parse_IsVar(line, &var)) { - FinishDependencyGroup(); - Parse_DoVar(&var, VAR_GLOBAL); - return TRUE; - } - return FALSE; + + if (!Parse_IsVar(line, &var)) + return FALSE; + + FinishDependencyGroup(); + Parse_DoVar(&var, VAR_GLOBAL); + return TRUE; } static char * @@ -3002,19 +2952,12 @@ FindSemicolon(char *p) continue; } - if (*p == '$' && (p[1] == '(' || p[1] == '{')) { + if (*p == '$' && (p[1] == '(' || p[1] == '{')) level++; - continue; - } - - if (level > 0 && (*p == ')' || *p == '}')) { + else if (level > 0 && (*p == ')' || *p == '}')) level--; - continue; - } - - if (level == 0 && *p == ';') { + else if (level == 0 && *p == ';') break; - } } return p; } @@ -3055,7 +2998,7 @@ ParseDependency(char *line) * * Parsing the line first would also prevent that targets * generated from variable expressions are interpreted as the - * dependency operator, such as in "target${:U:} middle: source", + * dependency operator, such as in "target${:U\:} middle: source", * in which the middle is interpreted as a source, not a target. */ @@ -3063,7 +3006,7 @@ ParseDependency(char *line) * dependency lines. * * Ideally, only the right-hand side would allow undefined - * variables since it is common to have no dependencies. + * variables since it is common to have optional dependencies. * Having undefined variables on the left-hand side is more * unusual though. Since both sides are expanded in a single * pass, there is not much choice what to do here. @@ -3073,7 +3016,7 @@ ParseDependency(char *line) * Var_Parse does not print any parse errors in such a case. * It simply returns the special empty string var_Error, * which cannot be detected in the result of Var_Subst. */ - eflags = DEBUG(LINT) ? VARE_WANTRES : VARE_UNDEFERR | VARE_WANTRES; + eflags = opts.lint ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR; (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line); /* TODO: handle errors */ @@ -3129,8 +3072,8 @@ ParseLine(char *line) ParseDependency(line); } -/* Parse a top-level makefile into its component parts, incorporating them - * into the global dependency graph. +/* Parse a top-level makefile, incorporating its content into the global + * dependency graph. * * Input: * name The name of the file being read @@ -3145,7 +3088,6 @@ Parse_File(const char *name, int fd) lf = loadfile(name, fd); assert(targets == NULL); - fatals = 0; if (name == NULL) name = "(stdin)"; @@ -3166,7 +3108,7 @@ Parse_File(const char *name, int fd) FinishDependencyGroup(); - if (fatals) { + if (fatals != 0) { (void)fflush(stdout); (void)fprintf(stderr, "%s: Fatal errors encountered -- cannot continue", @@ -3206,19 +3148,9 @@ Parse_End(void) } -/*- - *----------------------------------------------------------------------- - * 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. - * - *----------------------------------------------------------------------- +/* + * Return a list containing the single main target to create. + * If no such target exists, we Punt with an obnoxious error message. */ GNodeList * Parse_MainName(void) @@ -3227,15 +3159,17 @@ Parse_MainName(void) mainList = Lst_New(); - if (mainNode == NULL) { + if (mainNode == NULL) Punt("no target to make."); - /*NOTREACHED*/ - } else if (mainNode->type & OP_DOUBLEDEP) { + + if (mainNode->type & OP_DOUBLEDEP) { Lst_Append(mainList, mainNode); Lst_AppendAll(mainList, mainNode->cohorts); } else Lst_Append(mainList, mainNode); + Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); + return mainList; } @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $ */ +/* $NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $ */ /*- * Copyright (c) 1988, 1989, 1990, 1993 @@ -71,7 +71,7 @@ #include "make.h" /* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ -MAKE_RCSID("$NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $"); /* Return the concatenation of s1 and s2, freshly allocated. */ char * @@ -116,15 +116,13 @@ str_concat4(const char *s1, const char *s2, const char *s3, const char *s4) } /* Fracture a string into an array of words (as delineated by tabs or spaces) - * taking quotation marks into account. Leading tabs/spaces are ignored. + * taking quotation marks into account. * * If expand is TRUE, quotes are removed and escape sequences such as \r, \t, - * etc... are expanded. In this case, the return value is NULL on parse - * errors. + * etc... are expanded. In this case, return NULL on parse errors. * - * Returns the fractured words, which must be freed later using Words_Free. - * If expand was TRUE and there was a parse error, words is NULL, and in that - * case, nothing needs to be freed. + * Returns the fractured words, which must be freed later using Words_Free, + * unless the returned Words.words was NULL. */ Words Str_Words(const char *str, Boolean expand) @@ -139,9 +137,8 @@ Str_Words(const char *str, Boolean expand) char *word_end; const char *str_p; - /* skip leading space chars. */ - for (; *str == ' ' || *str == '\t'; ++str) - continue; + /* XXX: why only hspace, not whitespace? */ + cpp_skip_hspace(&str); /* skip leading space chars. */ /* words_buf holds the words, separated by '\0'. */ str_len = strlen(str); @@ -239,7 +236,7 @@ Str_Words(const char *str, Boolean expand) case '\n': /* hmmm; fix it up as best we can */ ch = '\\'; - --str_p; + str_p--; break; case 'b': ch = '\b'; @@ -264,21 +261,15 @@ Str_Words(const char *str, Boolean expand) *word_end++ = ch; } done: - words[words_len] = NULL; + words[words_len] = NULL; /* useful for argv */ return (Words){ words, words_len, words_buf }; } /* * Str_Match -- Test if a string matches a pattern like "*.[ch]". + * The following special characters are known *?\[] (as in fnmatch(3)). * - * XXX this function does not detect or report malformed patterns. - * - * Results: - * Non-zero is returned if string matches the pattern, 0 otherwise. The - * matching operation permits the following special characters in the - * pattern: *?\[] (as in fnmatch(3)). - * - * Side effects: None. + * XXX: this function does not detect or report malformed patterns. */ Boolean Str_Match(const char *str, const char *pat) @@ -286,12 +277,12 @@ Str_Match(const char *str, const char *pat) 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 + * string. If so, we succeeded. If we're at the end of the * pattern but not at the end of the string, we failed. */ - if (*pat == 0) - return *str == 0; - if (*str == 0 && *pat != '*') + if (*pat == '\0') + return *str == '\0'; + if (*str == '\0' && *pat != '*') return FALSE; /* @@ -302,9 +293,9 @@ Str_Match(const char *str, const char *pat) pat++; while (*pat == '*') pat++; - if (*pat == 0) + if (*pat == '\0') return TRUE; - while (*str != 0) { + while (*str != '\0') { if (Str_Match(str, pat)) return TRUE; str++; @@ -327,15 +318,18 @@ Str_Match(const char *str, const char *pat) pat += neg ? 2 : 1; for (;;) { - if (*pat == ']' || *pat == 0) { + if (*pat == ']' || *pat == '\0') { if (neg) break; return FALSE; } + /* XXX: This naive comparison makes the parser + * for the pattern dependent on the actual of + * the string. This is unpredictable. */ if (*pat == *str) break; if (pat[1] == '-') { - if (pat[2] == 0) + if (pat[2] == '\0') return neg; if (*pat <= *str && pat[2] >= *str) break; @@ -345,11 +339,11 @@ Str_Match(const char *str, const char *pat) } pat++; } - if (neg && *pat != ']' && *pat != 0) + if (neg && *pat != ']' && *pat != '\0') return FALSE; - while (*pat != ']' && *pat != 0) + while (*pat != ']' && *pat != '\0') pat++; - if (*pat == 0) + if (*pat == '\0') pat--; goto thisCharOK; } @@ -360,7 +354,7 @@ Str_Match(const char *str, const char *pat) */ if (*pat == '\\') { pat++; - if (*pat == 0) + if (*pat == '\0') return FALSE; } @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,36 +68,23 @@ * SUCH DAMAGE. */ -/*- - * suff.c -- - * Functions to maintain suffix lists and find implicit dependents - * using suffix transformation rules +/* + * Maintain suffix lists and find implicit dependents using suffix + * transformation rules such as ".c.o". * * Interface: - * Suff_Init Initialize all things to do with suffixes. + * Suff_Init Initialize the module. * - * Suff_End Clean up the module + * Suff_End Clean up 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<suffix>: targets. In addition, all - * directories given for suffixes labeled as - * include files or libraries, using the .INCLUDES - * or .LIBS targets, are played with using - * Dir_MakeFlags to create the .INCLUDES and - * .LIBS global variables. + * Suff_DoPaths Extend the search path of each suffix to include the + * default search path. * * Suff_ClearSuffixes - * Clear out all the suffixes and defined - * transformations. + * Clear out all the suffixes and transformations. * * Suff_IsTransform - * Return TRUE if the passed string is the lhs - * of a transformation rule. + * See if the passed string is a transformation rule. * * Suff_AddSuffix Add the passed string as another known suffix. * @@ -109,9 +96,7 @@ * 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. + * Add another transformation to the suffix graph. * * Suff_SetNull Define the suffix to consider the suffix of * any file that doesn't have a known one. @@ -129,14 +114,11 @@ #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $"); #define SUFF_DEBUG0(text) DEBUG0(SUFF, text) #define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1) #define SUFF_DEBUG2(fmt, arg1, arg2) DEBUG2(SUFF, fmt, arg1, arg2) -#define SUFF_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(SUFF, fmt, arg1, arg2, arg3) -#define SUFF_DEBUG4(fmt, arg1, arg2, arg3, arg4) \ - DEBUG4(SUFF, fmt, arg1, arg2, arg3, arg4) typedef List SuffList; typedef ListNode SuffListNode; @@ -149,9 +131,11 @@ static SuffList *sufflist; /* List of suffixes */ static SuffList *suffClean; /* List of suffixes to be cleaned */ #endif static SrcList *srclist; /* List of sources */ -static GNodeList *transforms; /* List of transformation rules */ -static int sNum = 0; /* Counter for assigning suffix numbers */ +/* List of transformation rules, such as ".c.o" */ +static GNodeList *transforms; + +static int sNum = 0; /* Counter for assigning suffix numbers */ typedef enum SuffFlags { SUFF_INCLUDE = 0x01, /* One which is #include'd */ @@ -165,21 +149,29 @@ ENUM_FLAGS_RTTI_3(SuffFlags, typedef List SuffListList; -/* - * Structure describing an individual suffix. - */ typedef struct Suff { - char *name; /* The suffix itself, such as ".c" */ - size_t nameLen; /* Length of the name, to avoid strlen calls */ - SuffFlags flags; /* Type of suffix */ - SearchPath *searchPath; /* The path along which files of this suffix - * may be found */ - int sNum; /* The suffix number */ - int refCount; /* Reference count of list membership - * and several other places */ - SuffList *parents; /* Suffixes we have a transformation to */ - SuffList *children; /* Suffixes we have a transformation from */ - SuffListList *ref; /* Lists in which this suffix is referenced */ + /* The suffix itself, such as ".c" */ + char *name; + /* Length of the name, to avoid strlen calls */ + size_t nameLen; + /* Type of suffix */ + SuffFlags flags; + /* The path along which files of this suffix may be found */ + SearchPath *searchPath; + /* The suffix number; TODO: document the purpose of this number */ + int sNum; + /* Reference count of list membership and several other places */ + int refCount; + /* Suffixes we have a transformation to */ + SuffList *parents; + /* Suffixes we have a transformation from */ + SuffList *children; + + /* Lists in which this suffix is referenced. + * XXX: These lists are used nowhere, they are just appended to, for no + * apparent reason. They do have the side effect of increasing refCount + * though. */ + SuffListList *ref; } Suff; /* @@ -191,37 +183,26 @@ typedef struct Src { 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 + int numChildren; /* Count of existing children (so we don't free * this thing too early or never nuke it) */ #ifdef DEBUG_SRC SrcList *childrenList; #endif } Src; -static Suff *suffNull; /* The NULL suffix for this run */ -static Suff *emptySuff; /* The empty suffix required for POSIX - * single-suffix transformation rules */ +/* TODO: Document the difference between suffNull and emptySuff. */ +/* The NULL suffix for this run */ +static Suff *suffNull; +/* The empty suffix required for POSIX single-suffix transformation rules */ +static Suff *emptySuff; static void SuffFindDeps(GNode *, SrcList *); static void SuffExpandWildcards(GNodeListNode *, GNode *); - /*************** Lst Predicates ****************/ -/*- - *----------------------------------------------------------------------- - * SuffStrIsPrefix -- - * See if pref is a prefix of str. - * - * Input: - * pref possible prefix - * str string to check - * - * Results: - * NULL if it ain't, pointer to character in str after prefix if so - * - * Side Effects: - * None - *----------------------------------------------------------------------- +/* + * See if pref is a prefix of str. + * Return NULL if it ain't, pointer to character in str after prefix if so. */ static const char * SuffStrIsPrefix(const char *pref, const char *str) @@ -231,18 +212,12 @@ SuffStrIsPrefix(const char *pref, const char *str) str++; } - return *pref ? NULL : str; + return *pref != '\0' ? NULL : str; } -/* See if suff is a suffix of str. - * - * Input: - * s possible suffix - * nameLen length of the string to examine - * nameEnd end of the string to examine - * - * Results: - * NULL if it ain't, pointer to the start of suffix in str if it is. +/* + * See if suff is a suffix of name. + * Return NULL if it ain't, pointer to the start of suffix in name if it is. */ static const char * SuffSuffGetSuffix(const Suff *s, size_t nameLen, const char *nameEnd) @@ -302,10 +277,8 @@ FindTransformByName(const char *name) return NULL; } - /*********** Maintenance Functions ************/ - static void -SuffUnRef(SuffList *list, Suff *suff) +SuffList_Unref(SuffList *list, Suff *suff) { SuffListNode *ln = Lst_FindDatum(list, suff); if (ln != NULL) { @@ -318,45 +291,46 @@ SuffUnRef(SuffList *list, Suff *suff) static void SuffFree(void *sp) { - Suff *s = sp; + Suff *suff = sp; - if (s == suffNull) + if (suff == suffNull) suffNull = NULL; - if (s == emptySuff) + if (suff == emptySuff) emptySuff = NULL; #if 0 /* We don't delete suffixes in order, so we cannot use this */ - if (s->refCount) - Punt("Internal error deleting suffix `%s' with refcount = %d", s->name, - s->refCount); + if (suff->refCount != 0) + Punt("Internal error deleting suffix `%s' with refcount = %d", + suff->name, suff->refCount); #endif - Lst_Free(s->ref); - Lst_Free(s->children); - Lst_Free(s->parents); - Lst_Destroy(s->searchPath, Dir_Destroy); + Lst_Free(suff->ref); + Lst_Free(suff->children); + Lst_Free(suff->parents); + Lst_Destroy(suff->searchPath, Dir_Destroy); - free(s->name); - free(s); + free(suff->name); + free(suff); } /* Remove the suffix from the list, and free if it is otherwise unused. */ static void -SuffRemove(SuffList *list, Suff *suff) +SuffList_Remove(SuffList *list, Suff *suff) { - SuffUnRef(list, suff); + SuffList_Unref(list, suff); if (suff->refCount == 0) { - SuffUnRef(sufflist, suff); + /* XXX: can lead to suff->refCount == -1 */ + SuffList_Unref(sufflist, suff); SuffFree(suff); } } /* Insert the suffix into the list, keeping the list ordered by suffix - * numbers. */ + * number. */ static void -SuffInsert(SuffList *list, Suff *suff) +SuffList_Insert(SuffList *list, Suff *suff) { SuffListNode *ln; Suff *listSuff = NULL; @@ -374,8 +348,8 @@ SuffInsert(SuffList *list, Suff *suff) suff->refCount++; Lst_Append(suff->ref, list); } else if (listSuff->sNum != suff->sNum) { - SUFF_DEBUG4("inserting \"%s\" (%d) before \"%s\" (%d)\n", - suff->name, suff->sNum, listSuff->name, listSuff->sNum); + DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n", + suff->name, suff->sNum, listSuff->name, listSuff->sNum); Lst_InsertBefore(list, ln, suff); suff->refCount++; Lst_Append(suff->ref, list); @@ -384,29 +358,38 @@ SuffInsert(SuffList *list, Suff *suff) } } +static void +SuffRelate(Suff *srcSuff, Suff *targSuff) +{ + SuffList_Insert(targSuff->children, srcSuff); + SuffList_Insert(srcSuff->parents, targSuff); +} + static Suff * SuffNew(const char *name) { - Suff *s = bmake_malloc(sizeof(Suff)); - - s->name = bmake_strdup(name); - s->nameLen = strlen(s->name); - s->searchPath = Lst_New(); - s->children = Lst_New(); - s->parents = Lst_New(); - s->ref = Lst_New(); - s->sNum = sNum++; - s->flags = 0; - s->refCount = 1; - - return s; + Suff *suff = bmake_malloc(sizeof *suff); + + suff->name = bmake_strdup(name); + suff->nameLen = strlen(suff->name); + suff->searchPath = Lst_New(); + suff->children = Lst_New(); + suff->parents = Lst_New(); + suff->ref = Lst_New(); + suff->sNum = sNum++; + suff->flags = 0; + suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */ + + return suff; } -/* 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. */ +/* + * 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 when a line '.SUFFIXES:' with an empty suffixes list is + * encountered in a makefile. + */ void Suff_ClearSuffixes(void) { @@ -477,7 +460,8 @@ SuffParseTransform(const char *str, Suff **out_src, Suff **out_targ) } /* Return TRUE if the given string is a transformation rule, that is, a - * concatenation of two known suffixes. */ + * concatenation of two known suffixes such as ".c.o" or a single suffix + * such as ".o". */ Boolean Suff_IsTransform(const char *str) { @@ -500,18 +484,16 @@ Suff_IsTransform(const char *str) GNode * Suff_AddTransform(const char *name) { - GNode *gn; /* GNode of transformation rule */ - Suff *s, /* source suffix */ - *t; /* target suffix */ - Boolean ok; + Suff *srcSuff; + Suff *targSuff; - gn = FindTransformByName(name); + GNode *gn = FindTransformByName(name); if (gn == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ - gn = Targ_NewGN(name); + gn = GNode_New(name); Lst_Append(transforms, gn); } else { /* @@ -528,17 +510,18 @@ Suff_AddTransform(const char *name) gn->type = OP_TRANSFORM; - ok = SuffParseTransform(name, &s, &t); - assert(ok); - (void)ok; + { + Boolean ok = SuffParseTransform(name, &srcSuff, &targSuff); + assert(ok); + (void)ok; + } /* * link the two together in the proper relationship and order */ SUFF_DEBUG2("defining transformation from `%s' to `%s'\n", - s->name, t->name); - SuffInsert(t->children, s); - SuffInsert(s->parents, t); + srcSuff->name, targSuff->name); + SuffRelate(srcSuff, targSuff); return gn; } @@ -557,40 +540,29 @@ Suff_EndTransform(GNode *gn) { if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts)) gn = gn->cohorts->last->datum; + if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { - Suff *s, *t; + Suff *srcSuff, *targSuff; /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ - if (SuffParseTransform(gn->name, &s, &t)) { - SuffList *p; - - SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n", - s->name, t->name); + if (SuffParseTransform(gn->name, &srcSuff, &targSuff)) { /* - * Store s->parents because s could be deleted in SuffRemove + * Remember parents since srcSuff could be deleted in + * SuffList_Remove */ - p = s->parents; + SuffList *srcSuffParents = srcSuff->parents; - /* - * Remove the source from the target's children list. We check for a - * nil return to handle a beanhead saying something like - * .c.o .c.o: - * - * We'll be called twice when the next target is seen, but .c and .o - * are only linked once... - */ - SuffRemove(t->children, s); + SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n", + srcSuff->name, targSuff->name); - /* - * Remove the target from the source's parents list - */ - SuffRemove(p, t); + SuffList_Remove(targSuff->children, srcSuff); + SuffList_Remove(srcSuffParents, targSuff); } } else if (gn->type & OP_TRANSFORM) { SUFF_DEBUG1("transformation %s complete\n", gn->name); @@ -625,8 +597,7 @@ SuffRebuildGraph(GNode *transform, Suff *suff) Suff *to = FindSuffByName(toName); if (to != NULL) { /* Link in and return, since it can't be anything else. */ - SuffInsert(to->children, suff); - SuffInsert(suff->parents, to); + SuffRelate(suff, to); return; } } @@ -637,12 +608,8 @@ SuffRebuildGraph(GNode *transform, Suff *suff) toName = SuffSuffGetSuffix(suff, nameLen, name + nameLen); if (toName != NULL) { Suff *from = FindSuffByNameLen(name, (size_t)(toName - name)); - - if (from != NULL) { - /* establish the proper relationship */ - SuffInsert(suff->children, from); - SuffInsert(from->parents, suff); - } + if (from != NULL) + SuffRelate(from, suff); } } @@ -659,7 +626,7 @@ SuffRebuildGraph(GNode *transform, Suff *suff) static Boolean SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r) { - Suff *s, *t; + Suff *srcSuff, *targSuff; char *ptr; if (*inout_main == NULL && *gs_r && !(target->type & OP_NOTARGET)) { @@ -675,7 +642,7 @@ SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r) ptr == target->name) return FALSE; - if (SuffParseTransform(target->name, &s, &t)) { + if (SuffParseTransform(target->name, &srcSuff, &targSuff)) { if (*inout_main == target) { *gs_r = TRUE; *inout_main = NULL; @@ -688,9 +655,8 @@ SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r) * link the two together in the proper relationship and order */ SUFF_DEBUG2("defining transformation from `%s' to `%s'\n", - s->name, t->name); - SuffInsert(t->children, s); - SuffInsert(s->parents, t); + srcSuff->name, targSuff->name); + SuffRelate(srcSuff, targSuff); } return FALSE; } @@ -753,14 +719,18 @@ Suff_GetPath(const char *sname) return s != NULL ? s->searchPath : NULL; } -/* Extend the search paths for all suffixes to include the default search - * path. +/* + * Extend the search paths for all suffixes to include the default search + * path (dirSearchPath). + * + * The default search path can be defined using the special target '.PATH'. + * The search path of each suffix can be defined using the special target + * '.PATH<suffix>'. * - * 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. + * If paths were specified for the ".h" suffix, the directories are stuffed + * into a global variable called ".INCLUDES" with each directory preceded by + * '-I'. The same is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is '-L'. */ void Suff_DoPaths(void) @@ -777,14 +747,12 @@ Suff_DoPaths(void) Suff *s = ln->datum; if (!Lst_IsEmpty(s->searchPath)) { #ifdef INCLUDES - if (s->flags & SUFF_INCLUDE) { + if (s->flags & SUFF_INCLUDE) Dir_Concat(inIncludes, s->searchPath); - } #endif #ifdef LIBRARIES - if (s->flags & SUFF_LIBRARY) { + if (s->flags & SUFF_LIBRARY) Dir_Concat(inLibs, s->searchPath); - } #endif Dir_Concat(s->searchPath, dirSearchPath); } else { @@ -857,7 +825,7 @@ SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn) src->suff = suff; src->parent = parent; src->node = gn; - src->children = 0; + src->numChildren = 0; #ifdef DEBUG_SRC src->childrenList = Lst_New(); #endif @@ -867,11 +835,11 @@ SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn) static void SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName, - const char *debug_tag) + const char *debug_tag MAKE_ATTR_UNUSED) { Src *s2 = SrcNew(srcName, targ->pref, suff, targ, NULL); suff->refCount++; - targ->children++; + targ->numChildren++; Lst_Append(srcList, s2); #ifdef DEBUG_SRC Lst_Append(targ->childrenList, s2); @@ -883,7 +851,7 @@ SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName, /* 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. + * the prefix is used unaltered as the filename in the Src structure. * * Input: * suff suffix for which to create a Src structure @@ -904,19 +872,14 @@ SuffAddSources(Suff *suff, SrcList *srcList, Src *targ) SuffAddSrc(suff, srcList, targ, str_concat2(targ->pref, suff->name), "2"); } -/* Add all the children of targ as Src structures to the given list. - * - * Input: - * l list to which to add the new level - * targ Src structure to use as the parent - */ +/* Add all the children of targ to the list. */ static void -SuffAddLevel(SrcList *l, Src *targ) +SuffAddLevel(SrcList *srcs, Src *targ) { SrcListNode *ln; for (ln = targ->suff->children->first; ln != NULL; ln = ln->next) { Suff *childSuff = ln->datum; - SuffAddSources(childSuff, l, targ); + SuffAddSources(childSuff, srcs, targ); } } @@ -933,34 +896,34 @@ SuffRemoveSrc(SrcList *l) #endif for (ln = l->first; ln != NULL; ln = ln->next) { - Src *s = ln->datum; + Src *src = ln->datum; - if (s->children == 0) { - free(s->file); - if (s->parent == NULL) - free(s->pref); + if (src->numChildren == 0) { + free(src->file); + if (src->parent == NULL) + free(src->pref); else { #ifdef DEBUG_SRC - SrcListNode *ln2 = Lst_FindDatum(s->parent->childrenList, s); + SrcListNode *ln2 = Lst_FindDatum(src->parent->childrenList, src); if (ln2 != NULL) - Lst_Remove(s->parent->childrenList, ln2); + Lst_Remove(src->parent->childrenList, ln2); #endif - s->parent->children--; + src->parent->numChildren--; } #ifdef DEBUG_SRC debug_printf("free: list %p src %p children %d\n", - l, s, s->children); - Lst_Free(s->childrenList); + l, src, src->children); + Lst_Free(src->childrenList); #endif Lst_Remove(l, ln); - free(s); + free(src); return TRUE; } #ifdef DEBUG_SRC else { debug_printf("keep: list %p src %p children %d:", - l, s, s->children); - SrcList_PrintAddrs(s->childrenList); + l, src, src->children); + SrcList_PrintAddrs(src->childrenList); } #endif } @@ -968,14 +931,7 @@ SuffRemoveSrc(SrcList *l) return FALSE; } -/* Find the first existing file/target in the list srcs. - * - * Input: - * srcs list of Src structures to search through - * - * Results: - * The lowest structure in the chain of transformations, or NULL. - */ +/* Find the first existing file/target in srcs. */ static Src * SuffFindThem(SrcList *srcs, SrcList *slst) { @@ -1068,10 +1024,7 @@ SuffFindCmds(Src *targ, SrcList *slst) } if (strncmp(cp, targ->pref, prefLen) != 0) continue; - /* - * The node matches the prefix ok, see if it has a known - * suffix. - */ + /* The node matches the prefix ok, see if it has a known suffix. */ suff = FindSuffByName(cp + prefLen); if (suff == NULL) continue; @@ -1100,7 +1053,7 @@ SuffFindCmds(Src *targ, SrcList *slst) */ ret = SrcNew(bmake_strdup(sgn->name), targ->pref, suff, targ, sgn); suff->refCount++; - targ->children++; + targ->numChildren++; #ifdef DEBUG_SRC debug_printf("3 add targ %p ret %p\n", targ, ret); Lst_Append(targ->childrenList, ret); @@ -1147,7 +1100,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn) } SUFF_DEBUG1("Expanding \"%s\"...", cgn->name); - (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR|VARE_WANTRES, &cp); + (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp); /* TODO: handle errors */ { @@ -1166,15 +1119,15 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn) /* * 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 + * Unfortunately, we can't use Str_Words because 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; + start = cp; + pp_skip_hspace(&start); cp = start; while (*cp != '\0') { if (*cp == ' ' || *cp == '\t') { @@ -1185,9 +1138,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn) *cp++ = '\0'; gn = Targ_GetNode(start); Lst_Append(members, gn); - while (*cp == ' ' || *cp == '\t') { - cp++; - } + pp_skip_hspace(&cp); start = cp; /* Continue at the next non-space. */ } else if (*cp == '$') { /* @@ -1200,7 +1151,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn) /* XXX: Why VARE_WANTRES when the result is not used? */ (void)Var_Parse(&nested_p, pgn, - VARE_UNDEFERR|VARE_WANTRES, + VARE_WANTRES | VARE_UNDEFERR, &junk, &freeIt); /* TODO: handle errors */ if (junk == var_Error) { @@ -1213,7 +1164,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn) } free(freeIt); - } else if (*cp == '\\' && cp[1] != '\0') { + } else if (cp[0] == '\\' && cp[1] != '\0') { /* * Escaped something -- skip over it */ @@ -1277,7 +1228,7 @@ static void SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; - StringList *explist; + StringList *expansions; if (!Dir_HasWildcards(cgn->name)) return; @@ -1285,15 +1236,15 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) /* * Expand the word along the chosen path */ - explist = Lst_New(); - Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); + expansions = Lst_New(); + Dir_Expand(cgn->name, Suff_FindPath(cgn), expansions); - while (!Lst_IsEmpty(explist)) { + while (!Lst_IsEmpty(expansions)) { GNode *gn; /* * Fetch next expansion off the list and find its GNode */ - char *cp = Lst_Dequeue(explist); + char *cp = Lst_Dequeue(expansions); SUFF_DEBUG1("%s...", cp); gn = Targ_GetNode(cp); @@ -1304,7 +1255,7 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) pgn->unmade++; } - Lst_Free(explist); + Lst_Free(expansions); SUFF_DEBUG0("\n"); @@ -1319,7 +1270,7 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn) /* Find a path along which to expand the node. * - * If the word has a known suffix, use that path. + * If the node has a known suffix, use that path. * If it has no known suffix, use the default system search path. * * Input: @@ -1359,77 +1310,61 @@ Suff_FindPath(GNode* gn) /* Apply a transformation rule, given the source and target nodes and * suffixes. * - * Input: - * tGn Target node - * sGn Source node - * t Target suffix - * s Source suffix + * The source and target are linked and the commands from the transformation + * are added to the target node's commands list. The target also inherits all + * the sources for the transformation rule. * * 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) +SuffApplyTransform(GNode *tgn, GNode *sgn, Suff *tsuff, Suff *ssuff) { - GNodeListNode *ln, *nln; /* General node */ + GNodeListNode *ln; char *tname; /* Name of transformation rule */ GNode *gn; /* Node for same */ /* * Form the proper links between the target and source. */ - Lst_Append(tGn->children, sGn); - Lst_Append(sGn->parents, tGn); - tGn->unmade++; + Lst_Append(tgn->children, sgn); + Lst_Append(sgn->parents, tgn); + tgn->unmade++; /* * Locate the transformation rule itself */ - tname = str_concat2(s->name, t->name); + tname = str_concat2(ssuff->name, tsuff->name); gn = FindTransformByName(tname); free(tname); if (gn == 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. - */ + /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ return FALSE; } - SUFF_DEBUG3("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); + DEBUG3(SUFF,"\tapplying %s -> %s to \"%s\"\n", + ssuff->name, tsuff->name, tgn->name); - /* - * Record last child for expansion purposes - */ - ln = tGn->children->last; + /* Record last child; Make_HandleUse may add child nodes. */ + ln = tgn->children->last; - /* - * Pass the buck to Make_HandleUse to apply the rule - */ - (void)Make_HandleUse(gn, tGn); + /* Apply the rule. */ + Make_HandleUse(gn, tgn); - /* - * Deal with wildcards and variables in any acquired sources - */ - for (ln = ln != NULL ? ln->next : NULL; ln != NULL; ln = nln) { - nln = ln->next; - SuffExpandChildren(ln, tGn); + /* Deal with wildcards and variables in any acquired sources. */ + ln = ln != NULL ? ln->next : NULL; + while (ln != NULL) { + GNodeListNode *nln = ln->next; + SuffExpandChildren(ln, tgn); + ln = nln; } /* - * Keep track of another parent to which this beast is transformed so + * Keep track of another parent to which this node is transformed so * the .IMPSRC variable can be set correctly for the parent. */ - Lst_Append(sGn->implicitParents, tGn); + Lst_Append(sgn->implicitParents, tgn); return TRUE; } @@ -1496,10 +1431,7 @@ SuffFindArchiveDeps(GNode *gn, SrcList *slst) Var_Set(TARGET, GNode_VarTarget(mem), gn); ms = mem->suffix; - if (ms == NULL) { - /* - * Didn't know what it was -- use .NULL suffix if not in make mode - */ + if (ms == NULL) { /* Didn't know what it was. */ SUFF_DEBUG0("using null suffix\n"); ms = suffNull; } @@ -1555,16 +1487,16 @@ SuffFindArchiveDeps(GNode *gn, SrcList *slst) * Replace the opening and closing parens now we've no need of the separate * pieces. */ - *eoarch = '('; *eoname = ')'; + *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 (!GNode_IsTarget(gn)) { + if (!GNode_IsTarget(gn)) gn->type |= OP_DEPENDS; - } /* * Flag the member as such so we remember to look in the archive for @@ -1766,11 +1698,10 @@ SuffFindNormalDeps(GNode *gn, SrcList *slst) * No known transformations -- use the first suffix found * for setting the local variables. */ - if (!Lst_IsEmpty(targs)) { + if (targs->first != NULL) targ = targs->first->datum; - } else { + else targ = NULL; - } } else { /* * Work up the transformation path to find the suffix of the @@ -1783,7 +1714,7 @@ SuffFindNormalDeps(GNode *gn, SrcList *slst) Var_Set(TARGET, GNode_Path(gn), gn); - pref = (targ != NULL) ? targ->pref : gn->name; + pref = targ != NULL ? targ->pref : gn->name; Var_Set(PREFIX, pref, gn); /* @@ -1810,9 +1741,8 @@ sfnd_abort: * If the suffix indicates that the target is a library, mark that in * the node's type field. */ - if (targ->suff->flags & SUFF_LIBRARY) { + if (targ->suff->flags & SUFF_LIBRARY) gn->type |= OP_LIB; - } /* * Check for overriding transformation rule implied by sources @@ -1825,10 +1755,9 @@ sfnd_abort: * 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_FindDatum(slst, bottom) == NULL) { + while (bottom != NULL && bottom->parent != NULL) { + if (Lst_FindDatum(slst, bottom) == NULL) Lst_Append(slst, bottom); - } bottom = bottom->parent; } bottom = src; @@ -1854,9 +1783,8 @@ sfnd_abort: * transformation rule. Also, the unmade field of gn is incremented. * Etc. */ - if (bottom->node == NULL) { + if (bottom->node == NULL) bottom->node = Targ_GetNode(bottom->file); - } for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; @@ -1866,9 +1794,8 @@ sfnd_abort: src->node->suffix = src->suff; src->node->suffix->refCount++; - if (targ->node == NULL) { + if (targ->node == NULL) targ->node = Targ_GetNode(targ->file); - } SuffApplyTransform(targ->node, src->node, targ->suff, src->suff); @@ -1880,18 +1807,15 @@ sfnd_abort: * 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. + * node, so all we need to do is set the standard 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) + if (gn->suffix != NULL) gn->suffix->refCount--; gn->suffix = src->suff; gn->suffix->refCount++; @@ -1901,9 +1825,8 @@ sfnd_abort: * two lists. */ sfnd_return: - if (bottom) - if (Lst_FindDatum(slst, bottom) == NULL) - Lst_Append(slst, bottom); + if (bottom != NULL && Lst_FindDatum(slst, bottom) == NULL) + Lst_Append(slst, bottom); while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) continue; @@ -1913,7 +1836,7 @@ sfnd_return: } -/* Find implicit sources for the target described by the graph node. +/* Find implicit sources for the target. * * 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 @@ -1995,21 +1918,20 @@ SuffFindDeps(GNode *gn, SrcList *slst) void Suff_SetNull(const char *name) { - Suff *s = FindSuffByName(name); - if (s == NULL) { + Suff *suff = FindSuffByName(name); + if (suff == NULL) { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", name); return; } - if (suffNull != NULL) { + if (suffNull != NULL) suffNull->flags &= ~(unsigned)SUFF_NULL; - } - s->flags |= SUFF_NULL; + suff->flags |= SUFF_NULL; /* * XXX: Here's where the transformation mangling would take place */ - suffNull = s; + suffNull = suff; } /* Initialize the suffixes module. */ @@ -2047,8 +1969,6 @@ Suff_End(void) } -/********************* DEBUGGING FUNCTIONS **********************/ - static void PrintSuffNames(const char *prefix, SuffList *suffs) { @@ -2063,23 +1983,24 @@ PrintSuffNames(const char *prefix, SuffList *suffs) } static void -PrintSuff(Suff *s) +PrintSuff(Suff *suff) { - debug_printf("# \"%s\" (num %d, ref %d)", s->name, s->sNum, s->refCount); - if (s->flags != 0) { + debug_printf("# \"%s\" (num %d, ref %d)", + suff->name, suff->sNum, suff->refCount); + if (suff->flags != 0) { char flags_buf[SuffFlags_ToStringSize]; debug_printf(" (%s)", Enum_FlagsToString(flags_buf, sizeof flags_buf, - s->flags, SuffFlags_ToStringSpecs)); + suff->flags, SuffFlags_ToStringSpecs)); } debug_printf("\n"); - PrintSuffNames("To", s->parents); - PrintSuffNames("From", s->children); + PrintSuffNames("To", suff->parents); + PrintSuffNames("From", suff->children); debug_printf("#\tSearch Path: "); - Dir_PrintPath(s->searchPath); + Dir_PrintPath(suff->searchPath); debug_printf("\n"); } @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,19 +68,17 @@ * SUCH DAMAGE. */ -/*- - * targ.c -- - * Functions for maintaining the Lst allTargets. Target nodes are - * kept in two structures: a Lst and a hash table. +/* + * Maintaining the targets and sources, which are both implemented as GNode. * * Interface: - * Targ_Init Initialization procedure. + * Targ_Init Initialize the module. * - * Targ_End Clean up the module + * Targ_End Clean up the module. * * Targ_List Return the list of all targets so far. * - * Targ_NewGN Create a new GNode for the passed target + * GNode_New Create a new GNode for the passed target * (string). The node is *not* placed in the * hash table, though all its fields are * initialized. @@ -121,23 +119,26 @@ #include "dir.h" /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $"); -static GNodeList *allTargets; /* the list of all targets found so far */ -#ifdef CLEANUP -static GNodeList *allGNs; /* List of all the GNodes */ -#endif -static HashTable targets; /* a hash table of same */ +/* All target nodes found so far, but not the source nodes. */ +static GNodeList *allTargets; +static HashTable allTargetsByName; #ifdef CLEANUP -static void TargFreeGN(void *); +static GNodeList *allNodes; + +static void GNode_Free(void *); #endif void Targ_Init(void) { allTargets = Lst_New(); - HashTable_Init(&targets); + HashTable_Init(&allTargetsByName); +#ifdef CLEANUP + allNodes = Lst_New(); +#endif } void @@ -146,56 +147,67 @@ Targ_End(void) Targ_Stats(); #ifdef CLEANUP Lst_Free(allTargets); - if (allGNs != NULL) - Lst_Destroy(allGNs, TargFreeGN); - HashTable_Done(&targets); + HashTable_Done(&allTargetsByName); + Lst_Destroy(allNodes, GNode_Free); #endif } void Targ_Stats(void) { - HashTable_DebugStats(&targets, "targets"); + HashTable_DebugStats(&allTargetsByName, "targets"); } -/* Return the list of all targets. */ +/* + * Return the list of all targets, which are all nodes that appear on the + * left-hand side of a dependency declaration such as "target: source". + * The returned list does not contain pure sources. + */ GNodeList * Targ_List(void) { return allTargets; } -/* Create and initialize a new graph node. The gnode is added to the list of - * all gnodes. +/* Create a new graph node, but don't register it anywhere. * - * Input: - * name the name of the node, such as "clean", "src.c", ".END" + * Graph nodes that appear on the left-hand side of a dependency line such + * as "target: source" are called targets. XXX: In some cases (like the + * .ALLTARGETS variable), all nodes are called targets as well, even if they + * never appear on the left-hand side. This is a mistake. + * + * Typical names for graph nodes are: + * "src.c" (an ordinary file) + * "clean" (a .PHONY target) + * ".END" (a special hook target) + * "-lm" (a library) + * "libc.a(isspace.o)" (an archive member) */ GNode * -Targ_NewGN(const char *name) +GNode_New(const char *name) { GNode *gn; - gn = bmake_malloc(sizeof(GNode)); + gn = bmake_malloc(sizeof *gn); gn->name = bmake_strdup(name); gn->uname = NULL; gn->path = NULL; gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0; - gn->unmade = 0; - gn->unmade_cohorts = 0; - gn->cohort_num[0] = '\0'; - gn->centurion = NULL; - gn->made = UNMADE; gn->flags = 0; - gn->checked_seqno = 0; + gn->made = UNMADE; + gn->unmade = 0; gn->mtime = 0; gn->youngestChild = NULL; gn->implicitParents = Lst_New(); - gn->cohorts = Lst_New(); gn->parents = Lst_New(); gn->children = Lst_New(); gn->order_pred = Lst_New(); gn->order_succ = Lst_New(); + gn->cohorts = Lst_New(); + gn->cohort_num[0] = '\0'; + gn->unmade_cohorts = 0; + gn->centurion = NULL; + gn->checked_seqno = 0; HashTable_Init(&gn->context); gn->commands = Lst_New(); gn->suffix = NULL; @@ -203,9 +215,7 @@ Targ_NewGN(const char *name) gn->lineno = 0; #ifdef CLEANUP - if (allGNs == NULL) - allGNs = Lst_New(); - Lst_Append(allGNs, gn); + Lst_Append(allNodes, gn); #endif return gn; @@ -213,24 +223,29 @@ Targ_NewGN(const char *name) #ifdef CLEANUP static void -TargFreeGN(void *gnp) +GNode_Free(void *gnp) { GNode *gn = gnp; free(gn->name); free(gn->uname); free(gn->path); - - Lst_Free(gn->implicitParents); - Lst_Free(gn->cohorts); - Lst_Free(gn->parents); - Lst_Free(gn->children); - Lst_Free(gn->order_succ); - Lst_Free(gn->order_pred); - HashTable_Done(&gn->context); - Lst_Free(gn->commands); - - /* XXX: does gn->suffix need to be freed? It is reference-counted. */ + /* gn->youngestChild is not owned by this node. */ + Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */ + Lst_Free(gn->parents); /* as they are not owned by this node. */ + Lst_Free(gn->children); /* likewise */ + Lst_Free(gn->order_pred); /* likewise */ + Lst_Free(gn->order_succ); /* likewise */ + Lst_Free(gn->cohorts); /* likewise */ + HashTable_Done(&gn->context); /* ... but not the variables themselves, + * even though they are owned by this node. + * XXX: they should probably be freed. */ + Lst_Free(gn->commands); /* ... but not the commands themselves, + * as they may be shared with other nodes. */ + /* gn->suffix is not owned by this node. */ + /* XXX: gn->suffix should be unreferenced here. This requires a thorough + * check that the reference counting is done correctly in all places, + * otherwise a suffix might be freed too early. */ free(gn); } @@ -240,7 +255,7 @@ TargFreeGN(void *gnp) GNode * Targ_FindNode(const char *name) { - return HashTable_FindValue(&targets, name); + return HashTable_FindValue(&allTargetsByName, name); } /* Get the existing global node, or create it. */ @@ -248,7 +263,7 @@ GNode * Targ_GetNode(const char *name) { Boolean isNew; - HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew); + HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew); if (!isNew) return HashEntry_Get(he); @@ -259,14 +274,16 @@ Targ_GetNode(const char *name) } } -/* Create a node, register it in .ALLTARGETS but don't store it in the +/* + * Create a node, register it in .ALLTARGETS but don't store it in the * table of global nodes. This means it cannot be found by name. * - * This is used for internal nodes, such as cohorts or .WAIT nodes. */ + * This is used for internal nodes, such as cohorts or .WAIT nodes. + */ GNode * Targ_NewInternalNode(const char *name) { - GNode *gn = Targ_NewGN(name); + GNode *gn = GNode_New(name); Var_Append(".ALLTARGETS", name, VAR_GLOBAL); Lst_Append(allTargets, gn); if (doing_depend) @@ -274,8 +291,10 @@ Targ_NewInternalNode(const char *name) return gn; } -/* Return the .END node, which contains the commands to be executed when - * everything else is done. */ +/* + * Return the .END node, which contains the commands to be run when + * everything else has been made. + */ GNode *Targ_GetEndNode(void) { /* Save the node locally to avoid having to search for it all the time. */ @@ -303,31 +322,33 @@ Targ_FindList(StringList *names) /* Return true if should ignore errors when creating gn. */ Boolean -Targ_Ignore(GNode *gn) +Targ_Ignore(const GNode *gn) { return opts.ignoreErrors || gn->type & OP_IGNORE; } /* Return true if be silent when creating gn. */ Boolean -Targ_Silent(GNode *gn) +Targ_Silent(const GNode *gn) { return opts.beSilent || gn->type & OP_SILENT; } /* See if the given target is precious. */ Boolean -Targ_Precious(GNode *gn) +Targ_Precious(const GNode *gn) { + /* XXX: Why are '::' targets precious? */ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); } -/******************* DEBUG INFO PRINTING ****************/ - -static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ +/* + * The main target to be made; only for debugging output. + * See mainNode in parse.c for the definitive source. + */ +static GNode *mainTarg; -/* Set our idea of the main target we'll be creating. Used for debugging - * output. */ +/* Remember the main target to make; only used for debugging. */ void Targ_SetMain(GNode *gn) { @@ -459,12 +480,12 @@ Targ_PrintNode(GNode *gn, int pass) debug_printf("# *** MAIN TARGET ***\n"); } if (pass >= 2) { - if (gn->unmade) { + if (gn->unmade > 0) { debug_printf("# %d unmade children\n", gn->unmade); } else { debug_printf("# No unmade children\n"); } - if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { + if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { if (gn->mtime != 0) { debug_printf("# last modified %s: %s\n", Targ_FmtTime(gn->mtime), @@ -532,20 +553,27 @@ Targ_PrintGraph(int pass) { debug_printf("#*** Input graph:\n"); Targ_PrintNodes(allTargets, pass); - debug_printf("\n\n"); - debug_printf("#\n# Files that are only sources:\n"); + debug_printf("\n"); + debug_printf("\n"); + + debug_printf("#\n"); + debug_printf("# Files that are only sources:\n"); PrintOnlySources(); + debug_printf("#*** Global Variables:\n"); Var_Dump(VAR_GLOBAL); + debug_printf("#*** Command-line Variables:\n"); Var_Dump(VAR_CMDLINE); + debug_printf("\n"); Dir_PrintDirectories(); debug_printf("\n"); + Suff_PrintAll(); } -/* Propagate some type information to cohort nodes (those from the :: +/* Propagate some type information to cohort nodes (those from the '::' * dependency operator). * * Should be called after the makefiles are parsed but before any action is diff --git a/unit-tests/Makefile b/unit-tests/Makefile index 0940d55671ee..cca63155e868 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $ +# $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $ # -# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $ +# $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $ # # Unit tests for make(1) # @@ -36,8 +36,11 @@ # src/tests/usr.bin/make/t_make.sh as well. #TESTS+= archive TESTS+= archive-suffix +TESTS+= cmd-errors +TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline +TESTS+= cmdline-undefined TESTS+= comment TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric-eq @@ -58,8 +61,10 @@ TESTS+= cond-func-target TESTS+= cond-late TESTS+= cond-op TESTS+= cond-op-and +TESTS+= cond-op-and-lint TESTS+= cond-op-not TESTS+= cond-op-or +TESTS+= cond-op-or-lint TESTS+= cond-op-parentheses TESTS+= cond-short TESTS+= cond-token-number @@ -144,6 +149,7 @@ TESTS+= directive-for TESTS+= directive-for-generating-endif TESTS+= directive-hyphen-include TESTS+= directive-if +TESTS+= directive-if-nested TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef @@ -156,7 +162,6 @@ TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning -TESTS+= directives TESTS+= dollar TESTS+= doterror TESTS+= dotwait @@ -169,9 +174,11 @@ TESTS+= export-env TESTS+= export-variants TESTS+= forloop TESTS+= forsubst +TESTS+= gnode-submake TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main +TESTS+= job-flags #TESTS+= job-output-long-lines TESTS+= lint TESTS+= make-exported @@ -180,6 +187,7 @@ TESTS+= modmatch TESTS+= modmisc TESTS+= modts TESTS+= modword +TESTS+= objdir-writable TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir @@ -223,6 +231,7 @@ TESTS+= opt-query TESTS+= opt-raw TESTS+= opt-silent TESTS+= opt-touch +TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal @@ -248,7 +257,9 @@ TESTS+= sh-multi-line TESTS+= sh-single-line TESTS+= shell-csh TESTS+= shell-custom +.if exists(/bin/ksh) TESTS+= shell-ksh +.endif TESTS+= shell-sh TESTS+= suff-add-later TESTS+= suff-clear-regular @@ -256,6 +267,7 @@ TESTS+= suff-clear-single TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-rebuild +TESTS+= suff-self TESTS+= suff-transform-endless TESTS+= suff-transform-expand TESTS+= suff-transform-select @@ -366,38 +378,54 @@ TESTS+= varname-makeflags TESTS+= varname-pwd TESTS+= varname-vpath TESTS+= varparse-dynamic +TESTS+= varparse-errors TESTS+= varparse-mod TESTS+= varparse-undef-partial TESTS+= varquote -TESTS+= varshell +# Ideas for more tests: +# char-0020-space.mk +# char-005C-backslash.mk +# escape-cond-str.mk +# escape-cond-func-arg.mk +# escape-cond-func-arg.mk +# escape-varmod.mk +# escape-varmod-define.mk +# escape-varmod-match.mk +# escape-varname.mk +# escape-varassign-varname.mk +# escape-varassign-varname-cmdline.mk +# escape-varassign-value.mk +# escape-varassign-value-cmdline.mk +# escape-dependency-source.mk +# escape-dependency-target.mk +# escape-for-varname.mk +# escape-for-item.mk +# posix-*.mk (see posix.mk and posix1.mk) + +.if ${.OBJDIR} != ${.CURDIR} +RO_OBJDIR:= ${.OBJDIR}/roobj +.else +RO_OBJDIR:= ${TMPDIR:U/tmp}/roobj +.endif # Additional environment variables for some of the tests. # The base environment is -i PATH="$PATH". +ENV.depsrc-optional+= TZ=UTC ENV.envfirst= FROM_ENV=value-from-env +ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR} ENV.varmisc= FROM_ENV=env ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmod-localtime+= TZ=Europe/Berlin +ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # Override make flags for some of the tests; default is -k. # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline FLAGS.directive-ifmake= first second -FLAGS.doterror= # none -FLAGS.envfirst= -e -FLAGS.export= # none -FLAGS.opt-ignore= -i -FLAGS.opt-keep-going= -k -FLAGS.opt-no-action= -n -FLAGS.opt-query= -q -FLAGS.opt-var-expanded= -v VAR -v VALUE -FLAGS.opt-var-literal= -V VAR -V VALUE -FLAGS.opt-warnings-as-errors= -W -FLAGS.order= -j1 -FLAGS.recursive= -dL -FLAGS.sh-leading-plus= -n -FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain' +FLAGS.doterror= # none, especially not -k +FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.export= \ @@ -406,6 +434,9 @@ SED_CMDS.export= \ .for t in export-all export-env SED_CMDS.$t= ${SED_CMDS.export} .endfor +SED_CMDS.directive-export-gmake= \ + ${:D dash is a pain } \ + -e /non-zero/d SED_CMDS.job-output-long-lines= \ ${:D Job separators on their own line are ok. } \ -e '/^--- job-[ab] ---$$/d' \ @@ -417,6 +448,7 @@ SED_CMDS.job-output-long-lines= \ ${:D marker should always be at the beginning of the line. } \ -e '/^aa*--- job-b ---$$/d' \ -e '/^bb*--- job-a ---$$/d' +SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g' SED_CMDS.opt-debug-graph1= \ -e 's,${.CURDIR},CURDIR,' SED_CMDS.opt-debug-graph1+= \ @@ -428,11 +460,14 @@ SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,' +SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,' +SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }' +SED_CMDS.vardebug= \ + ${:D canonicalize .SHELL } \ + -e 's,${.SHELL},</path/to/shell>,' SED_CMDS.varmod-subst-regex+= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,' -SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,' -SED_CMDS.varshell+= -e '/command/s,No such.*,not found,' SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' @@ -442,7 +477,7 @@ SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' # Some tests need an additional round of postprocessing. POSTPROC.deptgt-suffixes= \ ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p' -POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p' +POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p' # Some tests reuse other tests, which makes them unnecessarily fragile. @@ -519,6 +554,8 @@ MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc # always pretend .MAKE was called 'make' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' +_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,' +_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' # replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' # strip ${.CURDIR}/ from the output diff --git a/unit-tests/archive-suffix.mk b/unit-tests/archive-suffix.mk index 9f7fa219c667..a216fd2d3c6a 100755 --- a/unit-tests/archive-suffix.mk +++ b/unit-tests/archive-suffix.mk @@ -1,11 +1,11 @@ -# $NetBSD: archive-suffix.mk,v 1.1 2020/08/29 14:47:26 rillig Exp $ +# $NetBSD: archive-suffix.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Between 2020-08-23 and 2020-08-30, the below code produced an assertion -# failure in Var_Set_with_flags, triggered by Compat_Make, when setting the +# failure in Var_SetWithFlags, triggered by Compat_Make, when setting the # .IMPSRC of an archive node to its .TARGET. # # The code assumed that the .TARGET variable of every node would be set, but -# but that is not guaranteed. +# that is not guaranteed. # # Between 2016-03-15 and 2016-03-16 the behavior of the below code changed. # Until 2016-03-15, it remade the target, starting with 2016-03-16 it says diff --git a/unit-tests/archive.mk b/unit-tests/archive.mk index 2f91005f988c..f8815cf40a40 100644 --- a/unit-tests/archive.mk +++ b/unit-tests/archive.mk @@ -1,18 +1,15 @@ -# $NetBSD: archive.mk,v 1.10 2020/10/09 06:44:42 rillig Exp $ +# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $ # # Very basic demonstration of handling archives, based on the description # in PSD.doc/tutorial.ms. # # This test aims at covering the code, not at being an introduction to -# archive handling. That's why it is more complicated and detailed than -# strictly necessary. +# archive handling. That's why it deviates from the tutorial style of +# several other tests. ARCHIVE= libprog.a FILES= archive.mk modmisc.mk varmisc.mk -MAKE_CMD= ${.MAKE} -f ${MAKEFILE} -RUN?= @set -eu; - all: .if ${.PARSEDIR:tA} != ${.CURDIR:tA} @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} @@ -20,13 +17,13 @@ all: # The following targets create and remove files. The filesystem cache in # dir.c would probably not handle this correctly, therefore each of the # targets is run in its separate sub-make. - ${RUN} ${MAKE_CMD} remove-archive - ${RUN} ${MAKE_CMD} create-archive - ${RUN} ${MAKE_CMD} list-archive - ${RUN} ${MAKE_CMD} list-archive-wildcard - ${RUN} ${MAKE_CMD} depend-on-existing-member - ${RUN} ${MAKE_CMD} depend-on-nonexistent-member - ${RUN} ${MAKE_CMD} remove-archive + @${MAKE} -f ${MAKEFILE} remove-archive + @${MAKE} -f ${MAKEFILE} create-archive + @${MAKE} -f ${MAKEFILE} list-archive + @${MAKE} -f ${MAKEFILE} list-archive-wildcard + @${MAKE} -f ${MAKEFILE} depend-on-existing-member + @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member + @${MAKE} -f ${MAKEFILE} remove-archive create-archive: ${ARCHIVE} pre post @@ -43,15 +40,16 @@ list-archive: ${ARCHIVE} pre post # XXX: I had expected that this dependency would select all *.mk files from # the archive. Instead, the globbing is done in the current directory. +# # To prevent an overly long file list, the pattern is restricted to [at]*.mk. list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post - ${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} + @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post - ${RUN} echo $@ + @echo $@ depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post - ${RUN} echo $@ + @echo $@ remove-archive: pre post rm -f ${ARCHIVE} diff --git a/unit-tests/cmd-errors-lint.exp b/unit-tests/cmd-errors-lint.exp new file mode 100644 index 000000000000..09924c538de0 --- /dev/null +++ b/unit-tests/cmd-errors-lint.exp @@ -0,0 +1,9 @@ +: undefined +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier 'Z' +: unknown-modifier +: end +exit status 2 diff --git a/unit-tests/cmd-errors-lint.mk b/unit-tests/cmd-errors-lint.mk new file mode 100644 index 000000000000..371e12af0f4f --- /dev/null +++ b/unit-tests/cmd-errors-lint.mk @@ -0,0 +1,32 @@ +# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed. + +.MAKEFLAGS: -dL + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} + +end: + : $@ diff --git a/unit-tests/cmd-errors.exp b/unit-tests/cmd-errors.exp new file mode 100644 index 000000000000..6d9c6bb7f890 --- /dev/null +++ b/unit-tests/cmd-errors.exp @@ -0,0 +1,9 @@ +: undefined eol +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier 'Z' +: unknown-modifier eol +: end eol +exit status 0 diff --git a/unit-tests/cmd-errors.mk b/unit-tests/cmd-errors.mk new file mode 100644 index 000000000000..5ad4be311873 --- /dev/null +++ b/unit-tests/cmd-errors.mk @@ -0,0 +1,30 @@ +# $NetBSD: cmd-errors.mk,v 1.3 2020/11/09 23:36:34 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed. + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} eol + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} eol + +end: + : $@ eol + +# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0. diff --git a/unit-tests/cmd-interrupt.mk b/unit-tests/cmd-interrupt.mk index 033f3307bd2e..fa0d85fc9063 100755 --- a/unit-tests/cmd-interrupt.mk +++ b/unit-tests/cmd-interrupt.mk @@ -1,4 +1,4 @@ -# $NetBSD: cmd-interrupt.mk,v 1.2 2020/08/28 18:16:22 rillig Exp $ +# $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Tests for interrupting a command. # @@ -22,7 +22,7 @@ all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-af clean-before clean-after: .PHONY @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious -interrupt-ordinary: .PHONY +interrupt-ordinary: @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok } diff --git a/unit-tests/cmdline-undefined.exp b/unit-tests/cmdline-undefined.exp new file mode 100644 index 000000000000..977ceee6dbf5 --- /dev/null +++ b/unit-tests/cmdline-undefined.exp @@ -0,0 +1,17 @@ +The = assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +The := assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +exit status 0 diff --git a/unit-tests/cmdline-undefined.mk b/unit-tests/cmdline-undefined.mk new file mode 100644 index 000000000000..5a3375cbbfb8 --- /dev/null +++ b/unit-tests/cmdline-undefined.mk @@ -0,0 +1,40 @@ +# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $ +# +# Tests for undefined variable expressions in the command line. + +all: + # When the command line is parsed, variable assignments using the + # '=' assignment operator do get their variable name expanded + # (which probably occurs rarely in practice, if at all), but their + # variable value is not expanded, as usual. + # + @echo 'The = assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE='Undefined is $${UNDEFINED}.' + @echo + + # The interesting case is using the ':=' assignment operator, which + # expands its right-hand side. But only those variables that are + # defined. + @echo 'The := assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE:='Undefined is $${UNDEFINED}.' + @echo + +.if make(print-undefined) + +.MAKEFLAGS: MAKEFLAGS_ASSIGN='Undefined is $${UNDEFINED}.' +.MAKEFLAGS: MAKEFLAGS_SUBST:='Undefined is $${UNDEFINED}.' + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +UNDEFINED?= now defined + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +print-undefined: +.endif diff --git a/unit-tests/cmdline.mk b/unit-tests/cmdline.mk index c12c31220cb5..cd88cead4558 100644 --- a/unit-tests/cmdline.mk +++ b/unit-tests/cmdline.mk @@ -1,8 +1,7 @@ -# $NetBSD: cmdline.mk,v 1.1 2020/07/28 22:44:44 rillig Exp $ +# $NetBSD: cmdline.mk,v 1.2 2020/11/15 14:07:53 rillig Exp $ # # Tests for command line parsing and related special variables. -RUN?= @set -eu; TMPBASE?= /tmp SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID @@ -14,14 +13,14 @@ all: prepare-dirs all: makeobjdir-direct makeobjdir-indirect prepare-dirs: - ${RUN} rm -rf ${DIR2} ${DIR12} - ${RUN} mkdir -p ${DIR2} ${DIR12} + @rm -rf ${DIR2} ${DIR12} + @mkdir -p ${DIR2} ${DIR12} # The .OBJDIR can be set via the MAKEOBJDIR command line variable. # It must be a command line variable; an environment variable would not work. makeobjdir-direct: @echo $@: - ${RUN} ${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir + @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir # The .OBJDIR can be set via the MAKEOBJDIR command line variable, # and that variable could even contain the usual modifiers. @@ -31,7 +30,7 @@ makeobjdir-direct: # see MAKE_CMD. makeobjdir-indirect: @echo $@: - ${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir + @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir show-objdir: @echo $@: ${.OBJDIR:Q} diff --git a/unit-tests/comment.mk b/unit-tests/comment.mk index 1cdcfcdd86ba..d4fb041104a7 100644 --- a/unit-tests/comment.mk +++ b/unit-tests/comment.mk @@ -1,4 +1,4 @@ -# $NetBSD: comment.mk,v 1.2 2020/09/07 19:17:36 rillig Exp $ +# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Demonstrate how comments are written in makefiles. @@ -12,7 +12,7 @@ that \ goes \ on and on. - # Comments can be indented, but that is rather unusual. + # Comments can be indented with spaces, but that is rather unusual. # Comments can be indented with a tab. # These are not shell commands, they are just makefile comments. @@ -21,6 +21,8 @@ on and on. .endif # And after the closing directive. VAR= # This comment makes the variable value empty. + # ParseGetLine removes any whitespace before the + # comment. .if ${VAR} != "" . error .endif @@ -35,7 +37,9 @@ VAR= value . error .endif -# This is NOT an escaped comment due to the double backslashes \\ +# This comment ends with 2 backslashes. An even number of backslashes does +# not count as a line continuation, therefore the variable assignment that +# follows is actively interpreted. \\ VAR= not part of the comment .if ${VAR} != "not part of the comment" . error @@ -55,7 +59,7 @@ WORDS= ${VAR:[#]} [# . error .endif -# An odd number of comment signs makes a line continuation, \\\ +# An odd number of backslashes makes a line continuation, \\\ no matter if it is 3 or 5 \\\\\ or 9 backslashes. \\\\\\\\\ This is the last line of the comment. diff --git a/unit-tests/cond-cmp-numeric-eq.exp b/unit-tests/cond-cmp-numeric-eq.exp index 1f12e858a66c..64e383ef32a2 100644 --- a/unit-tests/cond-cmp-numeric-eq.exp +++ b/unit-tests/cond-cmp-numeric-eq.exp @@ -1,6 +1,6 @@ -make: "cond-cmp-numeric-eq.mk" line 54: warning: Unknown operator -make: "cond-cmp-numeric-eq.mk" line 54: Malformed conditional (!(12345 = 12345)) -make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345)) +make: "cond-cmp-numeric-eq.mk" line 67: warning: Unknown operator +make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345)) +make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345)) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-cmp-numeric-eq.mk b/unit-tests/cond-cmp-numeric-eq.mk index 0e77d364ac94..c6b39876e75e 100755 --- a/unit-tests/cond-cmp-numeric-eq.mk +++ b/unit-tests/cond-cmp-numeric-eq.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-numeric-eq.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $ # # Tests for numeric comparisons with the == operator in .if conditions. @@ -49,6 +49,19 @@ . error .endif +# Because an IEEE 754 double can only hold integers with a mantissa of 53 +# bits, these two numbers are considered the same. The 993 is rounded down +# to the 992. +.if 9007199254740993 == 9007199254740992 +.else +. error +.endif +# The 995 is rounded up, the 997 is rounded down. +.if 9007199254740995 == 9007199254740997 +.else +. error Probably a misconfiguration in the floating point environment, \ + or maybe a machine without IEEE 754 floating point support. +.endif # There is no = operator for numbers. .if !(12345 = 12345) diff --git a/unit-tests/cond-cmp-numeric.exp b/unit-tests/cond-cmp-numeric.exp index 67d882e0c628..578d53228f6e 100644 --- a/unit-tests/cond-cmp-numeric.exp +++ b/unit-tests/cond-cmp-numeric.exp @@ -6,6 +6,10 @@ make: "cond-cmp-numeric.mk" line 16: warning: String comparison operator must be make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN) CondParser_Eval: !(${:UNaN} == NaN) lhs = "NaN", rhs = "NaN", op = == +CondParser_Eval: 123 ! 123 +lhs = 123.000000, rhs = 123.000000, op = ! +make: "cond-cmp-numeric.mk" line 34: warning: Unknown operator +make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-cmp-numeric.mk b/unit-tests/cond-cmp-numeric.mk index 67358ddaf86b..b1ec3e719d47 100644 --- a/unit-tests/cond-cmp-numeric.mk +++ b/unit-tests/cond-cmp-numeric.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-numeric.mk,v 1.3 2020/09/12 18:01:51 rillig Exp $ +# $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $ # # Tests for numeric comparisons in .if conditions. @@ -25,5 +25,17 @@ . error .endif +# The parsing code in CondParser_Comparison only performs a light check on +# whether the operator is valid, leaving the rest of the work to the +# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this +# parse error is properly reported. +# +# XXX: The warning message does not mention the actual operator. +.if 123 ! 123 +. error +.else +. error +.endif + all: @:; diff --git a/unit-tests/cond-cmp-string.exp b/unit-tests/cond-cmp-string.exp index 735b7cda4430..6e6c218267bc 100644 --- a/unit-tests/cond-cmp-string.exp +++ b/unit-tests/cond-cmp-string.exp @@ -1,8 +1,8 @@ make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str) -make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing") -make: "cond-cmp-string.mk" line 42: warning: String comparison operator must be either == or != -make: "cond-cmp-string.mk" line 42: Malformed conditional (!("value" = "value")) -make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" === "value")) +make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing") +make: "cond-cmp-string.mk" line 49: warning: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value")) +make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value")) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-cmp-string.mk b/unit-tests/cond-cmp-string.mk index 6af457925e97..6b5cba83bef7 100644 --- a/unit-tests/cond-cmp-string.mk +++ b/unit-tests/cond-cmp-string.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-string.mk,v 1.11 2020/10/30 14:53:31 rillig Exp $ +# $NetBSD: cond-cmp-string.mk,v 1.13 2020/11/15 14:07:53 rillig Exp $ # # Tests for string comparisons in .if conditions. @@ -19,9 +19,14 @@ . error .endif -# The left-hand side of the comparison requires a defined variable. -# The variable named "" is not defined, but applying the :U modifier to it -# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here. +# The left-hand side of the comparison requires that any variable expression +# is defined. +# +# The variable named "" is never defined, nevertheless it can be used as a +# starting point for variable expressions. Applying the :U modifier to such +# an undefined expression turns it into a defined expression. +# +# See ApplyModifier_Defined and VEF_DEF. .if ${:Ustr} != "str" . error .endif @@ -33,9 +38,11 @@ .endif # It is not possible to concatenate two string literals to form a single -# string. +# string. In C, Python and the shell this is possible, but not in make. .if "string" != "str""ing" . error +.else +. error .endif # There is no = operator for strings. @@ -88,3 +95,16 @@ .if ${:U word } != " ${:Uword} " . error .endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. +.if 12345 != "12345" +. error +.endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. The ".0" in the left-hand side makes the two +# sides of the equation unequal. +.if 12345.0 == "12345" +. error +.endif diff --git a/unit-tests/cond-cmp-unary.exp b/unit-tests/cond-cmp-unary.exp index 39a9383953dd..89f90dc1651f 100755 --- a/unit-tests/cond-cmp-unary.exp +++ b/unit-tests/cond-cmp-unary.exp @@ -1 +1,2 @@ +make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty. exit status 0 diff --git a/unit-tests/cond-cmp-unary.mk b/unit-tests/cond-cmp-unary.mk index 88ce79bf1a99..168de0f30e3f 100755 --- a/unit-tests/cond-cmp-unary.mk +++ b/unit-tests/cond-cmp-unary.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-unary.mk,v 1.1 2020/09/14 06:22:59 rillig Exp $ +# $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 rillig Exp $ # # Tests for unary comparisons in .if conditions, that is, comparisons with # a single operand. If the operand is a number, it is compared to zero, @@ -25,6 +25,9 @@ .endif # The empty string may come from a variable expression. +# +# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in +# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber. .if ${:U} . error .endif @@ -40,4 +43,16 @@ . error .endif +# A string of whitespace should evaluate to false. +# +# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip +# whitespace before testing for the end. This was probably an oversight in +# a commit from 1992-04-15 saying "A variable is empty when it just contains +# spaces". +.if ${:U } +. info This is only reached because of a bug in EvalNotEmpty. +.else +. error +.endif + all: # nothing diff --git a/unit-tests/cond-func-commands.mk b/unit-tests/cond-func-commands.mk index c6e1724c72f3..e127a8ebdc03 100644 --- a/unit-tests/cond-func-commands.mk +++ b/unit-tests/cond-func-commands.mk @@ -1,10 +1,11 @@ -# $NetBSD: cond-func-commands.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $ # # Tests for the commands() function in .if conditions. .MAIN: all -# The target "target" does not exist yet, therefore it cannot have commands. +# At this point, the target 'target' does not exist yet, therefore it cannot +# have commands. Sounds obvious, but good to know that it is really so. .if commands(target) . error .endif diff --git a/unit-tests/cond-func-defined.exp b/unit-tests/cond-func-defined.exp index 70c6342a02c3..caf66e39938f 100644 --- a/unit-tests/cond-func-defined.exp +++ b/unit-tests/cond-func-defined.exp @@ -1,5 +1,10 @@ make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined() make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B)) +make: "cond-func-defined.mk" line 33: warning: Missing closing parenthesis for defined() +make: "cond-func-defined.mk" line 33: Malformed conditional (defined(DEF) +make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are +make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable +make: "cond-func-defined.mk" line 47: involved, even if it feels like it. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-func-defined.mk b/unit-tests/cond-func-defined.mk index ec4feae05839..2aa49ccbf147 100644 --- a/unit-tests/cond-func-defined.mk +++ b/unit-tests/cond-func-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-defined.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $ # # Tests for the defined() function in .if conditions. @@ -29,5 +29,24 @@ ${:UA B}= variable name with spaces . error .endif +# Parse error: missing closing parenthesis; see ParseFuncArg. +.if defined(DEF +. error +.else +. error +.endif + +# Variables from .for loops are not defined. +# See directive-for.mk for more details. +.for var in value +. if defined(var) +. error +. else +. info In .for loops, variable expressions for the loop variables are +. info substituted at evaluation time. There is no actual variable +. info involved, even if it feels like it. +. endif +.endfor + all: @:; diff --git a/unit-tests/cond-func-empty.exp b/unit-tests/cond-func-empty.exp index 39a9383953dd..77a4edd47f49 100644 --- a/unit-tests/cond-func-empty.exp +++ b/unit-tests/cond-func-empty.exp @@ -1 +1,5 @@ -exit status 0 +make: "cond-func-empty.mk" line 152: Unclosed variable "WORD" +make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index f93b45895e6e..f43d99bf92c5 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,11 +1,10 @@ -# $NetBSD: cond-func-empty.mk,v 1.8 2020/09/23 08:11:28 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.10 2020/11/15 14:07:53 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests a variable # expression for emptiness. # # Note that the argument in the parentheses is indeed a variable name, -# optionally followed by variable modifiers. This is like the defined() -# function. +# optionally followed by variable modifiers. # .undef UNDEF @@ -25,13 +24,15 @@ WORD= word . error .endif -# The :S modifier replaces the empty value with an actual word, and -# after that the expression is no longer empty. Because the variable -# was undefined in the first place, the expression has the flag VAR_JUNK -# but not VAR_KEEP, therefore it is still considered undefined. -# Only very few variable modifiers turn an undefined variable expression -# into a defined variable expression. The :U and :D modifiers belong to -# that group, but :S doesn't (see VAR_KEEP). +# The :S modifier replaces the empty value with an actual word. The +# expression is now no longer empty, but it is still possible to see whether +# the expression was based on an undefined variable. The expression has the +# flag VEF_UNDEF. +# +# The expression does not have the flag VEF_DEF though, therefore it is still +# considered undefined. Yes, indeed, undefined but not empty. There are a +# few variable modifiers that turn an undefined expression into a defined +# expression, among them :U and :D, but not :S. # # XXX: This is hard to explain to someone who doesn't know these # implementation details. @@ -49,13 +50,14 @@ WORD= word .endif # And now to the surprising part. Applying the following :S modifier to the -# undefined variable makes it non-empty, but the marker VAR_JUNK is preserved -# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide -# whether the variable is defined or not. This kind of makes sense since the -# :U modifier tests the _variable_, not the _expression_. +# undefined expression makes it non-empty, but the marker VEF_UNDEF is +# preserved nevertheless. The :U modifier that follows only looks at the +# VEF_UNDEF flag to decide whether the variable is defined or not. This kind +# of makes sense since the :U modifier tests the _variable_, not the +# _expression_. # -# But since the variable was undefined to begin with, the fallback value is -# used in this expression. +# But since the variable was undefined to begin with, the fallback value from +# the :U modifier is used in this expression. # .if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" . error @@ -128,7 +130,7 @@ ${:U }= space # If everything goes well, the argument expands to "WORD", and that variable # is defined at the beginning of this file. The surrounding 'W' and 'D' # ensure that the parser in ParseEmptyArg has the correct position, both -# before and after the call to Var_ParsePP. +# before and after the call to Var_Parse. .if empty(W${:UOR}D) . error .endif @@ -146,5 +148,12 @@ ${:U WORD }= variable name with spaces . error .endif +# Parse error: missing closing parenthesis. +.if empty(WORD +. error +.else +. error +.endif + all: @:; diff --git a/unit-tests/cond-func.exp b/unit-tests/cond-func.exp index 0069ed75726c..73b6273d0b09 100644 --- a/unit-tests/cond-func.exp +++ b/unit-tests/cond-func.exp @@ -1,9 +1,15 @@ -make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 29: Malformed conditional (!defined(A B)) -make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B)) -make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined() -make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B)) +make: "cond-func.mk" line 36: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 36: Malformed conditional (!defined(A B)) +make: "cond-func.mk" line 51: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 51: Malformed conditional (!defined(A&B)) +make: "cond-func.mk" line 54: warning: Missing closing parenthesis for defined() +make: "cond-func.mk" line 54: Malformed conditional (!defined(A|B)) +make: "cond-func.mk" line 94: The empty variable is never defined. +make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 119: Symbols may start with a function name. +make: "cond-func.mk" line 124: Symbols may start with a function name. +make: "cond-func.mk" line 130: Malformed conditional (defined() make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-func.mk b/unit-tests/cond-func.mk index 8e7362182429..4ff43b72ef88 100644 --- a/unit-tests/cond-func.mk +++ b/unit-tests/cond-func.mk @@ -1,15 +1,22 @@ -# $NetBSD: cond-func.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. # # The below test uses the function defined(...) since it has no side-effects, -# the other functions (except empty(...)) would work equally well. +# the other functions (except empty(...)) would work equally well. The +# function empty is special because it uses a different parsing algorithm for +# its argument. DEF= defined ${:UA B}= variable name with spaces ${:UVAR(value)}= variable name with parentheses -${:UVAR{value}}= variable name with braces +${:UVAR{value}}= variable name with balanced braces + +# Really strange variable names must be given indirectly via another variable, +# so that no unbalanced braces appear in the top-level expression. +VARNAME_UNBALANCED_BRACES= VAR{{{value +${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces .if !defined(DEF) . error @@ -59,6 +66,12 @@ ${:UVAR{value}}= variable name with braces . error .endif +# Braces do not have any special meaning when parsing arguments. +# They don't need to be balanced. +.if !defined(VAR{{{value) +. error +.endif + # There may be spaces around the operators and parentheses, and even # inside the parentheses. The spaces inside the parentheses are not # allowed for the empty() function (see cond-func-empty.mk), therefore @@ -67,5 +80,58 @@ ${:UVAR{value}}= variable name with braces . error .endif +# The following condition is interpreted as defined(A) && defined(B). +# In lack of a function call expression, each kind of .if directive has a +# default function that is called when a bare word is parsed. For the plain +# .if directive, this function is defined(); see "struct If ifs" in cond.c. +.if A&B +. error +.endif + +.if defined() +. error +.else +. info The empty variable is never defined. +.endif + +# The plain word 'defined' is interpreted as '!empty(defined)'. +# That variable is not defined (yet). +.if defined +. error +.else +. info A plain function name is parsed as !empty(...). +.endif + +# If a variable named 'defined' is actually defined and not empty, the plain +# symbol 'defined' evaluates to true. +defined= non-empty +.if defined +. info A plain function name is parsed as !empty(...). +.else +. error +.endif + +# A plain symbol name may start with one of the function names, in this case +# 'defined'. +.if defined-var +. error +.else +. info Symbols may start with a function name. +.endif + +defined-var= non-empty +.if defined-var +. info Symbols may start with a function name. +.else +. error +.endif + +# Missing closing parenthesis when parsing the function argument. +.if defined( +. error +.else +. error +.endif + all: @:; diff --git a/unit-tests/cond-late.mk b/unit-tests/cond-late.mk index 397f5febd480..4df3df2cf1d4 100644 --- a/unit-tests/cond-late.mk +++ b/unit-tests/cond-late.mk @@ -1,7 +1,9 @@ -# $NetBSD: cond-late.mk,v 1.2 2020/07/25 20:37:46 rillig Exp $ +# $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ # # Using the :? modifier, variable expressions can contain conditional -# expressions that are evaluated late. Any variables appearing in these +# expressions that are evaluated late, at expansion time. +# +# Any variables appearing in these # conditions are expanded before parsing the condition. This is # different from many other places. # @@ -11,15 +13,15 @@ # They should also not contain operators like == or <, since these are # actually interpreted as these operators. This is demonstrated below. # -# If the order of evaluation were to change to first parse the condition -# and then expand the variables, the output would change from the -# current "yes no" to "yes yes", since both variables are non-empty. all: cond-literal COND.true= "yes" == "yes" COND.false= "yes" != "yes" +# If the order of evaluation were to change to first parse the condition +# and then expand the variables, the output would change from the +# current "yes no" to "yes yes", since both variables are non-empty. cond-literal: @echo ${ ${COND.true} :?yes:no} @echo ${ ${COND.false} :?yes:no} diff --git a/unit-tests/cond-op-and-lint.exp b/unit-tests/cond-op-and-lint.exp new file mode 100644 index 000000000000..8817fd0d658b --- /dev/null +++ b/unit-tests/cond-op-and-lint.exp @@ -0,0 +1,4 @@ +make: "cond-op-and-lint.mk" line 9: Unknown operator '&' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-op-and-lint.mk b/unit-tests/cond-op-and-lint.mk new file mode 100644 index 000000000000..6262339016f5 --- /dev/null +++ b/unit-tests/cond-op-and-lint.mk @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the && operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '&' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 & 0 +. error +.else +. error +.endif diff --git a/unit-tests/cond-op-not.exp b/unit-tests/cond-op-not.exp index 39a9383953dd..37f57b7fdfa7 100644 --- a/unit-tests/cond-op-not.exp +++ b/unit-tests/cond-op-not.exp @@ -1 +1,6 @@ +make: "cond-op-not.mk" line 29: Not empty evaluates to true. +make: "cond-op-not.mk" line 37: Not space evaluates to false. +make: "cond-op-not.mk" line 41: Not 0 evaluates to true. +make: "cond-op-not.mk" line 49: Not 1 evaluates to false. +make: "cond-op-not.mk" line 55: Not word evaluates to false. exit status 0 diff --git a/unit-tests/cond-op-not.mk b/unit-tests/cond-op-not.mk index d929318785a5..388c62d8898f 100644 --- a/unit-tests/cond-op-not.mk +++ b/unit-tests/cond-op-not.mk @@ -1,6 +1,6 @@ -# $NetBSD: cond-op-not.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-op-not.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $ # -# Tests for the ! operator in .if conditions. +# Tests for the ! operator in .if conditions, which negates its argument. # The exclamation mark negates its operand. .if !1 @@ -18,5 +18,42 @@ . error .endif +# The operator '==' binds more tightly than '!'. +# This is unusual since most other programming languages define the precedence +# to be the other way round. +.if !${:Uexpression} == "expression" +. error +.endif + +.if !${:U} +. info Not empty evaluates to true. +.else +. info Not empty evaluates to false. +.endif + +.if !${:U } +. info Not space evaluates to true. +.else +. info Not space evaluates to false. +.endif + +.if !${:U0} +. info Not 0 evaluates to true. +.else +. info Not 0 evaluates to false. +.endif + +.if !${:U1} +. info Not 1 evaluates to true. +.else +. info Not 1 evaluates to false. +.endif + +.if !${:Uword} +. info Not word evaluates to true. +.else +. info Not word evaluates to false. +.endif + all: @:; diff --git a/unit-tests/cond-op-or-lint.exp b/unit-tests/cond-op-or-lint.exp new file mode 100644 index 000000000000..8abae99b6c4c --- /dev/null +++ b/unit-tests/cond-op-or-lint.exp @@ -0,0 +1,4 @@ +make: "cond-op-or-lint.mk" line 9: Unknown operator '|' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-op-or-lint.mk b/unit-tests/cond-op-or-lint.mk new file mode 100644 index 000000000000..aa29e9a6c2f2 --- /dev/null +++ b/unit-tests/cond-op-or-lint.mk @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the || operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '|' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 | 0 +. error +.else +. error +.endif diff --git a/unit-tests/cond-op-parentheses.exp b/unit-tests/cond-op-parentheses.exp index 39a9383953dd..a0fa137af4ca 100644 --- a/unit-tests/cond-op-parentheses.exp +++ b/unit-tests/cond-op-parentheses.exp @@ -1 +1,2 @@ +make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112. exit status 0 diff --git a/unit-tests/cond-op-parentheses.mk b/unit-tests/cond-op-parentheses.mk index 6c48d83dd2be..39ebc1607455 100644 --- a/unit-tests/cond-op-parentheses.mk +++ b/unit-tests/cond-op-parentheses.mk @@ -1,8 +1,19 @@ -# $NetBSD: cond-op-parentheses.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-op-parentheses.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ # # Tests for parentheses in .if conditions. # TODO: Implementation +# Test for deeply nested conditions. +.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + 1 \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) +. info Parentheses can be nested at least to depth 112. +.else +. error +.endif + all: @:; diff --git a/unit-tests/cond-op.exp b/unit-tests/cond-op.exp index fd5bc20f673c..1a66e5ffeabd 100644 --- a/unit-tests/cond-op.exp +++ b/unit-tests/cond-op.exp @@ -1,7 +1,16 @@ -make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word) -make: "cond-op.mk" line 70: Malformed conditional (0 ${ERR::=evaluated}) -make: "cond-op.mk" line 74: warning: After detecting a parse error, the rest is evaluated. -make: "cond-op.mk" line 78: Parsing continues until here. +make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word) +make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated}) +make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated. +make: "cond-op.mk" line 83: Parsing continues until here. +make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C) +make: "cond-op.mk" line 93: 0 0 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 0 1 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 0 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 0 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 1 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 1 1 => 1 1 1 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-op.mk b/unit-tests/cond-op.mk index 71c4f7b66441..170355f6c0ad 100644 --- a/unit-tests/cond-op.mk +++ b/unit-tests/cond-op.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-op.mk,v 1.8 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-op.mk,v 1.10 2020/11/15 14:58:14 rillig Exp $ # # Tests for operators like &&, ||, ! in .if conditions. # @@ -9,8 +9,8 @@ # cond-op-parentheses.mk # In make, && binds more tightly than ||, like in C. -# If make had the same precedence for both && and ||, the result would be -# different. +# If make had the same precedence for both && and ||, like in the shell, +# the result would be different. # If || were to bind more tightly than &&, the result would be different # as well. .if !(1 || 1 && 0) @@ -18,13 +18,17 @@ .endif # If make were to interpret the && and || operators like the shell, the -# implicit binding would be this: +# previous condition would be interpreted as: .if (1 || 1) && 0 . error .endif # The precedence of the ! operator is different from C though. It has a -# lower precedence than the comparison operators. +# lower precedence than the comparison operators. Negating a condition +# does not need parentheses. +# +# This kind of condition looks so unfamiliar that it doesn't occur in +# practice. .if !"word" == "word" . error .endif @@ -36,7 +40,8 @@ # TODO: Demonstrate that the precedence of the ! and == operators actually # makes a difference. There is a simple example for sure, I just cannot -# wrap my head around it. +# wrap my head around it right now. See the truth table generator below +# for an example that doesn't require much thought. # This condition is malformed because the '!' on the right-hand side must not # appear unquoted. If any, it must be enclosed in quotes. @@ -71,11 +76,27 @@ . error .endif .if ${ERR:Uundefined} == evaluated -. warning After detecting a parse error, the rest is evaluated. +. info After detecting a parse error, the rest is evaluated. .endif # Just in case that parsing should ever stop on the first error. .info Parsing continues until here. +# Demonstration that '&&' has higher precedence than '||'. +.info A B C => (A || B) && C A || B && C A || (B && C) +.for a in 0 1 +. for b in 0 1 +. for c in 0 1 +. for r1 in ${ ($a || $b) && $c :?1:0} +. for r2 in ${ $a || $b && $c :?1:0} +. for r3 in ${ $a || ($b && $c) :?1:0} +. info $a $b $c => ${r1} ${r2} ${r3} +. endfor +. endfor +. endfor +. endfor +. endfor +.endfor + all: @:; diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk index 9e3b0575eb78..077684be33fc 100644 --- a/unit-tests/cond-short.mk +++ b/unit-tests/cond-short.mk @@ -1,11 +1,14 @@ -# $NetBSD: cond-short.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.12 2020/11/15 14:58:14 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. +# This is called 'short-circuit evaluation' and is the usual evaluation +# mode in most programming languages. A notable exception is Ada, which +# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'. # # Between 2015-10-11 and 2020-06-28, the right-hand side of an && or || # operator was always evaluated, which was wrong. -# +# TODO: Had the evaluation been correct at some time before 2015-11-12? # The && operator. @@ -113,6 +116,9 @@ VAR= # empty again, for the following tests # make sure these do not cause complaint #.MAKEFLAGS: -dc +# TODO: Rewrite this whole section and check all the conditions and variables. +# Several of the assumptions are probably wrong here. +# TODO: replace 'x=' with '.info' or '.error'. V42= 42 iV1= ${V42} iV2= ${V66} @@ -167,5 +173,16 @@ x= Fail .endif x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo +# TODO: Has this always worked? There may have been a time, maybe around +# 2000, when make would complain about the "Malformed conditional" because +# UNDEF is not defined. +.if defined(UNDEF) && ${UNDEF} != "undefined" +. error +.endif + +# TODO: Test each modifier to make sure it is skipped when it is irrelevant +# for the result. Since this test is already quite long, do that in another +# test. + all: @:;: diff --git a/unit-tests/cond-token-number.exp b/unit-tests/cond-token-number.exp index ea1f92797ff4..b5bfaf95d575 100644 --- a/unit-tests/cond-token-number.exp +++ b/unit-tests/cond-token-number.exp @@ -1,8 +1,8 @@ -make: "cond-token-number.mk" line 13: Malformed conditional (-0) -make: "cond-token-number.mk" line 21: Malformed conditional (+0) -make: "cond-token-number.mk" line 29: Malformed conditional (!-1) -make: "cond-token-number.mk" line 37: Malformed conditional (!+1) -make: "cond-token-number.mk" line 54: End of the tests. +make: "cond-token-number.mk" line 15: Malformed conditional (-0) +make: "cond-token-number.mk" line 25: Malformed conditional (+0) +make: "cond-token-number.mk" line 35: Malformed conditional (!-1) +make: "cond-token-number.mk" line 45: Malformed conditional (!+1) +make: "cond-token-number.mk" line 80: End of the tests. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-token-number.mk b/unit-tests/cond-token-number.mk index 4171a07d56c6..93e2646a60eb 100644 --- a/unit-tests/cond-token-number.mk +++ b/unit-tests/cond-token-number.mk @@ -1,6 +1,8 @@ -# $NetBSD: cond-token-number.mk,v 1.3 2020/09/14 06:22:59 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ # # Tests for number tokens in .if conditions. +# +# TODO: Add introduction. .if 0 . error @@ -12,6 +14,8 @@ # See the ch_isdigit call in CondParser_String. .if -0 . error +.else +. error .endif # Even though +0 is a number and would be accepted by strtod, it is not @@ -20,6 +24,8 @@ # See the ch_isdigit call in CondParser_String. .if +0 . error +.else +. error .endif # Even though -1 is a number and would be accepted by strtod, it is not @@ -28,6 +34,8 @@ # See the ch_isdigit call in CondParser_String. .if !-1 . error +.else +. error .endif # Even though +1 is a number and would be accepted by strtod, it is not @@ -36,6 +44,8 @@ # See the ch_isdigit call in CondParser_String. .if !+1 . error +.else +. error .endif # When the number comes from a variable expression though, it may be signed. @@ -50,6 +60,22 @@ . error .endif +# Hexadecimal numbers are accepted. +.if 0x0 +. error +.endif +.if 0x1 +.else +. error +.endif + +# This is not a hexadecimal number, even though it has an x. +# It is interpreted as a string instead, effectively meaning defined(3x4). +.if 3x4 +.else +. error +.endif + # Ensure that parsing continues until here. .info End of the tests. diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk index ba9934f5b882..a5ffa37a5c84 100644 --- a/unit-tests/cond-token-plain.mk +++ b/unit-tests/cond-token-plain.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-plain.mk,v 1.4 2020/09/12 17:47:24 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. @@ -14,7 +14,7 @@ # parser gets to see it. # # XXX: The error message is missing for this malformed condition. -# The right-hand side of the comparison is just a '"'. +# The right-hand side of the comparison is just a '"', before unescaping. .if ${:U} != "#hash" . error .endif @@ -31,16 +31,19 @@ # comment handling anymore. The comments are supposed to be stripped off # in a very early parsing phase. # +# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the +# original problems. This workaround is probably not needed anymore. +# # XXX: Missing error message for the malformed condition. The right-hand -# side is double-quotes, backslash, backslash. -# XXX: It is unexpected that the right-hand side evaluates to a single -# backslash. +# side before unescaping is double-quotes, backslash, backslash. .if ${:U\\} != "\\#hash" . error .endif # The right-hand side of a comparison is not parsed as a token, therefore # the code from CondParser_Token does not apply to it. +# TODO: Explain the consequences. +# TODO: Does this mean that more syntactic variants are allowed here? .if ${:U\#hash} != \#hash . error .endif diff --git a/unit-tests/cond-token-string.exp b/unit-tests/cond-token-string.exp index 39a9383953dd..5df4cc675bb1 100644 --- a/unit-tests/cond-token-string.exp +++ b/unit-tests/cond-token-string.exp @@ -1 +1,8 @@ -exit status 0 +make: Unknown modifier 'Z' +make: "cond-token-string.mk" line 9: Malformed conditional ("" != "${:Uvalue:Z}") +make: "cond-token-string.mk" line 18: xvalue is not defined. +make: "cond-token-string.mk" line 24: Malformed conditional (x${:Uvalue} == "") +make: "cond-token-string.mk" line 33: Expected. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/cond-token-string.mk b/unit-tests/cond-token-string.mk index 1a8019754824..7e110806d408 100644 --- a/unit-tests/cond-token-string.mk +++ b/unit-tests/cond-token-string.mk @@ -1,8 +1,39 @@ -# $NetBSD: cond-token-string.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: cond-token-string.mk,v 1.3 2020/11/10 22:23:37 rillig Exp $ # # Tests for quoted and unquoted string literals in .if conditions. # TODO: Implementation +# Cover the code in CondParser_String that frees the memory after parsing +# a variable expression based on an undefined variable. +.if "" != "${:Uvalue:Z}" +. error +.else +. error +.endif + +.if x${:Uvalue} +. error +.else +. info xvalue is not defined. +.endif + +# The 'x' produces a "Malformed conditional" since the left-hand side of a +# comparison in an .if directive must be either a variable expression, a +# quoted string literal or a number that starts with a digit. +.if x${:Uvalue} == "" +. error +.else +. error +.endif + +# In plain words, a '\' can be used to escape any character, just as in +# double-quoted string literals. See CondParser_String. +.if \x${:Uvalue} == "xvalue" +. info Expected. +.else +. error +.endif + all: @:; diff --git a/unit-tests/cond-token-var.exp b/unit-tests/cond-token-var.exp index eb71a43c55f3..fcd92d12a3da 100644 --- a/unit-tests/cond-token-var.exp +++ b/unit-tests/cond-token-var.exp @@ -1,7 +1,7 @@ -make: "cond-token-var.mk" line 9: ok -make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF}) -make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF}) -make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF}) +make: "cond-token-var.mk" line 20: ok +make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF}) +make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF}) +make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-token-var.mk b/unit-tests/cond-token-var.mk index 6e55bd2d552d..30eba87ad4d2 100644 --- a/unit-tests/cond-token-var.mk +++ b/unit-tests/cond-token-var.mk @@ -1,6 +1,17 @@ -# $NetBSD: cond-token-var.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ # -# Tests for variables in .if conditions. +# Tests for variable expressions in .if conditions. +# +# Note the fine distinction between a variable and a variable expression. +# A variable has a name and a value. To access the value, one writes a +# variable expression of the form ${VAR}. This is a simple variable +# expression. Variable expressions can get more complicated by adding +# variable modifiers such as in ${VAR:Mpattern}. +# +# XXX: Strictly speaking, variable modifiers should be called expression +# modifiers instead since they only modify the expression, not the variable. +# Well, except for the assignment modifiers, these do indeed change the value +# of the variable. DEF= defined @@ -12,11 +23,13 @@ DEF= defined .endif # A variable that appears on the left-hand side must be defined. +# The following line thus generates a parse error. .if ${UNDEF} == ${DEF} . error .endif # A variable that appears on the right-hand side must be defined. +# The following line thus generates a parse error. .if ${DEF} == ${UNDEF} . error .endif @@ -25,10 +38,11 @@ DEF= defined .if ${DEF} .endif -# An undefined variable generates a warning. +# An undefined variable on its own generates a parse error. .if ${UNDEF} .endif -# The :U modifier turns an undefined variable into an ordinary expression. +# The :U modifier turns an undefined expression into a defined expression. +# Since the expression is defined now, it doesn't generate any parse error. .if ${UNDEF:U} .endif diff --git a/unit-tests/cond-undef-lint.exp b/unit-tests/cond-undef-lint.exp index 365edae61275..2c4feb0376ff 100755 --- a/unit-tests/cond-undef-lint.exp +++ b/unit-tests/cond-undef-lint.exp @@ -1,7 +1,7 @@ make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined -make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined +make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-undef-lint.mk b/unit-tests/cond-undef-lint.mk index 334ceb594054..9dfd1bd53252 100755 --- a/unit-tests/cond-undef-lint.mk +++ b/unit-tests/cond-undef-lint.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-undef-lint.mk,v 1.2 2020/09/14 07:13:29 rillig Exp $ +# $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ # # Tests for defined and undefined variables in .if conditions, in lint mode. # @@ -42,6 +42,10 @@ DEF= defined .endif # The variable VAR.defined is not defined and thus generates an error message. +# +# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least +# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect +# expression, as in the next example? .if ${VAR.${DEF}} . error .else diff --git a/unit-tests/cond1.exp b/unit-tests/cond1.exp index 38d616babdfb..b9db035833be 100644 --- a/unit-tests/cond1.exp +++ b/unit-tests/cond1.exp @@ -1,5 +1,5 @@ -make: "cond1.mk" line 75: warning: extra else -make: "cond1.mk" line 85: warning: extra else +make: "cond1.mk" line 80: warning: extra else +make: "cond1.mk" line 90: warning: extra else 2 is prime A='other' B='unknown' C='clever' o='no,no' Passed: diff --git a/unit-tests/cond1.mk b/unit-tests/cond1.mk index eedff8c1530b..53908c2dacf1 100644 --- a/unit-tests/cond1.mk +++ b/unit-tests/cond1.mk @@ -1,4 +1,9 @@ -# $NetBSD: cond1.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ + +# TODO: Convert these tests into tutorial form. +# TODO: Split these tests by topic. +# TODO: Use better variable names and expression values that actually express +# the intended behavior. uname(1) has nothing to do with conditions. # hard code these! TEST_UNAME_S= NetBSD diff --git a/unit-tests/dep-double-colon.mk b/unit-tests/dep-double-colon.mk index 67a28a4315cd..70fddbcf21a4 100644 --- a/unit-tests/dep-double-colon.mk +++ b/unit-tests/dep-double-colon.mk @@ -1,6 +1,9 @@ -# $NetBSD: dep-double-colon.mk,v 1.4 2020/09/26 15:41:53 rillig Exp $ +# $NetBSD: dep-double-colon.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the :: operator in dependency declarations. +# Tests for the '::' operator in dependency declarations, which allows +# several dependency groups for a single node, each having its own attributes +# and dependencies. In the code, the additional dependency groups are called +# cohorts. all:: @echo 'command 1a' diff --git a/unit-tests/dep-exclam.mk b/unit-tests/dep-exclam.mk index 2779a66ea6e3..5f6b72da3fe0 100644 --- a/unit-tests/dep-exclam.mk +++ b/unit-tests/dep-exclam.mk @@ -1,6 +1,11 @@ -# $NetBSD: dep-exclam.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: dep-exclam.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the ! operator in dependency declarations. +# Tests for the ! operator in dependency declarations, which always re-creates +# the target, whether or not it is out of date. +# +# TODO: Is this related to OP_PHONY? +# TODO: Is this related to OP_EXEC? +# TODO: Is this related to OP_MAKE? # TODO: Implementation diff --git a/unit-tests/depsrc-ignore.mk b/unit-tests/depsrc-ignore.mk index 1be3eabe8806..aea7d24941ab 100644 --- a/unit-tests/depsrc-ignore.mk +++ b/unit-tests/depsrc-ignore.mk @@ -1,11 +1,10 @@ -# $NetBSD: depsrc-ignore.mk,v 1.4 2020/08/29 16:13:27 rillig Exp $ +# $NetBSD: depsrc-ignore.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .IGNORE in dependency declarations, # which ignores any command failures for that target. # -# Even though ignore-errors fails, the all target is still made. -# Since the all target is not marked with .IGNORE, it stops at the -# first failing command. +# Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is +# not marked with .IGNORE, it stops at the first failing command. # # XXX: The ordering of the messages in the output is confusing. # The "ignored" comes much too late to be related to the "false @@ -24,8 +23,8 @@ # This is what actually happens, as of 2020-08-29. To verify it, set the # following breakpoints in CompatRunCommand: # -# * the "!silent" line, to see all commands. -# * the "fflush" line, to see stdout being flushed. +# * the "!silent" line, to see all commands +# * the "fflush" line, to see stdout being flushed # * the "status = WEXITSTATUS" line # * the "(continuing)" line # * the "(ignored)" line diff --git a/unit-tests/depsrc-make.mk b/unit-tests/depsrc-make.mk index f7b5a9cca8ff..8069007e6e11 100644 --- a/unit-tests/depsrc-make.mk +++ b/unit-tests/depsrc-make.mk @@ -1,9 +1,11 @@ -# $NetBSD: depsrc-make.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# $NetBSD: depsrc-make.mk,v 1.4 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .MAKE in dependency declarations, which # executes the commands of the target even if the -n or -t command line # options are given. +# TODO: Add a test for the -t command line option. + .MAKEFLAGS: -n all: this-is-made diff --git a/unit-tests/depsrc-optional.exp b/unit-tests/depsrc-optional.exp index 05f2ac6624c4..fce85b3cb38e 100644 --- a/unit-tests/depsrc-optional.exp +++ b/unit-tests/depsrc-optional.exp @@ -1,2 +1,20 @@ -`all' is up to date. +Make_ExpandUse: examine all +ExamineLater: need to examine "important" +Make_ExpandUse: examine important +ExamineLater: need to examine "optional" +ExamineLater: need to examine "optional-cohort" +Make_ExpandUse: examine optional +Make_ExpandUse: examine optional-cohort +Examining optional...non-existent...up-to-date. +Examining optional-cohort...non-existent...:: operator and no sources...out-of-date. +: A leaf node using '::' is considered out-of-date. + recheck(optional-cohort): update time from 0:00:00 Jan 01, 1970 to now +Examining important...non-existent...modified before source "optional-cohort"...out-of-date. +: important is made. + recheck(important): update time from 0:00:00 Jan 01, 1970 to now +Examining all...non-existent...modified before source "important"...out-of-date. +: all is made. + recheck(all): update time from 0:00:00 Jan 01, 1970 to now +Examining .END...non-existent...non-existent and no sources...out-of-date. + recheck(.END): update time from 0:00:00 Jan 01, 1970 to now exit status 0 diff --git a/unit-tests/depsrc-optional.mk b/unit-tests/depsrc-optional.mk index 75ae38bf3194..f12eeca2b3cb 100644 --- a/unit-tests/depsrc-optional.mk +++ b/unit-tests/depsrc-optional.mk @@ -1,18 +1,21 @@ -# $NetBSD: depsrc-optional.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# $NetBSD: depsrc-optional.mk,v 1.5 2020/11/08 10:33:47 rillig Exp $ # # Tests for the special source .OPTIONAL in dependency declarations, # which ignores the target if make cannot find out how to create it. # # TODO: Describe practical use cases for this feature. -# TODO: Explain why the commands for "important" are not executed. -# I had thought that only the "optional" commands were skipped. - all: important : ${.TARGET} is made. -important: optional +important: optional optional-cohort : ${.TARGET} is made. optional: .OPTIONAL - : This is not executed. + : An optional leaf node is not executed. + +# See IsOODateRegular. +optional-cohort:: .OPTIONAL + : A leaf node using '::' is considered out-of-date. + +.MAKEFLAGS: -dm diff --git a/unit-tests/depsrc-precious.mk b/unit-tests/depsrc-precious.mk index 699b83d767b1..e8522a300790 100644 --- a/unit-tests/depsrc-precious.mk +++ b/unit-tests/depsrc-precious.mk @@ -1,6 +1,14 @@ -# $NetBSD: depsrc-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc-precious.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special source .PRECIOUS in dependency declarations. +# Tests for the special source .PRECIOUS in dependency declarations, which +# is only relevant if the commands for the target fail or are interrupted. +# In such a case, the target file is usually removed, to avoid having +# half-finished files with a timestamp suggesting the file were up-to-date. +# +# For targets marked with .PRECIOUS, the target file is not removed. +# The author of the makefile is then responsible for avoiding the above +# situation, in which the target would be wrongly considered up-to-date, +# just because its timestamp says so. # TODO: Implementation diff --git a/unit-tests/depsrc-usebefore.mk b/unit-tests/depsrc-usebefore.mk index c6be2bae0a9c..001cfb0d71c1 100644 --- a/unit-tests/depsrc-usebefore.mk +++ b/unit-tests/depsrc-usebefore.mk @@ -1,7 +1,11 @@ -# $NetBSD: depsrc-usebefore.mk,v 1.5 2020/08/22 11:53:18 rillig Exp $ +# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special source .USEBEFORE in dependency declarations, # which allows to prepend common commands to other targets. +# +# See also: +# .USE +# depsrc-use.mk all: action directly diff --git a/unit-tests/depsrc.mk b/unit-tests/depsrc.mk index d461e1111d0f..15b27286de22 100644 --- a/unit-tests/depsrc.mk +++ b/unit-tests/depsrc.mk @@ -1,9 +1,11 @@ -# $NetBSD: depsrc.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: depsrc.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for special sources (those starting with a dot, followed by # uppercase letters) in dependency declarations, such as .PHONY. # TODO: Implementation +# TODO: Test 'target: ${:U.SILENT}' + all: @:; diff --git a/unit-tests/deptgt-begin.exp b/unit-tests/deptgt-begin.exp index 527020f6818d..abc80afe9964 100644 --- a/unit-tests/deptgt-begin.exp +++ b/unit-tests/deptgt-begin.exp @@ -1,4 +1,7 @@ +make: "deptgt-begin.mk" line 17: warning: duplicate script for target ".BEGIN" ignored +make: "deptgt-begin.mk" line 8: warning: using previous script for ".BEGIN" defined here : parse time +: Making before-begin before .BEGIN. : .BEGIN : all exit status 0 diff --git a/unit-tests/deptgt-begin.mk b/unit-tests/deptgt-begin.mk index c6ca2f4aa3c7..b71d78f371ed 100644 --- a/unit-tests/deptgt-begin.mk +++ b/unit-tests/deptgt-begin.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt-begin.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $ +# $NetBSD: deptgt-begin.mk,v 1.5 2020/11/15 22:28:08 rillig Exp $ # # Tests for the special target .BEGIN in dependency declarations, # which is a container for commands that are run before any other @@ -7,6 +7,40 @@ .BEGIN: : $@ +# To register a custom action to be run at the beginning, the simplest way is +# to directly place some commands on the '.BEGIN' target. This doesn't scale +# though, since the ':' dependency operator prevents that any other place may +# add its commands after this. +# +# There are several ways to resolve this situation, which are detailed below. +.BEGIN: + : Making another $@. + +# One way to run commands at the beginning is to define a custom target and +# make the .BEGIN depend on that target. This way, the commands from the +# custom target are run even before the .BEGIN target. +.BEGIN: before-begin +before-begin: .PHONY .NOTMAIN + : Making $@ before .BEGIN. + +# Another way is to define a custom target and make that a .USE dependency. +# For the .BEGIN target, .USE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use +use: .USE .NOTMAIN + : Making $@ from a .USE dependency. + +# Same as with .USE, but run the commands before the main commands from the +# .BEGIN target. +# +# For the .BEGIN target, .USEBEFORE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use-before +use-before: .USEBEFORE .NOTMAIN + : Making $@ from a .USEBEFORE dependency. + all: : $@ diff --git a/unit-tests/deptgt-error.mk b/unit-tests/deptgt-error.mk index 07bc1b5d3408..5d515b95afc3 100644 --- a/unit-tests/deptgt-error.mk +++ b/unit-tests/deptgt-error.mk @@ -1,6 +1,7 @@ -# $NetBSD: deptgt-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .ERROR in dependency declarations. +# Tests for the special target .ERROR in dependency declarations, which +# collects commands that are run when another target fails. # TODO: Implementation diff --git a/unit-tests/deptgt-ignore.mk b/unit-tests/deptgt-ignore.mk index 6ace0841f28b..49c14d2cfd43 100644 --- a/unit-tests/deptgt-ignore.mk +++ b/unit-tests/deptgt-ignore.mk @@ -1,6 +1,7 @@ -# $NetBSD: deptgt-ignore.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .IGNORE in dependency declarations. +# Tests for the special target .IGNORE in dependency declarations, which +# does not stop if a command from this target exits with a non-zero status. # TODO: Implementation diff --git a/unit-tests/deptgt-interrupt.mk b/unit-tests/deptgt-interrupt.mk index d555f864563e..d94009a52e05 100644 --- a/unit-tests/deptgt-interrupt.mk +++ b/unit-tests/deptgt-interrupt.mk @@ -1,6 +1,8 @@ -# $NetBSD: deptgt-interrupt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .INTERRUPT in dependency declarations. +# Tests for the special target .INTERRUPT in dependency declarations, which +# collects commands to be run when make is interrupted while building another +# target. # TODO: Implementation diff --git a/unit-tests/deptgt-main.mk b/unit-tests/deptgt-main.mk index cf1b1b4fd340..84d05dc25ed6 100644 --- a/unit-tests/deptgt-main.mk +++ b/unit-tests/deptgt-main.mk @@ -1,6 +1,8 @@ -# $NetBSD: deptgt-main.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # -# Tests for the special target .MAIN in dependency declarations. +# Tests for the special target .MAIN in dependency declarations, which defines +# the main target. This main target is built if no target has been specified +# on the command line or via MAKEFLAGS. # TODO: Implementation diff --git a/unit-tests/deptgt-makeflags.exp b/unit-tests/deptgt-makeflags.exp index a226cafc9ba1..7eb54eba7f30 100644 --- a/unit-tests/deptgt-makeflags.exp +++ b/unit-tests/deptgt-makeflags.exp @@ -6,4 +6,5 @@ Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES lhs = "$$", rhs = "$$", op = != Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0 +make: Unterminated quoted string [make VAR=initial UNBALANCED='] exit status 0 diff --git a/unit-tests/deptgt-makeflags.mk b/unit-tests/deptgt-makeflags.mk index 70a9cd0b590b..0a0f410e14c4 100644 --- a/unit-tests/deptgt-makeflags.mk +++ b/unit-tests/deptgt-makeflags.mk @@ -1,30 +1,35 @@ -# $NetBSD: deptgt-makeflags.mk,v 1.4 2020/10/23 14:48:49 rillig Exp $ +# $NetBSD: deptgt-makeflags.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the special target .MAKEFLAGS in dependency declarations, # which adds command line options later, at parse time. +# +# In these unit tests, it is often used to temporarily toggle the debug log +# during parsing. # The -D option sets a variable in the "Global" scope and thus can be # undefined later. .MAKEFLAGS: -D VAR - .if ${VAR} != 1 . error .endif +# Variables that are set via the -D command line option are normal global +# variables and can thus be undefined later. .undef VAR - .if defined(VAR) . error .endif +# The -D command line option can define a variable again, after it has been +# undefined. .MAKEFLAGS: -D VAR - .if ${VAR} != 1 . error .endif +# The "dependency" for .MAKEFLAGS is split into words, interpreting the usual +# quotes and escape sequences from the backslash. .MAKEFLAGS: VAR="value"' with'\ spaces - .if ${VAR} != "value with spaces" . error .endif @@ -32,7 +37,6 @@ # Variables set on the command line as VAR=value are placed in the # "Command" scope and thus cannot be undefined. .undef VAR - .if ${VAR} != "value with spaces" . error .endif @@ -47,5 +51,43 @@ .endif .MAKEFLAGS: -d0 +# An empty command line is skipped. +.MAKEFLAGS: # none + +# Escape sequences like \n are interpreted. +# The following line looks as if it assigned a newline to nl, but it doesn't. +# Instead, the \n ends up as a line that is then interpreted as a variable +# assignment. At that point, the line is simply "nl=\n", and the \n is +# skipped since it is whitespace (see Parse_IsVar). +.MAKEFLAGS: nl="\n" +.if ${nl} != "" +. error +.endif + +# Next try at defining another newline variable. Since whitespace around the +# variable value is trimmed, two empty variable expressions surround the +# literal newline now. This prevents the newline from being skipped during +# parsing. The ':=' assignment operator expands the empty variable +# expressions, leaving only the newline as the variable value. +# +# This is one of the very few ways (maybe even the only one) to inject literal +# newlines into a line that is being parsed. This may confuse the parser. +# For example, in cond.c the parser only expects horizontal whitespace (' ' +# and '\t'), but no newlines. +#.MAKEFLAGS: -dcpv +.MAKEFLAGS: nl:="$${:U}\n$${:U}" +.if ${nl} != ${.newline} +. error +.endif +#.MAKEFLAGS: -d0 + +# Unbalanced quotes produce an error message. If they occur anywhere in the +# command line, the whole command line is skipped. +.MAKEFLAGS: VAR=previous +.MAKEFLAGS: VAR=initial UNBALANCED=' +.if ${VAR} != "previous" +. error +.endif + all: @:; diff --git a/unit-tests/deptgt-silent.exp b/unit-tests/deptgt-silent.exp index 6cab88f53144..c9b89da0ae5e 100644 --- a/unit-tests/deptgt-silent.exp +++ b/unit-tests/deptgt-silent.exp @@ -1,3 +1,5 @@ +echo 'This is a loud command.' +This is a loud command. This is not echoed because of the @. This is not echoed because of the .SILENT. exit status 0 diff --git a/unit-tests/deptgt-silent.mk b/unit-tests/deptgt-silent.mk index 64d54c00e632..335a1e897295 100644 --- a/unit-tests/deptgt-silent.mk +++ b/unit-tests/deptgt-silent.mk @@ -1,10 +1,15 @@ -# $NetBSD: deptgt-silent.mk,v 1.3 2020/09/10 21:40:50 rillig Exp $ +# $NetBSD: deptgt-silent.mk,v 1.4 2020/11/15 20:49:20 rillig Exp $ # # Tests for the special target .SILENT in dependency declarations. .SILENT: all -all: +all: loud @echo 'This is not echoed because of the @.' # Without the .SILENT, the following command would be echoed. echo 'This is not echoed because of the .SILENT.' + +# Demonstrate that the .SILENT only applies to the particular target. +# This is unlike .DELETE_ON_ERROR, which is a global setting. +loud: .PHONY + echo 'This is a loud command.' diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp index 2b899313b938..fee0563f5b37 100644 --- a/unit-tests/deptgt.exp +++ b/unit-tests/deptgt.exp @@ -1,5 +1,13 @@ make: "deptgt.mk" line 10: warning: Extra target ignored make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL" +ParseReadLine (34): '${:U}: empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (35): ' : command for empty targets list' +ParseReadLine (36): ': empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (37): ' : command for empty targets list' +ParseReadLine (38): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/deptgt.mk b/unit-tests/deptgt.mk index 517ed5aa2b75..83f81b6f58ed 100644 --- a/unit-tests/deptgt.mk +++ b/unit-tests/deptgt.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt.mk,v 1.8 2020/10/18 13:02:10 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.9 2020/11/15 11:57:00 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. @@ -27,5 +27,15 @@ target1 target2: sources # targets := [target1, target2] VAR=value # targets := NULL : command3 # parse error, since targets == NULL +# In a dependency declaration, the list of targets can be empty. +# It doesn't matter whether the empty string is generated by a variable +# expression or whether it is just omitted. +.MAKEFLAGS: -dp +${:U}: empty-source + : command for empty targets list +: empty-source + : command for empty targets list +.MAKEFLAGS: -d0 + all: @:; diff --git a/unit-tests/dir.mk b/unit-tests/dir.mk index 24ce47d959ed..55e22d88b803 100644 --- a/unit-tests/dir.mk +++ b/unit-tests/dir.mk @@ -1,4 +1,4 @@ -# $NetBSD: dir.mk,v 1.7 2020/10/31 21:30:03 rillig Exp $ +# $NetBSD: dir.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ # # Tests for dir.c. @@ -64,7 +64,8 @@ fetch fetch-post extract extract-post: : $@ # The expansions may have duplicates. -# These are merged together because of the dependency line. +# When the source of the dependency line is expanded later, each of the +# expanded words will be the same. all: dup-{1,1,1,1,1,1,1} dup-1: diff --git a/unit-tests/directive-elif.exp b/unit-tests/directive-elif.exp index 39a9383953dd..6219b4896795 100644 --- a/unit-tests/directive-elif.exp +++ b/unit-tests/directive-elif.exp @@ -1 +1,17 @@ -exit status 0 +make: "directive-elif.mk" line 7: begin .elif misspellings tests, part 1 +make: "directive-elif.mk" line 9: 1-then +make: "directive-elif.mk" line 18: begin .elif misspellings tests, part 2 +make: "directive-elif.mk" line 29: begin .elif misspellings tests, part 3 +make: "directive-elif.mk" line 41: which branch is taken on misspelling after false? +make: "directive-elif.mk" line 49: else +make: "directive-elif.mk" line 52: which branch is taken on misspelling after true? +make: "directive-elif.mk" line 54: 1-then +make: "directive-elif.mk" line 55: Unknown directive "elsif" +make: "directive-elif.mk" line 56: 1-elsif +make: "directive-elif.mk" line 57: Unknown directive "elsif" +make: "directive-elif.mk" line 58: 2-elsif +make: "directive-elif.mk" line 64: if-less elif +make: "directive-elif.mk" line 69: warning: extra elif +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-elif.mk b/unit-tests/directive-elif.mk index fc8bb7e7d197..f9a43abfffc1 100644 --- a/unit-tests/directive-elif.mk +++ b/unit-tests/directive-elif.mk @@ -1,8 +1,72 @@ -# $NetBSD: directive-elif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-elif.mk,v 1.6 2020/11/12 19:46:36 rillig Exp $ # # Tests for the .elif directive. # TODO: Implementation +.info begin .elif misspellings tests, part 1 +.if 1 +. info 1-then +.elif 1 # ok +. info 1-elif +.elsif 1 # oops: misspelled +. info 1-elsif +.elseif 1 # oops: misspelled +. info 1-elseif +.endif + +.info begin .elif misspellings tests, part 2 +.if 0 +. info 0-then +.elif 0 # ok +. info 0-elif +.elsif 0 # oops: misspelled +. info 0-elsif +.elseif 0 # oops: misspelled +. info 0-elseif +.endif + +.info begin .elif misspellings tests, part 3 +.if 0 +. info 0-then +.elsif 0 # oops: misspelled +. info 0-elsif +.endif +.if 0 +. info 0-then +.elseif 0 # oops: misspelled +. info 0-elseif +.endif + +.info which branch is taken on misspelling after false? +.if 0 +. info 0-then +.elsif 1 +. info 1-elsif +.elsif 2 +. info 2-elsif +.else +. info else +.endif + +.info which branch is taken on misspelling after true? +.if 1 +. info 1-then +.elsif 1 +. info 1-elsif +.elsif 2 +. info 2-elsif +.else +. info else +.endif + +# Expect: "if-less elif" +.elif 0 + +.if 1 +.else +# Expect: "warning: if-less elif" +.elif +.endif + all: - @:; diff --git a/unit-tests/directive-else.exp b/unit-tests/directive-else.exp index 387577099b81..ca60595745a9 100644 --- a/unit-tests/directive-else.exp +++ b/unit-tests/directive-else.exp @@ -1,8 +1,11 @@ -make: "directive-else.mk" line 10: ok -make: "directive-else.mk" line 14: ok -make: "directive-else.mk" line 20: if-less else -make: "directive-else.mk" line 26: ok -make: "directive-else.mk" line 27: warning: extra else +make: "directive-else.mk" line 11: The .else directive does not take arguments. +make: "directive-else.mk" line 12: ok +make: "directive-else.mk" line 16: ok +make: "directive-else.mk" line 17: The .else directive does not take arguments. +make: "directive-else.mk" line 22: if-less else +make: "directive-else.mk" line 28: ok +make: "directive-else.mk" line 29: warning: extra else +make: "directive-else.mk" line 42: The .else directive does not take arguments. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-else.mk b/unit-tests/directive-else.mk index 8fbbb5189ad5..fdd94007a1fa 100644 --- a/unit-tests/directive-else.mk +++ b/unit-tests/directive-else.mk @@ -1,7 +1,9 @@ -# $NetBSD: directive-else.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: directive-else.mk,v 1.6 2020/11/13 09:01:59 rillig Exp $ # # Tests for the .else directive. +.MAKEFLAGS: -dL # To enable the check for ".else <cond>" + # The .else directive does not take any arguments. # As of 2020-08-29, make doesn't warn about this. .if 0 @@ -28,5 +30,17 @@ . info After an extra .else, everything is skipped. .endif +# An .else may have a comment. This comment does not count as an argument, +# therefore no parse error. +.if 0 +.else # comment +.endif + +# A variable expression does count as an argument, even if it is empty. +# XXX: This should be a parse error. +.if 0 +.else ${:U} +.endif + all: @:; diff --git a/unit-tests/directive-endif.mk b/unit-tests/directive-endif.mk index 12b608ffbeee..b0b531af2f06 100644 --- a/unit-tests/directive-endif.mk +++ b/unit-tests/directive-endif.mk @@ -1,8 +1,27 @@ -# $NetBSD: directive-endif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-endif.mk,v 1.3 2020/11/12 22:40:11 rillig Exp $ # # Tests for the .endif directive. +# +# See also: +# Cond_EvalLine # TODO: Implementation +.MAKEFLAGS: -dL + +# Error: .endif does not take arguments +# XXX: Missing error message +.if 0 +.endif 0 + +# Error: .endif does not take arguments +# XXX: Missing error message +.if 1 +.endif 1 + +# Comments are allowed after an '.endif'. +.if 2 +.endif # comment + all: @:; diff --git a/unit-tests/directive-export-env.mk b/unit-tests/directive-export-env.mk index 49d1edb9f6fe..82b5e8087c6d 100644 --- a/unit-tests/directive-export-env.mk +++ b/unit-tests/directive-export-env.mk @@ -1,8 +1,12 @@ -# $NetBSD: directive-export-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-export-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export-env directive. # TODO: Implementation +.export-en # oops: misspelled +.export-env +.export-environment # oops: misspelled + all: @:; diff --git a/unit-tests/directive-export-gmake.mk b/unit-tests/directive-export-gmake.mk index 51de8fe55f64..d94cd9debf64 100644 --- a/unit-tests/directive-export-gmake.mk +++ b/unit-tests/directive-export-gmake.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export-gmake.mk,v 1.2 2020/10/19 18:59:53 rillig Exp $ +# $NetBSD: directive-export-gmake.mk,v 1.3 2020/11/17 20:16:44 rillig Exp $ # # Tests for the export directive (without leading dot), as in GNU make. @@ -39,8 +39,8 @@ export VAR= leading spaces # may be silently discarded. One such shell is dash, which is the default # shell on Ubuntu and Debian. export VAR =trailing space in varname -.if ${:!env | grep trailing!} != "VAR =trailing space in varname" -. if ${:!env | grep trailing!} != "" # for dash +.if ${:!env | grep trailing || true!} != "VAR =trailing space in varname" +. if ${:!env | grep trailing || true!} != "" # for dash . error . endif .endif diff --git a/unit-tests/directive-export-literal.mk b/unit-tests/directive-export-literal.mk index 817c836fd6c4..51e5b522a3b9 100644 --- a/unit-tests/directive-export-literal.mk +++ b/unit-tests/directive-export-literal.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export-literal.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: directive-export-literal.mk,v 1.6 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export-literal directive, which exports a variable value # without expanding it. @@ -7,5 +7,9 @@ UT_VAR= value with ${UNEXPANDED} expression .export-literal UT_VAR +.export-litera # oops: misspelled +.export-literal # oops: missing argument +.export-literally # oops: misspelled + all: @echo "$$UT_VAR" diff --git a/unit-tests/directive-export.exp b/unit-tests/directive-export.exp index 39a9383953dd..bd828b63c10c 100644 --- a/unit-tests/directive-export.exp +++ b/unit-tests/directive-export.exp @@ -1 +1,4 @@ -exit status 0 +make: "directive-export.mk" line 25: Unknown directive "expor" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk index 6fbf8f09d4dc..bae50aecbdaf 100644 --- a/unit-tests/directive-export.mk +++ b/unit-tests/directive-export.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export.mk,v 1.3 2020/10/29 17:27:12 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .export directive. @@ -21,5 +21,11 @@ VAR= value $$ ${INDIRECT} . error .endif +# Tests for parsing the .export directive. +.expor # misspelled +.export # oops: missing argument +.export VARNAME +.exporting works # oops: misspelled + all: @:; diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp index 88ab45529e3f..af610cc34edd 100755 --- a/unit-tests/directive-for.exp +++ b/unit-tests/directive-for.exp @@ -1,19 +1,19 @@ -make: "directive-for.mk" line 100: outer -make: "directive-for.mk" line 125: a:\ a:\file.txt -make: "directive-for.mk" line 125: d:\\ -make: "directive-for.mk" line 125: d:\\file.txt -make: "directive-for.mk" line 132: ( ( ( -make: "directive-for.mk" line 132: [ [ [ -make: "directive-for.mk" line 132: { { { -make: "directive-for.mk" line 132: ) ) ) -make: "directive-for.mk" line 132: ] ] ] -make: "directive-for.mk" line 132: } } } -make: "directive-for.mk" line 132: (()) (()) (()) -make: "directive-for.mk" line 132: [[]] [[]] [[]] -make: "directive-for.mk" line 132: {{}} {{}} {{}} -make: "directive-for.mk" line 132: )( )( )( -make: "directive-for.mk" line 132: ][ ][ ][ -make: "directive-for.mk" line 132: }{ }{ }{ -make: "directive-for.mk" line 140: outer value value -make: "directive-for.mk" line 140: outer "quoted" \"quoted\" +make: "directive-for.mk" line 108: outer +make: "directive-for.mk" line 133: a:\ a:\file.txt +make: "directive-for.mk" line 133: d:\\ +make: "directive-for.mk" line 133: d:\\file.txt +make: "directive-for.mk" line 140: ( ( ( +make: "directive-for.mk" line 140: [ [ [ +make: "directive-for.mk" line 140: { { { +make: "directive-for.mk" line 140: ) ) ) +make: "directive-for.mk" line 140: ] ] ] +make: "directive-for.mk" line 140: } } } +make: "directive-for.mk" line 140: (()) (()) (()) +make: "directive-for.mk" line 140: [[]] [[]] [[]] +make: "directive-for.mk" line 140: {{}} {{}} {{}} +make: "directive-for.mk" line 140: )( )( )( +make: "directive-for.mk" line 140: ][ ][ ][ +make: "directive-for.mk" line 140: }{ }{ }{ +make: "directive-for.mk" line 148: outer value value +make: "directive-for.mk" line 148: outer "quoted" \"quoted\" exit status 0 diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk index e0987d331a14..93f0a14f5892 100755 --- a/unit-tests/directive-for.mk +++ b/unit-tests/directive-for.mk @@ -1,6 +1,13 @@ -# $NetBSD: directive-for.mk,v 1.8 2020/10/25 15:49:03 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.9 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .for directive. +# +# TODO: Describe naming conventions for the loop variables. +# .for f in values +# .for file in values +# .for _FILE_ in values +# .for .FILE. in values +# .for _f_ in values # Using the .for loop, lists of values can be produced. # In simple cases, the :@var@${var}@ variable modifier can be used to @@ -15,6 +22,7 @@ NUMBERS+= ${num} .endif # The .for loop also works for multiple iteration variables. +# This is something that the variable modifier :@ cannot do. .for name value in VARNAME value NAME2 value2 ${name}= ${value} .endfor @@ -26,7 +34,7 @@ ${name}= ${value} # just like the :M or :S variable modifiers. # # Until 2012-06-03, it had split the items exactly at whitespace, without -# taking the quotes into account. +# taking the quotes into account. This had resulted in 10 words. # .undef WORDS .for var in one t\ w\ o "three three" 'four four' `five six` diff --git a/unit-tests/directive-if-nested.exp b/unit-tests/directive-if-nested.exp new file mode 100644 index 000000000000..1a9ae02f07b2 --- /dev/null +++ b/unit-tests/directive-if-nested.exp @@ -0,0 +1,2 @@ +make: "directive-if-nested.inc" line 1001: deeply nested .if directives +exit status 0 diff --git a/unit-tests/directive-if-nested.mk b/unit-tests/directive-if-nested.mk new file mode 100644 index 000000000000..19c8e9452660 --- /dev/null +++ b/unit-tests/directive-if-nested.mk @@ -0,0 +1,25 @@ +# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $ +# +# Tests for deeply nested .if directives. By default, memory for 128 nested +# .if directives is pre-allocated, any deeper nesting is reallocated. +# +# See also: +# Cond_EvalLine + +GEN= directive-if-nested.inc + +all: set-up test tear-down + +set-up: .PHONY + @{ printf '.if %s\n' ${:U:range=1000}; \ + printf '.info deeply nested .if directives\n'; \ + printf '.endif # %s\n' ${:U:range=1000}; \ + printf '\n'; \ + printf 'all:\n'; \ + } > ${GEN} + +test: .PHONY + @${MAKE} -f ${GEN} + +tear-down: .PHONY + @rm -f ${GEN} diff --git a/unit-tests/directive-if.exp b/unit-tests/directive-if.exp index 39a9383953dd..21a33fe4cfd6 100644 --- a/unit-tests/directive-if.exp +++ b/unit-tests/directive-if.exp @@ -1 +1,15 @@ -exit status 0 +make: "directive-if.mk" line 13: 0 evaluates to false. +make: "directive-if.mk" line 17: 1 evaluates to true. +make: "directive-if.mk" line 40: Unknown directive "ifx" +make: "directive-if.mk" line 41: Unknown directive "error" +make: "directive-if.mk" line 42: if-less else +make: "directive-if.mk" line 43: Unknown directive "error" +make: "directive-if.mk" line 44: if-less endif +make: "directive-if.mk" line 47: Malformed conditional () +make: "directive-if.mk" line 57: Quotes in plain words are probably a mistake. +make: "directive-if.mk" line 66: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 70: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 76: Don't do this, always put a space around comparison operators. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-if.mk b/unit-tests/directive-if.mk index 72d7d0e2d920..3b1d13c7a0c0 100644 --- a/unit-tests/directive-if.mk +++ b/unit-tests/directive-if.mk @@ -1,8 +1,81 @@ -# $NetBSD: directive-if.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-if.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .if directive. +# +# See also: +# cond-*.mk # TODO: Implementation +.if 0 +. error +.else +. info 0 evaluates to false. +.endif + +.if 1 +. info 1 evaluates to true. +.else +. error +.endif + +# There is no '.ifx'. +# +# The commit from 2005-05-01 intended to detect this situation, but it failed +# to do this since the call to is_token had its arguments switched. They were +# expected as (str, token, token_len) but were actually passed as (token, str, +# token_len). This made is_token return true even if the directive was +# directly followed by alphanumerical characters, which was wrong. The +# typical cases produced an error message such as "Malformed conditional +# (x 123)", while the intended error message was "Unknown directive". +# +# Back at that time, the commits only modified the main code but did not add +# the corresponding unit tests. This allowed the bug to hide for more than +# 15 years. +# +# Since 2020-11-10, the correct error message is produced. The '.ifx' is no +# longer interpreted as a variant of '.if', therefore the '.error' and '.else' +# are interpreted as ordinary directives, producing the error messages +# "if-less else" and "if-less endif". +.ifx 123 +. error +.else +. error +.endif + +# Missing condition. +.if +. error +.else +. error +.endif + +# A plain word must not start with a '"'. It may contain a embedded quotes +# though, which are kept. The quotes need not be balanced. The next space +# ends the word, and the remaining " || 1" is parsed as "or true". +.if ${:Uplain"""""} == plain""""" || 1 +. info Quotes in plain words are probably a mistake. +# XXX: Accepting quotes in plain words is probably a mistake as well. +.else +. error +.endif + +.if0 +. error +.else +. info Don't do this, always put a space after a directive. +.endif + +.if${:U-3} +. info Don't do this, always put a space after a directive. +.else +. error +.endif + +.if${:U-3}>-4 +. info Don't do this, always put a space around comparison operators. +.else +. error +.endif + all: - @:; diff --git a/unit-tests/directive-ifdef.exp b/unit-tests/directive-ifdef.exp index 39a9383953dd..b8453ecc4124 100644 --- a/unit-tests/directive-ifdef.exp +++ b/unit-tests/directive-ifdef.exp @@ -1 +1,2 @@ +make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible. exit status 0 diff --git a/unit-tests/directive-ifdef.mk b/unit-tests/directive-ifdef.mk index 64f073fb5ae1..516e095c464f 100644 --- a/unit-tests/directive-ifdef.mk +++ b/unit-tests/directive-ifdef.mk @@ -1,8 +1,18 @@ -# $NetBSD: directive-ifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-ifdef.mk,v 1.3 2020/11/08 22:38:28 rillig Exp $ # # Tests for the .ifdef directive. # TODO: Implementation +DEFINED= defined + +# It looks redundant to have a call to defined() in an .ifdef, but it's +# possible. The .ifdef only affects plain symbols, not function calls. +.ifdef defined(DEFINED) +. info Function calls in .ifdef are possible. +.else +. error +.endif + all: @:; diff --git a/unit-tests/directive-ifmake.exp b/unit-tests/directive-ifmake.exp index 5fefeb252b48..fd4bcae151a0 100644 --- a/unit-tests/directive-ifmake.exp +++ b/unit-tests/directive-ifmake.exp @@ -1,9 +1,10 @@ -make: "directive-ifmake.mk" line 8: ok: positive condition works -make: "directive-ifmake.mk" line 19: ok: negation works -make: "directive-ifmake.mk" line 25: ok: double negation works -make: "directive-ifmake.mk" line 32: ok: both mentioned -make: "directive-ifmake.mk" line 39: ok: only those mentioned -make: "directive-ifmake.mk" line 49: Targets can even be added at parse time. +make: "directive-ifmake.mk" line 13: ok: positive condition works +make: "directive-ifmake.mk" line 24: ok: negation works +make: "directive-ifmake.mk" line 33: ok: double negation works +make: "directive-ifmake.mk" line 40: ok: both mentioned +make: "directive-ifmake.mk" line 47: ok: only those mentioned +make: "directive-ifmake.mk" line 57: Targets can even be added at parse time. +make: "directive-ifmake.mk" line 75: ok : first : second : late-target diff --git a/unit-tests/directive-ifmake.mk b/unit-tests/directive-ifmake.mk index 20329bc5ce25..4d49add72626 100644 --- a/unit-tests/directive-ifmake.mk +++ b/unit-tests/directive-ifmake.mk @@ -1,44 +1,52 @@ -# $NetBSD: directive-ifmake.mk,v 1.4 2020/08/30 14:25:45 rillig Exp $ +# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .ifmake directive, which provides a shortcut for asking # whether a certain target is requested to be made from the command line. +# +# TODO: Describe why the shortcut may be useful (if it's useful at all), +# instead of sticking to the simple '.if' only. + +# The targets 'first' and 'second' are passed in on the command line. # This is the most basic form. .ifmake first -.info ok: positive condition works +. info ok: positive condition works .else -.warning positive condition fails +. warning positive condition fails .endif # The not operator works as expected. # An alternative interpretation were that this condition is asking whether # the target "!first" was requested. To distinguish this, see the next test. .ifmake !first -.warning unexpected +. warning unexpected .else -.info ok: negation works +. info ok: negation works .endif # See if the exclamation mark really means "not", or if it is just part of -# the target name. +# the target name. Since it means 'not', the two exclamation marks are +# effectively ignored, and 'first' is indeed a requested target. If the +# exclamation mark were part of the name instead, the name would be '!!first', +# and such a target was not requested to be made. .ifmake !!first -.info ok: double negation works +. info ok: double negation works .else -.warning double negation fails +. warning double negation fails .endif # Multiple targets can be combined using the && and || operators. .ifmake first && second -.info ok: both mentioned +. info ok: both mentioned .else -.warning && does not work as expected +. warning && does not work as expected .endif # Negation also works in complex conditions. .ifmake first && !unmentioned -.info ok: only those mentioned +. info ok: only those mentioned .else -.warning && with ! does not work as expected +. warning && with ! does not work as expected .endif # Using the .MAKEFLAGS special dependency target, arbitrary command @@ -46,10 +54,29 @@ # possible to extend the targets to be made. .MAKEFLAGS: late-target .ifmake late-target -.info Targets can even be added at parse time. +. info Targets can even be added at parse time. +.else +. info No, targets cannot be added at parse time anymore. +.endif + +# Numbers are interpreted as numbers, no matter whether the directive is +# a plain .if or an .ifmake. +.ifmake 0 +. error +.endif +.ifmake 1 .else -.info No, targets cannot be added at parse time anymore. +. error .endif +# A condition that consists of a variable expression only (without any +# comparison operator) can be used with .if and the other .ifxxx directives. +.ifmake ${:Ufirst} +. info ok +.else +. error +.endif + + first second unmentioned late-target: : $@ diff --git a/unit-tests/directive-include.exp b/unit-tests/directive-include.exp index 3a75ce38728b..af56eefb2b88 100755 --- a/unit-tests/directive-include.exp +++ b/unit-tests/directive-include.exp @@ -2,4 +2,7 @@ CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != -exit status 0 +make: "directive-include.mk" line 25: Could not find nonexistent.mk +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk index d71b27ef2c63..120706cef8d7 100755 --- a/unit-tests/directive-include.mk +++ b/unit-tests/directive-include.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-include.mk,v 1.3 2020/10/31 23:01:23 rillig Exp $ +# $NetBSD: directive-include.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .include directive, which includes another file. @@ -22,5 +22,10 @@ . error .endif +.include "nonexistent.mk" +.include "/dev/null" # size 0 +# including a directory technically succeeds, but shouldn't. +#.include "." # directory + all: @:; diff --git a/unit-tests/directive-info.exp b/unit-tests/directive-info.exp index 39a9383953dd..971f417b706a 100644 --- a/unit-tests/directive-info.exp +++ b/unit-tests/directive-info.exp @@ -1 +1,14 @@ -exit status 0 +make: "directive-info.mk" line 7: begin .info tests +make: "directive-info.mk" line 8: Unknown directive "inf" +make: "directive-info.mk" line 9: Unknown directive "info" +make: "directive-info.mk" line 10: message +make: "directive-info.mk" line 11: indented message +make: "directive-info.mk" line 12: Unknown directive "information" +make: "directive-info.mk" line 13: message +make: "directive-info.mk" line 18: Unknown directive "info" +make: "directive-info.mk" line 19: Unknown directive "info" +make: "directive-info.mk" line 22: Unknown directive "info-message" +make: "directive-info.mk" line 23: no-target: no-source +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-info.mk b/unit-tests/directive-info.mk index 3eb972ad7a0e..bbfc80ea0c9a 100644 --- a/unit-tests/directive-info.mk +++ b/unit-tests/directive-info.mk @@ -1,8 +1,27 @@ -# $NetBSD: directive-info.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-info.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ # # Tests for the .info directive. # TODO: Implementation +.info begin .info tests +.inf # misspelled +.info # oops: message should be "missing parameter" +.info message +.info indented message +.information +.information message # oops: misspelled +.info.man: # not a message, but possibly a suffix rule + +# Even if lines would have trailing whitespace, this would be trimmed by +# ParseGetLine. +.info +.info # comment + +.info: message # This is a dependency declaration. +.info-message # This is an unknown directive. +.info no-target: no-source # This is a .info directive, not a dependency. +# See directive.mk for more tests of this kind. + all: @:; diff --git a/unit-tests/directive-sinclude.mk b/unit-tests/directive-sinclude.mk index b1faa7d41e65..1932e7b3ba13 100755 --- a/unit-tests/directive-sinclude.mk +++ b/unit-tests/directive-sinclude.mk @@ -1,7 +1,11 @@ -# $NetBSD: directive-sinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $ # # Tests for the .sinclude directive, which includes another file, # silently skipping it if it cannot be opened. +# +# The 'silently skipping' only applies to the case where the file cannot be +# opened. Parse errors and other errors are handled the same way as in the +# other .include directives. # TODO: Implementation diff --git a/unit-tests/directive-undef.exp b/unit-tests/directive-undef.exp index 39a9383953dd..303d5a3e2a27 100644 --- a/unit-tests/directive-undef.exp +++ b/unit-tests/directive-undef.exp @@ -1 +1,4 @@ -exit status 0 +make: "directive-undef.mk" line 16: Unknown directive "unde" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-undef.mk b/unit-tests/directive-undef.mk index aac110437686..c72513a1bf5a 100644 --- a/unit-tests/directive-undef.mk +++ b/unit-tests/directive-undef.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-undef.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .undef directive. @@ -13,5 +13,9 @@ . warning $1$2$3 .endif +.unde # misspelled +.undef # oops: missing argument +.undefined # oops: misspelled + all: @:; diff --git a/unit-tests/directive-unexport-env.mk b/unit-tests/directive-unexport-env.mk index 34d867d706ef..637286af0d6b 100644 --- a/unit-tests/directive-unexport-env.mk +++ b/unit-tests/directive-unexport-env.mk @@ -1,8 +1,12 @@ -# $NetBSD: directive-unexport-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-unexport-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .unexport-env directive. # TODO: Implementation +.unexport-en # oops: misspelled +.unexport-env # ok +.unexport-environment # oops: misspelled + all: @:; diff --git a/unit-tests/directive-unexport.exp b/unit-tests/directive-unexport.exp index 263a2fedcc0b..72b24e7344fc 100644 --- a/unit-tests/directive-unexport.exp +++ b/unit-tests/directive-unexport.exp @@ -2,4 +2,7 @@ make: "directive-unexport.mk" line 14: UT_A=a UT_B=b UT_C=c make: "directive-unexport.mk" line 15: UT_A UT_B UT_C make: "directive-unexport.mk" line 23: UT_A=a UT_B=b UT_C=c make: "directive-unexport.mk" line 24: -exit status 0 +make: "directive-unexport.mk" line 26: Unknown directive "unexpor" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-unexport.mk b/unit-tests/directive-unexport.mk index b44932904805..3ba4a1b1f307 100644 --- a/unit-tests/directive-unexport.mk +++ b/unit-tests/directive-unexport.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-unexport.mk,v 1.4 2020/10/30 23:54:42 sjg Exp $ +# $NetBSD: directive-unexport.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .unexport directive. @@ -23,5 +23,9 @@ UT_C= c .info ${:!env|sort|grep '^UT_'!} .info ${.MAKE.EXPORTED} +.unexpor # misspelled +.unexport # oops: missing argument +.unexporting works # oops: misspelled + all: @:; diff --git a/unit-tests/directive-warning.exp b/unit-tests/directive-warning.exp index 39a9383953dd..630285fd3612 100644 --- a/unit-tests/directive-warning.exp +++ b/unit-tests/directive-warning.exp @@ -1 +1,11 @@ -exit status 0 +make: "directive-warning.mk" line 7: Unknown directive "warn" +make: "directive-warning.mk" line 8: Unknown directive "warn" +make: "directive-warning.mk" line 9: Unknown directive "warnin" +make: "directive-warning.mk" line 10: Unknown directive "warnin" +make: "directive-warning.mk" line 11: Unknown directive "warning" +make: "directive-warning.mk" line 12: warning: message +make: "directive-warning.mk" line 13: Unknown directive "warnings" +make: "directive-warning.mk" line 14: warning: messages +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive-warning.mk b/unit-tests/directive-warning.mk index e1e636e3ec9f..75560aa9e4df 100644 --- a/unit-tests/directive-warning.mk +++ b/unit-tests/directive-warning.mk @@ -1,8 +1,17 @@ -# $NetBSD: directive-warning.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive-warning.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $ # # Tests for the .warning directive. # TODO: Implementation +.warn # misspelled +.warn message # misspelled +.warnin # misspelled +.warnin message # misspelled +.warning # oops: should be "missing argument" +.warning message # ok +.warnings # misspelled +.warnings messages # oops + all: @:; diff --git a/unit-tests/directive.exp b/unit-tests/directive.exp index 39a9383953dd..b93d768169ab 100644 --- a/unit-tests/directive.exp +++ b/unit-tests/directive.exp @@ -1 +1,12 @@ -exit status 0 +make: "directive.mk" line 9: Unknown directive "indented" +make: "directive.mk" line 10: Unknown directive "indented" +make: "directive.mk" line 11: Unknown directive "indented" +make: "directive.mk" line 15: Unknown directive "info" +Global:.info = +Global:.info = value +make: "directive.mk" line 26: := value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/directive.mk b/unit-tests/directive.mk index 8d01a49a34cf..d463ce4f009a 100644 --- a/unit-tests/directive.mk +++ b/unit-tests/directive.mk @@ -1,8 +1,35 @@ -# $NetBSD: directive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ # # Tests for the preprocessing directives, such as .if or .info. # TODO: Implementation +# Unknown directives are correctly named in the error messages, +# even if they are indented. +.indented none +. indented 2 spaces +. indented tab + +# Directives must be written directly, not indirectly via variable +# expressions. +.${:Uinfo} directives cannot be indirect + +# There is no directive called '.target', therefore this is parsed as a +# dependency declaration with 2 targets and 1 source. +.target target: source + +# This looks ambiguous. It could be either an .info message or a variable +# assignment. It is a variable assignment. +.MAKEFLAGS: -dv +.info:= value +.info?= value # This is a variable assignment as well. +.info := value # The space after the '.info' makes this + # a directive. +.MAKEFLAGS: -d0 + +# This is a dependency since directives must be given directly. +# Not even the space after the '.info' can change anything about this. +.${:Uinfo} : source + all: @:; diff --git a/unit-tests/directives.exp b/unit-tests/directives.exp deleted file mode 100644 index 15dd9aa9e962..000000000000 --- a/unit-tests/directives.exp +++ /dev/null @@ -1,42 +0,0 @@ -make: "directives.mk" line 10: begin .export tests -make: "directives.mk" line 11: Unknown directive "expor" -make: "directives.mk" line 20: begin .export-env tests -make: "directives.mk" line 30: begin .export-literal tests -make: "directives.mk" line 40: begin .info tests -make: "directives.mk" line 41: Unknown directive "inf" -make: "directives.mk" line 42: Unknown directive "info" -make: "directives.mk" line 43: message -make: "directives.mk" line 44: indented message -make: "directives.mk" line 45: Unknown directive "information" -make: "directives.mk" line 46: message -make: "directives.mk" line 50: begin .undef tests -make: "directives.mk" line 51: Unknown directive "unde" -make: "directives.mk" line 60: begin .unexport tests -make: "directives.mk" line 61: Unknown directive "unexpor" -make: "directives.mk" line 70: begin .unexport-env tests -make: "directives.mk" line 80: begin .warning tests -make: "directives.mk" line 81: Unknown directive "warn" -make: "directives.mk" line 82: Unknown directive "warnin" -make: "directives.mk" line 83: Unknown directive "warning" -make: "directives.mk" line 84: warning: message -make: "directives.mk" line 85: Unknown directive "warnings" -make: "directives.mk" line 86: warning: messages -make: "directives.mk" line 90: begin .elif misspellings tests, part 1 -make: "directives.mk" line 100: begin .elif misspellings tests, part 2 -make: "directives.mk" line 110: begin .elif misspellings tests, part 3 -make: "directives.mk" line 120: which branch is taken on misspelling after false? -make: "directives.mk" line 127: else taken -make: "directives.mk" line 130: which branch is taken on misspelling after true? -make: "directives.mk" line 132: Unknown directive "elsif" -make: "directives.mk" line 133: 1 taken -make: "directives.mk" line 134: Unknown directive "elsif" -make: "directives.mk" line 135: 2 taken -make: "directives.mk" line 140: Unknown directive "indented" -make: "directives.mk" line 141: Unknown directive "indented" -make: "directives.mk" line 142: Unknown directive "indented" -make: "directives.mk" line 143: Unknown directive "info" -make: "directives.mk" line 150: Could not find nonexistent.mk -make: "directives.mk" line 160: end of the tests -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -exit status 1 diff --git a/unit-tests/directives.mk b/unit-tests/directives.mk deleted file mode 100644 index 436a5d35e4ec..000000000000 --- a/unit-tests/directives.mk +++ /dev/null @@ -1,163 +0,0 @@ -# $NetBSD: directives.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ -# -# Tests for parsing directives, in the same order as in the manual page. -# -# Each test group has 10 lines, to keep the line numbers in directives.exp -# stable. -# -# no tests for .error since it exits immediately, see ParseMessage. - -.info begin .export tests -.expor # misspelled -.export # oops: missing argument -.export VARNAME -.exporting works # oops: misspelled - - - - - -.info begin .export-env tests -.export-en # oops: misspelled -.export-env -.export-environment # oops: misspelled - - - - - - -.info begin .export-literal tests -.export-litera # oops: misspelled -.export-literal # oops: missing argument -.export-literal VARNAME -.export-literally # oops: misspelled - - - - - -.info begin .info tests -.inf # misspelled -.info # oops: message should be "missing parameter" -.info message -.info indented message -.information -.information message # oops: misspelled -.info.man: # not a message, but a suffix rule - - -.info begin .undef tests -.unde # misspelled -.undef # oops: missing argument -.undefined # oops: misspelled -.undef VARNAME - - - - - -.info begin .unexport tests -.unexpor # misspelled -.unexport # oops: missing argument -.unexport VARNAME # ok -.unexporting works # oops: misspelled - - - - - -.info begin .unexport-env tests -.unexport-en # misspelled -.unexport-env # ok -.unexport-environment # oops: misspelled - - - - - - -.info begin .warning tests -.warn # misspelled -.warnin # misspelled -.warning # oops: should be "missing argument" -.warning message # ok -.warnings # misspelled -.warnings messages # oops - - - -.info begin .elif misspellings tests, part 1 -.if 1 -.elif 1 # ok -.elsif 1 # oops: misspelled -.elseif 1 # oops: misspelled -.endif - - - - -.info begin .elif misspellings tests, part 2 -.if 0 -.elif 0 # ok -.elsif 0 # oops: misspelled -.elseif 0 # oops: misspelled -.endif - - - - -.info begin .elif misspellings tests, part 3 -.if 0 -.elsif 0 # oops: misspelled -.endif -.if 0 -.elseif 0 # oops: misspelled -.endif - - - -.info which branch is taken on misspelling after false? -.if 0 -.elsif 1 -. info 1 taken -.elsif 2 -. info 2 taken -.else -. info else taken -.endif - -.info which branch is taken on misspelling after true? -.if 1 -.elsif 1 -. info 1 taken -.elsif 2 -. info 2 taken -.else -. info else taken -.endif - -.indented none -. indented 2 spaces -. indented tab -.${:Uinfo} directives cannot be indirect - - - - - - -.include "nonexistent.mk" -.include "/dev/null" # size 0 -# including a directory technically succeeds, but shouldn't. -#.include "." # directory - - - - - - -.info end of the tests - -all: - @: diff --git a/unit-tests/dollar.exp b/unit-tests/dollar.exp index 496adc02f157..9b8c9d638a87 100644 --- a/unit-tests/dollar.exp +++ b/unit-tests/dollar.exp @@ -1,7 +1,7 @@ Printing dollar from literals and variables -To survive the parser, a dollar character must be doubled. +To survive the parser, a dollar sign must be doubled. 1 dollar literal => <single-quote-var-value> 1 dollar literal eol => <> 2 dollar literal => <$> diff --git a/unit-tests/dollar.mk b/unit-tests/dollar.mk index b7f7d9fa7c9e..8a06c5bf722b 100644 --- a/unit-tests/dollar.mk +++ b/unit-tests/dollar.mk @@ -1,6 +1,6 @@ -# $NetBSD: dollar.mk,v 1.3 2020/05/17 09:37:48 rillig Exp $ +# $NetBSD: dollar.mk,v 1.4 2020/11/03 18:21:36 rillig Exp $ # -# Test the various places where a dollar character can appear and +# Test the various places where a dollar sign can appear and # see what happens. There are lots of surprises here. # @@ -30,7 +30,7 @@ ${:U'}= single-quote-var-value' all: $H 'Printing dollar from literals and variables' - $C 'To survive the parser, a dollar character must be doubled.' + $C 'To survive the parser, a dollar sign must be doubled.' $T '1 dollar literal' '$' $T '1 dollar literal eol' ''$ $T '2 dollar literal' '$$' diff --git a/unit-tests/envfirst.mk b/unit-tests/envfirst.mk index fedcc0a75056..b5cfc4f5c578 100644 --- a/unit-tests/envfirst.mk +++ b/unit-tests/envfirst.mk @@ -1,7 +1,9 @@ -# $NetBSD: envfirst.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: envfirst.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # The -e option makes environment variables stronger than global variables. +.MAKEFLAGS: -e + .if ${FROM_ENV} != value-from-env . error ${FROM_ENV} .endif diff --git a/unit-tests/error.exp b/unit-tests/error.exp index 8d0ebef89e8e..3adc099a4625 100644 --- a/unit-tests/error.exp +++ b/unit-tests/error.exp @@ -1,6 +1,6 @@ -make: "error.mk" line 3: just FYI -make: "error.mk" line 4: warning: this could be serious -make: "error.mk" line 5: this is fatal +make: "error.mk" line 6: just FYI +make: "error.mk" line 7: warning: this could be serious +make: "error.mk" line 8: this is fatal make: stopped in unit-tests exit status 1 diff --git a/unit-tests/error.mk b/unit-tests/error.mk index 6e2654dbea72..0029b3bc6aa9 100644 --- a/unit-tests/error.mk +++ b/unit-tests/error.mk @@ -1,10 +1,12 @@ -# $NetBSD: error.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: error.mk,v 1.3 2020/11/03 17:38:45 rillig Exp $ +# +# Demonstrate that the .error directive exits immediately, without +# continuing parsing until the end of the file. .info just FYI .warning this could be serious .error this is fatal +.info this is not reached because of the .error above all: - -.info.html: - @echo this should be ignored + : this is not reached because of the .error diff --git a/unit-tests/escape.mk b/unit-tests/escape.mk index 264b124bf1ad..8bdd3ad2ab49 100644 --- a/unit-tests/escape.mk +++ b/unit-tests/escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: escape.mk,v 1.13 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig Exp $ # # Test backslash escaping. @@ -187,7 +187,7 @@ var-1bsnl-space: .PHONY __printvars \ # Backslash-newline in a command is retained. # # The "#" in "# second line without space" makes it a comment instead -# of a syntax error if the preceding line is parsed incorretly. +# of a syntax error if the preceding line is parsed incorrectly. # The ":" in "third line':" makes it look like the start of a # target instead of a syntax error if the first line is parsed incorrectly. # @@ -220,7 +220,7 @@ cmd-1bsnl-eof: # XXX: This may differ from POSIX, but matches gmake. # # When make passes two backslashes to the shell, the shell will pass one -# backslash to the echo commant. +# backslash to the echo command. # all: cmd-2bsnl cmd-2bsnl: .PHONY diff --git a/unit-tests/forloop.exp b/unit-tests/forloop.exp index 63b67c15bcc6..422711b41247 100644 --- a/unit-tests/forloop.exp +++ b/unit-tests/forloop.exp @@ -1,17 +1,17 @@ -x=one -x="two and three" -x=four -x="five" -x=-I/this -x=-I"This or that" -x=-Ithat -x="-DTHIS=\"this and that\"" -cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" -newline-item=(a) -a=one b="two and three" -a=four b="five" -a=ONE b="TWO AND THREE" -a=FOUR b="FIVE" +make: "forloop.mk" line 14: x=one +make: "forloop.mk" line 14: x="two and three" +make: "forloop.mk" line 14: x=four +make: "forloop.mk" line 14: x="five" +make: "forloop.mk" line 20: x=-I/this +make: "forloop.mk" line 20: x=-I"This or that" +make: "forloop.mk" line 20: x=-Ithat +make: "forloop.mk" line 20: x="-DTHIS=\"this and that\"" +make: "forloop.mk" line 27: cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +make: "forloop.mk" line 41: newline-item=(a) +make: "forloop.mk" line 47: a=one b="two and three" +make: "forloop.mk" line 47: a=four b="five" +make: "forloop.mk" line 47: a=ONE b="TWO AND THREE" +make: "forloop.mk" line 47: a=FOUR b="FIVE" We expect an error next: make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables make: Fatal errors encountered -- cannot continue diff --git a/unit-tests/forloop.mk b/unit-tests/forloop.mk index 0162ffa27a07..cef05cbe4c61 100644 --- a/unit-tests/forloop.mk +++ b/unit-tests/forloop.mk @@ -1,4 +1,4 @@ -# $NetBSD: forloop.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: forloop.mk,v 1.7 2020/11/03 17:37:57 rillig Exp $ all: for-loop @@ -11,40 +11,40 @@ XTRA_LIST= xtra .else . for x in ${LIST} -X!= echo 'x=$x' >&2; echo +. info x=$x . endfor CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" cfl= . for x in ${CFL} -X!= echo 'x=$x' >&2; echo +. info x=$x . if empty(cfl) cfl= $x . else cfl+= $x . endif . endfor -X!= echo 'cfl=${cfl}' >&2; echo +. info cfl=${cfl} . if ${cfl} != ${CFL} -. error ${.newline}'${cfl}' != ${.newline}'${CFL}' +. error ${.newline}${cfl} != ${.newline}${CFL} . endif . for a b in ${EMPTY} -X!= echo 'a=$a b=$b' >&2; echo +. info a=$a b=$b . endfor # Since at least 1993, iteration stops at the first newline. # Back then, the .newline variable didn't exist, therefore it was unlikely # that a newline ever occurred. . for var in a${.newline}b${.newline}c -X!= echo 'newline-item=('${var:Q}')' 1>&2; echo +. info newline-item=(${var}) . endfor .endif # for-fail .for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} -X!= echo 'a=$a b=$b' >&2; echo +. info a=$a b=$b .endfor for-loop: diff --git a/unit-tests/forsubst.mk b/unit-tests/forsubst.mk index 79af3d9a45fb..9f293ab7f94e 100644 --- a/unit-tests/forsubst.mk +++ b/unit-tests/forsubst.mk @@ -1,4 +1,16 @@ -# $NetBSD: forsubst.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ +# $NetBSD: forsubst.mk,v 1.3 2020/11/03 17:59:27 rillig Exp $ +# +# The parser used to break dependency lines at ';' without regard for +# substitution patterns. Back then, the first ';' was interpreted as the +# separator between the dependency and its commands. This (perhaps coupled +# with the new handling of .for variables in ${:U<value>...) caused +# interesting results for lines like: +# +# .for file in ${LIST} +# for-subst: ${file:S;^;${here}/;g} +# .endfor +# +# See the commit to unit-tests/forsubst (without the .mk) from 2009-10-07. all: for-subst diff --git a/unit-tests/gnode-submake.exp b/unit-tests/gnode-submake.exp new file mode 100644 index 000000000000..dbce13efaefa --- /dev/null +++ b/unit-tests/gnode-submake.exp @@ -0,0 +1,11 @@ +#*** Input graph: +# all, made UNMADE, type OP_DEPENDS, flags none +# makeinfo, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# make-index, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot-modifier, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# parentheses-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# parentheses-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none + +exit status 0 diff --git a/unit-tests/gnode-submake.mk b/unit-tests/gnode-submake.mk new file mode 100644 index 000000000000..40ff20276df9 --- /dev/null +++ b/unit-tests/gnode-submake.mk @@ -0,0 +1,42 @@ +# $NetBSD: gnode-submake.mk,v 1.1 2020/11/07 23:25:06 rillig Exp $ +# +# Test whether OP_SUBMAKE is determined correctly. If it is, this node's +# shell commands are connected to the make process via pipes, to coordinate +# the number of running jobs. +# +# Determining whether a node is a sub-make node happens when the node is +# parsed. This information is only used in parallel mode, but the result +# from parsing is available in compat mode as well. + +.MAKEFLAGS: -n -dg1 + +all: makeinfo make-index +all: braces-dot braces-no-dot +all: braces-no-dot-modifier +all: parentheses-dot parentheses-no-dot + +makeinfo: + # The command contains the substring "make", but not as a whole word. + : makeinfo submake + +make-index: + # The command contains the word "make", therefore it is considered a + # possible sub-make. It isn't really, but that doesn't hurt. + : make-index + +braces-dot: + : ${.MAKE} + +braces-no-dot: + : ${MAKE} + +braces-no-dot-modifier: + # The command refers to MAKE, but not in its pure form. Therefore it + # is not considered a sub-make. + : ${MAKE:T} + +parentheses-dot: + : $(.MAKE) + +parentheses-no-dot: + : $(MAKE) diff --git a/unit-tests/include-sub.mk b/unit-tests/include-sub.mk index aeb7c3a69082..0b8dc77398ab 100644 --- a/unit-tests/include-sub.mk +++ b/unit-tests/include-sub.mk @@ -1,4 +1,4 @@ -# $NetBSD: include-sub.mk,v 1.6 2020/10/25 12:08:53 rillig Exp $ +# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $ .if ${.INCLUDEDFROMFILE} == "include-main.mk" . info sub-before-ok @@ -20,7 +20,7 @@ # To see the variable 'includes' in action: # # Breakpoints: -# Parse_File at "PtrVector_Push(&includes, curFile)" +# Parse_File at "Vector_Push(&includes)" # ParseMessage at entry # Watches: # ((const IFile *[10])(*includes.items)) diff --git a/unit-tests/job-flags.exp b/unit-tests/job-flags.exp new file mode 100644 index 000000000000..b0c81d8f7094 --- /dev/null +++ b/unit-tests/job-flags.exp @@ -0,0 +1,12 @@ +.BEGIN +silent +ignore +true in ignore +false in ignore +*** [ignore] Error code 1 (ignored) +false without indentation +false space +false tab +*** [ignore-cmds] Error code 1 (ignored) +.END +exit status 0 diff --git a/unit-tests/job-flags.mk b/unit-tests/job-flags.mk new file mode 100644 index 000000000000..d4c3b5d43643 --- /dev/null +++ b/unit-tests/job-flags.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-flags.mk,v 1.2 2020/11/14 13:17:47 rillig Exp $ +# +# Tests for Job.flags, which are controlled by special source dependencies +# like .SILENT or .IGNORE, as well as the command line options -s or -i. + +.MAKEFLAGS: -j1 + +all: silent .WAIT ignore .WAIT ignore-cmds + +.BEGIN: + @echo $@ + +silent: .SILENT .PHONY + echo $@ + +ignore: .IGNORE .PHONY + @echo $@ + true in $@ + false in $@ + @echo 'Still there in $@' + +ignore-cmds: .PHONY + # This node is not marked .IGNORE; individual commands can be switched + # to ignore mode by prefixing them with a '-'. + -false without indentation + # This also works if the '-' is indented by a space or a tab. + # Leading whitespace is stripped off by ParseLine_ShellCommand. + -false space + -false tab + +.END: + @echo $@ diff --git a/unit-tests/moderrs.mk b/unit-tests/moderrs.mk index 77ba39a3d57d..8fdcb496ee29 100644 --- a/unit-tests/moderrs.mk +++ b/unit-tests/moderrs.mk @@ -1,4 +1,4 @@ -# $NetBSD: moderrs.mk,v 1.24 2020/11/01 14:36:25 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.25 2020/11/15 20:20:58 rillig Exp $ # # various modifier error tests @@ -123,7 +123,7 @@ mod-regex-delimiter: print-header print-footer # always set; some may be missing. Warn about these. # # Since there is no way to turn off this warning, the combination of -# alternative matches and capturing groups is not widely used. +# alternative matches and capturing groups is seldom used, if at all. # # A newly added modifier 'U' such as in :C,(a.)|(b.),\1\2,U might be added # for treating undefined capturing groups as empty, but that would create a diff --git a/unit-tests/modmisc.mk b/unit-tests/modmisc.mk index f57e679cd4da..64a84ce0dadd 100644 --- a/unit-tests/modmisc.mk +++ b/unit-tests/modmisc.mk @@ -1,4 +1,4 @@ -# $NetBSD: modmisc.mk,v 1.49 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: modmisc.mk,v 1.51 2020/11/15 20:20:58 rillig Exp $ # # miscellaneous modifier tests @@ -48,9 +48,9 @@ emptyvar: @echo @:${:@var@${var}@} # The :U modifier turns even the "" variable into something that has a value. -# The resulting variable is empty, but is still considered to contain a -# single empty word. This word can be accessed by the :S and :C modifiers, -# but not by the :@ modifier since it explicitly skips empty words. +# The value of the resulting expression is empty, but is still considered to +# contain a single empty word. This word can be accessed by the :S and :C +# modifiers, but not by the :@ modifier since it explicitly skips empty words. undefvar: @echo S:${:U:S,^$,empty,} @echo C:${:U:C,^$,empty,} @@ -60,12 +60,12 @@ undefvar: mod-quote: @echo $@: new${.newline:Q}${.newline:Q}line -# Cover the bmake_realloc in brk_string. +# Cover the bmake_realloc in Str_Words. mod-break-many-words: @echo $@: ${UNDEF:U:range=500:[#]} # To apply a modifier indirectly via another variable, the whole -# modifier must be put into a single variable. +# modifier must be put into a single variable expression. .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" . warning unexpected .endif diff --git a/unit-tests/modts.mk b/unit-tests/modts.mk index 67ba7eb97078..4776c5818ea5 100644 --- a/unit-tests/modts.mk +++ b/unit-tests/modts.mk @@ -1,7 +1,6 @@ -# $NetBSD: modts.mk,v 1.7 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ -LIST= one two three -LIST+= four five six +LIST= one two three four five six FU_mod-ts= a / b / cool diff --git a/unit-tests/modword.mk b/unit-tests/modword.mk index 43edf6a32b77..383c9dca975b 100644 --- a/unit-tests/modword.mk +++ b/unit-tests/modword.mk @@ -1,6 +1,7 @@ -# $NetBSD: modword.mk,v 1.4 2020/11/01 13:55:31 rillig Exp $ +# $NetBSD: modword.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Test behaviour of new :[] modifier +# TODO: When was this modifier new? all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw diff --git a/unit-tests/objdir-writable.exp b/unit-tests/objdir-writable.exp new file mode 100644 index 000000000000..f15cf914a7f5 --- /dev/null +++ b/unit-tests/objdir-writable.exp @@ -0,0 +1,5 @@ +make warning: OBJDIR/roobj: Permission denied. +/tmp +OBJDIR/roobj +OBJDIR/roobj +exit status 0 diff --git a/unit-tests/objdir-writable.mk b/unit-tests/objdir-writable.mk new file mode 100644 index 000000000000..9fc1c69afb56 --- /dev/null +++ b/unit-tests/objdir-writable.mk @@ -0,0 +1,31 @@ +# $NetBSD: objdir-writable.mk,v 1.4 2020/11/14 07:36:00 sjg Exp $ + +# test checking for writable objdir + +RO_OBJDIR?= ${TMPDIR:U/tmp}/roobj + +.if make(do-objdir) +# this should succeed +.OBJDIR: ${RO_OBJDIR} + +do-objdir: +.else +all: no-objdir ro-objdir explicit-objdir + +# make it now +x!= echo; mkdir -p ${RO_OBJDIR}; chmod 555 ${RO_OBJDIR} + +.END: rm-objdir +rm-objdir: + @rmdir ${RO_OBJDIR} + +no-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR + +ro-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no + +explicit-objdir: + @MAKEOBJDIR=/tmp ${.MAKE} -r -f ${MAKEFILE:tA} -C /tmp do-objdir -V .OBJDIR +.endif + diff --git a/unit-tests/opt-chdir.exp b/unit-tests/opt-chdir.exp index 39a9383953dd..d20f9eb2f07b 100644 --- a/unit-tests/opt-chdir.exp +++ b/unit-tests/opt-chdir.exp @@ -1 +1,6 @@ +make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long +*** Error code 1 (ignored) +cwd: / +make: chdir /nonexistent: No such file or directory +*** Error code 1 (ignored) exit status 0 diff --git a/unit-tests/opt-chdir.mk b/unit-tests/opt-chdir.mk index 8735fddbef9e..20241f02740e 100644 --- a/unit-tests/opt-chdir.mk +++ b/unit-tests/opt-chdir.mk @@ -1,8 +1,27 @@ -# $NetBSD: opt-chdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $ # -# Tests for the -C command line option. +# Tests for the -C command line option, which changes the directory at the +# beginning. +# +# This option has been available since 2009-08-27. + +.MAKEFLAGS: -d0 # switch stdout to line-buffered + +all: chdir-filename-too-long +all: chdir-root +all: chdir-nonexistent + +# Try to overflow the internal buffer for .CURDIR, which is curdir. +chdir-filename-too-long: .PHONY .IGNORE + # 5000 slashes, separated by dots: /./././.../././ + @${MAKE} -C ${:U:range=5000:@@/@:ts.} -# TODO: Implementation +# Changing to another directory is possible via the command line. +# In this test, it is the root directory since almost any other directory +# is not guaranteed to exist on every platform. +chdir-root: .PHONY .IGNORE + @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -C / -V 'cwd: $${.CURDIR}' -all: - @:; +# Trying to change to a nonexistent directory exits immediately. +chdir-nonexistent: .PHONY .IGNORE + @${MAKE} -C /nonexistent diff --git a/unit-tests/opt-debug-jobs.exp b/unit-tests/opt-debug-jobs.exp index 1214dce781b5..0431867756a1 100644 --- a/unit-tests/opt-debug-jobs.exp +++ b/unit-tests/opt-debug-jobs.exp @@ -10,7 +10,9 @@ echo ": variable" echo ": 'single' and \"double\" quotes" { : 'single' and "double" quotes } || exit $? -Running all locally +{ sleep 1 +} || exit $? +Running all Command: sh JobExec(all): pid <pid> added to jobs table job table @ job started diff --git a/unit-tests/opt-debug-jobs.mk b/unit-tests/opt-debug-jobs.mk index fb65d4f3356c..f3732df7e25d 100644 --- a/unit-tests/opt-debug-jobs.mk +++ b/unit-tests/opt-debug-jobs.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-jobs.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $ # # Tests for the -dj command line option, which adds debug logging about # running jobs in multiple shells. @@ -24,3 +24,10 @@ all: # This allows to copy and paste the whole command, without having # to unescape anything. : 'single' and "double" quotes + + # Avoid a race condition in the debug output. Without sleeping, + # it is not guaranteed that the two lines "exited/stopped" and + # "JobFinish" are output earlier than the stdout of the actual shell + # commands. The '@' prefix avoids that this final command gets into + # another race condition with the "exited/stopped" line. + @sleep 1 diff --git a/unit-tests/opt-ignore.mk b/unit-tests/opt-ignore.mk index ff11c1e15c31..f776e8a27229 100644 --- a/unit-tests/opt-ignore.mk +++ b/unit-tests/opt-ignore.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-ignore.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ +# $NetBSD: opt-ignore.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -i command line option, which ignores the exit status of the # shell commands, and just continues with the next command, even from the same @@ -11,6 +11,7 @@ # failed? .MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -i all: dependency other diff --git a/unit-tests/opt-keep-going.mk b/unit-tests/opt-keep-going.mk index ec4adfa00e62..72f605246712 100644 --- a/unit-tests/opt-keep-going.mk +++ b/unit-tests/opt-keep-going.mk @@ -1,10 +1,11 @@ -# $NetBSD: opt-keep-going.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $ +# $NetBSD: opt-keep-going.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -k command line option, which stops building a target as soon # as an error is detected, but continues building the other, independent # targets, as far as possible. .MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -k all: dependency other diff --git a/unit-tests/opt-no-action.mk b/unit-tests/opt-no-action.mk index 32b3b1564acb..91c05b2dc00f 100644 --- a/unit-tests/opt-no-action.mk +++ b/unit-tests/opt-no-action.mk @@ -1,9 +1,11 @@ -# $NetBSD: opt-no-action.mk,v 1.3 2020/08/19 05:25:26 rillig Exp $ +# $NetBSD: opt-no-action.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -n command line option, which runs almost no commands. # It just outputs them, to be inspected by human readers. # Only commands that are in a .MAKE target or prefixed by '+' are run. +.MAKEFLAGS: -n + # This command cannot be prevented from being run since it is used at parse # time, and any later variable assignments may depend on its result. != echo 'command during parsing' 1>&2; echo diff --git a/unit-tests/opt-query.mk b/unit-tests/opt-query.mk index 04e605991140..0a7d5219a8fe 100644 --- a/unit-tests/opt-query.mk +++ b/unit-tests/opt-query.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-query.mk,v 1.3 2020/08/19 05:13:18 rillig Exp $ +# $NetBSD: opt-query.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -q command line option. # @@ -6,6 +6,8 @@ # None of the commands in the targets are run, not even those that are # prefixed with '+'. +.MAKEFLAGS: -q + # This command cannot be prevented from being run since it is used at parse # time, and any later variable assignments may depend on its result. != echo 'command during parsing' 1>&2; echo diff --git a/unit-tests/opt-touch-jobs.exp b/unit-tests/opt-touch-jobs.exp new file mode 100644 index 000000000000..0cfde5135198 --- /dev/null +++ b/unit-tests/opt-touch-jobs.exp @@ -0,0 +1,4 @@ +: Making opt-touch-make. +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +exit status 0 diff --git a/unit-tests/opt-touch-jobs.mk b/unit-tests/opt-touch-jobs.mk new file mode 100644 index 000000000000..4402d76e9578 --- /dev/null +++ b/unit-tests/opt-touch-jobs.mk @@ -0,0 +1,30 @@ +# $NetBSD: opt-touch-jobs.mk,v 1.1 2020/11/14 15:35:20 rillig Exp $ +# +# Tests for the -t command line option in jobs mode. + +.MAKEFLAGS: -j1 +.MAKEFLAGS: -t +.MAKEFLAGS: opt-touch-phony +.MAKEFLAGS: opt-touch-join +.MAKEFLAGS: opt-touch-use +.MAKEFLAGS: opt-touch-make + +opt-touch-phony: .PHONY + : Making $@. + +opt-touch-join: .JOIN + : Making $@. + +opt-touch-use: .USE + : Making use of $@. + +# Even though it is listed last, in the output it appears first. +# This is because it is the only node that actually needs to be run. +# The "is up to date" of the other nodes happens after all jobs have +# finished, by Make_Run > MakePrintStatusList > MakePrintStatus. +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } diff --git a/unit-tests/opt-touch.exp b/unit-tests/opt-touch.exp index 39a9383953dd..c9e6c7890f42 100644 --- a/unit-tests/opt-touch.exp +++ b/unit-tests/opt-touch.exp @@ -1 +1,4 @@ +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +: Making opt-touch-make. exit status 0 diff --git a/unit-tests/opt-touch.mk b/unit-tests/opt-touch.mk index 5093c5cad6ac..defb7c59e0ad 100644 --- a/unit-tests/opt-touch.mk +++ b/unit-tests/opt-touch.mk @@ -1,8 +1,21 @@ -# $NetBSD: opt-touch.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt-touch.mk,v 1.4 2020/11/14 14:13:09 rillig Exp $ # # Tests for the -t command line option. -# TODO: Implementation +.MAKEFLAGS: -t opt-touch-phony opt-touch-join opt-touch-use opt-touch-make -all: - @:; +opt-touch-phony: .PHONY + : Making $@. + +opt-touch-join: .JOIN + : Making $@. + +opt-touch-use: .USE + : Making use of $@. + +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } diff --git a/unit-tests/opt-var-expanded.mk b/unit-tests/opt-var-expanded.mk index 0b4088a82082..f7203374b8cc 100644 --- a/unit-tests/opt-var-expanded.mk +++ b/unit-tests/opt-var-expanded.mk @@ -1,6 +1,8 @@ -# $NetBSD: opt-var-expanded.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-var-expanded.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -v command line option. +.MAKEFLAGS: -v VAR -v VALUE + VAR= other ${VALUE} $$$$ VALUE= value diff --git a/unit-tests/opt-var-literal.mk b/unit-tests/opt-var-literal.mk index a819e7537105..d236e389d259 100644 --- a/unit-tests/opt-var-literal.mk +++ b/unit-tests/opt-var-literal.mk @@ -1,6 +1,8 @@ -# $NetBSD: opt-var-literal.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-var-literal.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -V command line option. +.MAKEFLAGS: -V VAR -V VALUE + VAR= other ${VALUE} $$$$ VALUE= value diff --git a/unit-tests/opt-warnings-as-errors.exp b/unit-tests/opt-warnings-as-errors.exp index bd54bb673f08..278c9469607f 100644 --- a/unit-tests/opt-warnings-as-errors.exp +++ b/unit-tests/opt-warnings-as-errors.exp @@ -1,6 +1,6 @@ -make: "opt-warnings-as-errors.mk" line 5: warning: message 1 +make: "opt-warnings-as-errors.mk" line 7: warning: message 1 make: parsing warnings being treated as errors -make: "opt-warnings-as-errors.mk" line 6: warning: message 2 +make: "opt-warnings-as-errors.mk" line 8: warning: message 2 parsing continues make: Fatal errors encountered -- cannot continue make: stopped in unit-tests diff --git a/unit-tests/opt-warnings-as-errors.mk b/unit-tests/opt-warnings-as-errors.mk index 905753410db0..2302dd4794b5 100644 --- a/unit-tests/opt-warnings-as-errors.mk +++ b/unit-tests/opt-warnings-as-errors.mk @@ -1,7 +1,9 @@ -# $NetBSD: opt-warnings-as-errors.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $ +# $NetBSD: opt-warnings-as-errors.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for the -W command line option, which turns warnings into errors. +.MAKEFLAGS: -W + .warning message 1 .warning message 2 diff --git a/unit-tests/opt.exp b/unit-tests/opt.exp index 39a9383953dd..11344ae0c359 100644 --- a/unit-tests/opt.exp +++ b/unit-tests/opt.exp @@ -1 +1,22 @@ +make -r -f /dev/null -V MAKEFLAGS + -r -k -d 0 + +make -: +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + +make -r -f /dev/null -- -VAR=value -f /dev/null +make: don't know how to make -f (continuing) +`/dev/null' is up to date. + +make -? +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + exit status 0 diff --git a/unit-tests/opt.mk b/unit-tests/opt.mk index eae430965df7..0931a66d3d15 100644 --- a/unit-tests/opt.mk +++ b/unit-tests/opt.mk @@ -1,8 +1,28 @@ -# $NetBSD: opt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: opt.mk,v 1.6 2020/11/18 01:06:59 sjg Exp $ # # Tests for the command line options. -# TODO: Implementation +.MAKEFLAGS: -d0 # make stdout line-buffered -all: - @:; +all: .IGNORE + # The options from the top-level make are passed to the sub-makes via + # the environment variable MAKEFLAGS. This is where the " -r -k -d 0" + # comes from. See MainParseArg. + ${MAKE} -r -f /dev/null -V MAKEFLAGS + @echo + + # Just to see how the custom argument parsing code reacts to a syntax + # error. The colon is used in the options string, marking an option + # that takes arguments. It is not an option by itself, though. + ${MAKE} -: + @echo + + # See whether a '--' stops handling of command line options, like in + # standard getopt programs. Yes, it does, and it treats the + # second '-f' as a target to be created. + ${MAKE} -r -f /dev/null -- -VAR=value -f /dev/null + @echo + + # This is the normal way to print the usage of a command. + ${MAKE} -? + @echo diff --git a/unit-tests/order.mk b/unit-tests/order.mk index f90b627d9e5f..9b94016c1841 100644 --- a/unit-tests/order.mk +++ b/unit-tests/order.mk @@ -1,10 +1,12 @@ -# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: order.mk,v 1.2 2020/11/09 20:50:56 rillig Exp $ # Test that .ORDER is handled correctly. # The explicit dependency the.o: the.h will make us examine the.h # the .ORDER will prevent us building it immediately, # we should then examine the.c rather than stop. +.MAKEFLAGS: -j1 + all: the.o .ORDER: the.c the.h diff --git a/unit-tests/recursive.exp b/unit-tests/recursive.exp index bb5db75a474c..36cd1c989532 100644 --- a/unit-tests/recursive.exp +++ b/unit-tests/recursive.exp @@ -1,5 +1,5 @@ -make: "recursive.mk" line 34: Unclosed variable "MISSING_PAREN" -make: "recursive.mk" line 35: Unclosed variable "MISSING_BRACE" +make: "recursive.mk" line 36: Unclosed variable "MISSING_PAREN" +make: "recursive.mk" line 37: Unclosed variable "MISSING_BRACE" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk index 6e5d8a2ca8b5..73a8409fe030 100644 --- a/unit-tests/recursive.mk +++ b/unit-tests/recursive.mk @@ -1,4 +1,4 @@ -# $NetBSD: recursive.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: recursive.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # In -dL mode, a variable may get expanded before it makes sense. # This would stop make from doing anything since the "recursive" error @@ -11,6 +11,8 @@ # Seen in pkgsrc/x11/libXfixes, and probably many more package that use # GNU Automake. +.MAKEFLAGS: -dL + AM_V_lt= ${am__v_lt_${V}} am__v_lt_= ${am__v_lt_${AM_DEFAULT_VERBOSITY}} am__v_lt_0= --silent diff --git a/unit-tests/sh-leading-at.exp b/unit-tests/sh-leading-at.exp index 5ffa84690a40..8347fda085f7 100644 --- a/unit-tests/sh-leading-at.exp +++ b/unit-tests/sh-leading-at.exp @@ -2,4 +2,5 @@ ok space after @ echo 'echoed' echoed +3 exit status 0 diff --git a/unit-tests/sh-leading-at.mk b/unit-tests/sh-leading-at.mk index 19a6e59e4e6a..9f98005ec088 100644 --- a/unit-tests/sh-leading-at.mk +++ b/unit-tests/sh-leading-at.mk @@ -1,10 +1,18 @@ -# $NetBSD: sh-leading-at.mk,v 1.3 2020/08/22 09:16:08 rillig Exp $ +# $NetBSD: sh-leading-at.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for shell commands preceded by an '@', to suppress printing # the command to stdout. +# +# See also: +# .SILENT +# depsrc-silent.mk +# opt-silent.mk all: @ @echo 'ok' @ echo 'space after @' echo 'echoed' + # The leading '@' can be repeated. + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@echo '3' diff --git a/unit-tests/sh-leading-hyphen.mk b/unit-tests/sh-leading-hyphen.mk index 94be43495afb..d760abb9afdd 100644 --- a/unit-tests/sh-leading-hyphen.mk +++ b/unit-tests/sh-leading-hyphen.mk @@ -1,7 +1,12 @@ -# $NetBSD: sh-leading-hyphen.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: sh-leading-hyphen.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for shell commands preceded by a '-', to ignore the exit status of # the command line. +# +# See also: +# .IGNORE +# depsrc-ignore.mk +# opt-ignore.mk # TODO: Implementation diff --git a/unit-tests/sh-leading-plus.mk b/unit-tests/sh-leading-plus.mk index 75279d7d57fd..ff57b4a38a7d 100644 --- a/unit-tests/sh-leading-plus.mk +++ b/unit-tests/sh-leading-plus.mk @@ -1,8 +1,10 @@ -# $NetBSD: sh-leading-plus.mk,v 1.3 2020/08/23 14:46:33 rillig Exp $ +# $NetBSD: sh-leading-plus.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ # # Tests for shell commands preceded by a '+', to run them even if # the command line option -n is given. +.MAKEFLAGS: -n + all: @echo 'this command is not run' @+echo 'this command is run' diff --git a/unit-tests/sh-meta-chars.mk b/unit-tests/sh-meta-chars.mk index 126ca2ceb118..a029c73a855c 100644 --- a/unit-tests/sh-meta-chars.mk +++ b/unit-tests/sh-meta-chars.mk @@ -1,9 +1,13 @@ -# $NetBSD: sh-meta-chars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: sh-meta-chars.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ # # Tests for running shell commands that contain meta-characters. # # These meta-characters decide whether the command is run by the shell -# or executed directly via execv. See Cmd_Exec for details. +# or executed directly via execv, but only in compatibility mode, not +# in jobs mode, and only if MAKE_NATIVE is defined during compilation. +# +# See also: +# Compat_RunCommand, useShell # TODO: Implementation diff --git a/unit-tests/suff-self.exp b/unit-tests/suff-self.exp new file mode 100644 index 000000000000..4e70762209a2 --- /dev/null +++ b/unit-tests/suff-self.exp @@ -0,0 +1,3 @@ +make: Graph cycles through suff-self.suff +`all' not remade because of errors. +exit status 0 diff --git a/unit-tests/suff-self.mk b/unit-tests/suff-self.mk new file mode 100644 index 000000000000..8874cc5157e8 --- /dev/null +++ b/unit-tests/suff-self.mk @@ -0,0 +1,11 @@ +# $NetBSD: suff-self.mk,v 1.1 2020/11/16 15:12:16 rillig Exp $ +# +# See what happens if someone defines a self-referencing suffix +# transformation rule. + +.SUFFIXES: .suff + +.suff.suff: + : Making ${.TARGET} out of ${.IMPSRC}. + +all: suff-self.suff diff --git a/unit-tests/use-inference.mk b/unit-tests/use-inference.mk index b0e5017bc6fb..cde3c772edaa 100644 --- a/unit-tests/use-inference.mk +++ b/unit-tests/use-inference.mk @@ -1,4 +1,4 @@ -# $NetBSD: use-inference.mk,v 1.1 2020/08/09 16:32:28 rillig Exp $ +# $NetBSD: use-inference.mk,v 1.2 2020/11/05 00:41:04 rillig Exp $ # # Demonstrate that .USE rules do not have an effect on inference rules. # At least not in the special case where the inference rule does not @@ -33,3 +33,6 @@ use-inference.from: # assume it exists # This is strange since make definitely knows about the .from.to suffix # inference rule. But it seems to ignore it, maybe because it doesn't # have any associated commands. + +# XXX: Despite the error message "don't know how to make", the exit status +# is 0. This is inconsistent. diff --git a/unit-tests/var-class-local.exp b/unit-tests/var-class-local.exp index f1595c6810fb..db85b47cae06 100644 --- a/unit-tests/var-class-local.exp +++ b/unit-tests/var-class-local.exp @@ -1,2 +1,5 @@ +: Making var-class-local.c out of nothing. +: Making var-class-local.o from var-class-local.c. +: Making basename "var-class-local.o" in "." from "var-class-local.c" in ".". : all overwritten exit status 0 diff --git a/unit-tests/var-class-local.mk b/unit-tests/var-class-local.mk index 68696ea854af..f9d56e539ff0 100644 --- a/unit-tests/var-class-local.mk +++ b/unit-tests/var-class-local.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-class-local.mk,v 1.4 2020/10/25 09:46:25 rillig Exp $ +# $NetBSD: var-class-local.mk,v 1.5 2020/11/05 18:08:39 rillig Exp $ # # Tests for target-local variables, such as ${.TARGET} or $@. @@ -25,6 +25,20 @@ .endif all: + +.SUFFIXES: .c .o + +var-class-local.c: + : Making ${.TARGET} out of nothing. + +.c.o: + : Making ${.TARGET} from ${.IMPSRC}. + + # The local variables @F, @D, <F, <D are legacy forms. + # See the manual page for details. + : Making basename "${@F}" in "${@D}" from "${<F}" in "${<D}". + +all: var-class-local.o # The ::= modifier overwrites the .TARGET variable in the node # 'all', not in the global scope. This can be seen with the -dv # option, looking for "all:@ = overwritten". diff --git a/unit-tests/var-op-assign.exp b/unit-tests/var-op-assign.exp index 0e9e2d211a5f..0562ffcda733 100644 --- a/unit-tests/var-op-assign.exp +++ b/unit-tests/var-op-assign.exp @@ -1,6 +1,6 @@ this will be evaluated later -make: "var-op-assign.mk" line 52: Need an operator -make: "var-op-assign.mk" line 86: Parsing still continues until here. +make: "var-op-assign.mk" line 59: Need an operator +make: "var-op-assign.mk" line 93: Parsing still continues until here. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/var-op-assign.mk b/unit-tests/var-op-assign.mk index c988d4141b83..3bcc3de0ba0e 100644 --- a/unit-tests/var-op-assign.mk +++ b/unit-tests/var-op-assign.mk @@ -1,11 +1,12 @@ -# $NetBSD: var-op-assign.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: var-op-assign.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the = variable assignment operator, which overwrites an existing # variable or creates it. # This is a simple variable assignment. # To the left of the assignment operator '=' there is the variable name, -# and to the right is the variable value. +# and to the right is the variable value. The variable value is stored as-is, +# it is not expanded in any way. # VAR= value @@ -36,9 +37,15 @@ VAR= new value and \# some $$ special characters # comment # The variable value may contain references to other variables. # In this example, the reference is to the variable with the empty name, -# which always expands to an empty string. This alone would not produce -# any side-effects, therefore the variable has a :!...! modifier that -# executes a shell command. +# which is never defined. +# +# This alone would not produce any side-effects, therefore the variable has +# a :!...! modifier that executes a shell command. The :!...! modifier turns +# an undefined expression into a defined one, see ApplyModifier_ShellCommand, +# the call to ApplyModifiersState_Define. +# +# Since the right-hand side of a '=' assignment is not expanded at the time +# when the variable is defined, the first command is not run at all. VAR= ${:! echo 'not yet evaluated' 1>&2 !} VAR= ${:! echo 'this will be evaluated later' 1>&2 !} @@ -48,7 +55,7 @@ VAR= ${:! echo 'this will be evaluated later' 1>&2 !} .endif # In a variable assignment, the variable name must consist of a single word. -# +# The following line therefore generates a parse error. VARIABLE NAME= variable value # But if the whitespace appears inside parentheses or braces, everything is diff --git a/unit-tests/var-op-expand.exp b/unit-tests/var-op-expand.exp index 39a9383953dd..8ccbbd5ae92d 100644 --- a/unit-tests/var-op-expand.exp +++ b/unit-tests/var-op-expand.exp @@ -1 +1,10 @@ +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_ASSIGN_ = undef value +Var_Parse: ${UNDEF} with VARE_WANTRES +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_SUBST_${UNDEF} = +Var_Parse: ${UNDEF} with VARE_WANTRES +Global:VAR_SUBST_ = undef value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk index 07c5fb647759..0b5ddbbc0386 100644 --- a/unit-tests/var-op-expand.mk +++ b/unit-tests/var-op-expand.mk @@ -1,9 +1,27 @@ -# $NetBSD: var-op-expand.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-op-expand.mk,v 1.4 2020/11/08 14:00:52 rillig Exp $ # # Tests for the := variable assignment operator, which expands its # right-hand side. # TODO: Implementation +# XXX: edge case: When a variable name refers to an undefined variable, the +# behavior differs between the '=' and the ':=' assignment operators. +# This bug exists since var.c 1.42 from 2000-05-11. +# +# The '=' operator expands the undefined variable to an empty string, thus +# assigning to VAR_ASSIGN_. In the name of variables to be set, it should +# really be forbidden to refer to undefined variables. +# +# The ':=' operator expands the variable name twice. In one of these +# expansions, the undefined variable expression is preserved (controlled by +# preserveUndefined in VarAssign_EvalSubst), in the other expansion it expands +# to an empty string. This way, 2 variables are created using a single +# variable assignment. It's magic. :-/ +.MAKEFLAGS: -dv +VAR_ASSIGN_${UNDEF}= undef value +VAR_SUBST_${UNDEF}:= undef value +.MAKEFLAGS: -d0 + all: @:; diff --git a/unit-tests/var-op-shell.exp b/unit-tests/var-op-shell.exp index 39a9383953dd..caea85ab5daa 100644 --- a/unit-tests/var-op-shell.exp +++ b/unit-tests/var-op-shell.exp @@ -1 +1,7 @@ +make: "var-op-shell.mk" line 28: warning: "echo "failed"; false" returned non-zero status +make: "var-op-shell.mk" line 34: warning: "false" returned non-zero status +make: "var-op-shell.mk" line 59: warning: "kill -14 $$" exited on a signal +/bin/no/such/command: not found +make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status +stderr exit status 0 diff --git a/unit-tests/var-op-shell.mk b/unit-tests/var-op-shell.mk index 83580a89e6c2..7b52513e1131 100644 --- a/unit-tests/var-op-shell.mk +++ b/unit-tests/var-op-shell.mk @@ -1,9 +1,84 @@ -# $NetBSD: var-op-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-op-shell.mk,v 1.3 2020/11/09 20:39:46 rillig Exp $ # # Tests for the != variable assignment operator, which runs its right-hand # side through the shell. -# TODO: Implementation +# The variable OUTPUT gets the output from running the shell command. +OUTPUT!= echo "success"'ful' +.if ${OUTPUT} != "successful" +. error +.endif + +# Since 2014-08-20, the output of the shell command may be empty. +# +# On 1996-05-29, when the '!=' assignment operator and Cmd_Exec were added, +# an empty output produced the error message "Couldn't read shell's output +# for \"%s\"". +# +# The error message is still there but reserved for technical errors. +# It may be possible to trigger the error message by killing the shell after +# reading part of its output. +OUTPUT!= true +.if ${OUTPUT} != "" +. error +.endif + +# The output of a shell command that failed is processed nevertheless. +# TODO: Make this an error in lint mode. +OUTPUT!= echo "failed"; false +.if ${OUTPUT} != "failed" +. error +.endif + +# A command with empty output may fail as well. +OUTPUT!= false +.if ${OUTPUT} != "" +. error +.endif + +# In the output of the command, each newline is replaced with a space. +# Except for the very last one, which is discarded. +OUTPUT!= echo "line 1"; echo "line 2" +.if ${OUTPUT} != "line 1 line 2" +. error +.endif + +# A failing command in the middle results in the exit status 0, which in the +# end means that the whole sequence of commands succeeded. +OUTPUT!= echo "before"; false; echo "after" +.if ${OUTPUT} != "before after" +. error +.endif + +# NB: The signal number must be numeric since some shells (which ones?) don't +# accept symbolic signal names. 14 is typically SIGALRM. +# +# XXX: The number of the signal is not mentioned in the warning since that +# would have been difficult to implement; currently the errfmt is a format +# string containing a single %s conversion. +OUTPUT!= kill -14 $$$$ +.if ${OUTPUT} != "" +. error +.endif + +# A nonexistent command produces a non-zero exit status. +OUTPUT!= /bin/no/such/command +.if ${OUTPUT} != "" +. error +.endif + +# The output from the shell's stderr is not captured, it just passes through. +OUTPUT!= echo "stdout"; echo "stderr" 1>&2 +.if ${OUTPUT} != "stdout" +. error +.endif + +# The 8 dollar signs end up as 4 dollar signs when expanded. The shell sees +# the command "echo '$$$$'". The 4 dollar signs are stored in OUTPUT, and +# when that variable is expanded, they expand to 2 dollar signs. +OUTPUT!= echo '$$$$$$$$' +.if ${OUTPUT} != "\$\$" +. error +.endif all: - @:; diff --git a/unit-tests/var-op-sunsh.mk b/unit-tests/var-op-sunsh.mk index efef19bf1567..0e16b2b42d34 100644 --- a/unit-tests/var-op-sunsh.mk +++ b/unit-tests/var-op-sunsh.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-sunsh.mk,v 1.5 2020/10/04 08:32:52 rillig Exp $ +# $NetBSD: var-op-sunsh.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :sh= variable assignment operator, which runs its right-hand # side through the shell. It is a seldom-used alternative to the != @@ -118,5 +118,7 @@ VAR :sh += echo two . error ${VAR} .endif +# TODO: test VAR:sh!=command + all: @:; diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp index 474a46ee1b5c..06c8b590e1b1 100644 --- a/unit-tests/vardebug.exp +++ b/unit-tests/vardebug.exp @@ -77,7 +77,7 @@ make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown}) Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED}) Global:delete .SHELL (not found) -Command:.SHELL = /bin/sh +Command:.SHELL = </path/to/shell> Command:.SHELL = overwritten ignored (read-only) Global:.MAKEFLAGS = -r -k -d v -d Global:.MAKEFLAGS = -r -k -d v -d 0 diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk index e2d53129a932..aced1a0554d1 100644 --- a/unit-tests/varmisc.mk +++ b/unit-tests/varmisc.mk @@ -1,5 +1,5 @@ -# $Id: varmisc.mk,v 1.20 2020/10/26 17:43:57 sjg Exp $ -# $NetBSD: varmisc.mk,v 1.26 2020/10/24 08:50:17 rillig Exp $ +# $Id: varmisc.mk,v 1.21 2020/11/11 23:08:50 sjg Exp $ +# $NetBSD: varmisc.mk,v 1.28 2020/11/07 00:07:02 rillig Exp $ # # Miscellaneous variable tests. @@ -88,7 +88,7 @@ VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif -# begin .MAKE.SAVE_DOLLARS; see Var_Set_with_flags and s2Boolean. +# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean. SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off SD_4_DOLLARS= $$$$ diff --git a/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp index 39a9383953dd..7f61cc426305 100644 --- a/unit-tests/varmod-defined.exp +++ b/unit-tests/varmod-defined.exp @@ -1 +1,23 @@ +Global:8_DOLLARS = $$$$$$$$ +Global:VAR = +Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR +Global:VAR = $$$$$$$$ +Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR +Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR +Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Global:VAR = $$$$$$$$ +Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR +Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Modifier part: "var" +Modifier part: "${8_DOLLARS}" +ModifyWords: split "$$$$$$$$" into 1 words +Global:var = $$$$$$$$ +Var_Parse: ${8_DOLLARS} with VARE_WANTRES +ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$" +Global:delete var +Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none) +Global:VAR = $$$$ +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/varmod-defined.mk b/unit-tests/varmod-defined.mk index a722ebf666e6..59b9d79d754b 100644 --- a/unit-tests/varmod-defined.mk +++ b/unit-tests/varmod-defined.mk @@ -1,8 +1,10 @@ -# $NetBSD: varmod-defined.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.9 2020/11/12 00:40:55 rillig Exp $ # # Tests for the :D variable modifier, which returns the given string # if the variable is defined. It is closely related to the :U modifier. +.MAKE.SAVE_DOLLARS= yes + DEF= defined .undef UNDEF @@ -85,5 +87,19 @@ DEF= defined # TODO: Add more tests for parsing the plain text part, to cover each branch # of ApplyModifier_Defined. +# The :D and :U modifiers behave differently from the :@var@ modifier in +# that they preserve dollars in a ':=' assignment. This is because +# ApplyModifier_Defined passes the eflags unmodified to Var_Parse, unlike +# ApplyModifier_Loop, which uses ParseModifierPart, which in turn removes +# VARE_KEEP_DOLLAR from eflags. +# +# XXX: This inconsistency is documented nowhere. +.MAKEFLAGS: -dv +8_DOLLARS= $$$$$$$$ +VAR:= ${8_DOLLARS} +VAR:= ${VAR:D${8_DOLLARS}} +VAR:= ${VAR:@var@${8_DOLLARS}@} +.MAKEFLAGS: -d0 + all: @:; diff --git a/unit-tests/varmod-exclam-shell.mk b/unit-tests/varmod-exclam-shell.mk index eaad8275805f..14b3d2510b72 100644 --- a/unit-tests/varmod-exclam-shell.mk +++ b/unit-tests/varmod-exclam-shell.mk @@ -1,27 +1,36 @@ -# $NetBSD: varmod-exclam-shell.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-exclam-shell.mk,v 1.4 2020/11/03 18:42:33 rillig Exp $ # -# Tests for the :!cmd! variable modifier. +# Tests for the :!cmd! variable modifier, which evaluates the modifier +# argument, independent of the value or the name of the original variable. .if ${:!echo hello | tr 'l' 'l'!} != "hello" -. warning unexpected +. error .endif # The output is truncated at the first null byte. # Cmd_Exec returns only a string pointer without length information. +# Truncating the output is not necessarily intended but may also be a side +# effect from the implementation. Having null bytes in the output of a +# shell command is so unusual that it doesn't matter in practice. .if ${:!echo hello | tr 'l' '\0'!} != "he" -. warning unexpected +. error .endif +# The newline at the end of the output is stripped. .if ${:!echo!} != "" -. warning A newline at the end of the output must be stripped. +. error .endif +# Only the final newline of the output is stripped. All other newlines are +# converted to spaces. .if ${:!echo;echo!} != " " -. warning Only a single newline at the end of the output is stripped. +. error .endif +# Each newline in the output is converted to a space, except for the newline +# at the end of the output, which is stripped. .if ${:!echo;echo;echo;echo!} != " " -. warning Other newlines in the output are converted to spaces. +. error .endif all: diff --git a/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp index ac1527a11c9e..75518c08117f 100644 --- a/unit-tests/varmod-ifelse.exp +++ b/unit-tests/varmod-ifelse.exp @@ -3,6 +3,14 @@ make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expressi make: Bad conditional expression ` == ""' in == ""?bad-assign:bad-assign make: Bad conditional expression ` == ""' in == ""?bad-cond:bad-cond make: "varmod-ifelse.mk" line 44: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) +make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no +make: "varmod-ifelse.mk" line 66: Malformed conditional (${1 == == 2:?yes:no} != "") +CondParser_Eval: "${1 == == 2:?yes:no}" != "" +CondParser_Eval: 1 == == 2 +lhs = 1.000000, rhs = 0.000000, op = == +make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no +lhs = "", rhs = "", op = != +make: "varmod-ifelse.mk" line 92: warning: Oops, the parse error should have been propagated. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk index 8bd67195282c..ea94dc875e4d 100644 --- a/unit-tests/varmod-ifelse.mk +++ b/unit-tests/varmod-ifelse.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-ifelse.mk,v 1.5 2020/10/23 14:24:51 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.6 2020/11/12 00:29:55 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. @@ -57,5 +57,41 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} . error .endif +# This line generates 2 error messages. The first comes from evaluating the +# malformed conditional "1 == == 2", which is reported as "Bad conditional +# expression" by ApplyModifier_IfElse. The variable expression containing that +# conditional therefore returns a parse error from Var_Parse, and this parse +# error propagates to CondEvalExpression, where the "Malformed conditional" +# comes from. +.if ${1 == == 2:?yes:no} != "" +. error +.else +. error +.endif + +# If the "Bad conditional expression" appears in a quoted string literal, the +# error message "Malformed conditional" is not printed, leaving only the "Bad +# conditional expression". +# +# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse +# being called without VARE_UNDEFERR being set. When ApplyModifier_IfElse +# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the +# value of the variable expression is still undefined. CondParser_String is +# then supposed to do proper error handling, but since varUndefined is local +# to var.c, it cannot distinguish this return value from an ordinary empty +# string. The left-hand side of the comparison is therefore just an empty +# string, which is obviously equal to the empty string on the right-hand side. +# +# XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The +# condition should be detected as being malformed before any comparison is +# done since there is no well-formed comparison in the condition at all. +.MAKEFLAGS: -dc +.if "${1 == == 2:?yes:no}" != "" +. error +.else +. warning Oops, the parse error should have been propagated. +.endif +.MAKEFLAGS: -d0 + all: @:; diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp index 497de68df82c..66cfd6f51e16 100644 --- a/unit-tests/varmod-loop.exp +++ b/unit-tests/varmod-loop.exp @@ -1,3 +1,11 @@ +ParseReadLine (117): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$' +CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +lhs = "$$$$ $$$$ $$$$", rhs = "$$$$ $$$$ $$$$", op = != +ParseReadLine (122): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}' +CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +lhs = "$$ $$$$ $$$$", rhs = "$$ $$$$ $$$$", op = != +ParseReadLine (147): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) :+one+ +two+ +three+: :x1y x2y x3y: :x1y x2y x3y: diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk index a3fbbfceae8d..654c449d7bfa 100644 --- a/unit-tests/varmod-loop.mk +++ b/unit-tests/varmod-loop.mk @@ -1,7 +1,9 @@ -# $NetBSD: varmod-loop.mk,v 1.5 2020/10/31 12:34:03 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.8 2020/11/12 00:40:55 rillig Exp $ # # Tests for the :@var@...${var}...@ variable modifier. +.MAKE.SAVE_DOLLARS= yes + all: mod-loop-varname all: mod-loop-resolve all: mod-loop-varname-dollar @@ -57,7 +59,7 @@ mod-loop-varname-dollar: @echo $@:${1 2 3:L:@v$$@($v)@:Q}. @echo $@:${1 2 3:L:@v$$$@($v)@:Q}. -# Demonstrate that it is possible to generate dollar characters using the +# Demonstrate that it is possible to generate dollar signs using the # :@ modifier. # # These are edge cases that could have resulted in a parse error as well @@ -100,3 +102,46 @@ mod-loop-dollar: .if defined(var) . error .endif + +# Assignment using the ':=' operator, combined with the :@var@ modifier +# +8_DOLLARS= $$$$$$$$ +# This string literal is written with 8 dollars, and this is saved as the +# variable value. But as soon as this value is evaluated, it goes through +# Var_Subst, which replaces each '$$' with a single '$'. This could be +# prevented by VARE_KEEP_DOLLAR, but that flag is usually removed before +# expanding subexpressions. See ApplyModifier_Loop and ParseModifierPart +# for examples. +# +.MAKEFLAGS: -dcp +USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ +.if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +. error +.endif +# +SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} +# The ':=' assignment operator evaluates the variable value using the flag +# VARE_KEEP_DOLLAR, which means that some dollar signs are preserved, but not +# all. The dollar signs in the top-level expression and in the indirect +# ${8_DOLLARS} are preserved. +# +# The variable modifier :@var@ does not preserve the dollar signs though, no +# matter in which context it is evaluated. What happens in detail is: +# First, the modifier part "${8_DOLLARS}" is parsed without expanding it. +# Next, each word of the value is expanded on its own, and at this moment +# in ApplyModifier_Loop, the VARE_KEEP_DOLLAR flag is not passed down to +# ModifyWords, resulting in "$$$$" for the first word of USE_8_DOLLARS. +# +# The remaining words of USE_8_DOLLARS are not affected by any variable +# modifier and are thus expanded with the flag VARE_KEEP_DOLLAR in action. +# The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value +# "$$$$ $$$$$$$$ $$$$$$$$". +# +# The variable expression in the condition then expands this raw stored value +# once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no +# longer take place since they had only been active during the evaluation of +# the variable assignment. +.if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +. error +.endif +.MAKEFLAGS: -d0 diff --git a/unit-tests/varmod-match.mk b/unit-tests/varmod-match.mk index 5e16a9cc8bf1..9b56fb451eda 100644 --- a/unit-tests/varmod-match.mk +++ b/unit-tests/varmod-match.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-match.mk,v 1.5 2020/09/13 05:36:26 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.6 2020/11/15 18:33:41 rillig Exp $ # # Tests for the :M variable modifier, which filters words that match the # given pattern. @@ -51,5 +51,10 @@ ${:U*}= asterisk . error .endif +# TODO: ${VAR:M(((}}}} +# TODO: ${VAR:M{{{)))} +# TODO: ${VAR:M${UNBALANCED}} +# TODO: ${VAR:M${:U(((\}\}\}}} + all: @:; diff --git a/unit-tests/varmod-order-shuffle.mk b/unit-tests/varmod-order-shuffle.mk index 4ad057e0a810..185141b6c4a5 100644 --- a/unit-tests/varmod-order-shuffle.mk +++ b/unit-tests/varmod-order-shuffle.mk @@ -1,8 +1,10 @@ -# $NetBSD: varmod-order-shuffle.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-order-shuffle.mk,v 1.6 2020/11/09 20:16:33 rillig Exp $ # # Tests for the :Ox variable modifier, which returns the words of the # variable, shuffled. # +# The variable modifier :Ox is available since 2005-06-01. +# # As of 2020-08-16, make uses random(3) seeded by the current time in seconds. # This makes the random numbers completely predictable since there is no other # part of make that uses random numbers. diff --git a/unit-tests/varmod-shell.exp b/unit-tests/varmod-shell.exp index 39a9383953dd..9aef0c9e5acc 100644 --- a/unit-tests/varmod-shell.exp +++ b/unit-tests/varmod-shell.exp @@ -1 +1,3 @@ +make: "echo word; false" returned non-zero status +make: "echo word; false" returned non-zero status exit status 0 diff --git a/unit-tests/varmod-shell.mk b/unit-tests/varmod-shell.mk index 052968004f1b..db82e302f2a8 100644 --- a/unit-tests/varmod-shell.mk +++ b/unit-tests/varmod-shell.mk @@ -1,9 +1,35 @@ -# $NetBSD: varmod-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.5 2020/11/17 20:11:02 rillig Exp $ # # Tests for the :sh variable modifier, which runs the shell command # given by the variable value and returns its output. +# +# This modifier has been added on 2000-04-29. +# +# See also: +# ApplyModifier_ShellCommand # TODO: Implementation +# The command to be run is enclosed between exclamation marks. +# The previous value of the expression is irrelevant for this modifier. +# The :!cmd! modifier turns an undefined expression into a defined one. +.if ${:!echo word!} != "word" +. error +.endif + +# If the command exits with non-zero, an error message is printed. +# XXX: Processing continues as usual though. +# +# Between 2000-04-29 and 2020-11-17, the error message mentioned the previous +# value of the expression (which is usually an empty string) instead of the +# command that was executed. It's strange that such a simple bug could +# survive such a long time. +.if ${:!echo word; false!} != "word" +. error +.endif +.if ${:Uprevious value:!echo word; false!} != "word" +. error +.endif + all: @:; diff --git a/unit-tests/varmod-subst.exp b/unit-tests/varmod-subst.exp index e752fb8058a8..3122c17b1ed3 100644 --- a/unit-tests/varmod-subst.exp +++ b/unit-tests/varmod-subst.exp @@ -9,28 +9,39 @@ mod-subst-delimiter: 1 two 3 horizontal tabulator 1 two 3 space 1 two 3 exclamation mark -1 two 3 double quotes -1 two 3 hash -1 two 3 dollar -1 two 3 percent +1 two 3 quotation mark +1 two 3 number sign +1 two 3 dollar sign +1 two 3 percent sign +1 two 3 ampersand 1 two 3 apostrophe -1 two 3 opening parenthesis -1 two 3 closing parenthesis +1 two 3 left parenthesis +1 two 3 right parenthesis +1 two 3 asterisk +1 two 3 plus sign +1 two 3 comma +1 two 3 hyphen-minus +1 two 3 full stop +1 two 3 solidus 1 two 3 digit 1 two 3 colon -1 two 3 less than sign -1 two 3 equal sign -1 two 3 greater than sign +1 two 3 semicolon +1 two 3 less-than sign +1 two 3 equals sign +1 two 3 greater-than sign 1 two 3 question mark -1 two 3 at -1 two 3 letter -1 two 3 opening bracket -1 two 3 backslash -1 two 3 closing bracket -1 two 3 caret -1 two 3 opening brace +1 two 3 commercial at +1 two 3 capital letter +1 two 3 left square bracket +1 two 3 reverse solidus +1 two 3 right square bracket +1 two 3 circumflex accent +1 two 3 low line +1 two 3 grave accent +1 two 3 small letter +1 two 3 left curly bracket 1 two 3 vertical line -1 two 3 closing brace +1 two 3 right curly bracket 1 two 3 tilde mod-subst-chain: A B c. diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk index 5fc657ea10ee..3c3ee673c07a 100644 --- a/unit-tests/varmod-subst.mk +++ b/unit-tests/varmod-subst.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :S,from,to, variable modifier. @@ -8,55 +8,72 @@ all: mod-subst-chain all: mod-subst-dollar WORDS= sequences of letters + .if ${WORDS:S,,,} != ${WORDS} . warning The empty pattern matches something. .endif + .if ${WORDS:S,e,*,1} != "s*quences of letters" . warning The :S modifier flag '1' is not applied exactly once. .endif + .if ${WORDS:S,f,*,1} != "sequences o* letters" . warning The :S modifier flag '1' is only applied to the first word,\ not to the first occurrence. .endif + .if ${WORDS:S,e,*,} != "s*quences of l*tters" . warning The :S modifier does not replace every first match per word. .endif + .if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs" . warning The :S modifier flag 'g' does not replace every occurrence. .endif + .if ${WORDS:S,^sequ,occurr,} != "occurrences of letters" . warning The :S modifier fails for a short match anchored at the start. .endif + .if ${WORDS:S,^of,with,} != "sequences with letters" . warning The :S modifier fails for an exact match anchored at the start. .endif + .if ${WORDS:S,^office,does not match,} != ${WORDS} . warning The :S modifier matches a too long pattern anchored at the start. .endif + .if ${WORDS:S,f$,r,} != "sequences or letters" . warning The :S modifier fails for a short match anchored at the end. .endif + .if ${WORDS:S,s$,,} != "sequence of letter" . warning The :S modifier fails to replace one occurrence per word. .endif + .if ${WORDS:S,of$,,} != "sequences letters" . warning The :S modifier fails for an exact match anchored at the end. .endif + .if ${WORDS:S,eof$,,} != ${WORDS} . warning The :S modifier matches a too long pattern anchored at the end. .endif + .if ${WORDS:S,^of$,,} != "sequences letters" . warning The :S modifier does not match a word anchored at both ends. .endif + .if ${WORDS:S,^o$,,} != ${WORDS} . warning The :S modifier matches a prefix anchored at both ends. .endif + .if ${WORDS:S,^f$,,} != ${WORDS} . warning The :S modifier matches a suffix anchored at both ends. .endif + .if ${WORDS:S,^eof$,,} != ${WORDS} . warning The :S modifier matches a too long prefix anchored at both ends. .endif + .if ${WORDS:S,^office$,,} != ${WORDS} . warning The :S modifier matches a too long suffix anchored at both ends. .endif @@ -78,30 +95,41 @@ mod-subst-delimiter: @echo ${:U1 2 3:S 2 two :Q} horizontal tabulator @echo ${:U1 2 3:S 2 two :Q} space @echo ${:U1 2 3:S!2!two!:Q} exclamation mark - @echo ${:U1 2 3:S"2"two":Q} double quotes + @echo ${:U1 2 3:S"2"two":Q} quotation mark # In shell command lines, the hash does not need to be escaped. # It needs to be escaped in variable assignment lines though. - @echo ${:U1 2 3:S#2#two#:Q} hash - @echo ${:U1 2 3:S$2$two$:Q} dollar - @echo ${:U1 2 3:S%2%two%:Q} percent + @echo ${:U1 2 3:S#2#two#:Q} number sign + @echo ${:U1 2 3:S$2$two$:Q} dollar sign + @echo ${:U1 2 3:S%2%two%:Q} percent sign + @echo ${:U1 2 3:S&2&two&:Q} ampersand @echo ${:U1 2 3:S'2'two':Q} apostrophe - @echo ${:U1 2 3:S(2(two(:Q} opening parenthesis - @echo ${:U1 2 3:S)2)two):Q} closing parenthesis + @echo ${:U1 2 3:S(2(two(:Q} left parenthesis + @echo ${:U1 2 3:S)2)two):Q} right parenthesis + @echo ${:U1 2 3:S*2*two*:Q} asterisk + @echo ${:U1 2 3:S+2+two+:Q} plus sign + @echo ${:U1 2 3:S,2,two,:Q} comma + @echo ${:U1 2 3:S-2-two-:Q} hyphen-minus + @echo ${:U1 2 3:S.2.two.:Q} full stop + @echo ${:U1 2 3:S/2/two/:Q} solidus @echo ${:U1 2 3:S121two1:Q} digit @echo ${:U1 2 3:S:2:two::Q} colon - @echo ${:U1 2 3:S<2<two<:Q} less than sign - @echo ${:U1 2 3:S=2=two=:Q} equal sign - @echo ${:U1 2 3:S>2>two>:Q} greater than sign + @echo ${:U1 2 3:S;2;two;:Q} semicolon + @echo ${:U1 2 3:S<2<two<:Q} less-than sign + @echo ${:U1 2 3:S=2=two=:Q} equals sign + @echo ${:U1 2 3:S>2>two>:Q} greater-than sign @echo ${:U1 2 3:S?2?two?:Q} question mark - @echo ${:U1 2 3:S@2@two@:Q} at - @echo ${:U1 2 3:Sa2atwoa:Q} letter - @echo ${:U1 2 3:S[2[two[:Q} opening bracket - @echo ${:U1 2 3:S\2\two\:Q} backslash - @echo ${:U1 2 3:S]2]two]:Q} closing bracket - @echo ${:U1 2 3:S^2^two^:Q} caret - @echo ${:U1 2 3:S{2{two{:Q} opening brace + @echo ${:U1 2 3:S@2@two@:Q} commercial at + @echo ${:U1 2 3:SA2AtwoA:Q} capital letter + @echo ${:U1 2 3:S[2[two[:Q} left square bracket + @echo ${:U1 2 3:S\2\two\:Q} reverse solidus + @echo ${:U1 2 3:S]2]two]:Q} right square bracket + @echo ${:U1 2 3:S^2^two^:Q} circumflex accent + @echo ${:U1 2 3:S_2_two_:Q} low line + @echo ${:U1 2 3:S`2`two`:Q} grave accent + @echo ${:U1 2 3:Sa2atwoa:Q} small letter + @echo ${:U1 2 3:S{2{two{:Q} left curly bracket @echo ${:U1 2 3:S|2|two|:Q} vertical line - @echo ${:U1 2 3:S}2}two}:Q} closing brace + @echo ${:U1 2 3:S}2}two}:Q} right curly bracket @echo ${:U1 2 3:S~2~two~:Q} tilde # The :S and :C modifiers can be chained without a separating ':'. @@ -121,7 +149,7 @@ mod-subst-chain: # modifiers with the matching modifiers. @echo ${:Uvalue:S,a,x,i}. -# No matter how many dollar characters there are, they all get merged +# No matter how many dollar signs there are, they all get merged # into a single dollar by the :S modifier. # # As of 2020-08-09, this is because ParseModifierPart sees a '$' and @@ -145,7 +173,7 @@ mod-subst-dollar: @echo $@:${:U40:S,^,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$,:Q}: # This generates no dollar at all: @echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}: -# Here is an alternative way to generate dollar characters. +# Here is an alternative way to generate dollar signs. # It's unexpectedly complicated though. @echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}: # In modifiers, dollars are escaped using the backslash, not using another diff --git a/unit-tests/varmod-to-abs.exp b/unit-tests/varmod-to-abs.exp index 39a9383953dd..426b4d39744f 100644 --- a/unit-tests/varmod-to-abs.exp +++ b/unit-tests/varmod-to-abs.exp @@ -1 +1,5 @@ +make: "varmod-to-abs.mk" line 18: does-not-exist.c +make: "varmod-to-abs.mk" line 19: does-not-exist.c +cached_realpath: varmod-to-abs.mk -> varmod-to-abs.mk +make: "varmod-to-abs.mk" line 23: varmod-to-abs.mk exit status 0 diff --git a/unit-tests/varmod-to-abs.mk b/unit-tests/varmod-to-abs.mk index 7a74e89088e5..7f23318487e3 100644 --- a/unit-tests/varmod-to-abs.mk +++ b/unit-tests/varmod-to-abs.mk @@ -1,9 +1,28 @@ -# $NetBSD: varmod-to-abs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varmod-to-abs.mk,v 1.5 2020/11/15 05:48:17 rillig Exp $ # # Tests for the :tA variable modifier, which returns the absolute path for # each of the words in the variable value. # TODO: Implementation +# Between 2016-06-03 and 2020-11-14, it was possible to trick the :tA modifier +# into resolving completely unrelated absolute paths by defining a global +# variable with the same name as the path that is to be resolved. There were +# a few restrictions though: The "redirected" path had to start with a slash, +# and it had to exist (see ModifyWord_Realpath). +# +# This unintended behavior was caused by cached_realpath using a GNode for +# keeping the cache, just like the GNode for global variables. +.MAKEFLAGS: -dd +does-not-exist.c= /dev/null +.info ${does-not-exist.c:L:tA} +.info ${does-not-exist.c:L:tA} + +# The output of the following line is modified by the global _SED_CMDS in +# unit-tests/Makefile. See the .rawout file for the truth. +.info ${MAKEFILE:tA} + +.MAKEFLAGS: -d0 + all: @:; diff --git a/unit-tests/varmod-to-lower.mk b/unit-tests/varmod-to-lower.mk index 6ab4af740fae..19d3406054b7 100644 --- a/unit-tests/varmod-to-lower.mk +++ b/unit-tests/varmod-to-lower.mk @@ -1,7 +1,9 @@ -# $NetBSD: varmod-to-lower.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-to-lower.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :tl variable modifier, which returns the words in the # variable value, converted to lowercase. +# +# TODO: What about non-ASCII characters? ISO-8859-1, UTF-8? .if ${:UUPPER:tl} != "upper" . error diff --git a/unit-tests/varmod-to-separator.mk b/unit-tests/varmod-to-separator.mk index 89aa3d978bee..08c6126ecc68 100644 --- a/unit-tests/varmod-to-separator.mk +++ b/unit-tests/varmod-to-separator.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-to-separator.mk,v 1.6 2020/11/01 14:36:25 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. @@ -166,4 +166,10 @@ WORDS= one two three four five six . info This line is not reached. .endif +# TODO: This modifier used to accept decimal numbers as well, in the form +# ':ts\120'. When has this been changed to octal, and what happens now +# for ':ts\90' ('Z' in decimal ASCII, undefined in octal)? + +# TODO: :ts\x1F600 + all: diff --git a/unit-tests/varmod-undefined.mk b/unit-tests/varmod-undefined.mk index 1beaa763716b..e06fc73244ab 100644 --- a/unit-tests/varmod-undefined.mk +++ b/unit-tests/varmod-undefined.mk @@ -1,9 +1,10 @@ -# $NetBSD: varmod-undefined.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-undefined.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ # # Tests for the :U variable modifier, which returns the given string # if the variable is undefined. # # See also: +# directive-for.mk # varmod-defined.mk # The pattern ${:Uword} is heavily used when expanding .for loops. diff --git a/unit-tests/varmod.exp b/unit-tests/varmod.exp index e4257bb0b596..a80979e1410d 100644 --- a/unit-tests/varmod.exp +++ b/unit-tests/varmod.exp @@ -1,6 +1,8 @@ make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """ make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """ make: "varmod.mk" line 47: Dollar followed by nothing +make: "varmod.mk" line 56: Missing delimiter ':' after modifier "P" +make: "varmod.mk" line 57: Unknown directive "error" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk index 0a2a4c52c29b..b496bdd206a2 100644 --- a/unit-tests/varmod.mk +++ b/unit-tests/varmod.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod.mk,v 1.3 2020/09/13 07:42:20 rillig Exp $ +# $NetBSD: varmod.mk,v 1.4 2020/11/02 17:30:22 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. @@ -48,4 +48,13 @@ DOLLAR2= ${:U\$} . error .endif +# The variable modifier :P does not fall back to the SysV modifier. +# Therefore the modifier :P=RE generates a parse error. +# XXX: The .error should not be reached since the variable expression is +# malformed. +VAR= STOP +.if ${VAR:P=RE} != "STORE" +. error +.endif + all: # nothing diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp index 704d4863d4e9..f219515444cf 100755 --- a/unit-tests/varname-dot-shell.exp +++ b/unit-tests/varname-dot-shell.exp @@ -1,6 +1,6 @@ ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' Global:ORIG_SHELL = -Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_ASSIGN +Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR Global:delete .SHELL (not found) Command:.SHELL = (details omitted) Global:ORIG_SHELL = (details omitted) diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp index ba465cc3eff2..24c596e1c568 100644 --- a/unit-tests/varname-empty.exp +++ b/unit-tests/varname-empty.exp @@ -1,5 +1,5 @@ Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored -Var_Set("", "cmline-plain", ...) name expands to empty string - ignored +Var_Set("", "cmdline-plain", ...) name expands to empty string - ignored Var_Set("", "default", ...) name expands to empty string - ignored Var_Set("", "assigned", ...) name expands to empty string - ignored Var_Set("", "appended", ...) name expands to empty string - ignored diff --git a/unit-tests/varname-makefile.exp b/unit-tests/varname-makefile.exp index 39a9383953dd..67919c400193 100755 --- a/unit-tests/varname-makefile.exp +++ b/unit-tests/varname-makefile.exp @@ -1 +1,2 @@ +: In the end, MAKEFILE is /dev/null. exit status 0 diff --git a/unit-tests/varname-makefile.mk b/unit-tests/varname-makefile.mk index 785fe9301df2..641c53de9cf8 100755 --- a/unit-tests/varname-makefile.mk +++ b/unit-tests/varname-makefile.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-makefile.mk,v 1.2 2020/09/05 06:25:38 rillig Exp $ +# $NetBSD: varname-makefile.mk,v 1.3 2020/11/09 22:36:44 rillig Exp $ # # Tests for the special MAKEFILE variable, which contains the current # makefile from the -f command line option. @@ -41,4 +41,9 @@ MAKEFILE= overwritten .endif all: - @:; + # MAKEFILE is the file that appeared last in the command line. + : In the end, MAKEFILE is ${MAKEFILE}. + +# Additional makefiles can be added while reading a makefile. They will be +# read in order. +.MAKEFLAGS: -f /dev/null diff --git a/unit-tests/varname-vpath.exp b/unit-tests/varname-vpath.exp index 39a9383953dd..bf7a3036e99d 100644 --- a/unit-tests/varname-vpath.exp +++ b/unit-tests/varname-vpath.exp @@ -1 +1,12 @@ +CondParser_Eval: !defined(TEST_MAIN) +CondParser_Eval: exists(file-in-subdirectory) +exists(file-in-subdirectory) result is "" +CondParser_Eval: exists(file2-in-subdirectory) +exists(file2-in-subdirectory) result is "" +CondParser_Eval: exists(file-in-subdirectory) +exists(file-in-subdirectory) result is "varname-vpath.dir/file-in-subdirectory" +: yes 1 +CondParser_Eval: exists(file2-in-subdirectory) +exists(file2-in-subdirectory) result is "varname-vpath.dir2/file2-in-subdirectory" +: yes 2 exit status 0 diff --git a/unit-tests/varname-vpath.mk b/unit-tests/varname-vpath.mk index 8924647a5072..65fe9945c202 100644 --- a/unit-tests/varname-vpath.mk +++ b/unit-tests/varname-vpath.mk @@ -1,8 +1,42 @@ -# $NetBSD: varname-vpath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: varname-vpath.mk,v 1.3 2020/11/10 00:19:19 rillig Exp $ # -# Tests for the special VPATH variable. +# Tests for the special VPATH variable, which is an obsolete way of +# specifying a colon-separated search path. This search path is not active +# when the makefiles are read, but only later when the shell commands are run. +# +# Instead of the VPATH, better use the -I option or the special target .PATH. + +.if !defined(TEST_MAIN) + +all: .SILENT + rm -rf varname-vpath.dir + mkdir varname-vpath.dir + touch varname-vpath.dir/file-in-subdirectory + rm -rf varname-vpath.dir2 + mkdir varname-vpath.dir2 + touch varname-vpath.dir2/file2-in-subdirectory + + TEST_MAIN=yes VPATH=varname-vpath.dir:varname-vpath.dir2 \ + ${MAKE} -f ${MAKEFILE} -dc -# TODO: Implementation + rm -r varname-vpath.dir + rm -r varname-vpath.dir2 + +.else + +# The VPATH variable does not take effect at parse time. +# It is evaluated only once, between reading the makefiles and making the +# targets. Therefore it could also be an ordinary variable, it doesn't need +# to be an environment variable or a command line variable. +. if exists(file-in-subdirectory) +. error +. endif +. if exists(file2-in-subdirectory) +. error +. endif all: - @:; + : ${exists(file-in-subdirectory):L:?yes 1:no 1} + : ${exists(file2-in-subdirectory):L:?yes 2:no 2} + +.endif diff --git a/unit-tests/varname.exp b/unit-tests/varname.exp index 39a9383953dd..93962d7fb7b7 100644 --- a/unit-tests/varname.exp +++ b/unit-tests/varname.exp @@ -1 +1,24 @@ -exit status 0 +Global:VAR{{{}}} = 3 braces +Var_Parse: ${VAR{{{}}}}" != "3 braces" with VARE_WANTRES +Global:VARNAME = VAR((( +Var_Parse: ${VARNAME} with VARE_WANTRES +Global:VAR((( = 3 open parentheses +Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" with VARE_WANTRES +Var_Parse: ${:UVAR(((}= try1 with VARE_UNDEFERR|VARE_WANTRES +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UVAR(((} is "VAR(((" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Global:.ALLTARGETS = VAR(((=) +make: "varname.mk" line 30: No closing parenthesis in archive specification +make: "varname.mk" line 30: Error in archive specification: "VAR" +Var_Parse: ${:UVAR\(\(\(}= try2 with VARE_UNDEFERR|VARE_WANTRES +Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF) +Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF) +Global:.ALLTARGETS = VAR(((=) VAR\(\(\(= +make: "varname.mk" line 35: Need an operator +Var_Parse: ${VARNAME} with VARE_WANTRES +Global:VAR((( = try3 +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/varname.mk b/unit-tests/varname.mk index e9fbc89873ca..f586c7602cb7 100644 --- a/unit-tests/varname.mk +++ b/unit-tests/varname.mk @@ -1,8 +1,44 @@ -# $NetBSD: varname.mk,v 1.4 2020/10/18 08:47:54 rillig Exp $ +# $NetBSD: varname.mk,v 1.8 2020/11/02 22:59:48 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. +# And for variable names in general. -# TODO: Implementation +.MAKEFLAGS: -dv + +# In variable names, braces are allowed, but they must be balanced. +# Parentheses and braces may be mixed. +VAR{{{}}}= 3 braces +.if "${VAR{{{}}}}" != "3 braces" +. error +.endif + +# In variable expressions, the parser works differently. It doesn't treat +# braces and parentheses equally, therefore the first closing brace already +# marks the end of the variable name. +VARNAME= VAR((( +${VARNAME}= 3 open parentheses +.if "${VAR(((}}}}" != "3 open parentheses}}}" +. error +.endif + +# In the above test, the variable name is constructed indirectly. Neither +# of the following expressions produces the intended effect. +# +# This is not a variable assignment since the parentheses and braces are not +# balanced. At the end of the line, there are still 3 levels open, which +# means the variable name is not finished. +${:UVAR(((}= try1 +# On the left-hand side of a variable assignments, the backslash is not parsed +# as an escape character, therefore the parentheses still count to the nesting +# level, which at the end of the line is still 3. Therefore this is not a +# variable assignment as well. +${:UVAR\(\(\(}= try2 +# To assign to a variable with an arbitrary name, the variable name has to +# come from an external source, not the text that is parsed in the assignment +# itself. This is exactly the reason why further above, the indirect +# ${VARNAME} works, while all other attempts fail. +${VARNAME}= try3 + +.MAKEFLAGS: -d0 all: - @:; diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp new file mode 100644 index 000000000000..39a9383953dd --- /dev/null +++ b/unit-tests/varparse-errors.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk new file mode 100644 index 000000000000..42f5b65a728e --- /dev/null +++ b/unit-tests/varparse-errors.mk @@ -0,0 +1,35 @@ +# $NetBSD: varparse-errors.mk,v 1.1 2020/11/08 16:44:47 rillig Exp $ + +# Tests for parsing and evaluating all kinds of variable expressions. +# +# This is the basis for redesigning the error handling in Var_Parse and +# Var_Subst, collecting typical and not so typical use cases. +# +# See also: +# VarParseResult +# Var_Parse +# Var_Subst + +PLAIN= plain value + +LITERAL_DOLLAR= To get a dollar, double $$ it. + +INDIRECT= An ${:Uindirect} value. + +REF_UNDEF= A reference to an ${UNDEF}undefined variable. + +ERR_UNCLOSED= An ${UNCLOSED variable expression. + +ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier. + +ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}. + +# In a conditional, a variable expression that is not enclosed in quotes is +# expanded using the flags VARE_UNDEFERR and VARE_WANTRES. +# The variable itself must be defined. +# It may refer to undefined variables though. +.if ${REF_UNDEF} != "A reference to an undefined variable." +. error +.endif + +all: diff --git a/unit-tests/varparse-undef-partial.mk b/unit-tests/varparse-undef-partial.mk index 4851b6d9d567..27f44d79b31a 100644 --- a/unit-tests/varparse-undef-partial.mk +++ b/unit-tests/varparse-undef-partial.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-undef-partial.mk,v 1.2 2020/09/27 09:53:41 rillig Exp $ +# $NetBSD: varparse-undef-partial.mk,v 1.3 2020/11/04 05:10:01 rillig Exp $ # When an undefined variable is expanded in a ':=' assignment, only the # initial '$' of the variable expression is skipped by the parser, while @@ -9,7 +9,7 @@ LIST= ${DEF} ${UNDEF} ${VAR.${PARAM}} end DEF= defined PARAM= :Q -# The expression ${VAR.{PARAM}} refers to the variable named "VAR.:Q", +# The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q", # with the ":Q" being part of the name. This variable is not defined, # therefore the initial '$' of that whole expression is skipped by the # parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest @@ -28,15 +28,15 @@ VAR.= var-dot without parameter ${:UVAR.\:Q}= var-dot with parameter :Q # At this point, the variable "VAR." is defined, therefore the expression -# ${VAR.:Q} is expanded as usual. +# ${VAR.:Q} is expanded, consisting of the variable name "VAR." and the +# modifier ":Q". .if ${EVAL} != "defined var-dot\\ without\\ parameter end" . error ${EVAL} .endif # In contrast to the previous line, evaluating the original LIST again now -# produces a different result since the ":Q" has already been inserted -# literally into the expression. The variable named "VAR.:Q" is defined, -# therefore it is resolved as usual. The ":Q" is interpreted as part of the +# produces a different result since the variable named "VAR.:Q" is now +# defined. It is expanded as usual, interpreting the ":Q" as part of the # variable name, as would be expected from reading the variable expression. EVAL:= ${LIST} .if ${EVAL} != "defined var-dot with parameter :Q end" diff --git a/unit-tests/varshell.exp b/unit-tests/varshell.exp deleted file mode 100644 index 54df3527ed1c..000000000000 --- a/unit-tests/varshell.exp +++ /dev/null @@ -1,10 +0,0 @@ -make: "varshell.mk" line 6: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status -make: "varshell.mk" line 9: warning: "false" returned non-zero status -make: "varshell.mk" line 10: warning: "echo "output before the error"; false" returned non-zero status -EXEC_FAILED='' -TERMINATED_BY_SIGNAL='' -ERROR_NO_OUTPUT='' -ERROR_WITH_OUTPUT='output before the error' -NO_ERROR_NO_OUTPUT='' -NO_ERROR_WITH_OUTPUT='this is good' -exit status 0 diff --git a/unit-tests/varshell.mk b/unit-tests/varshell.mk deleted file mode 100644 index 113c265cce3c..000000000000 --- a/unit-tests/varshell.mk +++ /dev/null @@ -1,20 +0,0 @@ -# $Id: varshell.mk,v 1.6 2020/10/26 17:55:23 sjg Exp $ -# $NetBSD: varshell.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $ -# -# Test VAR != shell command - -EXEC_FAILED!= /bin/no/such/command 2> /dev/null -# SunOS cannot handle this one -#TERMINATED_BY_SIGNAL!= kill -14 $$$$ -ERROR_NO_OUTPUT!= false -ERROR_WITH_OUTPUT!= echo "output before the error"; false -NO_ERROR_NO_OUTPUT!= true -NO_ERROR_WITH_OUTPUT!= echo "this is good" - -allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \ - NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT - -all: -.for v in ${allvars} - @echo ${v}=\'${${v}}\' -.endfor @@ -1,9 +1,9 @@ -/* $NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $ */ +/* $NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $ */ /* * Missing stuff from OS's * - * $Id: util.c,v 1.39 2020/10/10 19:42:02 sjg Exp $ + * $Id: util.c,v 1.41 2020/11/18 03:58:32 sjg Exp $ */ #include <sys/param.h> @@ -13,7 +13,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $"); +MAKE_RCSID("$NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $"); #if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR) extern int errno, sys_nerr; @@ -24,10 +24,9 @@ strerror(int e) { static char buf[100]; if (e < 0 || e >= sys_nerr) { - snprintf(buf, sizeof(buf), "Unknown error %d", e); + snprintf(buf, sizeof buf, "Unknown error %d", e); return buf; - } - else + } else return sys_errlist[e]; } #endif @@ -96,7 +95,7 @@ setenv(const char *name, const char *value, int rewrite) } if (*value == '=') /* no `=' in value */ - ++value; + value++; l_value = strlen(value); /* find if already exists */ @@ -291,8 +290,7 @@ getwd(char *pathname) for (d = readdir(dp); d != NULL; d = readdir(dp)) if (d->d_fileno == st_cur.st_ino) break; - } - else { + } else { /* * Parent has a different device. This is a mount point so we * need to stat every member @@ -384,14 +382,14 @@ vsnprintf(char *s, size_t n, const char *fmt, va_list args) * We cast to void * to make everyone happy. */ fakebuf._ptr = (void *)s; - fakebuf._cnt = n-1; + fakebuf._cnt = n - 1; fakebuf._file = -1; _doprnt(fmt, args, &fakebuf); fakebuf._cnt++; putc('\0', &fakebuf); - if (fakebuf._cnt<0) + if (fakebuf._cnt < 0) fakebuf._cnt = 0; - return n-fakebuf._cnt-1; + return n - fakebuf._cnt - 1; #else #ifndef _PATH_DEVNULL # define _PATH_DEVNULL "/dev/null" @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $ */ +/* $NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -138,7 +138,7 @@ #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $"); #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1) #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2) @@ -146,7 +146,7 @@ MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $"); #define VAR_DEBUG4(fmt, arg1, arg2, arg3, arg4) DEBUG4(VAR, fmt, arg1, arg2, arg3, arg4) ENUM_FLAGS_RTTI_3(VarEvalFlags, - VARE_UNDEFERR, VARE_WANTRES, VARE_ASSIGN); + VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR); /* * This lets us tell if we have replaced the original environ @@ -165,11 +165,6 @@ char var_Error[] = ""; * be deferred until it is defined in an actual target. */ static char varUndefined[] = ""; -/* Special return value for Var_Parse, just to avoid allocating empty strings. - * In contrast to var_Error and varUndefined, this is not an error marker but - * just an ordinary successful return value. */ -static char emptyString[] = ""; - /* * Traditionally this make consumed $$ during := like any other expansion. * Other make's do not, and this make follows straight since 2016-01-09. @@ -274,6 +269,7 @@ typedef enum VarExportedMode { static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; typedef enum VarExportFlags { + VAR_EXPORT_NORMAL = 0, /* * We pass this to Var_Export when doing the initial export * or after updating an exported var. @@ -300,7 +296,7 @@ VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags) Var *var = bmake_malloc(sizeof *var); var->name = name; var->name_freeIt = name_freeIt; - Buf_Init(&var->val, value_len + 1); + Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); var->flags = flags; return var; @@ -447,7 +443,7 @@ VarFreeEnv(Var *v, Boolean freeValue) /* Add a new variable of the given name and value to the given context. * The name and val arguments are duplicated so they may safely be freed. */ static void -VarAdd(const char *name, const char *val, GNode *ctxt, VarSet_Flags flags) +VarAdd(const char *name, const char *val, GNode *ctxt, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&ctxt->context, name, NULL); Var *v = VarNew(he->key /* aliased */, NULL, val, @@ -531,7 +527,7 @@ Var_Export1(const char *name, VarExportFlags flags) if (!MayExport(name)) return FALSE; - v = VarFind(name, VAR_GLOBAL, 0); + v = VarFind(name, VAR_GLOBAL, FALSE); if (v == NULL) return FALSE; @@ -597,7 +593,7 @@ Var_ExportVars(void) * children see a correctly incremented value. */ char tmp[BUFSIZ]; - snprintf(tmp, sizeof(tmp), "%d", makelevel + 1); + snprintf(tmp, sizeof tmp, "%d", makelevel + 1); setenv(MAKE_LEVEL_ENV, tmp, 1); if (var_exportedVars == VAR_EXPORTED_NONE) @@ -610,7 +606,7 @@ Var_ExportVars(void) HashIter_Init(&hi, &VAR_GLOBAL->context); while (HashIter_Next(&hi) != NULL) { Var *var = hi.entry->value; - Var_Export1(var->name, 0); + Var_Export1(var->name, VAR_EXPORT_NORMAL); } return; } @@ -622,7 +618,7 @@ Var_ExportVars(void) size_t i; for (i = 0; i < words.len; i++) - Var_Export1(words.words[i], 0); + Var_Export1(words.words[i], VAR_EXPORT_NORMAL); Words_Free(words); } free(val); @@ -741,7 +737,7 @@ Var_UnExport(const char *str) Words words = Str_Words(varnames, FALSE); for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; - v = VarFind(varname, VAR_GLOBAL, 0); + v = VarFind(varname, VAR_GLOBAL, FALSE); if (v == NULL) { VAR_DEBUG1("Not unexporting \"%s\" (not found)\n", varname); continue; @@ -780,8 +776,8 @@ Var_UnExport(const char *str) /* See Var_Set for documentation. */ void -Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, - VarSet_Flags flags) +Var_SetWithFlags(const char *name, const char *val, GNode *ctxt, + VarSetFlags flags) { const char *unexpanded_name = name; char *name_freeIt = NULL; @@ -804,7 +800,7 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, } if (ctxt == VAR_GLOBAL) { - v = VarFind(name, VAR_CMDLINE, 0); + v = VarFind(name, VAR_CMDLINE, FALSE); if (v != NULL) { if (v->flags & VAR_FROM_CMD) { VAR_DEBUG3("%s:%s = %s ignored!\n", ctxt->name, name, val); @@ -819,9 +815,9 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, * here will override anything in a lower context, so there's not much * point in searching them all just to save a bit of memory... */ - v = VarFind(name, ctxt, 0); + v = VarFind(name, ctxt, FALSE); if (v == NULL) { - if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT)) { + if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) { /* * This var would normally prevent the same name being added * to VAR_GLOBAL, so delete it from there if needed. @@ -850,9 +846,9 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, * to the environment (as per POSIX standard) * Other than internals. */ - if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT) && name[0] != '.') { + if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') { if (v == NULL) - v = VarFind(name, ctxt, 0); /* we just added it */ + v = VarFind(name, ctxt, FALSE); /* we just added it */ v->flags |= VAR_FROM_CMD; /* @@ -867,7 +863,7 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt, Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL); } if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) - save_dollars = s2Boolean(val, save_dollars); + save_dollars = ParseBoolean(val, save_dollars); out: free(name_freeIt); @@ -902,7 +898,7 @@ out: void Var_Set(const char *name, const char *val, GNode *ctxt) { - Var_Set_with_flags(name, val, ctxt, 0); + Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE); } /*- @@ -965,8 +961,6 @@ Var_Append(const char *name, const char *val, GNode *ctxt) ctxt->name, name, Buf_GetAll(&v->val, NULL)); if (v->flags & VAR_FROM_ENV) { - HashEntry *h; - /* * If the original variable came from the environment, we * have to install it in the global context (we could place @@ -974,8 +968,9 @@ Var_Append(const char *name, const char *val, GNode *ctxt) * export other variables...) */ v->flags &= ~(unsigned)VAR_FROM_ENV; - h = HashTable_CreateEntry(&ctxt->context, name, NULL); - HashEntry_Set(h, v); + /* This is the only place where a variable is created whose + * v->name is not the same as ctxt->context->key. */ + HashTable_Set(&ctxt->context, name, v); } } free(name_freeIt); @@ -1061,7 +1056,7 @@ typedef struct SepBuf { static void SepBuf_Init(SepBuf *buf, char sep) { - Buf_Init(&buf->buf, 32 /* bytes */); + Buf_InitSize(&buf->buf, 32); buf->needSep = FALSE; buf->sep = sep; } @@ -1354,9 +1349,9 @@ nosub: #ifndef NO_REGEX /* Print the error caused by a regcomp or regexec call. */ static void -VarREError(int reerr, regex_t *pat, const char *str) +VarREError(int reerr, const regex_t *pat, const char *str) { - size_t errlen = regerror(reerr, pat, 0, 0); + size_t errlen = regerror(reerr, pat, NULL, 0); char *errbuf = bmake_malloc(errlen); regerror(reerr, pat, errbuf, errlen); Error("%s: %s", str, errbuf); @@ -1470,7 +1465,7 @@ ModifyWord_Loop(const char *word, SepBuf *buf, void *data) return; args = data; - Var_Set_with_flags(args->tvar, word, args->ctx, VAR_NO_EXPORT); + Var_SetWithFlags(args->tvar, word, args->ctx, VAR_SET_NO_EXPORT); (void)Var_Subst(args->str, args->ctx, args->eflags, &s); /* TODO: handle errors */ @@ -1501,7 +1496,7 @@ VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, if (oneBigWord) { /* fake what Str_Words() would do if there were only one word */ words.len = 1; - words.words = bmake_malloc((words.len + 1) * sizeof(char *)); + words.words = bmake_malloc((words.len + 1) * sizeof(words.words[0])); words.freeIt = bmake_strdup(str); words.words[0] = words.freeIt; words.words[1] = NULL; @@ -1609,7 +1604,7 @@ Words_JoinFree(Words words) Buffer buf; size_t i; - Buf_Init(&buf, 0); + Buf_Init(&buf); for (i = 0; i < words.len; i++) { if (i != 0) @@ -1646,7 +1641,7 @@ static char * VarQuote(const char *str, Boolean quoteDollar) { Buffer buf; - Buf_Init(&buf, 0); + Buf_Init(&buf); for (; *str != '\0'; str++) { if (*str == '\n') { @@ -1733,19 +1728,24 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) { char buf[BUFSIZ]; - if (!tim) + if (tim == 0) time(&tim); - if (!*fmt) + if (*fmt == '\0') fmt = "%c"; - strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&tim) : localtime(&tim)); + strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim)); - buf[sizeof(buf) - 1] = '\0'; + buf[sizeof buf - 1] = '\0'; return bmake_strdup(buf); } -/* The ApplyModifier functions all work in the same way. They get the - * current parsing position (pp) and parse the modifier from there. The - * modifier typically lasts until the next ':', or a closing '}' or ')' +/* + * The ApplyModifier functions take an expression that is being evaluated. + * Their task is to apply a single modifier to the expression. + * To do this, they parse the modifier and its parameters from pp and apply + * the parsed modifier to the current value of the expression, generating a + * new value from it. + * + * The modifier typically lasts until the next ':', or a closing '}' or ')' * (taken from st->endc), or the end of the string (parse error). * * The high-level behavior of these functions is: @@ -1758,7 +1758,11 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * * If parsing succeeds, the parsing position *pp is updated to point to the * first character following the modifier, which typically is either ':' or - * st->endc. + * st->endc. The modifier doesn't have to check for this delimiter character, + * this is done by ApplyModifiers. + * + * XXX: As of 2020-11-15, some modifiers such as :S, :C, :P, :L do not + * need to be followed by a ':' or endc; this was an unintended mistake. * * If parsing fails because of a missing delimiter (as in the :S, :C or :@ * modifiers), return AMR_CLEANUP. @@ -1767,8 +1771,8 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * try the SysV modifier ${VAR:from=to} as fallback. This should only be * done as long as there have been no side effects from evaluating nested * variables, to avoid evaluating them more than once. In this case, the - * parsing position must not be updated. (XXX: Why not? The original parsing - * position is well-known in ApplyModifiers.) + * parsing position may or may not be updated. (XXX: Why not? The original + * parsing position is well-known in ApplyModifiers.) * * If parsing fails and the SysV modifier ${VAR:from=to} should not be used * as a fallback, either issue an error message using Error or Parse_Error @@ -1786,7 +1790,7 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * during parsing though. * * Evaluating the modifier usually takes the current value of the variable - * expression from st->val, or the variable name from st->v->name and stores + * expression from st->val, or the variable name from st->var->name and stores * the result in st->newVal. * * If evaluating fails (as of 2020-08-23), an error message is printed using @@ -1819,7 +1823,7 @@ ENUM_FLAGS_RTTI_2(VarExprFlags, typedef struct ApplyModifiersState { const char startc; /* '\0' or '{' or '(' */ const char endc; /* '\0' or '}' or ')' */ - Var * const v; + Var * const var; GNode * const ctxt; const VarEvalFlags eflags; @@ -1896,7 +1900,7 @@ ParseModifierPart( Buffer buf; const char *p; - Buf_Init(&buf, 0); + Buf_Init(&buf); /* * Skim through until the matching delimiter is found; pick up variable @@ -1934,7 +1938,7 @@ ParseModifierPart( const char *nested_p = p; const char *nested_val; void *nested_val_freeIt; - VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_ASSIGN; + VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_KEEP_DOLLAR; (void)Var_Parse(&nested_p, st->ctxt, nested_eflags, &nested_val, &nested_val_freeIt); @@ -1982,7 +1986,8 @@ ParseModifierPart( if (*p != delim) { *pp = p; - Error("Unfinished modifier for %s ('%c' missing)", st->v->name, delim); + Error("Unfinished modifier for %s ('%c' missing)", + st->var->name, delim); *out_part = NULL; return VPR_PARSE_MSG; } @@ -1997,7 +2002,7 @@ ParseModifierPart( } /* Test whether mod starts with modname, followed by a delimiter. */ -static Boolean +MAKE_INLINE Boolean ModMatch(const char *mod, const char *modname, char endc) { size_t n = strlen(modname); @@ -2006,7 +2011,7 @@ ModMatch(const char *mod, const char *modname, char endc) } /* Test whether mod starts with modname, followed by a delimiter or '='. */ -static inline Boolean +MAKE_INLINE Boolean ModMatchEq(const char *mod, const char *modname, char endc) { size_t n = strlen(modname); @@ -2080,35 +2085,35 @@ ApplyModifier_Loop(const char **pp, ApplyModifiersState *st) { struct ModifyWord_LoopArgs args; char prev_sep; - VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; VarParseResult res; args.ctx = st->ctxt; (*pp)++; /* Skip the first '@' */ - res = ParseModifierPart(pp, '@', eflags, st, + res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.tvar, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; - if (DEBUG(LINT) && strchr(args.tvar, '$') != NULL) { + if (opts.lint && strchr(args.tvar, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier of \"%s\", the variable name \"%s\" " "must not contain a dollar.", - st->v->name, args.tvar); + st->var->name, args.tvar); return AMR_CLEANUP; } - res = ParseModifierPart(pp, '@', eflags, st, + res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.str, NULL, NULL, NULL); if (res != VPR_OK) return AMR_CLEANUP; - args.eflags = st->eflags & (VARE_UNDEFERR | VARE_WANTRES); + args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR; prev_sep = st->sep; st->sep = ' '; /* XXX: should be st->sep for consistency */ st->newVal = ModifyWords(st->val, ModifyWord_Loop, &args, st->oneBigWord, st->sep); st->sep = prev_sep; + /* XXX: Consider restoring the previous variable instead of deleting. */ Var_Delete(args.tvar, st->ctxt); free(args.tvar); free(args.str); @@ -2122,16 +2127,19 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st) Buffer buf; const char *p; - VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES; - if (st->eflags & VARE_WANTRES) { + VarEvalFlags eflags = VARE_NONE; + if (st->eflags & VARE_WANTRES) if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF)) - eflags |= VARE_WANTRES; - } + eflags = st->eflags; - Buf_Init(&buf, 0); + Buf_Init(&buf); p = *pp + 1; while (*p != st->endc && *p != ':' && *p != '\0') { + /* XXX: This code is similar to the one in Var_Parse. + * See if the code can be merged. + * See also ApplyModifier_Match. */ + /* Escaped delimiter or other special character */ if (*p == '\\') { char c = p[1]; @@ -2177,7 +2185,7 @@ static ApplyModifierResult ApplyModifier_Literal(const char **pp, ApplyModifiersState *st) { ApplyModifiersState_Define(st); - st->newVal = bmake_strdup(st->v->name); + st->newVal = bmake_strdup(st->var->name); (*pp)++; return AMR_OK; } @@ -2272,17 +2280,17 @@ ApplyModifier_Path(const char **pp, ApplyModifiersState *st) ApplyModifiersState_Define(st); - gn = Targ_FindNode(st->v->name); + gn = Targ_FindNode(st->var->name); if (gn == NULL || gn->type & OP_NOPATH) { path = NULL; } else if (gn->path != NULL) { path = bmake_strdup(gn->path); } else { SearchPath *searchPath = Suff_FindPath(gn); - path = Dir_FindFile(st->v->name, searchPath); + path = Dir_FindFile(st->var->name, searchPath); } if (path == NULL) - path = bmake_strdup(st->v->name); + path = bmake_strdup(st->var->name); st->newVal = path; (*pp)++; @@ -2307,11 +2315,10 @@ ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) if (st->eflags & VARE_WANTRES) st->newVal = Cmd_Exec(cmd, &errfmt); else - st->newVal = emptyString; - free(cmd); - + st->newVal = bmake_strdup(""); if (errfmt != NULL) - Error(errfmt, st->val); /* XXX: why still return AMR_OK? */ + Error(errfmt, cmd); /* XXX: why still return AMR_OK? */ + free(cmd); ApplyModifiersState_Define(st); return AMR_OK; @@ -2348,7 +2355,7 @@ ApplyModifier_Range(const char **pp, ApplyModifiersState *st) Words_Free(words); } - Buf_Init(&buf, 0); + Buf_Init(&buf); for (i = 0; i < n; i++) { if (i != 0) @@ -2374,8 +2381,11 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st) /* * In the loop below, ignore ':' unless we are at (or back to) the * original brace level. - * XXX This will likely not work right if $() and ${} are intermixed. + * XXX: This will likely not work right if $() and ${} are intermixed. */ + /* XXX: This code is similar to the one in Var_Parse. + * See if the code can be merged. + * See also ApplyModifier_Defined. */ int nest = 0; const char *p; for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) { @@ -2428,7 +2438,8 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st) free(old_pattern); } - VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", st->v->name, st->val, pattern); + VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", + st->var->name, st->val, pattern); callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; st->newVal = ModifyWords(st->val, callback, pattern, @@ -2754,7 +2765,7 @@ ApplyModifier_Words(const char **pp, ApplyModifiersState *st) size_t ac = words.len; Words_Free(words); - Buf_Init(&buf, 4); /* 3 digits + '\0' is usually enough */ + Buf_InitSize(&buf, 4); /* 3 digits + '\0' is usually enough */ Buf_AddInt(&buf, (int)ac); st->newVal = Buf_Destroy(&buf, FALSE); } @@ -2842,7 +2853,7 @@ ApplyModifier_Order(const char **pp, ApplyModifiersState *st) if (mod[1] == st->endc || mod[1] == ':') { /* :O sorts ascending */ - qsort(words.words, words.len, sizeof(char *), str_cmp_asc); + qsort(words.words, words.len, sizeof words.words[0], str_cmp_asc); } else if ((mod[1] == 'r' || mod[1] == 'x') && (mod[2] == st->endc || mod[2] == ':')) { @@ -2850,7 +2861,7 @@ ApplyModifier_Order(const char **pp, ApplyModifiersState *st) if (mod[1] == 'r') { /* :Or sorts descending */ - qsort(words.words, words.len, sizeof(char *), str_cmp_desc); + qsort(words.words, words.len, sizeof words.words[0], str_cmp_desc); } else { /* :Ox shuffles @@ -2885,16 +2896,16 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) VarParseResult res; Boolean value = FALSE; - VarEvalFlags then_eflags = st->eflags & ~(unsigned)VARE_WANTRES; - VarEvalFlags else_eflags = st->eflags & ~(unsigned)VARE_WANTRES; + VarEvalFlags then_eflags = VARE_NONE; + VarEvalFlags else_eflags = VARE_NONE; int cond_rc = COND_PARSE; /* anything other than COND_INVALID */ if (st->eflags & VARE_WANTRES) { - cond_rc = Cond_EvalCondition(st->v->name, &value); + cond_rc = Cond_EvalCondition(st->var->name, &value); if (cond_rc != COND_INVALID && value) - then_eflags |= VARE_WANTRES; + then_eflags = st->eflags; if (cond_rc != COND_INVALID && !value) - else_eflags |= VARE_WANTRES; + else_eflags = st->eflags; } (*pp)++; /* skip past the '?' */ @@ -2911,7 +2922,7 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) (*pp)--; if (cond_rc == COND_INVALID) { Error("Bad conditional expression `%s' in %s?%s:%s", - st->v->name, st->v->name, then_expr, else_expr); + st->var->name, st->var->name, then_expr, else_expr); return AMR_CLEANUP; } @@ -2965,14 +2976,14 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) return AMR_UNKNOWN; /* "::<unrecognised>" */ ok: - if (st->v->name[0] == '\0') { + if (st->var->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } v_ctxt = st->ctxt; /* context where v belongs */ if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) { - Var *gv = VarFind(st->v->name, st->ctxt, 0); + Var *gv = VarFind(st->var->name, st->ctxt, FALSE); if (gv == NULL) v_ctxt = VAR_GLOBAL; else @@ -3000,7 +3011,7 @@ ok: if (st->eflags & VARE_WANTRES) { switch (op[0]) { case '+': - Var_Append(st->v->name, val, v_ctxt); + Var_Append(st->var->name, val, v_ctxt); break; case '!': { const char *errfmt; @@ -3008,7 +3019,7 @@ ok: if (errfmt) Error(errfmt, val); else - Var_Set(st->v->name, cmd_output, v_ctxt); + Var_Set(st->var->name, cmd_output, v_ctxt); free(cmd_output); break; } @@ -3017,12 +3028,12 @@ ok: break; /* FALLTHROUGH */ default: - Var_Set(st->v->name, val, v_ctxt); + Var_Set(st->var->name, val, v_ctxt); break; } } free(val); - st->newVal = emptyString; + st->newVal = bmake_strdup(""); return AMR_OK; } @@ -3146,7 +3157,7 @@ ApplyModifier_SunShell(const char **pp, ApplyModifiersState *st) if (errfmt) Error(errfmt, st->val); } else - st->newVal = emptyString; + st->newVal = bmake_strdup(""); *pp = p + 2; return AMR_OK; } else @@ -3166,11 +3177,11 @@ LogBeforeApply(const ApplyModifiersState *st, const char *mod, const char endc) /* At this point, only the first character of the modifier can * be used since the end of the modifier is not yet known. */ debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n", - st->v->name, mod[0], is_single_char ? "" : "...", st->val, + st->var->name, mod[0], is_single_char ? "" : "...", st->val, Enum_FlagsToString(eflags_str, sizeof eflags_str, st->eflags, VarEvalFlags_ToStringSpecs), Enum_FlagsToString(vflags_str, sizeof vflags_str, - st->v->flags, VarFlags_ToStringSpecs), + st->var->flags, VarFlags_ToStringSpecs), Enum_FlagsToString(exprflags_str, sizeof exprflags_str, st->exprFlags, VarExprFlags_ToStringSpecs)); @@ -3186,11 +3197,11 @@ LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod) const char *newVal = st->newVal == var_Error ? "error" : st->newVal; debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n", - st->v->name, (int)(p - mod), mod, quot, newVal, quot, + st->var->name, (int)(p - mod), mod, quot, newVal, quot, Enum_FlagsToString(eflags_str, sizeof eflags_str, st->eflags, VarEvalFlags_ToStringSpecs), Enum_FlagsToString(vflags_str, sizeof vflags_str, - st->v->flags, VarFlags_ToStringSpecs), + st->var->flags, VarFlags_ToStringSpecs), Enum_FlagsToString(exprflags_str, sizeof exprflags_str, st->exprFlags, VarExprFlags_ToStringSpecs)); @@ -3272,20 +3283,18 @@ typedef enum ApplyModifiersIndirectResult { } ApplyModifiersIndirectResult; /* While expanding a variable expression, expand and apply indirect - * modifiers. */ + * modifiers such as in ${VAR:${M_indirect}}. */ static ApplyModifiersIndirectResult ApplyModifiersIndirect( ApplyModifiersState *const st, - const char **inout_p, - void **const out_freeIt + const char **const inout_p, + void **const inout_freeIt ) { const char *p = *inout_p; - const char *nested_p = p; - void *freeIt; - const char *rval; - char c; + const char *mods; + void *mods_freeIt; - (void)Var_Parse(&nested_p, st->ctxt, st->eflags, &rval, &freeIt); + (void)Var_Parse(&p, st->ctxt, st->eflags, &mods, &mods_freeIt); /* TODO: handle errors */ /* @@ -3293,44 +3302,41 @@ ApplyModifiersIndirect( * interested. This means the expression ${VAR:${M_1}${M_2}} * is not accepted, but ${VAR:${M_1}:${M_2}} is. */ - if (rval[0] != '\0' && - (c = *nested_p) != '\0' && c != ':' && c != st->endc) { - if (DEBUG(LINT)) + if (mods[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) { + if (opts.lint) Parse_Error(PARSE_FATAL, "Missing delimiter ':' after indirect modifier \"%.*s\"", - (int)(nested_p - p), p); + (int)(p - *inout_p), *inout_p); - free(freeIt); + free(mods_freeIt); /* XXX: apply_mods doesn't sound like "not interested". */ - /* XXX: Why is the indirect modifier parsed again by + /* XXX: Why is the indirect modifier parsed once more by * apply_mods? If any, p should be advanced to nested_p. */ return AMIR_APPLY_MODS; } VAR_DEBUG3("Indirect modifier \"%s\" from \"%.*s\"\n", - rval, (int)(size_t)(nested_p - p), p); - - p = nested_p; - - if (rval[0] != '\0') { - const char *rval_pp = rval; - st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->v, - &st->exprFlags, st->ctxt, st->eflags, out_freeIt); - if (st->val == var_Error - || (st->val == varUndefined && !(st->eflags & VARE_UNDEFERR)) - || *rval_pp != '\0') { - free(freeIt); + mods, (int)(p - *inout_p), *inout_p); + + if (mods[0] != '\0') { + const char *rval_pp = mods; + st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->var, + &st->exprFlags, st->ctxt, st->eflags, + inout_freeIt); + if (st->val == var_Error || st->val == varUndefined || + *rval_pp != '\0') { + free(mods_freeIt); *inout_p = p; return AMIR_OUT; /* error already reported */ } } - free(freeIt); + free(mods_freeIt); if (*p == ':') p++; else if (*p == '\0' && st->endc != '\0') { Error("Unclosed variable specification after complex " - "modifier (expecting '%c') for %s", st->endc, st->v->name); + "modifier (expecting '%c') for %s", st->endc, st->var->name); *inout_p = p; return AMIR_OUT; } @@ -3342,15 +3348,15 @@ ApplyModifiersIndirect( /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ static char * ApplyModifiers( - const char **pp, /* the parsing position, updated upon return */ + const char **const pp, /* the parsing position, updated upon return */ char *const val, /* the current value of the expression */ char const startc, /* '(' or '{', or '\0' for indirect modifiers */ char const endc, /* ')' or '}', or '\0' for indirect modifiers */ - Var * const v, - VarExprFlags *exprFlags, - GNode * const ctxt, /* for looking up and modifying variables */ + Var *const v, + VarExprFlags *const exprFlags, + GNode *const ctxt, /* for looking up and modifying variables */ VarEvalFlags const eflags, - void ** const out_freeIt /* free this after using the return value */ + void **const inout_freeIt /* free this after using the return value */ ) { ApplyModifiersState st = { startc, endc, v, ctxt, eflags, @@ -3369,11 +3375,18 @@ ApplyModifiers( assert(val != NULL); p = *pp; + + if (*p == '\0' && endc != '\0') { + Error("Unclosed variable expression (expecting '%c') for \"%s\"", + st.endc, st.var->name); + goto cleanup; + } + while (*p != '\0' && *p != endc) { if (*p == '$') { ApplyModifiersIndirectResult amir; - amir = ApplyModifiersIndirect(&st, &p, out_freeIt); + amir = ApplyModifiersIndirect(&st, &p, inout_freeIt); if (amir == AMIR_CONTINUE) continue; if (amir == AMIR_OUT) @@ -3396,6 +3409,9 @@ ApplyModifiers( if (res == AMR_UNKNOWN) { Error("Unknown modifier '%c'", *mod); + /* Guess the end of the current modifier. + * XXX: Skipping the rest of the modifier hides errors and leads + * to wrong results. Parsing should rather stop here. */ for (p++; *p != ':' && *p != st.endc && *p != '\0'; p++) continue; st.newVal = var_Error; @@ -3409,26 +3425,25 @@ ApplyModifiers( LogAfterApply(&st, p, mod); if (st.newVal != st.val) { - if (*out_freeIt) { + if (*inout_freeIt != NULL) { free(st.val); - *out_freeIt = NULL; + *inout_freeIt = NULL; } st.val = st.newVal; - if (st.val != var_Error && st.val != varUndefined && - st.val != emptyString) { - *out_freeIt = st.val; - } + if (st.val != var_Error && st.val != varUndefined) + *inout_freeIt = st.val; } if (*p == '\0' && st.endc != '\0') { Error("Unclosed variable specification (expecting '%c') " "for \"%s\" (value \"%s\") modifier %c", - st.endc, st.v->name, st.val, *mod); + st.endc, st.var->name, st.val, *mod); } else if (*p == ':') { p++; - } else if (DEBUG(LINT) && *p != '\0' && *p != endc) { + } else if (opts.lint && *p != '\0' && *p != endc) { Parse_Error(PARSE_FATAL, "Missing delimiter ':' after modifier \"%.*s\"", (int)(p - mod), mod); + /* TODO: propagate parse error to the enclosing expression */ } } out: @@ -3438,13 +3453,14 @@ out: return st.val; bad_modifier: + /* XXX: The modifier end is only guessed. */ Error("Bad modifier `:%.*s' for %s", - (int)strcspn(mod, ":)}"), mod, st.v->name); + (int)strcspn(mod, ":)}"), mod, st.var->name); cleanup: *pp = p; - free(*out_freeIt); - *out_freeIt = NULL; + free(*inout_freeIt); + *inout_freeIt = NULL; *exprFlags = st.exprFlags; return var_Error; } @@ -3513,7 +3529,7 @@ ParseVarname(const char **pp, char startc, char endc, const char *p = *pp; int depth = 1; - Buf_Init(&buf, 0); + Buf_Init(&buf); while (*p != '\0') { /* Track depth so we can spot parse errors. */ @@ -3528,12 +3544,12 @@ ParseVarname(const char **pp, char startc, char endc, /* A variable inside a variable, expand. */ if (*p == '$') { - void *freeIt; - const char *rval; - (void)Var_Parse(&p, ctxt, eflags, &rval, &freeIt); + const char *nested_val; + void *nested_val_freeIt; + (void)Var_Parse(&p, ctxt, eflags, &nested_val, &nested_val_freeIt); /* TODO: handle errors */ - Buf_AddStr(&buf, rval); - free(freeIt); + Buf_AddStr(&buf, nested_val); + free(nested_val_freeIt); } else { Buf_AddByte(&buf, *p); p++; @@ -3544,7 +3560,7 @@ ParseVarname(const char **pp, char startc, char endc, return Buf_Destroy(&buf, FALSE); } -static Boolean +static VarParseResult ValidShortVarname(char varname, const char *start) { switch (varname) { @@ -3555,11 +3571,11 @@ ValidShortVarname(char varname, const char *start) case '$': break; /* and continue below */ default: - return TRUE; + return VPR_OK; } - if (!DEBUG(LINT)) - return FALSE; + if (!opts.lint) + return VPR_PARSE_SILENT; if (varname == '$') Parse_Error(PARSE_FATAL, @@ -3570,23 +3586,20 @@ ValidShortVarname(char varname, const char *start) Parse_Error(PARSE_FATAL, "Invalid variable name '%c', at \"%s\"", varname, start); - return FALSE; + return VPR_PARSE_MSG; } /* Parse a single-character variable name such as $V or $@. * Return whether to continue parsing. */ static Boolean -ParseVarnameShort( - char startc, - const char **pp, - GNode *ctxt, - VarEvalFlags eflags, - VarParseResult *out_FALSE_res, - const char **out_FALSE_val, - Var **out_TRUE_var -) { +ParseVarnameShort(char startc, const char **pp, GNode *ctxt, + VarEvalFlags eflags, + VarParseResult *out_FALSE_res, const char **out_FALSE_val, + Var **out_TRUE_var) +{ char name[2]; Var *v; + VarParseResult vpr; /* * If it's not bounded by braces of some sort, life is much simpler. @@ -3594,10 +3607,11 @@ ParseVarnameShort( * value if it exists. */ - if (!ValidShortVarname(startc, *pp)) { + vpr = ValidShortVarname(startc, *pp); + if (vpr != VPR_OK) { (*pp)++; *out_FALSE_val = var_Error; - *out_FALSE_res = VPR_PARSE_MSG; + *out_FALSE_res = vpr; return FALSE; } @@ -3608,7 +3622,7 @@ ParseVarnameShort( *pp += 2; *out_FALSE_val = UndefinedShortVarValue(startc, ctxt, eflags); - if (DEBUG(LINT) && *out_FALSE_val == var_Error) { + if (opts.lint && *out_FALSE_val == var_Error) { Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); *out_FALSE_res = VPR_UNDEF_MSG; return FALSE; @@ -3621,20 +3635,83 @@ ParseVarnameShort( return TRUE; } +/* Find variables like @F or <D. */ +static Var * +FindLocalLegacyVar(const char *varname, size_t namelen, GNode *ctxt, + const char **out_extraModifiers) +{ + /* Only resolve these variables if ctxt is a "real" target. */ + if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL) + return NULL; + + if (namelen != 2) + return NULL; + if (varname[1] != 'F' && varname[1] != 'D') + return NULL; + if (strchr("@%?*!<>", varname[0]) == NULL) + return NULL; + + { + char name[] = { varname[0], '\0' }; + Var *v = VarFind(name, ctxt, FALSE); + + if (v != NULL) { + if (varname[1] == 'D') { + *out_extraModifiers = "H:"; + } else { /* F */ + *out_extraModifiers = "T:"; + } + } + return v; + } +} + +static VarParseResult +EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname, + VarEvalFlags eflags, + void **out_freeIt, const char **out_val) +{ + if (dynamic) { + char *pstr = bmake_strsedup(start, p); + free(varname); + *out_freeIt = pstr; + *out_val = pstr; + return VPR_OK; + } + + if ((eflags & VARE_UNDEFERR) && opts.lint) { + Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", varname); + free(varname); + *out_val = var_Error; + return VPR_UNDEF_MSG; + } + + if (eflags & VARE_UNDEFERR) { + free(varname); + *out_val = var_Error; + return VPR_UNDEF_SILENT; + } + + free(varname); + *out_val = varUndefined; + return VPR_OK; +} + /* Parse a long variable name enclosed in braces or parentheses such as $(VAR) * or ${VAR}, up to the closing brace or parenthesis, or in the case of * ${VAR:Modifiers}, up to the ':' that starts the modifiers. * Return whether to continue parsing. */ static Boolean ParseVarnameLong( - const char **pp, + const char *p, char startc, GNode *ctxt, VarEvalFlags eflags, + const char **out_FALSE_pp, VarParseResult *out_FALSE_res, const char **out_FALSE_val, - void **out_FALSE_freePtr, + void **out_FALSE_freeIt, char *out_TRUE_endc, const char **out_TRUE_p, @@ -3650,10 +3727,10 @@ ParseVarnameLong( Boolean haveModifier; Boolean dynamic = FALSE; - const char *const start = *pp; + const char *const start = p; char endc = startc == '(' ? ')' : '}'; - const char *p = start + 2; + p += 2; /* skip "${" or "$(" or "y(" */ varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen); if (*p == ':') { @@ -3662,8 +3739,8 @@ ParseVarnameLong( haveModifier = FALSE; } else { Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname); - *pp = p; free(varname); + *out_FALSE_pp = p; *out_FALSE_val = var_Error; *out_FALSE_res = VPR_PARSE_MSG; return FALSE; @@ -3674,28 +3751,8 @@ ParseVarnameLong( /* At this point, p points just after the variable name, * either at ':' or at endc. */ - /* - * Check also for bogus D and F forms of local variables since we're - * in a local context and the name is the right length. - */ - if (v == NULL && ctxt != VAR_CMDLINE && ctxt != VAR_GLOBAL && - namelen == 2 && (varname[1] == 'F' || varname[1] == 'D') && - strchr("@%?*!<>", varname[0]) != NULL) - { - /* - * Well, it's local -- go look for it. - */ - char name[] = { varname[0], '\0' }; - v = VarFind(name, ctxt, 0); - - if (v != NULL) { - if (varname[1] == 'D') { - *out_TRUE_extraModifiers = "H:"; - } else { /* F */ - *out_TRUE_extraModifiers = "T:"; - } - } - } + if (v == NULL) + v = FindLocalLegacyVar(varname, namelen, ctxt, out_TRUE_extraModifiers); if (v == NULL) { /* Defer expansion of dynamic variables if they appear in non-local @@ -3705,37 +3762,9 @@ ParseVarnameLong( if (!haveModifier) { p++; /* skip endc */ - *pp = p; - if (dynamic) { - char *pstr = bmake_strsedup(start, p); - free(varname); - *out_FALSE_res = VPR_OK; - *out_FALSE_freePtr = pstr; - *out_FALSE_val = pstr; - return FALSE; - } - - if ((eflags & VARE_UNDEFERR) && (eflags & VARE_WANTRES) && - DEBUG(LINT)) - { - Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", - varname); - free(varname); - *out_FALSE_res = VPR_UNDEF_MSG; - *out_FALSE_val = var_Error; - return FALSE; - } - - if (eflags & VARE_UNDEFERR) { - free(varname); - *out_FALSE_res = VPR_UNDEF_SILENT; - *out_FALSE_val = var_Error; - return FALSE; - } - - free(varname); - *out_FALSE_res = VPR_OK; - *out_FALSE_val = varUndefined; + *out_FALSE_pp = p; + *out_FALSE_res = EvalUndefined(dynamic, start, p, varname, eflags, + out_FALSE_freeIt, out_FALSE_val); return FALSE; } @@ -3763,56 +3792,51 @@ ParseVarnameLong( return TRUE; } -/*- - *----------------------------------------------------------------------- - * Var_Parse -- - * Given the start of a variable expression (such as $v, $(VAR), - * ${VAR:Mpattern}), extract the variable name, possibly some - * modifiers and find its value by applying the modifiers to the - * original value. - * - * When parsing a condition in ParseEmptyArg, pp may also point to - * the "y" of "empty(VARNAME:Modifiers)", which is syntactically - * identical. +/* + * Given the start of a variable expression (such as $v, $(VAR), + * ${VAR:Mpattern}), extract the variable name and value, and the modifiers, + * if any. While doing that, apply the modifiers to the value of the + * expression, forming its final value. A few of the modifiers such as :!cmd! + * or ::= have side effects. * * Input: - * str The string to parse - * ctxt The context for the variable - * flags Select the exact details of parsing - * out_val_freeIt Must be freed by the caller after using out_val - * - * Results: - * Returns the value of the variable expression, never NULL. - * Returns var_Error if there was a parse error and VARE_UNDEFERR was - * set. - * Returns varUndefined if there was an undefined variable and - * VARE_UNDEFERR was not set. - * - * Parsing should continue at *pp. - * TODO: Document the value of *pp on parse errors. It might be advanced - * by 0, or +1, or the index of the parse error, or the guessed end of the - * variable expression. - * - * If var_Error is returned, a diagnostic may or may not have been - * printed. XXX: This is inconsistent. - * - * If varUndefined is returned, a diagnostic may or may not have been - * printed. XXX: This is inconsistent. - * - * After using the returned value, *out_val_freeIt must be freed, - * preferably using bmake_free since it is NULL in most cases. + * *pp The string to parse. + * When parsing a condition in ParseEmptyArg, it may also + * point to the "y" of "empty(VARNAME:Modifiers)", which + * is syntactically the same. + * ctxt The context for finding variables + * eflags Control the exact details of parsing * - * Side Effects: - * Any effects from the modifiers, such as :!cmd! or ::=value. - *----------------------------------------------------------------------- + * Output: + * *pp The position where to continue parsing. + * TODO: After a parse error, the value of *pp is + * unspecified. It may not have been updated at all, + * point to some random character in the string, to the + * location of the parse error, or at the end of the + * string. + * *out_val The value of the variable expression, never NULL. + * *out_val var_Error if there was a parse error. + * *out_val var_Error if the base variable of the expression was + * undefined, eflags contains VARE_UNDEFERR, and none of + * the modifiers turned the undefined expression into a + * defined expression. + * XXX: It is not guaranteed that an error message has + * been printed. + * *out_val varUndefined if the base variable of the expression + * was undefined, eflags did not contain VARE_UNDEFERR, + * and none of the modifiers turned the undefined + * expression into a defined expression. + * XXX: It is not guaranteed that an error message has + * been printed. + * *out_val_freeIt Must be freed by the caller after using *out_val. */ /* coverity[+alloc : arg-*4] */ VarParseResult Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, const char **out_val, void **out_val_freeIt) { - const char *const start = *pp; - const char *p; + const char *p = *pp; + const char *const start = p; Boolean haveModifier; /* TRUE if have modifiers for the variable */ char startc; /* Starting character if variable in parens * or braces */ @@ -3824,7 +3848,7 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, * result is just the expression, unaltered */ const char *extramodifiers; Var *v; - char *nstr; + char *value; char eflags_str[VarEvalFlags_ToStringSize]; VarExprFlags exprFlags = 0; @@ -3840,17 +3864,17 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, * initialized. */ endc = '\0'; - startc = start[1]; + startc = p[1]; if (startc != '(' && startc != '{') { VarParseResult res; if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res, out_val, &v)) return res; haveModifier = FALSE; - p = start + 1; + p++; } else { VarParseResult res; - if (!ParseVarnameLong(pp, startc, ctxt, eflags, - &res, out_val, out_val_freeIt, + if (!ParseVarnameLong(p, startc, ctxt, eflags, + pp, &res, out_val, out_val_freeIt, &endc, &p, &v, &haveModifier, &extramodifiers, &dynamic, &exprFlags)) return res; @@ -3859,25 +3883,26 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, if (v->flags & VAR_IN_USE) Fatal("Variable %s is recursive.", v->name); - /* - * 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. - */ - nstr = Buf_GetAll(&v->val, NULL); - if (strchr(nstr, '$') != NULL && (eflags & VARE_WANTRES)) { + /* XXX: This assignment creates an alias to the current value of the + * variable. This means that as long as the value of the expression stays + * the same, the value of the variable must not change. + * Using the '::=' modifier, it could be possible to do exactly this. + * At the bottom of this function, the resulting value is compared to the + * then-current value of the variable. This might also invoke undefined + * behavior. */ + value = Buf_GetAll(&v->val, NULL); + + /* Before applying any modifiers, expand any nested expressions from the + * variable value. */ + if (strchr(value, '$') != NULL && (eflags & VARE_WANTRES)) { VarEvalFlags nested_eflags = eflags; - if (DEBUG(LINT)) + if (opts.lint) nested_eflags &= ~(unsigned)VARE_UNDEFERR; v->flags |= VAR_IN_USE; - (void)Var_Subst(nstr, ctxt, nested_eflags, &nstr); + (void)Var_Subst(value, ctxt, nested_eflags, &value); v->flags &= ~(unsigned)VAR_IN_USE; /* TODO: handle errors */ - *out_val_freeIt = nstr; + *out_val_freeIt = value; } if (haveModifier || extramodifiers != NULL) { @@ -3886,16 +3911,16 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, extraFree = NULL; if (extramodifiers != NULL) { const char *em = extramodifiers; - nstr = ApplyModifiers(&em, nstr, '(', ')', - v, &exprFlags, ctxt, eflags, &extraFree); + value = ApplyModifiers(&em, value, '\0', '\0', + v, &exprFlags, ctxt, eflags, &extraFree); } if (haveModifier) { /* Skip initial colon. */ p++; - nstr = ApplyModifiers(&p, nstr, startc, endc, - v, &exprFlags, ctxt, eflags, out_val_freeIt); + value = ApplyModifiers(&p, value, startc, endc, + v, &exprFlags, ctxt, eflags, out_val_freeIt); free(extraFree); } else { *out_val_freeIt = extraFree; @@ -3910,52 +3935,96 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags, if (v->flags & VAR_FROM_ENV) { /* Free the environment variable now since we own it, * but don't free the variable value if it will be returned. */ - Boolean keepValue = nstr == Buf_GetAll(&v->val, NULL); + Boolean keepValue = value == Buf_GetAll(&v->val, NULL); if (keepValue) - *out_val_freeIt = nstr; + *out_val_freeIt = value; (void)VarFreeEnv(v, !keepValue); } else if (exprFlags & VEF_UNDEF) { if (!(exprFlags & VEF_DEF)) { + /* TODO: Use a local variable instead of out_val_freeIt. + * Variables named out_* must only be written to. */ if (*out_val_freeIt != NULL) { free(*out_val_freeIt); *out_val_freeIt = NULL; } if (dynamic) { - nstr = bmake_strsedup(start, p); - *out_val_freeIt = nstr; + value = bmake_strsedup(start, p); + *out_val_freeIt = value; } else { /* The expression is still undefined, therefore discard the * actual value and return an error marker instead. */ - nstr = (eflags & VARE_UNDEFERR) ? var_Error : varUndefined; + value = eflags & VARE_UNDEFERR ? var_Error : varUndefined; } } - if (nstr != Buf_GetAll(&v->val, NULL)) + if (value != Buf_GetAll(&v->val, NULL)) Buf_Destroy(&v->val, TRUE); free(v->name_freeIt); free(v); } - *out_val = nstr; + *out_val = value; return VPR_UNKNOWN; } -/* Substitute for all variables in the given string in the given context. - * - * If eflags & VARE_UNDEFERR, Parse_Error will be called when an undefined - * variable is encountered. - * - * If eflags & VARE_WANTRES, any effects from the modifiers, such as ::=, - * :sh or !cmd! take place. +static void +VarSubstNested(const char **const pp, Buffer *const buf, GNode *const ctxt, + VarEvalFlags const eflags, Boolean *inout_errorReported) +{ + const char *p = *pp; + const char *nested_p = p; + const char *val; + void *val_freeIt; + + (void)Var_Parse(&nested_p, ctxt, eflags, &val, &val_freeIt); + /* TODO: handle errors */ + + if (val == var_Error || val == varUndefined) { + if (!preserveUndefined) { + p = nested_p; + } else if ((eflags & VARE_UNDEFERR) || val == var_Error) { + /* XXX: This condition is wrong. If val == var_Error, + * this doesn't necessarily mean there was an undefined + * variable. It could equally well be a parse error; see + * unit-tests/varmod-order.exp. */ + + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!*inout_errorReported) { + Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"", + (int)(size_t)(nested_p - p), p); + } + p = nested_p; + *inout_errorReported = TRUE; + } else { + /* Copy the initial '$' of the undefined expression, + * thereby deferring expansion of the expression, but + * expand nested expressions if already possible. + * See unit-tests/varparse-undef-partial.mk. */ + Buf_AddByte(buf, *p); + p++; + } + } else { + p = nested_p; + Buf_AddStr(buf, val); + } + + free(val_freeIt); + + *pp = p; +} + +/* Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the + * given string. * * Input: - * str the string which to substitute - * ctxt the context wherein to find variables - * eflags VARE_UNDEFERR if undefineds are an error - * VARE_WANTRES if we actually want the result - * VARE_ASSIGN if we are in a := assignment - * - * Results: - * The resulting string. + * str The string in which the variable expressions are + * expanded. + * ctxt The context in which to start searching for + * variables. The other contexts are searched as well. + * eflags Special effects during expansion. */ VarParseResult Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) @@ -3965,19 +4034,24 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) /* Set true if an error has already been reported, * to prevent a plethora of messages when recursing */ + /* XXX: Why is the 'static' necessary here? */ static Boolean errorReported; - Buf_Init(&buf, 0); + Buf_Init(&buf); errorReported = FALSE; while (*p != '\0') { if (p[0] == '$' && p[1] == '$') { /* A dollar sign may be escaped with another dollar sign. */ - if (save_dollars && (eflags & VARE_ASSIGN)) + if (save_dollars && (eflags & VARE_KEEP_DOLLAR)) Buf_AddByte(&buf, '$'); Buf_AddByte(&buf, '$'); p += 2; - } else if (*p != '$') { + + } else if (p[0] == '$') { + VarSubstNested(&p, &buf, ctxt, eflags, &errorReported); + + } else { /* * Skip as many characters as possible -- either to the end of * the string or to the next dollar sign (variable expression). @@ -3987,48 +4061,6 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) for (p++; *p != '$' && *p != '\0'; p++) continue; Buf_AddBytesBetween(&buf, plainStart, p); - } else { - const char *nested_p = p; - void *freeIt; - const char *val; - (void)Var_Parse(&nested_p, ctxt, eflags, &val, &freeIt); - /* TODO: handle errors */ - - if (val == var_Error || val == varUndefined) { - /* - * 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) { - p = nested_p; - } else if ((eflags & VARE_UNDEFERR) || val == var_Error) { - /* - * 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\"", - (int)(size_t)(nested_p - p), p); - } - p = nested_p; - errorReported = TRUE; - } else { - /* Copy the initial '$' of the undefined expression, - * thereby deferring expansion of the expression, but - * expand nested expressions if already possible. - * See unit-tests/varparse-undef-partial.mk. */ - Buf_AddByte(&buf, *p); - p++; - } - } else { - p = nested_p; - Buf_AddStr(&buf, val); - } - free(freeIt); - freeIt = NULL; } } @@ -4040,9 +4072,9 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res) void Var_Init(void) { - VAR_INTERNAL = Targ_NewGN("Internal"); - VAR_GLOBAL = Targ_NewGN("Global"); - VAR_CMDLINE = Targ_NewGN("Command"); + VAR_INTERNAL = GNode_New("Internal"); + VAR_GLOBAL = GNode_New("Global"); + VAR_CMDLINE = GNode_New("Command"); } /* Clean up the variables module. */ |