diff options
author | Simon J. Gerraty <sjg@FreeBSD.org> | 2021-06-25 18:16:24 +0000 |
---|---|---|
committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2021-06-25 18:16:24 +0000 |
commit | ee914ef902ae018bd4f67192832120f9bf05651f (patch) | |
tree | 4974406fb050a22beaceba7bd0d2dcedd0b49421 | |
parent | 8b6f73e37baf5c37946844ec335a84856b1a9033 (diff) |
Import bmake-20210621vendor/NetBSD/bmake/20210621
Lots more unit tests and code cleanup
Relevant changes from ChangeLog
o job.c: Print -de error information when running multiple jobs
o var.c: only report error for unmatched regex subexpression
when linting (-dL) since we cannot tell when an unmatched
subexpression is an expected result.
reduce memory allocations in the modifiers ':D' and ':U'
reduce memory allocation and strlen calls in modifier ':from=to'
in the ':Q' modifier, only allocate memory if necessary
improve performance for LazyBuf
reduce debug logging and memory allocation for ${:U...}
reduce verbosity of the -dv debug logging for standard cases
fix double varname expansion in the variable modifier '::='
o var.c: avoid evaluating many modifiers in parse only mode
in strict mode (-dL) many variable references are parsed twice,
the first time just to report parse errors early, so we want to
avoid side effects and wasted effort to the extent possible.
144 files changed, 5491 insertions, 3540 deletions
diff --git a/ChangeLog b/ChangeLog index 5cf6f9d8fe57..35235e1f8205 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,116 @@ +2021-06-21 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210621 + Merge with NetBSD make, pick up + o var.c: only report error for unmatched regex subexpression + when linting (-dL) since we cannot tell when an unmatched + subexpression is an expected result. + o move unmatched regex subexpression tests to + varmod-subst-regex.mk and enable strict (lint) mode + +2021-06-16 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210616 + Merge with NetBSD make, pick up + o more unit tests + o cond.c: rename If_Eval to EvalBare + improve function names for parsing conditions + o job.c: fix error handling of targets that cannot be made + o var.c: uncompress code in ApplyModifier_Unique + +2021-05-18 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210518 + Merge with NetBSD make, pick up + o fix unit-tests/opt-chdir to cope with /nonexistent existing. + o job.c: Print -de error information when running multiple jobs + +2021-04-20 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210420 + Merge with NetBSD make, pick up + o use C99 bool type + o convert VarEvalFlags back into an enum + o cond.c: do not complain when skipping the condition 'no >= 10' + o hash.c: avoid allocating memory for simple variable names + o job.c: use distinct wording for writing to the shell commands file + remove type name for the abort status in job handling + rename PrintOutput to PrintFilteredOutput to avoid confusion + o main.c: avoid double slash in name of temporary directory + o var.c: use straight quotes for error 'Bad conditional expression' + reduce memory allocations in the modifiers ':D' and ':U' + rename members of ModifyWord_LoopArgs + clean up pattern flags for the modifiers ':S' and ':C' + reduce memory allocation and strlen calls in modifier ':from=to' + in the ':Q' modifier, only allocate memory if necessary + improve performance for LazyBuf + remove redundant parameter from ParseVarnameLong + migrate ParseModifierPart to use Substring + avoid unnecessary calls to strlen when evaluating modifiers + migrate ModifyWord functions to use Substring + migrate handling of the modifier ':S,from,to,' to Substring + reduce debug logging and memory allocation for ${:U...} + reduce verbosity of the -dv debug logging for standard cases + clean up debug logging for ':M' and ':N' + disallow '$' in the variable name of the modifier ':@' + simplify access to the name of an expression during evaluation + +2021-03-30 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210330 + Merge with NetBSD make, pick up + o replace enum bit-field with struct bit-field for VarEvalFlags + o rename VARE_NONE to VARE_PARSE_ONLY + o var.c: rename ApplyModifiersState to ModChain + fix double varname expansion in the variable modifier '::=' + change debug log for variable evaluation flags to lowercase + +2021-03-14 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210314 + Merge with NetBSD make, pick up + o var.c: avoid evaluating many modifiers in parse only mode + in strict mode (-dL) many variable references are parsed twice, + the first time just to report parse errors early, so we want to + avoid side effects and wasted effort to the extent possible. + +2021-02-26 Simon J Gerraty <sjg@beast.crufty.net> + + * VERSION (_MAKE_VERSION): 20210226 + Merge with NetBSD make, pick up + o remove freestanding freeIt variables + link via FStr + o var.c: restructure code in ParseVarname to target human readers + improve error message for; + bad modifier in variable expression + unclosed modifier + unknown modifier + remove redundant parameter of ApplySingleModifier + explain non-obvious code around indirect variable modifiers + quote ':S' in error message about missing delimiter + extract ParseModifier_Match into separate function + add context information to error message about ':range' modifier + add quotes around variable name in an error message + reorder code in ModifyWords + use more common parameter order for VarSelectWords + make ModifyWord_Subst a little easier to understand + do not expand variable name from the command line twice + extract ExistsInCmdline from Var_SetWithFlags + save a hash map lookup when defining a cmdline variable + clean up VarAdd, Var_Delete, Var_ReexportVars + use bit-shift expressions for VarFlags constants + rename constants for VarFlags + rename ExprDefined constants for debug logging + rename ExprStatus to ExprDefined + split parameters for evaluating variable expressions + reduce redundant code around ModifyWords + print error about failed shell command before overwriting variable + clean up ValidShortVarname, ParseVarnameShort + rename VarExprStatus to ExprStatus + add functions for assigning the value of an expression + rename ApplyModifiersState_Define to Expr_Define + condense the code for parsing :S and :C modifiers + 2021-02-06 Simon J Gerraty <sjg@beast.crufty.net> * VERSION (_MAKE_VERSION): 20210206 @@ -63,6 +63,7 @@ realpath.c setenv.c sigcompat.c str.c +str.h stresep.c strlcpy.c suff.c @@ -399,6 +400,10 @@ 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/job-output-null.exp +unit-tests/job-output-null.mk +unit-tests/jobs-empty-commands-error.exp +unit-tests/jobs-empty-commands-error.mk unit-tests/jobs-empty-commands.exp unit-tests/jobs-empty-commands.mk unit-tests/jobs-error-indirect.exp @@ -439,6 +444,8 @@ unit-tests/opt-debug-curdir.exp unit-tests/opt-debug-curdir.mk unit-tests/opt-debug-dir.exp unit-tests/opt-debug-dir.mk +unit-tests/opt-debug-errors-jobs.exp +unit-tests/opt-debug-errors-jobs.mk unit-tests/opt-debug-errors.exp unit-tests/opt-debug-errors.mk unit-tests/opt-debug-file.exp @@ -627,6 +634,8 @@ unit-tests/var-class-local.exp unit-tests/var-class-local.mk unit-tests/var-class.exp unit-tests/var-class.mk +unit-tests/var-eval-short.exp +unit-tests/var-eval-short.mk unit-tests/var-op-append.exp unit-tests/var-op-append.mk unit-tests/var-op-assign.exp @@ -675,6 +684,8 @@ unit-tests/varmod-l-name-to-value.exp unit-tests/varmod-l-name-to-value.mk unit-tests/varmod-localtime.exp unit-tests/varmod-localtime.mk +unit-tests/varmod-loop-varname.exp +unit-tests/varmod-loop-varname.mk unit-tests/varmod-loop.exp unit-tests/varmod-loop.mk unit-tests/varmod-match-escape.exp @@ -709,6 +720,8 @@ unit-tests/varmod-subst-regex.exp unit-tests/varmod-subst-regex.mk unit-tests/varmod-subst.exp unit-tests/varmod-subst.mk +unit-tests/varmod-sun-shell.exp +unit-tests/varmod-sun-shell.mk unit-tests/varmod-sysv.exp unit-tests/varmod-sysv.mk unit-tests/varmod-tail.exp @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20210206 +_MAKE_VERSION=20210621 @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.197 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.200 2021/05/30 21:16:54 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -147,7 +147,7 @@ struct ar_hdr { #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.197 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.200 2021/05/30 21:16:54 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; @@ -216,6 +216,19 @@ ArchFree(void *ap) } #endif +/* Return "archive(member)". */ +static char * +FullName(const char *archive, const char *member) +{ + size_t len1 = strlen(archive); + size_t len3 = strlen(member); + char *result = bmake_malloc(len1 + 1 + len3 + 1 + 1); + memcpy(result, archive, len1); + memcpy(result + len1, "(", 1); + memcpy(result + len1 + 1, member, len3); + memcpy(result + len1 + 1 + len3, ")", 1 + 1); + return result; +} /* * Parse an archive specification such as "archive.a(member1 member2.${EXT})", @@ -228,10 +241,10 @@ ArchFree(void *ap) * scope The scope in which to expand variables. * * Output: - * return TRUE if it was a valid specification. + * return True if it was a valid specification. * *pp Points to the first non-space after the archive spec. */ -Boolean +bool Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) { char *cp; /* Pointer into line */ @@ -239,12 +252,12 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) MFStr libName; /* Library-part of specification */ char *memName; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ - Boolean expandLibName; /* Whether the parsed libName contains + bool expandLibName; /* Whether the parsed libName contains * variable expressions that need to be * expanded */ libName = MFStr_InitRefer(*pp); - expandLibName = FALSE; + expandLibName = false; for (cp = libName.str; *cp != '(' && *cp != '\0';) { if (*cp == '$') { @@ -252,18 +265,18 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) /* XXX: This code can probably be shortened. */ const char *nested_p = cp; FStr result; - Boolean isError; + bool isError; /* XXX: is expanded twice: once here and once below */ (void)Var_Parse(&nested_p, scope, - VARE_WANTRES | VARE_UNDEFERR, &result); + VARE_UNDEFERR, &result); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) - return FALSE; + return false; - expandLibName = TRUE; + expandLibName = true; cp += nested_p - cp; } else cp++; @@ -272,8 +285,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) *cp++ = '\0'; if (expandLibName) { char *expanded; - (void)Var_Subst(libName.str, scope, - VARE_WANTRES | VARE_UNDEFERR, &expanded); + (void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded); /* TODO: handle errors */ libName = MFStr_InitOwn(expanded); } @@ -285,7 +297,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) * place and skip to the end of it (either white-space or * a close paren). */ - Boolean doSubst = FALSE; + bool doSubst = false; pp_skip_whitespace(&cp); @@ -295,20 +307,19 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) /* Expand nested variable expressions. */ /* XXX: This code can probably be shortened. */ FStr result; - Boolean isError; + bool isError; const char *nested_p = cp; (void)Var_Parse(&nested_p, scope, - VARE_WANTRES | VARE_UNDEFERR, - &result); + VARE_UNDEFERR, &result); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) - return FALSE; + return false; - doSubst = TRUE; + doSubst = true; cp += nested_p - cp; } else { cp++; @@ -325,7 +336,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) Parse_Error(PARSE_FATAL, "No closing parenthesis " "in archive specification"); - return FALSE; + return false; } /* @@ -355,16 +366,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) char *p; char *unexpandedMemName = memName; - (void)Var_Subst(memName, scope, - VARE_WANTRES | VARE_UNDEFERR, - &memName); + (void)Var_Subst(memName, scope, VARE_UNDEFERR, + &memName); /* TODO: handle errors */ /* * Now form an archive spec and recurse to deal with * nested variables and multi-word variable values. */ - fullName = str_concat4(libName.str, "(", memName, ")"); + fullName = FullName(libName.str, memName); p = fullName; if (strchr(memName, '$') != NULL && @@ -383,7 +393,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) /* Error in nested call. */ free(fullName); /* XXX: does unexpandedMemName leak? */ - return FALSE; + return false; } free(fullName); /* XXX: does unexpandedMemName leak? */ @@ -394,8 +404,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) while (!Lst_IsEmpty(&members)) { char *member = Lst_Dequeue(&members); - char *fullname = str_concat4(libName.str, "(", - member, ")"); + char *fullname = FullName(libName.str, member); free(member); gn = Targ_GetNode(fullname); @@ -407,8 +416,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) Lst_Done(&members); } else { - char *fullname = str_concat4(libName.str, "(", memName, - ")"); + char *fullname = FullName(libName.str, memName); gn = Targ_GetNode(fullname); free(fullname); @@ -434,7 +442,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) /* We promised that pp would be set up at the next non-space. */ pp_skip_whitespace(&cp); *pp = cp; - return TRUE; + return true; } /* @@ -444,7 +452,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) * Input: * archive Path to the archive * member Name of member; only its basename is used. - * addToCache TRUE if archive should be cached if not already so. + * addToCache True if archive should be cached if not already so. * * Results: * The ar_hdr for the member, or NULL. @@ -452,7 +460,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) * See ArchFindMember for an almost identical copy of this code. */ static struct ar_hdr * -ArchStatMember(const char *archive, const char *member, Boolean addToCache) +ArchStatMember(const char *archive, const char *member, bool addToCache) { #define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1) FILE *arch; @@ -713,7 +721,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) #endif -static Boolean +static bool ArchiveMember_HasName(const struct ar_hdr *hdr, const char *name, size_t namelen) { @@ -721,22 +729,22 @@ ArchiveMember_HasName(const struct ar_hdr *hdr, const char *ar_name = hdr->AR_NAME; if (strncmp(ar_name, name, namelen) != 0) - return FALSE; + 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; + 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 true; - return FALSE; + return false; } /* @@ -951,7 +959,7 @@ Arch_UpdateMTime(GNode *gn) { struct ar_hdr *arh; - arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); + arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), true); if (arh != NULL) gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10); else @@ -1058,26 +1066,26 @@ Arch_FindLib(GNode *gn, SearchPath *path) * since this is used by 'ar' rules that affect the data contents of the * archive, not by ranlib rules, which affect the TOC. */ -Boolean +bool Arch_LibOODate(GNode *gn) { - Boolean oodate; + bool oodate; if (gn->type & OP_PHONY) { - oodate = TRUE; + oodate = true; } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) { - oodate = FALSE; + oodate = false; } else if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) || (gn->mtime > now) || (gn->youngestChild != NULL && gn->mtime < gn->youngestChild->mtime)) { - oodate = TRUE; + oodate = true; } else { #ifdef RANLIBMAG struct ar_hdr *arh; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents' mod time */ - arh = ArchStatMember(gn->path, RANLIBMAG, FALSE); + arh = ArchStatMember(gn->path, RANLIBMAG, false); if (arh != NULL) { modTimeTOC = (int)strtol(arh->ar_date, NULL, 10); @@ -1094,10 +1102,10 @@ Arch_LibOODate(GNode *gn) */ if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf("no toc..."); - oodate = TRUE; + oodate = true; } #else - oodate = FALSE; + oodate = false; #endif } return oodate; @@ -1119,7 +1127,7 @@ Arch_End(void) #endif } -Boolean +bool Arch_IsLib(GNode *gn) { static const char armag[] = "!<arch>\n"; @@ -1127,11 +1135,11 @@ Arch_IsLib(GNode *gn) int fd; if ((fd = open(gn->path, O_RDONLY)) == -1) - return FALSE; + return false; if (read(fd, buf, sizeof buf) != sizeof buf) { (void)close(fd); - return FALSE; + return false; } (void)close(fd); @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.42 2021/01/30 21:25:10 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.43 2021/04/03 11:08:40 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -101,7 +101,7 @@ Buf_AddByte(Buffer *buf, char byte) end[1] = '\0'; } -MAKE_INLINE Boolean +MAKE_INLINE bool Buf_EndsWith(const Buffer *buf, char ch) { return buf->len > 0 && buf->data[buf->len - 1] == ch; @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.224 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: compat.c,v 1.227 2021/04/27 15:19:25 christos Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -99,7 +99,7 @@ #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.224 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.227 2021/04/27 15:19:25 christos Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; @@ -164,7 +164,7 @@ CompatInterrupt(int signo) } static void -DebugFailedTarget(const char *cmd, GNode *gn) +DebugFailedTarget(const char *cmd, const GNode *gn) { const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", @@ -184,7 +184,7 @@ DebugFailedTarget(const char *cmd, GNode *gn) debug_printf("\n"); } -static Boolean +static bool UseShell(const char *cmd MAKE_ATTR_UNUSED) { #if !defined(MAKE_NATIVE) @@ -194,7 +194,7 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED) * behaviour. Or perhaps the shell has been replaced with something * that does extra logging, and that should not be bypassed. */ - return TRUE; + return true; #else /* * Search for meta characters in the command. If there are no meta @@ -227,22 +227,22 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) { char *cmdStart; /* Start of expanded command */ char *bp; - Boolean silent; /* Don't print command */ - Boolean doIt; /* Execute even if -n */ - volatile Boolean errCheck; /* Check errors */ + bool silent; /* Don't print command */ + bool doIt; /* Execute even if -n */ + volatile bool errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ WAIT_T status; /* Description of child's death */ pid_t cpid; /* Child actually found */ pid_t retstat; /* Result of wait */ const char **volatile av; /* Argument vector for thing to exec */ char **volatile mav; /* Copy of the argument vector for freeing */ - Boolean useShell; /* TRUE if command should be executed + bool useShell; /* True if command should be executed * using a shell */ const char *volatile cmd = cmdp; silent = (gn->type & OP_SILENT) != 0; errCheck = !(gn->type & OP_IGNORE); - doIt = FALSE; + doIt = false; (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); /* TODO: handle errors */ @@ -281,9 +281,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) if (*cmd == '@') silent = !DEBUG(LOUD); else if (*cmd == '-') - errCheck = FALSE; + errCheck = false; else if (*cmd == '+') { - doIt = TRUE; + doIt = true; if (shellName == NULL) /* we came here from jobs */ Shell_Init(); } else @@ -343,7 +343,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) * command into words to form an argument vector we can * execute. */ - Words words = Str_Words(cmd, FALSE); + Words words = Str_Words(cmd, false); mav = words.words; bp = words.freeIt; av = (void *)mav; @@ -392,7 +392,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) */ while ((retstat = wait(&reason)) != cpid) { if (retstat > 0) - JobReapChild(retstat, reason, FALSE); /* not ours? */ + JobReapChild(retstat, reason, false); /* not ours? */ if (retstat == -1 && errno != EINTR) { break; } @@ -425,7 +425,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) if (errCheck) { #ifdef USE_META if (useMeta) { - meta_job_error(NULL, gn, FALSE, status); + meta_job_error(NULL, gn, false, status); } #endif gn->made = ERROR; @@ -483,7 +483,7 @@ MakeNodes(GNodeList *gnodes, GNode *pgn) } } -static Boolean +static bool MakeUnmade(GNode *gn, GNode *pgn) { @@ -493,7 +493,7 @@ MakeUnmade(GNode *gn, GNode *pgn) * 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 to FALSE + * 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. */ @@ -508,7 +508,7 @@ MakeUnmade(GNode *gn, GNode *pgn) if (!(gn->flags & REMAKE)) { gn->made = ABORTED; pgn->flags &= ~(unsigned)REMAKE; - return FALSE; + return false; } if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) @@ -524,7 +524,7 @@ MakeUnmade(GNode *gn, GNode *pgn) if (!GNode_IsOODate(gn)) { gn->made = UPTODATE; DEBUG0(MAKE, "up-to-date.\n"); - return FALSE; + return false; } /* @@ -539,7 +539,7 @@ MakeUnmade(GNode *gn, GNode *pgn) * We need to be re-made. * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set. */ - Make_DoAllVar(gn); + GNode_SetLocalVars(gn); /* * Alter our type to tell if errors should be ignored or things @@ -596,7 +596,7 @@ MakeUnmade(GNode *gn, GNode *pgn) PrintOnError(gn, "\nStop."); exit(1); } - return TRUE; + return true; } static void @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.256 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.267 2021/06/11 14:52:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -95,7 +95,7 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.256 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.267 2021/06/11 14:52:03 rillig Exp $"); /* * The parsing of conditional expressions is based on this grammar: @@ -148,11 +148,11 @@ typedef struct CondParser { * expression has length > 0. The other '.if' variants delegate * to evalBare instead. */ - Boolean plain; + bool plain; /* The function to apply on unquoted bare words. */ - Boolean (*evalBare)(size_t, const char *); - Boolean negateEvalBare; + bool (*evalBare)(size_t, const char *); + bool negateEvalBare; const char *p; /* The remaining condition to parse */ Token curr; /* Single push-back token used in parsing */ @@ -163,10 +163,10 @@ typedef struct CondParser { * specific one, therefore it makes sense to suppress the standard * "Malformed conditional" message. */ - Boolean printedError; + bool printedError; } CondParser; -static CondResult CondParser_Or(CondParser *par, Boolean); +static CondResult CondParser_Or(CondParser *par, bool); static unsigned int cond_depth = 0; /* current .if nesting level */ static unsigned int cond_min_depth = 0; /* depth at makefile open */ @@ -178,21 +178,21 @@ static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" }; * In strict mode, the lhs must be a variable expression or a string literal * in quotes. In non-strict mode it may also be an unquoted string literal. * - * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) - * FALSE when CondEvalExpression is called from ApplyModifier_IfElse + * True when CondEvalExpression is called from Cond_EvalLine (.if etc). + * False when CondEvalExpression is called from ApplyModifier_IfElse * since lhs is already expanded, and at that point we cannot tell if * it was a variable reference or not. */ -static Boolean lhsStrict; +static bool lhsStrict; -static Boolean +static bool 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) +ToToken(bool cond) { return cond ? TOK_TRUE : TOK_FALSE; } @@ -228,7 +228,7 @@ CondParser_SkipWhitespace(CondParser *par) * Return the length of the argument, or 0 on error. */ static size_t -ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, +ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func, char **out_arg) { const char *p = *pp; @@ -264,11 +264,11 @@ ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, * so we don't need to do it. Nor do we return an * error, though perhaps we should. */ - VarEvalFlags eflags = doEval - ? VARE_WANTRES | VARE_UNDEFERR - : VARE_NONE; + VarEvalMode emode = doEval + ? VARE_UNDEFERR + : VARE_PARSE_ONLY; FStr nestedVal; - (void)Var_Parse(&p, SCOPE_CMDLINE, eflags, &nestedVal); + (void)Var_Parse(&p, SCOPE_CMDLINE, emode, &nestedVal); /* TODO: handle errors */ Buf_AddStr(&argBuf, nestedVal.str); FStr_Done(&nestedVal); @@ -290,7 +290,7 @@ ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, if (func != NULL && *p++ != ')') { Parse_Error(PARSE_FATAL, "Missing closing parenthesis for %s()", func); - par->printedError = TRUE; + par->printedError = true; return 0; } @@ -300,34 +300,34 @@ ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, /* Test whether the given variable is defined. */ /*ARGSUSED*/ -static Boolean +static bool FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { FStr value = Var_Value(SCOPE_CMDLINE, arg); - Boolean result = value.str != NULL; + bool result = value.str != NULL; FStr_Done(&value); return result; } /* See if the given target is being made. */ /*ARGSUSED*/ -static Boolean +static bool FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { StringListNode *ln; for (ln = opts.create.first; ln != NULL; ln = ln->next) if (Str_Match(ln->datum, arg)) - return TRUE; - return FALSE; + return true; + return false; } /* See if the given file exists. */ /*ARGSUSED*/ -static Boolean +static bool FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - Boolean result; + bool result; char *path; path = Dir_FindFile(arg, &dirSearchPath); @@ -340,7 +340,7 @@ FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) /* See if the given node exists and is an actual target. */ /*ARGSUSED*/ -static Boolean +static bool FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { GNode *gn = Targ_FindNode(arg); @@ -352,7 +352,7 @@ FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) * associated with it. */ /*ARGSUSED*/ -static Boolean +static bool FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { GNode *gn = Targ_FindNode(arg); @@ -365,10 +365,10 @@ FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) * then we try a floating point conversion instead. * * Results: - * Returns TRUE if the conversion succeeded. + * Returns true if the conversion succeeded. * Sets 'out_value' to the converted number. */ -static Boolean +static bool TryParseNumber(const char *str, double *out_value) { char *end; @@ -378,29 +378,30 @@ TryParseNumber(const char *str, double *out_value) errno = 0; if (str[0] == '\0') { /* XXX: why is an empty string a number? */ *out_value = 0.0; - return TRUE; + return true; } 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; + return true; } if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') - return FALSE; /* skip the expensive strtod call */ + return false; /* skip the expensive strtod call */ dbl_val = strtod(str, &end); if (*end != '\0') - return FALSE; + return false; *out_value = dbl_val; - return TRUE; + return true; } -static Boolean +static bool is_separator(char ch) { - return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; + return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || + ch == '>' || ch == '<' || ch == ')' /* but not '(' */; } /* @@ -409,24 +410,24 @@ is_separator(char ch) * * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} */ -static Boolean +static bool CondParser_StringExpr(CondParser *par, const char *start, - Boolean const doEval, Boolean const quoted, + bool const doEval, bool const quoted, Buffer *buf, FStr *const inout_str) { - VarEvalFlags eflags; + VarEvalMode emode; const char *nested_p; - Boolean atStart; + bool atStart; VarParseResult parseResult; /* if we are in quotes, an undefined variable is ok */ - eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR + emode = doEval && !quoted ? VARE_UNDEFERR : doEval ? VARE_WANTRES - : VARE_NONE; + : VARE_PARSE_ONLY; nested_p = par->p; atStart = nested_p == start; - parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, eflags, inout_str); + parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str); /* TODO: handle errors */ if (inout_str->str == var_Error) { if (parseResult == VPR_ERR) { @@ -436,11 +437,11 @@ CondParser_StringExpr(CondParser *par, const char *start, * * See cond-token-plain.mk $$$$$$$$. */ - par->printedError = TRUE; + par->printedError = true; } /* * XXX: Can there be any situation in which a returned - * var_Error requires freeIt? + * var_Error needs to be freed? */ FStr_Done(inout_str); /* @@ -448,7 +449,7 @@ CondParser_StringExpr(CondParser *par, const char *start, * what getting var_Error back with !doEval means. */ *inout_str = FStr_InitRefer(NULL); - return FALSE; + return false; } par->p = nested_p; @@ -458,30 +459,30 @@ CondParser_StringExpr(CondParser *par, const char *start, * comparison operator or is the end of the expression, we are done. */ if (atStart && is_separator(par->p[0])) - return FALSE; + return false; Buf_AddStr(buf, inout_str->str); FStr_Done(inout_str); *inout_str = FStr_InitRefer(NULL); /* not finished yet */ - return TRUE; + return true; } /* - * Parse a string from a variable reference or an optionally quoted - * string. This is called for the lhs and rhs of string comparisons. + * Parse a string from a variable expression or an optionally quoted + * string. This is called for the left-hand and right-hand sides of + * comparisons. * * Results: * Returns the string, absent any quotes, or NULL on error. - * Sets out_quoted if the string was quoted. - * Sets out_freeIt. + * Sets out_quoted if the leaf was a quoted string literal. */ static void -CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, - FStr *out_str, Boolean *out_quoted) +CondParser_Leaf(CondParser *par, bool doEval, bool strictLHS, + FStr *out_str, bool *out_quoted) { Buffer buf; FStr str; - Boolean quoted; + bool quoted; const char *start; Buf_Init(&buf); @@ -541,14 +542,14 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, got_str: str = FStr_InitOwn(buf.data); cleanup: - Buf_DoneData(&buf); + Buf_DoneData(&buf); /* XXX: memory leak on failure? */ *out_str = str; } -static Boolean -If_Eval(const CondParser *par, const char *arg, size_t arglen) +static bool +EvalBare(const CondParser *par, const char *arg, size_t arglen) { - Boolean res = par->evalBare(arglen, arg); + bool res = par->evalBare(arglen, arg); return par->negateEvalBare ? !res : res; } @@ -556,8 +557,8 @@ If_Eval(const CondParser *par, const char *arg, size_t arglen) * Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". */ -static Boolean -EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) +static bool +EvalNotEmpty(CondParser *par, const char *value, bool quoted) { double num; @@ -576,11 +577,11 @@ EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) if (par->plain) return value[0] != '\0'; - return If_Eval(par, value, strlen(value)); + return EvalBare(par, value, strlen(value)); } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ -static Boolean +static bool EvalCompareNum(double lhs, ComparisonOp op, double rhs) { DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); @@ -608,7 +609,7 @@ EvalCompareStr(CondParser *par, const char *lhs, if (op != EQ && op != NE) { Parse_Error(PARSE_FATAL, "String comparison operator must be either == or !="); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } @@ -619,8 +620,8 @@ EvalCompareStr(CondParser *par, const char *lhs, /* Evaluate a comparison, such as "${VAR} == 12345". */ static Token -EvalCompare(CondParser *par, const char *lhs, Boolean lhsQuoted, - ComparisonOp op, const char *rhs, Boolean rhsQuoted) +EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, + ComparisonOp op, const char *rhs, bool rhsQuoted) { double left, right; @@ -631,7 +632,7 @@ EvalCompare(CondParser *par, const char *lhs, Boolean lhsQuoted, return EvalCompareStr(par, lhs, op, rhs); } -static Boolean +static bool CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) { const char *p = par->p; @@ -655,14 +656,14 @@ CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) *out_op = NE; goto length_2; } - return FALSE; + return false; length_2: par->p = p + 2; - return TRUE; + return true; length_1: par->p = p + 1; - return TRUE; + return true; } /* @@ -674,18 +675,18 @@ length_1: * ${VAR:U0} < 12345 */ static Token -CondParser_Comparison(CondParser *par, Boolean doEval) +CondParser_Comparison(CondParser *par, bool doEval) { Token t = TOK_ERROR; FStr lhs, rhs; ComparisonOp op; - Boolean lhsQuoted, rhsQuoted; + bool lhsQuoted, rhsQuoted; /* * Parse the variable spec and skip over it, saving its * value in lhs. */ - CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted); + CondParser_Leaf(par, doEval, lhsStrict, &lhs, &lhsQuoted); if (lhs.str == NULL) goto done_lhs; @@ -702,11 +703,11 @@ CondParser_Comparison(CondParser *par, Boolean doEval) if (par->p[0] == '\0') { Parse_Error(PARSE_FATAL, "Missing right-hand-side of operator '%s'", opname[op]); - par->printedError = TRUE; + par->printedError = true; goto done_lhs; } - CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted); + CondParser_Leaf(par, doEval, false, &rhs, &rhsQuoted); if (rhs.str == NULL) goto done_rhs; @@ -731,7 +732,7 @@ done_lhs: /*ARGSUSED*/ static size_t ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, - Boolean doEval, const char *func MAKE_ATTR_UNUSED, + bool doEval, const char *func MAKE_ATTR_UNUSED, char **out_arg) { FStr val; @@ -741,8 +742,8 @@ ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, *out_arg = NULL; (*pp)--; /* Make (*pp)[1] point to the '('. */ - (void)Var_Parse(pp, SCOPE_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE, - &val); + (void)Var_Parse(pp, SCOPE_CMDLINE, + doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); /* TODO: handle errors */ /* If successful, *pp points beyond the closing ')' now. */ @@ -767,22 +768,23 @@ ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, } /*ARGSUSED*/ -static Boolean +static bool FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) { /* Magic values ahead, see ParseEmptyArg. */ return arglen == 1; } -static Boolean -CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) +/* Parse a function call expression, such as 'defined(${file})'. */ +static bool +CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) { static const struct fn_def { const char *fn_name; size_t fn_name_len; - size_t (*fn_parse)(CondParser *, const char **, Boolean, + size_t (*fn_parse)(CondParser *, const char **, bool, const char *, char **); - Boolean (*fn_eval)(size_t, const char *); + bool (*fn_eval)(size_t, const char *); } fns[] = { { "defined", 7, ParseFuncArg, FuncDefined }, { "make", 4, ParseFuncArg, FuncMake }, @@ -810,25 +812,25 @@ CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) if (arglen == 0 || arglen == (size_t)-1) { par->p = cp; *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; - return TRUE; + return true; } /* Evaluate the argument using the required function. */ *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); free(arg); par->p = cp; - return TRUE; + return true; } - return FALSE; + return false; } /* - * Parse a function call, a number, a variable expression or a string - * literal. + * Parse a comparison such as '${VAR} == "value"', or a simple leaf without + * operator, which is a number, a variable expression or a string literal. */ static Token -CondParser_LeafToken(CondParser *par, Boolean doEval) +CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; char *arg = NULL; @@ -836,9 +838,6 @@ CondParser_LeafToken(CondParser *par, Boolean doEval) const char *cp; 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]) || cp[0] == '-' || cp[0] == '+') @@ -852,10 +851,14 @@ CondParser_LeafToken(CondParser *par, Boolean doEval) * syntax would be invalid if we did "defined(a)" - so instead treat * as an expression. */ + /* + * XXX: Is it possible to have a variable expression evaluated twice + * at this point? + */ arglen = ParseFuncArg(par, &cp, doEval, NULL, &arg); cp1 = cp; cpp_skip_whitespace(&cp1); - if (*cp1 == '=' || *cp1 == '!') + if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>') return CondParser_Comparison(par, doEval); par->p = cp; @@ -865,14 +868,14 @@ CondParser_LeafToken(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 = ToToken(!doEval || If_Eval(par, arg, arglen)); + t = ToToken(!doEval || EvalBare(par, arg, arglen)); free(arg); return t; } /* Return the next token or comparison result from the parser. */ static Token -CondParser_Token(CondParser *par, Boolean doEval) +CondParser_Token(CondParser *par, bool doEval) { Token t; @@ -900,7 +903,7 @@ CondParser_Token(CondParser *par, Boolean doEval) par->p++; else if (opts.strict) { Parse_Error(PARSE_FATAL, "Unknown operator '|'"); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } return TOK_OR; @@ -911,7 +914,7 @@ CondParser_Token(CondParser *par, Boolean doEval) par->p++; else if (opts.strict) { Parse_Error(PARSE_FATAL, "Unknown operator '&'"); - par->printedError = TRUE; + par->printedError = true; return TOK_ERROR; } return TOK_AND; @@ -931,7 +934,9 @@ CondParser_Token(CondParser *par, Boolean doEval) return CondParser_Comparison(par, doEval); default: - return CondParser_LeafToken(par, doEval); + if (CondParser_FuncCall(par, doEval, &t)) + return t; + return CondParser_ComparisonOrLeaf(par, doEval); } } @@ -942,7 +947,7 @@ CondParser_Token(CondParser *par, Boolean doEval) * Term -> Leaf */ static CondResult -CondParser_Term(CondParser *par, Boolean doEval) +CondParser_Term(CondParser *par, bool doEval) { CondResult res; Token t; @@ -979,7 +984,7 @@ CondParser_Term(CondParser *par, Boolean doEval) * And -> Term */ static CondResult -CondParser_And(CondParser *par, Boolean doEval) +CondParser_And(CondParser *par, bool doEval) { CondResult res; Token op; @@ -992,7 +997,7 @@ CondParser_And(CondParser *par, Boolean doEval) if (op == TOK_AND) { if (res == CR_TRUE) return CondParser_And(par, doEval); - if (CondParser_And(par, FALSE) == CR_ERROR) + if (CondParser_And(par, false) == CR_ERROR) return CR_ERROR; return res; } @@ -1006,7 +1011,7 @@ CondParser_And(CondParser *par, Boolean doEval) * Or -> And */ static CondResult -CondParser_Or(CondParser *par, Boolean doEval) +CondParser_Or(CondParser *par, bool doEval) { CondResult res; Token op; @@ -1019,7 +1024,7 @@ CondParser_Or(CondParser *par, Boolean doEval) if (op == TOK_OR) { if (res == CR_FALSE) return CondParser_Or(par, doEval); - if (CondParser_Or(par, FALSE) == CR_ERROR) + if (CondParser_Or(par, false) == CR_ERROR) return CR_ERROR; return res; } @@ -1029,17 +1034,17 @@ CondParser_Or(CondParser *par, Boolean doEval) } static CondEvalResult -CondParser_Eval(CondParser *par, Boolean *out_value) +CondParser_Eval(CondParser *par, bool *out_value) { CondResult res; DEBUG1(COND, "CondParser_Eval: %s\n", par->p); - res = CondParser_Or(par, TRUE); + res = CondParser_Or(par, true); if (res == CR_ERROR) return COND_INVALID; - if (CondParser_Token(par, FALSE) != TOK_EOF) + if (CondParser_Token(par, false) != TOK_EOF) return COND_INVALID; *out_value = res == CR_TRUE; @@ -1058,9 +1063,9 @@ CondParser_Eval(CondParser *par, Boolean *out_value) * (*value) is set to the boolean value of the condition */ static CondEvalResult -CondEvalExpression(const char *cond, Boolean *out_value, Boolean plain, - Boolean (*evalBare)(size_t, const char *), Boolean negate, - Boolean eprint, Boolean strictLHS) +CondEvalExpression(const char *cond, bool *out_value, bool plain, + bool (*evalBare)(size_t, const char *), bool negate, + bool eprint, bool strictLHS) { CondParser par; CondEvalResult rval; @@ -1074,7 +1079,7 @@ CondEvalExpression(const char *cond, Boolean *out_value, Boolean plain, par.negateEvalBare = negate; par.p = cond; par.curr = TOK_NONE; - par.printedError = FALSE; + par.printedError = false; rval = CondParser_Eval(&par, out_value); @@ -1089,33 +1094,33 @@ CondEvalExpression(const char *cond, Boolean *out_value, Boolean plain, * ${"${VAR}" == value:?yes:no}. */ CondEvalResult -Cond_EvalCondition(const char *cond, Boolean *out_value) +Cond_EvalCondition(const char *cond, bool *out_value) { - return CondEvalExpression(cond, out_value, TRUE, - FuncDefined, FALSE, FALSE, FALSE); + return CondEvalExpression(cond, out_value, true, + FuncDefined, false, false, false); } -static Boolean +static bool IsEndif(const char *p) { return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); } -static Boolean -DetermineKindOfConditional(const char **pp, Boolean *out_plain, - Boolean (**out_evalBare)(size_t, const char *), - Boolean *out_negate) +static bool +DetermineKindOfConditional(const char **pp, bool *out_plain, + bool (**out_evalBare)(size_t, const char *), + bool *out_negate) { const char *p = *pp; p += 2; - *out_plain = FALSE; + *out_plain = false; *out_evalBare = FuncDefined; - *out_negate = FALSE; + *out_negate = false; if (*p == 'n') { p++; - *out_negate = TRUE; + *out_negate = true; } if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ p += 3; @@ -1123,7 +1128,7 @@ DetermineKindOfConditional(const char **pp, Boolean *out_plain, p += 4; *out_evalBare = FuncMake; } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ - *out_plain = TRUE; + *out_plain = true; } else { /* * TODO: Add error message about unknown directive, @@ -1132,11 +1137,11 @@ DetermineKindOfConditional(const char **pp, Boolean *out_plain, * * Example: .elifx 123 */ - return FALSE; + return false; } *pp = p; - return TRUE; + return true; } /* @@ -1161,9 +1166,9 @@ DetermineKindOfConditional(const char **pp, Boolean *out_plain, * * Results: * COND_PARSE to continue parsing the lines that follow the - * conditional (when <cond> evaluates to TRUE) + * conditional (when <cond> evaluates to true) * COND_SKIP to skip the lines after the conditional - * (when <cond> evaluates to 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 @@ -1174,17 +1179,17 @@ Cond_EvalLine(const char *line) { typedef enum IfState { - /* None of the previous <cond> evaluated to TRUE. */ + /* None of the previous <cond> evaluated to true. */ IFS_INITIAL = 0, - /* The previous <cond> evaluated to TRUE. + /* 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. */ + /* One of the previous <cond> evaluated to true. */ IFS_WAS_ACTIVE = 1 << 2 } IfState; @@ -1192,11 +1197,11 @@ Cond_EvalLine(const char *line) static enum IfState *cond_states = NULL; static unsigned int cond_states_cap = 128; - Boolean plain; - Boolean (*evalBare)(size_t, const char *); - Boolean negate; - Boolean isElif; - Boolean value; + bool plain; + bool (*evalBare)(size_t, const char *); + bool negate; + bool isElif; + bool value; IfState state; const char *p = line; @@ -1265,9 +1270,9 @@ Cond_EvalLine(const char *line) return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; } /* Assume for now it is an elif */ - isElif = TRUE; + isElif = true; } else - isElif = FALSE; + isElif = false; if (p[0] != 'i' || p[1] != 'f') { /* @@ -1323,7 +1328,7 @@ Cond_EvalLine(const char *line) /* And evaluate the conditional expression */ if (CondEvalExpression(p, &value, plain, evalBare, negate, - TRUE, TRUE) == COND_INVALID) { + true, true) == COND_INVALID) { /* Syntax error in conditional, error message already output. */ /* Skip everything to matching .endif */ /* XXX: An extra '.else' is not detected in this case. */ @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.270 2021/02/05 05:48:19 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.272 2021/04/04 10:13:09 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -86,7 +86,7 @@ * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. * * Dir_HasWildcards - * Returns TRUE if the name given it needs to + * Returns true if the name given it needs to * be wildcard-expanded. * * SearchPath_Expand @@ -138,7 +138,7 @@ #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.270 2021/02/05 05:48:19 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.272 2021/04/04 10:13:09 rillig Exp $"); /* * A search path is a list of CachedDir structures. A CachedDir has in it the @@ -217,7 +217,7 @@ struct CachedDir { * and "./." are different. * * Not sure what happens when .CURDIR is assigned a new value; see - * Parse_DoVar. + * Parse_Var. */ char *name; @@ -547,14 +547,14 @@ void Dir_SetPATH(void) { CachedDirListNode *ln; - Boolean seenDotLast = FALSE; /* true if we should search '.' last */ + bool seenDotLast = false; /* true if we should search '.' last */ Global_Delete(".PATH"); if ((ln = dirSearchPath.dirs.first) != NULL) { CachedDir *dir = ln->datum; if (dir == dotLast) { - seenDotLast = TRUE; + seenDotLast = true; Global_Append(".PATH", dotLast->name); } } @@ -591,34 +591,34 @@ Dir_SetPATH(void) * that make(1) should be expanding patterns, because then you have to set a * mechanism for escaping the expansion! * - * Return TRUE if the word should be expanded, FALSE otherwise. + * Return true if the word should be expanded, false otherwise. */ -Boolean +bool Dir_HasWildcards(const char *name) { const char *p; - Boolean wild = FALSE; + bool wild = false; int braces = 0, brackets = 0; for (p = name; *p != '\0'; p++) { switch (*p) { case '{': braces++; - wild = TRUE; + wild = true; break; case '}': braces--; break; case '[': brackets++; - wild = TRUE; + wild = true; break; case ']': brackets--; break; case '?': case '*': - wild = TRUE; + wild = true; break; default: break; @@ -647,7 +647,7 @@ static void DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) { const char *dirName = dir->name; - Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; + bool isDot = dirName[0] == '.' && dirName[1] == '\0'; HashIter hi; /* @@ -725,7 +725,7 @@ separator_comma(const char *p) return p; } -static Boolean +static bool contains_wildcard(const char *p) { for (; *p != '\0'; p++) { @@ -734,10 +734,10 @@ contains_wildcard(const char *p) case '?': case '{': case '[': - return TRUE; + return true; } } - return FALSE; + return false; } static char * @@ -1064,19 +1064,19 @@ DirFindDot(const char *name, const char *base) return NULL; } -static Boolean -FindFileRelative(SearchPath *path, Boolean seenDotLast, +static bool +FindFileRelative(SearchPath *path, bool seenDotLast, const char *name, char **out_file) { SearchPathNode *ln; - Boolean checkedDot = FALSE; + bool checkedDot = false; char *file; DEBUG0(DIR, " Trying subdirectories...\n"); if (!seenDotLast) { if (dot != NULL) { - checkedDot = TRUE; + checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) goto found; } @@ -1092,7 +1092,7 @@ FindFileRelative(SearchPath *path, Boolean seenDotLast, if (dir == dot) { if (checkedDot) continue; - checkedDot = TRUE; + checkedDot = true; } if ((file = DirLookupSubdir(dir, name)) != NULL) goto found; @@ -1100,7 +1100,7 @@ FindFileRelative(SearchPath *path, Boolean seenDotLast, if (seenDotLast) { if (dot != NULL && !checkedDot) { - checkedDot = TRUE; + checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) goto found; } @@ -1119,15 +1119,15 @@ FindFileRelative(SearchPath *path, Boolean seenDotLast, goto found; } - return FALSE; + return false; found: *out_file = file; - return TRUE; + return true; } -static Boolean -FindFileAbsolute(SearchPath *path, Boolean const seenDotLast, +static bool +FindFileAbsolute(SearchPath *path, bool const seenDotLast, const char *const name, const char *const base, char **out_file) { @@ -1162,7 +1162,7 @@ FindFileAbsolute(SearchPath *path, Boolean const seenDotLast, ((file = DirLookupAbs(cur, name, base)) != NULL)) goto found; - return FALSE; + return false; found: if (file[0] == '\0') { @@ -1170,7 +1170,7 @@ found: file = NULL; } *out_file = file; - return TRUE; + return true; } /* @@ -1194,7 +1194,7 @@ char * Dir_FindFile(const char *name, SearchPath *path) { char *file; /* the current filename to check */ - Boolean seenDotLast = FALSE; /* true if we should search dot last */ + bool seenDotLast = false; /* true if we should search dot last */ struct cached_stat cst; /* Buffer for stat, if necessary */ const char *trailing_dot = "."; const char *base = str_basename(name); @@ -1210,7 +1210,7 @@ Dir_FindFile(const char *name, SearchPath *path) if (path->dirs.first != NULL) { CachedDir *dir = path->dirs.first->datum; if (dir == dotLast) { - seenDotLast = TRUE; + seenDotLast = true; DEBUG0(DIR, "[dot last]..."); } } @@ -1471,7 +1471,7 @@ ResolveFullName(GNode *gn) * The found file is stored in gn->path, unless the node already had a path. */ void -Dir_UpdateMTime(GNode *gn, Boolean recheck) +Dir_UpdateMTime(GNode *gn, bool recheck) { char *fullName; struct cached_stat cst; @@ -1,4 +1,4 @@ -/* $NetBSD: dir.h,v 1.43 2021/02/05 05:48:19 rillig Exp $ */ +/* $NetBSD: dir.h,v 1.44 2021/04/03 11:08:40 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -82,11 +82,11 @@ void Dir_InitCur(const char *); void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); -Boolean Dir_HasWildcards(const char *); +bool Dir_HasWildcards(const char *); void SearchPath_Expand(SearchPath *, const char *, StringList *); char *Dir_FindFile(const char *, SearchPath *); char *Dir_FindHereOrAbove(const char *, const char *); -void Dir_UpdateMTime(GNode *, Boolean); +void Dir_UpdateMTime(GNode *, bool); CachedDir *SearchPath_Add(SearchPath *, const char *); char *SearchPath_ToFlags(SearchPath *, const char *); void SearchPath_Clear(SearchPath *); @@ -1,4 +1,4 @@ -/* $NetBSD: enum.h,v 1.18 2021/02/02 21:26:51 rillig Exp $ */ +/* $NetBSD: enum.h,v 1.19 2021/03/15 16:00:05 rillig Exp $ */ /* Copyright (c) 2020 Roland Illig <rillig@NetBSD.org> @@ -114,19 +114,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); /* * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 2 flags. - */ -#define ENUM_FLAGS_RTTI_2(typnam, v1, v2) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_1(v1), \ - ENUM__SPEC_1(v2)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_1(v1), \ - ENUM__JOIN_STR_1(v2))) - -/* - * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 3 flags. */ #define ENUM_FLAGS_RTTI_3(typnam, v1, v2, v3) \ @@ -140,19 +127,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); /* * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 4 flags. - */ -#define ENUM_FLAGS_RTTI_4(typnam, v1, v2, v3, v4) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_2(v1, v2), \ - ENUM__SPEC_2(v3, v4)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_2(v1, v2), \ - ENUM__JOIN_STR_2(v3, v4))) - -/* - * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 6 flags. */ #define ENUM_FLAGS_RTTI_6(typnam, v1, v2, v3, v4, v5, v6) \ @@ -166,19 +140,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); /* * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 8 flags. - */ -#define ENUM_FLAGS_RTTI_8(typnam, v1, v2, v3, v4, v5, v6, v7, v8) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_4(v1, v2, v3, v4), \ - ENUM__SPEC_4(v5, v6, v7, v8)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_4(v1, v2, v3, v4), \ - ENUM__JOIN_STR_4(v5, v6, v7, v8))) - -/* - * Declare the necessary data structures for calling Enum_FlagsToString * for an enum with 9 flags. */ #define ENUM_FLAGS_RTTI_9(typnam, v1, v2, v3, v4, v5, v6, v7, v8, v9) \ @@ -215,25 +176,4 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); ENUM__JOIN_STR_2(v29, v30), \ ENUM__JOIN_STR_1(v31))) -/* - * Declare the necessary data structures for calling Enum_FlagsToString - * for an enum with 32 flags. - */ -#define ENUM_FLAGS_RTTI_32(typnam, \ - v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16, \ - v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32) \ - ENUM__FLAGS_RTTI(typnam, \ - ENUM__SPECS_2( \ - ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16), \ - ENUM__SPEC_16(v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32)), \ - ENUM__JOIN_2( \ - ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ - v09, v10, v11, v12, v13, v14, v15, v16), \ - ENUM__JOIN_STR_16(v17, v18, v19, v20, v21, v22, v23, v24, \ - v25, v26, v27, v28, v29, v30, v31, v32))) - #endif @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.141 2021/02/04 21:33:13 rillig Exp $ */ +/* $NetBSD: for.c,v 1.142 2021/04/03 11:08:40 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -58,7 +58,7 @@ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.141 2021/02/04 21:33:13 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.142 2021/04/03 11:08:40 rillig Exp $"); /* One of the variables to the left of the "in" in a .for loop. */ @@ -75,7 +75,7 @@ typedef struct ForLoop { /* Is any of the names 1 character long? If so, when the variable values * are substituted, the parser must handle $V expressions as well, not * only ${V} and $(V). */ - Boolean short_var; + bool short_var; unsigned int sub_next; /* Where to continue iterating */ } ForLoop; @@ -94,7 +94,7 @@ ForLoop_New(void) f->items.words = NULL; f->items.freeIt = NULL; Buf_Init(&f->curBody); - f->short_var = FALSE; + f->short_var = false; f->sub_next = 0; return f; @@ -125,7 +125,7 @@ ForLoop_AddVar(ForLoop *f, const char *name, size_t len) var->nameLen = len; } -static Boolean +static bool ForLoop_ParseVarnames(ForLoop *f, const char **pp) { const char *p = *pp; @@ -136,7 +136,7 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) cpp_skip_whitespace(&p); if (*p == '\0') { Parse_Error(PARSE_FATAL, "missing `in' in for"); - return FALSE; + return false; } /* @@ -151,7 +151,7 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) break; } if (len == 1) - f->short_var = TRUE; + f->short_var = true; ForLoop_AddVar(f, p, len); p += len; @@ -159,14 +159,14 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp) if (f->vars.len == 0) { Parse_Error(PARSE_FATAL, "no iteration variables in for"); - return FALSE; + return false; } *pp = p; - return TRUE; + return true; } -static Boolean +static bool ForLoop_ParseItems(ForLoop *f, const char *p) { char *items; @@ -175,10 +175,10 @@ ForLoop_ParseItems(ForLoop *f, const char *p) if (Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES, &items) != VPR_OK) { Parse_Error(PARSE_FATAL, "Error in .for loop items"); - return FALSE; + return false; } - f->items = Str_Words(items, FALSE); + f->items = Str_Words(items, false); free(items); if (f->items.len == 1 && f->items.words[0][0] == '\0') @@ -189,19 +189,19 @@ ForLoop_ParseItems(ForLoop *f, const char *p) "Wrong number of words (%u) in .for " "substitution list with %u variables", (unsigned)f->items.len, (unsigned)f->vars.len); - return FALSE; + return false; } - return TRUE; + return true; } -static Boolean +static bool IsFor(const char *p) { return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]); } -static Boolean +static bool IsEndfor(const char *p) { return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 && @@ -257,9 +257,9 @@ For_Eval(const char *line) /* * Add another line to the .for loop that is being built up. - * Returns FALSE when the matching .endfor is reached. + * Returns false when the matching .endfor is reached. */ -Boolean +bool For_Accum(const char *line) { const char *p = line; @@ -271,7 +271,7 @@ For_Accum(const char *line) if (IsEndfor(p)) { DEBUG1(FOR, "For: end for %d\n", forLevel); if (--forLevel <= 0) - return FALSE; + return false; } else if (IsFor(p)) { forLevel++; DEBUG1(FOR, "For: new loop %d\n", forLevel); @@ -280,7 +280,7 @@ For_Accum(const char *line) Buf_AddStr(&accumFor->body, line); Buf_AddByte(&accumFor->body, '\n'); - return TRUE; + return true; } @@ -319,16 +319,16 @@ for_var_len(const char *var) * The .for loop substitutes the items as ${:U<value>...}, which means * that characters that break this syntax must be backslash-escaped. */ -static Boolean +static bool NeedsEscapes(const char *value, char endc) { const char *p; for (p = value; *p != '\0'; p++) { if (*p == ':' || *p == '$' || *p == '\\' || *p == endc) - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.61 2021/02/01 17:32:10 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.64 2021/04/11 12:46:54 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.61 2021/02/01 17:32:10 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.64 2021/04/11 12:46:54 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to @@ -84,7 +84,7 @@ MAKE_RCSID("$NetBSD: hash.c,v 1.61 2021/02/01 17:32:10 rillig Exp $"); /* This hash function matches Gosling's Emacs and java.lang.String. */ static unsigned int -hash(const char *key, size_t *out_keylen) +Hash_String(const char *key, size_t *out_keylen) { unsigned int h; const char *p; @@ -98,10 +98,17 @@ hash(const char *key, size_t *out_keylen) return h; } +/* This hash function matches Gosling's Emacs and java.lang.String. */ unsigned int -Hash_Hash(const char *key) +Hash_Substring(Substring key) { - return hash(key, NULL); + unsigned int h; + const char *p; + + h = 0; + for (p = key.start; p != key.end; p++) + h = 31 * h + (unsigned char)*p; + return h; } static HashEntry * @@ -126,6 +133,41 @@ HashTable_Find(HashTable *t, unsigned int h, const char *key) return e; } +static bool +HashEntry_KeyEquals(const HashEntry *he, Substring key) +{ + const char *heKey, *p; + + heKey = he->key; + for (p = key.start; p != key.end; p++, heKey++) + if (*p != *heKey || *heKey == '\0') + return false; + return *heKey == '\0'; +} + +static HashEntry * +HashTable_FindEntryBySubstring(HashTable *t, Substring key, unsigned int h) +{ + HashEntry *e; + unsigned int chainlen = 0; + +#ifdef DEBUG_HASH_LOOKUP + DEBUG4(HASH, "%s: %p h=%08x key=%.*s\n", __func__, t, h, + (int)Substring_Length(key), key.start); +#endif + + for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { + chainlen++; + if (e->key_hash == h && HashEntry_KeyEquals(e, key)) + break; + } + + if (chainlen > t->maxchain) + t->maxchain = chainlen; + + return e; +} + /* Set up the hash table. */ void HashTable_Init(HashTable *t) @@ -171,7 +213,7 @@ HashTable_Done(HashTable *t) HashEntry * HashTable_FindEntry(HashTable *t, const char *key) { - unsigned int h = hash(key, NULL); + unsigned int h = Hash_String(key, NULL); return HashTable_Find(t, h, key); } @@ -188,9 +230,9 @@ HashTable_FindValue(HashTable *t, const char *key) * or return NULL. */ void * -HashTable_FindValueHash(HashTable *t, const char *key, unsigned int h) +HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h) { - HashEntry *he = HashTable_Find(t, h, key); + HashEntry *he = HashTable_FindEntryBySubstring(t, key, h); return he != NULL ? he->value : NULL; } @@ -227,7 +269,7 @@ HashTable_Enlarge(HashTable *t) t->bucketsMask = newMask; t->buckets = newBuckets; DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n", - __func__, t, t->bucketsSize, t->numEntries, t->maxchain); + __func__, (void *)t, t->bucketsSize, t->numEntries, t->maxchain); t->maxchain = 0; } @@ -236,15 +278,15 @@ HashTable_Enlarge(HashTable *t) * Return in out_isNew whether a new entry has been created. */ HashEntry * -HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) +HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) { size_t keylen; - unsigned int h = hash(key, &keylen); + unsigned int h = Hash_String(key, &keylen); HashEntry *he = HashTable_Find(t, h, key); if (he != NULL) { if (out_isNew != NULL) - *out_isNew = FALSE; + *out_isNew = false; return he; } @@ -261,7 +303,7 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) t->numEntries++; if (out_isNew != NULL) - *out_isNew = TRUE; + *out_isNew = true; return he; } @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.38 2020/12/15 01:23:55 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.40 2021/04/11 12:46:54 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -124,9 +124,9 @@ void HashTable_Init(HashTable *); void HashTable_Done(HashTable *); HashEntry *HashTable_FindEntry(HashTable *, const char *); 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 *); +unsigned int Hash_Substring(Substring); +void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int); +HashEntry *HashTable_CreateEntry(HashTable *, const char *, bool *); HashEntry *HashTable_Set(HashTable *, const char *, void *); void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DebugStats(HashTable *, const char *); @@ -146,16 +146,16 @@ HashSet_Done(HashSet *set) HashTable_Done(&set->tbl); } -MAKE_INLINE Boolean +MAKE_INLINE bool HashSet_Add(HashSet *set, const char *key) { - Boolean isNew; + bool isNew; (void)HashTable_CreateEntry(&set->tbl, key, &isNew); return isNew; } -MAKE_INLINE Boolean +MAKE_INLINE bool HashSet_Contains(HashSet *set, const char *key) { return HashTable_FindEntry(&set->tbl, key) != NULL; diff --git a/import.sh b/import.sh index d4554a078951..b80e120daab1 100755 --- a/import.sh +++ b/import.sh @@ -68,19 +68,24 @@ VERSION=`grep '^_MAKE_VERSION' VERSION | sed 's,.*=[[:space:]]*,,'` rm -f *~ mkdir -p ../tmp +# new files are handled automatically +# but we need to rm if needed +# FILES are kept sorted so we can determine what was added and deleted +# but we need to take care dealing with re-ordering +(${GIT} diff FILES | sed -n '/^[+-][^+-]/p'; \ + ${GIT} diff mk/FILES | sed -n '/^[+-][^+-]/s,.,&mk/,p' ) > $TF.diffs +grep '^+' $TF.diffs | sed 's,^.,,' | sort > $TF.adds +grep '^-' $TF.diffs | sed 's,^.,,' | sort > $TF.rms +comm -13 $TF.adds $TF.rms > $TF.rm + if [ -z "$ECHO" ]; then - # new files are handled automatically - # but we need to rm if needed - $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm test -s $TF.rm && xargs rm -f < $TF.rm $GIT add -A $GIT diff --staged | tee ../tmp/bmake-import.diff echo "$GIT tag -a vendor/NetBSD/bmake/$VERSION" > ../tmp/bmake-post.sh echo "After you commit, run $here/../tmp/bmake-post.sh" else - # FILES is kept sorted so we can determine what was added and deleted - $GIT diff FILES | sed -n '/^+[^+]/s,^+,,p' > $TF.add - $GIT diff FILES | sed -n '/^-[^-]/s,^-,,p' > $TF.rm + comm -23 $TF.adds $TF.rms > $TF.add test -s $TF.rm && { echo Removing:; cat $TF.rm; } test -s $TF.add && { echo Adding:; cat $TF.add; } $GIT diff @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $ */ +/* $NetBSD: job.c,v 1.435 2021/06/16 09:47:51 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -155,7 +155,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.435 2021/06/16 09:47:51 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are @@ -181,7 +181,7 @@ MAKE_RCSID("$NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $"); * printf template for executing the command while ignoring the return * status. Finally runChkTmpl 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 + * hasErrCtl is false, the command will be executed anyway as is, and if it * causes an error, so be it. Any templates set up to echo the command will * escape any '$ ` \ "' characters in the command string to avoid unwanted * shell code injection, the escaped command is safe to use in double quotes. @@ -201,14 +201,14 @@ typedef struct Shell { */ const char *name; - Boolean hasEchoCtl; /* whether both echoOff and echoOn are there */ + bool hasEchoCtl; /* whether both echoOff and echoOn are there */ const char *echoOff; /* command to turn echoing off */ const char *echoOn; /* command to turn echoing back on */ const char *noPrint; /* text to skip when printing output from the * shell. This is usually the same as echoOff */ size_t noPrintLen; /* length of noPrint command */ - Boolean hasErrCtl; /* whether error checking can be controlled + bool hasErrCtl; /* whether error checking can be controlled * for individual commands */ const char *errOn; /* command to turn on error checking */ const char *errOff; /* command to turn off error checking */ @@ -230,16 +230,16 @@ typedef struct Shell { typedef struct CommandFlags { /* Whether to echo the command before or instead of running it. */ - Boolean echo; + bool echo; /* Run the command even in -n or -N mode. */ - Boolean always; + bool always; /* - * true if we turned error checking off before printing the command - * and need to turn it back on + * true if we turned error checking off before writing the command to + * the commands file and need to turn it back on */ - Boolean ignerr; + bool ignerr; } CommandFlags; /* @@ -252,7 +252,7 @@ typedef struct ShellWriter { FILE *f; /* we've sent 'set -x' */ - Boolean xtraced; + bool xtraced; } ShellWriter; @@ -260,14 +260,12 @@ typedef struct ShellWriter { * error handling variables */ static int job_errors = 0; /* number of errors reported */ -typedef enum AbortReason { /* why is the make aborting? */ +static enum { /* Why is the make aborting? */ ABORT_NONE, - ABORT_ERROR, /* Because of an error */ - ABORT_INTERRUPT, /* Because it was interrupted */ + ABORT_ERROR, /* Aborted because of an error */ + ABORT_INTERRUPT, /* Aborted because it was interrupted */ ABORT_WAIT /* Waiting for jobs to finish */ - /* XXX: "WAIT" is not a _reason_ for aborting, it's rather a status. */ -} AbortReason; -static AbortReason aborting = ABORT_NONE; +} aborting = ABORT_NONE; #define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ /* @@ -322,12 +320,12 @@ static Shell shells[] = { */ { DEFSHELL_CUSTOM, /* .name */ - FALSE, /* .hasEchoCtl */ + false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ @@ -345,12 +343,12 @@ static Shell shells[] = { */ { "sh", /* .name */ - FALSE, /* .hasEchoCtl */ + false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ @@ -371,12 +369,12 @@ static Shell shells[] = { */ { "ksh", /* .name */ - TRUE, /* .hasEchoCtl */ + true, /* .hasEchoCtl */ "set +v", /* .echoOff */ "set -v", /* .echoOn */ "set +v", /* .noPrint */ 6, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ @@ -394,12 +392,12 @@ static Shell shells[] = { */ { "csh", /* .name */ - TRUE, /* .hasEchoCtl */ + true, /* .hasEchoCtl */ "unset verbose", /* .echoOff */ "set verbose", /* .echoOn */ "unset verbose", /* .noPrint */ 13, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ + false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ @@ -426,8 +424,8 @@ static char *shell_freeIt = NULL; /* Allocated memory for custom .SHELL */ 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 Boolean lurking_children = FALSE; -static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ +static bool lurking_children = false; +static bool make_suspended = false; /* Whether we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to @@ -438,7 +436,7 @@ static Job **jobByFdIndex = NULL; static nfds_t fdsLen = 0; static void watchfd(Job *); static void clearfd(Job *); -static Boolean readyfd(Job *); +static bool readyfd(Job *); static char *targPrefix = NULL; /* To identify a job change in the output. */ static Job tokenWaitJob; /* token wait pseudo-job */ @@ -454,8 +452,8 @@ enum { static sigset_t caught_signals; /* Set of signals we handle */ static volatile sig_atomic_t caught_sigchld; -static void JobDoOutput(Job *, Boolean); -static void JobInterrupt(Boolean, int) MAKE_ATTR_DEAD; +static void CollectOutput(Job *, bool); +static void JobInterrupt(bool, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobSigReset(void); @@ -493,7 +491,7 @@ Job_FlagsToString(const Job *job, char *buf, size_t bufsize) } static void -job_table_dump(const char *where) +DumpJobs(const char *where) { Job *job; char flags[4]; @@ -646,7 +644,7 @@ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { /* Run .INTERRUPT target then exit */ - JobInterrupt(TRUE, signo); + JobInterrupt(true, signo); } /* @@ -657,7 +655,7 @@ MAKE_ATTR_DEAD static void JobPassSig_term(int signo) { /* Dont run .INTERRUPT target then exit */ - JobInterrupt(FALSE, signo); + JobInterrupt(false, signo); } static void @@ -667,7 +665,7 @@ JobPassSig_suspend(int signo) struct sigaction act; /* Suppress job started/continued messages */ - make_suspended = TRUE; + make_suspended = true; /* Pass the signal onto every job */ JobCondPassSig(signo); @@ -716,7 +714,7 @@ JobPassSig_suspend(int signo) } static Job * -JobFindPid(int pid, JobStatus status, Boolean isJobs) +JobFindPid(int pid, JobStatus status, bool isJobs) { Job *job; @@ -725,7 +723,7 @@ JobFindPid(int pid, JobStatus status, Boolean isJobs) return job; } if (DEBUG(JOB) && isJobs) - job_table_dump("no pid"); + DumpJobs("no pid"); return NULL; } @@ -734,17 +732,17 @@ static void ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) { char *p = *pp; - out_cmdFlags->echo = TRUE; - out_cmdFlags->ignerr = FALSE; - out_cmdFlags->always = FALSE; + out_cmdFlags->echo = true; + out_cmdFlags->ignerr = false; + out_cmdFlags->always = false; for (;;) { if (*p == '@') out_cmdFlags->echo = DEBUG(LOUD); else if (*p == '-') - out_cmdFlags->ignerr = TRUE; + out_cmdFlags->ignerr = true; else if (*p == '+') - out_cmdFlags->always = TRUE; + out_cmdFlags->always = true; else break; p++; @@ -775,7 +773,7 @@ EscapeShellDblQuot(const char *cmd) } static void -ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg) +ShellWriter_WriteFmt(ShellWriter *wr, const char *fmt, const char *arg) { DEBUG1(JOB, fmt, arg); @@ -785,56 +783,56 @@ ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg) } static void -ShellWriter_Println(ShellWriter *wr, const char *line) +ShellWriter_WriteLine(ShellWriter *wr, const char *line) { - ShellWriter_PrintFmt(wr, "%s\n", line); + ShellWriter_WriteFmt(wr, "%s\n", line); } static void ShellWriter_EchoOff(ShellWriter *wr) { if (shell->hasEchoCtl) - ShellWriter_Println(wr, shell->echoOff); + ShellWriter_WriteLine(wr, shell->echoOff); } static void ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd) { - ShellWriter_PrintFmt(wr, shell->echoTmpl, escCmd); + ShellWriter_WriteFmt(wr, shell->echoTmpl, escCmd); } static void ShellWriter_EchoOn(ShellWriter *wr) { if (shell->hasEchoCtl) - ShellWriter_Println(wr, shell->echoOn); + ShellWriter_WriteLine(wr, shell->echoOn); } static void ShellWriter_TraceOn(ShellWriter *wr) { if (!wr->xtraced) { - ShellWriter_Println(wr, "set -x"); - wr->xtraced = TRUE; + ShellWriter_WriteLine(wr, "set -x"); + wr->xtraced = true; } } static void -ShellWriter_ErrOff(ShellWriter *wr, Boolean echo) +ShellWriter_ErrOff(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); - ShellWriter_Println(wr, shell->errOff); + ShellWriter_WriteLine(wr, shell->errOff); if (echo) ShellWriter_EchoOn(wr); } static void -ShellWriter_ErrOn(ShellWriter *wr, Boolean echo) +ShellWriter_ErrOn(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); - ShellWriter_Println(wr, shell->errOn); + ShellWriter_WriteLine(wr, shell->errOn); if (echo) ShellWriter_EchoOn(wr); } @@ -845,11 +843,11 @@ ShellWriter_ErrOn(ShellWriter *wr, Boolean echo) * (configurable per shell). */ static void -JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, +JobWriteSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, const char *escCmd, const char **inout_cmdTemplate) { /* XXX: Why is the job modified at this point? */ - job->ignerr = TRUE; + job->ignerr = true; if (job->echo && inout_cmdFlags->echo) { ShellWriter_EchoOff(wr); @@ -859,7 +857,7 @@ JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, * Leave echoing off so the user doesn't see the commands * for toggling the error checking. */ - inout_cmdFlags->echo = FALSE; + inout_cmdFlags->echo = false; } else { if (inout_cmdFlags->echo) ShellWriter_EchoCmd(wr, escCmd); @@ -871,11 +869,11 @@ JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, * so pretend error checking is still on. * XXX: What effects does this have, and why is it necessary? */ - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } static void -JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean run, +JobWriteSpecials(Job *job, ShellWriter *wr, const char *escCmd, bool run, CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate) { if (!run) { @@ -883,38 +881,42 @@ JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean run, * If there is no command to run, there is no need to switch * error checking off and on again for nothing. */ - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } else if (shell->hasErrCtl) ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') { - JobPrintSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, + JobWriteSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, inout_cmdTemplate); } else - inout_cmdFlags->ignerr = FALSE; + inout_cmdFlags->ignerr = false; } /* - * Put out another command for the given job. + * Write a shell command to the job's commands file, to be run later. * * If the command starts with '@' and neither the -s nor the -n flag was - * given to make, we stick a shell-specific echoOff command in the script. + * given to make, stick a shell-specific echoOff command in the script. * * If the command starts with '-' and the shell has no error control (none - * of the predefined shells has that), we ignore errors for the entire job. - * XXX: Why ignore errors for the entire job? - * XXX: Even ignore errors for the commands before this command? + * of the predefined shells has that), ignore errors for the entire job. * - * If the command is just "...", all further commands of this job are skipped - * for now. They are attached to the .END node and will be run by Job_Finish - * after all other targets have been made. + * XXX: Why ignore errors for the entire job? This is even documented in the + * manual page, but without any rationale since there is no known rationale. + * + * XXX: The manual page says the '-' "affects the entire job", but that's not + * accurate. The '-' does not affect the commands before the '-'. + * + * If the command is just "...", skip all further commands of this job. These + * commands are attached to the .END node instead and will be run by + * Job_Finish after all other targets have been made. */ static void -JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) +JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) { - Boolean run; + bool run; CommandFlags cmdFlags; - /* Template for printing a command to the shell file */ + /* Template for writing a command to the shell file */ const char *cmdTemplate; char *xcmd; /* The expanded command */ char *xcmdStart; @@ -953,12 +955,12 @@ JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) ShellWriter_EchoOff(wr); } else { if (shell->hasErrCtl) - cmdFlags.echo = TRUE; + cmdFlags.echo = true; } } if (cmdFlags.ignerr) { - JobPrintSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); + JobWriteSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); } else { /* @@ -972,7 +974,7 @@ JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) if (job->echo && cmdFlags.echo) { ShellWriter_EchoOff(wr); ShellWriter_EchoCmd(wr, escCmd); - cmdFlags.echo = FALSE; + cmdFlags.echo = false; } /* * If it's a comment line or blank, avoid the possible @@ -982,14 +984,14 @@ JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) escCmd[0] == '\0' ? shell->runIgnTmpl : shell->runChkTmpl; - cmdFlags.ignerr = FALSE; + cmdFlags.ignerr = false; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0) ShellWriter_TraceOn(wr); - ShellWriter_PrintFmt(wr, cmdTemplate, xcmd); + ShellWriter_WriteFmt(wr, cmdTemplate, xcmd); free(xcmdStart); free(escCmd); @@ -1001,19 +1003,22 @@ JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) } /* - * Print all commands to the shell file that is later executed. + * Write all commands to the shell file that is later executed. * - * The special command "..." stops printing and saves the remaining commands + * The special command "..." stops writing and saves the remaining commands * to be executed later, when the target '.END' is made. * * Return whether at least one command was written to the shell file. */ -static Boolean -JobPrintCommands(Job *job) +static bool +JobWriteCommands(Job *job) { StringListNode *ln; - Boolean seen = FALSE; - ShellWriter wr = { job->cmdFILE, FALSE }; + bool seen = false; + ShellWriter wr; + + wr.f = job->cmdFILE; + wr.xtraced = false; for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; @@ -1024,8 +1029,8 @@ JobPrintCommands(Job *job) break; } - JobPrintCommand(job, &wr, ln, ln->datum); - seen = TRUE; + JobWriteCommand(job, &wr, ln, ln->datum); + seen = true; } return seen; @@ -1063,12 +1068,26 @@ JobClosePipes(Job *job) (void)close(job->outPipe); job->outPipe = -1; - JobDoOutput(job, TRUE); + CollectOutput(job, true); (void)close(job->inPipe); job->inPipe = -1; } static void +DebugFailedJob(const Job *job) +{ + const StringListNode *ln; + + if (!DEBUG(ERROR)) + return; + + debug_printf("\n*** Failed target: %s\n*** Failed commands:\n", + job->node->name); + for (ln = job->node->commands.first; ln != NULL; ln = ln->next) + debug_printf("\t%s\n", (const char *)ln->datum); +} + +static void JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) { SwitchOutputTo(job->node); @@ -1079,6 +1098,7 @@ JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) } #endif if (!shouldDieQuietly(job->node, -1)) { + DebugFailedJob(job); (void)printf("*** [%s] Error code %d%s\n", job->node->name, WEXITSTATUS(*inout_status), job->ignerr ? " (ignored)" : ""); @@ -1111,6 +1131,7 @@ static void JobFinishDoneSignaled(Job *job, WAIT_T status) { SwitchOutputTo(job->node); + DebugFailedJob(job); (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); if (deleteOnError) JobDeleteTarget(job->node); @@ -1143,7 +1164,7 @@ JobFinishDone(Job *job, WAIT_T *inout_status) static void JobFinish (Job *job, WAIT_T status) { - Boolean done, return_job_token; + bool done, return_job_token; DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", job->pid, job->node->name, status); @@ -1158,7 +1179,7 @@ JobFinish (Job *job, WAIT_T status) (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } - done = TRUE; + done = true; } else if (WIFEXITED(status)) { /* @@ -1172,7 +1193,7 @@ JobFinish (Job *job, WAIT_T status) } else { /* No need to close things down or anything. */ - done = FALSE; + done = false; } if (done) @@ -1186,13 +1207,13 @@ JobFinish (Job *job, WAIT_T status) } #endif - return_job_token = FALSE; + return_job_token = false; Trace_Log(JOBEND, job); if (!job->special) { if (WAIT_STATUS(status) != 0 || (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) - return_job_token = TRUE; + return_job_token = true; } if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && @@ -1205,7 +1226,7 @@ JobFinish (Job *job, WAIT_T status) JobSaveCommands(job); job->node->made = MADE; if (!job->special) - return_job_token = TRUE; + return_job_token = true; Make_Update(job->node); job->status = JOB_ST_FREE; } else if (status != 0) { @@ -1229,10 +1250,12 @@ static void TouchRegular(GNode *gn) { const char *file = GNode_Path(gn); - struct utimbuf times = { now, now }; + struct utimbuf times; int fd; char c; + times.actime = now; + times.modtime = now; if (utime(file, ×) >= 0) return; @@ -1262,7 +1285,7 @@ TouchRegular(GNode *gn) * If the file did not exist, it is created. */ void -Job_Touch(GNode *gn, Boolean echo) +Job_Touch(GNode *gn, bool echo) { if (gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL | @@ -1301,17 +1324,17 @@ Job_Touch(GNode *gn, Boolean echo) * abortProc Function to abort with message * * Results: - * TRUE if the commands list is/was ok. + * true if the commands list is/was ok. */ -Boolean +bool Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (GNode_IsTarget(gn)) - return TRUE; + return true; if (!Lst_IsEmpty(&gn->commands)) - return TRUE; + return true; if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children)) - return TRUE; + return true; /* * No commands. Look for .DEFAULT rule from which we might infer @@ -1330,12 +1353,12 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) */ Make_HandleUse(defaultNode, gn); Var_Set(gn, IMPSRC, GNode_VarTarget(gn)); - return TRUE; + return true; } - Dir_UpdateMTime(gn, FALSE); + Dir_UpdateMTime(gn, false); if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) - return TRUE; + return true; /* * The node wasn't the target of an operator. We have no .DEFAULT @@ -1351,25 +1374,25 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) "%s: %s, %d: ignoring stale %s for %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name); - return TRUE; + return true; } if (gn->type & OP_OPTIONAL) { (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", progname, gn->name, "ignored"); (void)fflush(stdout); - return TRUE; + return true; } if (opts.keepgoing) { (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", progname, gn->name, "continuing"); (void)fflush(stdout); - return FALSE; + return false; } abortProc("%s: don't know how to make %s. Stop", progname, gn->name); - return FALSE; + return false; } /* @@ -1526,7 +1549,7 @@ JobExec(Job *job, char **argv) if (DEBUG(JOB)) { debug_printf("JobExec(%s): pid %d added to jobs table\n", job->node->name, job->pid); - job_table_dump("job started"); + DumpJobs("job started"); } JobSigUnlock(&mask); } @@ -1577,7 +1600,7 @@ JobMakeArgv(Job *job, char **argv) } static void -JobWriteShellCommands(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) +JobWriteShellCommands(Job *job, GNode *gn, bool *out_run) { /* * tfile is the name of a file into which all shell commands @@ -1587,15 +1610,6 @@ JobWriteShellCommands(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) char tfile[MAXPATHLEN]; int tfd; /* File descriptor to the temp file */ - /* - * We're serious here, but if the commands were bogus, we're - * also dead... - */ - if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ - DieHorribly(); - } - tfd = Job_TempFile(TMPPAT, tfile, sizeof tfile); job->cmdFILE = fdopen(tfd, "w+"); @@ -1608,40 +1622,34 @@ JobWriteShellCommands(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) if (useMeta) { meta_job_start(job, gn); if (gn->type & OP_SILENT) /* might have changed */ - job->echo = FALSE; + job->echo = false; } #endif - *out_run = JobPrintCommands(job); + *out_run = JobWriteCommands(job); } /* - * Start a target-creation process going for the target described by the - * graph node gn. - * - * Input: - * gn target to create - * flags flags for the job to override normal ones. - * previous The previous Job structure for this node, if any. + * Start a target-creation process going for the target described by gn. * * Results: * JOB_ERROR if there was an error in the commands, JOB_FINISHED * if there isn't actually anything left to do for the job and * JOB_RUNNING if the job has been started. * - * Side Effects: + * Details: * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. * * NB: The return value is ignored by everyone. */ static JobStartResult -JobStart(GNode *gn, Boolean special) +JobStart(GNode *gn, bool special) { Job *job; /* new job descriptor */ char *argv[10]; /* Argument vector to shell */ - Boolean cmdsOK; /* true if the nodes commands were all right */ - Boolean run; + bool cmdsOK; /* true if the nodes commands were all right */ + bool run; for (job = job_table; job < job_table_end; job++) { if (job->status == JOB_ST_FREE) @@ -1669,7 +1677,16 @@ JobStart(GNode *gn, Boolean special) if (Lst_IsEmpty(&gn->commands)) { job->cmdFILE = stdout; - run = FALSE; + run = false; + + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } } else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || (!opts.noExecute && !opts.touchFlag)) { /* @@ -1679,22 +1696,31 @@ JobStart(GNode *gn, Boolean special) * virtual targets. */ - JobWriteShellCommands(job, gn, cmdsOK, &run); + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } + + JobWriteShellCommands(job, gn, &run); (void)fflush(job->cmdFILE); } else if (!GNode_ShouldExecute(gn)) { /* - * Just print all the commands to stdout in one fell swoop. + * Just write all the commands to stdout in one fell swoop. * This still sets up job->tailCmds correctly. */ SwitchOutputTo(gn); job->cmdFILE = stdout; if (cmdsOK) - JobPrintCommands(job); - run = FALSE; + JobWriteCommands(job); + run = false; (void)fflush(job->cmdFILE); } else { Job_Touch(gn, job->echo); - run = FALSE; + run = false; } /* If we're not supposed to execute a shell, don't. */ @@ -1734,12 +1760,15 @@ JobStart(GNode *gn, Boolean special) } /* - * Print the output of the shell command, skipping the noPrint text of the - * shell, if any. The default shell does not have noPrint though, which means - * that in all practical cases, handling the output is left to the caller. + * If the shell has an output filter (which only csh and ksh have by default), + * print the output of the child process, skipping the noPrint text of the + * shell. + * + * Return the part of the output that the calling function needs to output by + * itself. */ static char * -JobOutput(char *cp, char *endp) /* XXX: should all be const */ +PrintFilteredOutput(char *cp, char *endp) /* XXX: should all be const */ { char *ecp; /* XXX: should be const */ @@ -1786,14 +1815,14 @@ JobOutput(char *cp, char *endp) /* XXX: should all be const */ * * Input: * job the job whose output needs printing - * finish TRUE if this is the last time we'll be called + * finish true if this is the last time we'll be called * for this job */ static void -JobDoOutput(Job *job, Boolean finish) +CollectOutput(Job *job, bool finish) { - Boolean gotNL; /* true if got a newline */ - Boolean fbuf; /* true if our buffer filled up */ + bool gotNL; /* true if got a newline */ + bool fbuf; /* true if our buffer filled up */ size_t nr; /* number of bytes read */ size_t i; /* auxiliary index into outBuf */ size_t max; /* limit for i (end of current data) */ @@ -1801,8 +1830,8 @@ JobDoOutput(Job *job, Boolean finish) /* Read as many bytes as will fit in the buffer. */ again: - gotNL = FALSE; - fbuf = FALSE; + gotNL = false; + fbuf = false; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); @@ -1810,7 +1839,7 @@ again: if (errno == EAGAIN) return; if (DEBUG(JOB)) { - perror("JobDoOutput(piperead)"); + perror("CollectOutput(piperead)"); } nr = 0; } else { @@ -1826,25 +1855,27 @@ again: if (nr == 0 && job->curPos != 0) { job->outBuf[job->curPos] = '\n'; nr = 1; - finish = FALSE; + finish = false; } else if (nr == 0) { - finish = FALSE; + finish = false; } /* * Look for the last newline in the bytes we just got. If there is * one, break out of the loop with 'i' as its index and gotNL set - * TRUE. + * true. */ max = job->curPos + nr; for (i = job->curPos + nr - 1; i >= job->curPos && i != (size_t)-1; i--) { if (job->outBuf[i] == '\n') { - gotNL = TRUE; + gotNL = true; break; } else if (job->outBuf[i] == '\0') { /* - * Why? + * FIXME: The null characters are only replaced with + * space _after_ the last '\n'. Everywhere else they + * hide the rest of the command output. */ job->outBuf[i] = ' '; } @@ -1857,7 +1888,7 @@ again: * If we've run out of buffer space, we have no choice * but to print the stuff. sigh. */ - fbuf = TRUE; + fbuf = true; i = job->curPos; } } @@ -1876,10 +1907,16 @@ again: if (i >= job->curPos) { char *cp; - cp = JobOutput(job->outBuf, &job->outBuf[i]); + /* + * FIXME: SwitchOutputTo should be here, according to + * the comment above. But since PrintOutput does not + * do anything in the default shell, this bug has gone + * unnoticed until now. + */ + cp = PrintFilteredOutput(job->outBuf, &job->outBuf[i]); /* - * There's still more in that thar buffer. This time, + * There's still more in the output buffer. This time, * though, we know there's no newline at the end, so * we add one of our own free will. */ @@ -1917,7 +1954,7 @@ again: * end-of-file on the pipe. This is guaranteed to happen * eventually since the other end of the pipe is now closed * (we closed it explicitly and the child has exited). When - * we do get an EOF, finish will be set FALSE and we'll fall + * we do get an EOF, finish will be set false and we'll fall * through and out. */ goto again; @@ -1940,7 +1977,7 @@ JobRun(GNode *targ) Lst_Append(&lst, targ); (void)Make_Run(&lst); Lst_Done(&lst); - JobStart(targ, TRUE); + JobStart(targ, true); while (jobTokensRunning != 0) { Job_CatchOutput(); } @@ -1982,7 +2019,7 @@ Job_CatchChildren(void) while ((pid = waitpid((pid_t)-1, &status, WNOHANG | WUNTRACED)) > 0) { DEBUG2(JOB, "Process %d exited/stopped status %x.\n", pid, WAIT_STATUS(status)); - JobReapChild(pid, status, TRUE); + JobReapChild(pid, status, true); } } @@ -1991,7 +2028,7 @@ Job_CatchChildren(void) * this lets us reap jobs regardless. */ void -JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) +JobReapChild(pid_t pid, WAIT_T status, bool isJobs) { Job *job; /* job descriptor for dead child */ @@ -2025,7 +2062,7 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) (void)printf("*** [%s] Stopped -- signal %d\n", job->node->name, WSTOPSIG(status)); } - job->suspended = TRUE; + job->suspended = true; } (void)fflush(stdout); return; @@ -2088,7 +2125,7 @@ Job_CatchOutput(void) continue; job = jobByFdIndex[i]; if (job->status == JOB_ST_RUNNING) - JobDoOutput(job, FALSE); + CollectOutput(job, false); #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) /* * With meta mode, we may have activity on the job's filemon @@ -2113,7 +2150,7 @@ Job_CatchOutput(void) void Job_Make(GNode *gn) { - (void)JobStart(gn, FALSE); + (void)JobStart(gn, false); } static void @@ -2226,7 +2263,7 @@ Job_Init(void) if (rval > 0) continue; if (rval == 0) - lurking_children = TRUE; + lurking_children = true; break; } @@ -2324,7 +2361,7 @@ FindShellByName(const char *name) * line The shell spec * * Results: - * FALSE if the specification was incorrect. + * false if the specification was incorrect. * * Side Effects: * 'shell' points to a Shell structure (either predefined or @@ -2351,15 +2388,15 @@ FindShellByName(const char *name) * hasErrCtl True if shell has error checking control * newline String literal to represent a newline char * check Command to turn on error checking if hasErrCtl - * is TRUE or template of command to echo a command + * is true or template of command to echo a command * for which error checking is off if hasErrCtl is - * FALSE. + * false. * ignore Command to turn off error checking if hasErrCtl - * is TRUE or template of command to execute a + * is true or template of command to execute a * command so as to ignore any errors it returns if - * hasErrCtl is FALSE. + * hasErrCtl is false. */ -Boolean +bool Job_ParseShell(char *line) { Words wordsList; @@ -2368,7 +2405,7 @@ Job_ParseShell(char *line) size_t argc; char *path; Shell newShell; - Boolean fullSpec = FALSE; + bool fullSpec = false; Shell *sh; /* XXX: don't use line as an iterator variable */ @@ -2381,13 +2418,13 @@ Job_ParseShell(char *line) /* * Parse the specification by keyword */ - wordsList = Str_Words(line, TRUE); + wordsList = Str_Words(line, true); words = wordsList.words; argc = wordsList.len; path = wordsList.freeIt; if (words == NULL) { Error("Unterminated quoted string [%s]", line); - return FALSE; + return false; } shell_freeIt = path; @@ -2433,9 +2470,9 @@ Job_ParseShell(char *line) Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg); free(words); - return FALSE; + return false; } - fullSpec = TRUE; + fullSpec = true; } } @@ -2450,13 +2487,13 @@ Job_ParseShell(char *line) Parse_Error(PARSE_FATAL, "Neither path nor name specified"); free(words); - return FALSE; + return false; } else { if ((sh = FindShellByName(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); - return FALSE; + return false; } shell = sh; shellName = newShell.name; @@ -2473,7 +2510,7 @@ Job_ParseShell(char *line) } else { /* * The user provided a path. If s/he gave nothing else - * (fullSpec is FALSE), try and find a matching shell in the + * (fullSpec is false), try and find a matching shell in the * ones we know of. Else we just take the specification at * its word and copy it to a new location. In either case, * we need to record the path the user gave for the shell. @@ -2495,7 +2532,7 @@ Job_ParseShell(char *line) Parse_Error(PARSE_WARNING, "%s: No matching shell", shellName); free(words); - return FALSE; + return false; } shell = sh; } else { @@ -2507,7 +2544,7 @@ Job_ParseShell(char *line) } if (shell->echoOn != NULL && shell->echoOff != NULL) - shell->hasEchoCtl = TRUE; + shell->hasEchoCtl = true; if (!shell->hasErrCtl) { if (shell->echoTmpl == NULL) @@ -2521,7 +2558,7 @@ Job_ParseShell(char *line) * by the shell specification. */ free(words); - return TRUE; + return true; } /* @@ -2536,7 +2573,7 @@ Job_ParseShell(char *line) * signo signal received */ static void -JobInterrupt(Boolean runINTERRUPT, int signo) +JobInterrupt(bool runINTERRUPT, int signo) { Job *job; /* job descriptor in that element */ GNode *interrupt; /* the node describing the .INTERRUPT target */ @@ -2567,7 +2604,7 @@ JobInterrupt(Boolean runINTERRUPT, int signo) if (runINTERRUPT && !opts.touchFlag) { interrupt = Targ_FindNode(".INTERRUPT"); if (interrupt != NULL) { - opts.ignoreErrors = FALSE; + opts.ignoreErrors = false; JobRun(interrupt); } } @@ -2672,7 +2709,7 @@ JobRestartJobs(void) job->node->name); (void)fflush(stdout); } - job->suspended = FALSE; + job->suspended = false; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { debug_printf("Failed to send SIGCONT to %d\n", job->pid); @@ -2686,7 +2723,7 @@ JobRestartJobs(void) JobFinish(job, job->exit_status); } } - make_suspended = FALSE; + make_suspended = false; } static void @@ -2747,7 +2784,7 @@ clearfd(Job *job) job->inPollfd = NULL; } -static Boolean +static bool readyfd(Job *job) { if (job->inPollfd == NULL) @@ -2843,10 +2880,10 @@ Job_TokenReturn(void) * If pool is empty, set wantToken so that we wake up when a token is * released. * - * Returns TRUE if a token was withdrawn, and FALSE if the pool is currently + * Returns true if a token was withdrawn, and false if the pool is currently * empty. */ -Boolean +bool Job_TokenWithdraw(void) { char tok, tok1; @@ -2857,7 +2894,7 @@ Job_TokenWithdraw(void) getpid(), aborting, jobTokensRunning); if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) - return FALSE; + return false; count = read(tokenWaitJob.inPipe, &tok, 1); if (count == 0) @@ -2868,7 +2905,7 @@ Job_TokenWithdraw(void) } DEBUG1(JOB, "(%d) blocked for token\n", getpid()); wantToken = 1; - return FALSE; + return false; } if (count == 1 && tok != '+') { @@ -2894,7 +2931,7 @@ Job_TokenWithdraw(void) jobTokensRunning++; DEBUG1(JOB, "(%d) withdrew token\n", getpid()); - return TRUE; + return true; } /* @@ -2903,12 +2940,12 @@ Job_TokenWithdraw(void) * * Exits if the target fails. */ -Boolean +bool Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target); if (gn == NULL) - return FALSE; + return false; if (fname != NULL) Var_Set(gn, ALLSRC, fname); @@ -2919,7 +2956,7 @@ Job_RunTarget(const char *target, const char *fname) PrintOnError(gn, "\n\nStop."); exit(1); } - return TRUE; + return true; } #ifdef USE_SELECT @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.72 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: job.h,v 1.73 2021/04/03 11:08:40 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -157,14 +157,14 @@ typedef struct Job { JobStatus status; - Boolean suspended; + bool suspended; /* Ignore non-zero exits */ - Boolean ignerr; + bool ignerr; /* Output the command before or instead of running it. */ - Boolean echo; + bool echo; /* Target is a special one. */ - Boolean special; + bool special; int inPipe; /* Pipe for reading output from job */ int outPipe; /* Pipe for writing control commands */ @@ -188,22 +188,22 @@ extern int jobTokensRunning; /* tokens currently "out" */ void Shell_Init(void); const char *Shell_GetNewline(void); -void Job_Touch(GNode *, Boolean); -Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); +void Job_Touch(GNode *, bool); +bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); -Boolean Job_ParseShell(char *); +bool Job_ParseShell(char *); int Job_Finish(void); void Job_End(void); void Job_Wait(void); void Job_AbortAll(void); void Job_TokenReturn(void); -Boolean Job_TokenWithdraw(void); +bool Job_TokenWithdraw(void); void Job_ServerStart(int, int, int); void Job_SetPrefix(void); -Boolean Job_RunTarget(const char *, const char *); +bool Job_RunTarget(const char *, const char *); void Job_FlagsToString(const Job *, char *, size_t); int Job_TempFile(const char *, char *, size_t); @@ -1,4 +1,4 @@ -/* $NetBSD: lst.c,v 1.104 2021/02/01 19:39:31 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -34,7 +34,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: lst.c,v 1.104 2021/02/01 19:39:31 rillig Exp $"); +MAKE_RCSID("$NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 rillig Exp $"); static ListNode * LstNodeNew(ListNode *prev, ListNode *next, void *datum) @@ -205,7 +205,7 @@ Lst_FindDatum(List *list, const void *datum) /* * Move all nodes from src to the end of dst. - * The source list becomes empty but is not freed. + * The source list becomes indeterminate. */ void Lst_MoveAll(List *dst, List *src) @@ -219,6 +219,10 @@ Lst_MoveAll(List *dst, List *src) dst->last = src->last; } +#ifdef CLEANUP + src->first = NULL; + src->last = NULL; +#endif } /* Copy the element data from src to the start of dst. */ @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.96 2021/02/01 18:55:15 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.98 2021/04/03 11:08:40 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -123,15 +123,17 @@ void Lst_Free(List *); MAKE_INLINE void Lst_Init(List *list) { - list->first = NULL; - list->last = NULL; + list->first = NULL; + list->last = NULL; } /* Get information about a list */ -MAKE_INLINE Boolean +MAKE_INLINE bool Lst_IsEmpty(List *list) -{ return list->first == NULL; } +{ + return list->first == NULL; +} /* Find the first node that contains the given datum, or NULL. */ ListNode *Lst_FindDatum(List *, const void *); @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.533 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: main.c,v 1.540 2021/06/18 12:54:17 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -111,7 +111,7 @@ #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.533 2021/02/05 19:19:17 sjg Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.540 2021/06/18 12:54:17 rillig Exp $"); #if defined(MAKE_NATIVE) && !defined(lint) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " @@ -125,20 +125,20 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " CmdOpts opts; 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 */ +bool allPrecious; /* .PRECIOUS given on line by itself */ +bool deleteOnError; /* .DELETE_ON_ERROR: set */ static int maxJobTokens; /* -j argument */ -Boolean enterFlagObj; /* -w and objdir != srcdir */ +bool enterFlagObj; /* -w and objdir != srcdir */ 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 */ +bool doing_depend; /* Set while reading .depend */ +static bool 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 bool ignorePWD; /* if we use -C, PWD is meaningless */ static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ char curdir[MAXPATHLEN + 1]; /* Startup directory */ const char *progname; @@ -146,7 +146,7 @@ char *makeDependfile; pid_t myPid; int makelevel; -Boolean forceJobs = FALSE; +bool forceJobs = false; static int main_errors = 0; static HashTable cached_realpaths; @@ -293,7 +293,7 @@ MainParseArgDebug(const char *argvalue) debug |= DEBUG_JOB; break; case 'L': - opts.strict = TRUE; + opts.strict = true; break; case 'l': debug |= DEBUG_LOUD; @@ -317,7 +317,7 @@ MainParseArgDebug(const char *argvalue) debug |= DEBUG_TARG; break; case 'V': - opts.debugVflag = TRUE; + opts.debugVflag = true; break; case 'v': debug |= DEBUG_VAR; @@ -350,22 +350,22 @@ debug_setbuf: } /* Is path relative, or does it contain any relative component "." or ".."? */ -static Boolean +static bool IsRelativePath(const char *path) { - const char *cp; + const char *p; if (path[0] != '/') - return TRUE; - cp = path; - while ((cp = strstr(cp, "/.")) != NULL) { - cp += 2; - if (*cp == '.') - cp++; - if (cp[0] == '/' || cp[0] == '\0') - return TRUE; - } - return FALSE; + return true; + p = path; + while ((p = strstr(p, "/.")) != NULL) { + p += 2; + if (*p == '.') + p++; + if (*p == '/' || *p == '\0') + return true; + } + return false; } static void @@ -388,7 +388,7 @@ MainParseArgChdir(const char *argvalue) sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) strncpy(curdir, argvalue, MAXPATHLEN); - ignorePWD = TRUE; + ignorePWD = true; } static void @@ -411,7 +411,7 @@ MainParseArgJobsInternal(const char *argvalue) #endif jp_0 = -1; jp_1 = -1; - opts.compatMake = TRUE; + opts.compatMake = true; } else { Global_Append(MAKEFLAGS, "-J"); Global_Append(MAKEFLAGS, argvalue); @@ -423,7 +423,7 @@ MainParseArgJobs(const char *argvalue) { char *p; - forceJobs = TRUE; + forceJobs = true; opts.maxJobs = (int)strtol(argvalue, &p, 0); if (*p != '\0' || opts.maxJobs < 1) { (void)fprintf(stderr, @@ -454,14 +454,14 @@ MainParseArgSysInc(const char *argvalue) Global_Append(MAKEFLAGS, argvalue); } -static Boolean +static bool MainParseArg(char c, const char *argvalue) { switch (c) { case '\0': break; case 'B': - opts.compatMake = TRUE; + opts.compatMake = true; Global_Append(MAKEFLAGS, "-B"); Global_Set(MAKE_MODE, "compat"); break; @@ -469,7 +469,7 @@ MainParseArg(char c, const char *argvalue) MainParseArgChdir(argvalue); break; case 'D': - if (argvalue[0] == '\0') return FALSE; + if (argvalue[0] == '\0') return false; Global_SetExpand(argvalue, "1"); Global_Append(MAKEFLAGS, "-D"); Global_Append(MAKEFLAGS, argvalue); @@ -483,12 +483,12 @@ MainParseArg(char c, const char *argvalue) MainParseArgJobsInternal(argvalue); break; case 'N': - opts.noExecute = TRUE; - opts.noRecursiveExecute = TRUE; + opts.noExecute = true; + opts.noRecursiveExecute = true; Global_Append(MAKEFLAGS, "-N"); break; case 'S': - opts.keepgoing = FALSE; + opts.keepgoing = false; Global_Append(MAKEFLAGS, "-S"); break; case 'T': @@ -505,11 +505,11 @@ MainParseArg(char c, const char *argvalue) Global_Append(MAKEFLAGS, argvalue); break; case 'W': - opts.parseWarnFatal = TRUE; + opts.parseWarnFatal = true; /* XXX: why no Var_Append? */ break; case 'X': - opts.varNoExportEnv = TRUE; + opts.varNoExportEnv = true; Global_Append(MAKEFLAGS, "-X"); break; case 'd': @@ -523,21 +523,21 @@ MainParseArg(char c, const char *argvalue) MainParseArgDebug(argvalue); break; case 'e': - opts.checkEnvFirst = TRUE; + opts.checkEnvFirst = true; Global_Append(MAKEFLAGS, "-e"); break; case 'f': Lst_Append(&opts.makefiles, bmake_strdup(argvalue)); break; case 'i': - opts.ignoreErrors = TRUE; + opts.ignoreErrors = true; Global_Append(MAKEFLAGS, "-i"); break; case 'j': MainParseArgJobs(argvalue); break; case 'k': - opts.keepgoing = TRUE; + opts.keepgoing = true; Global_Append(MAKEFLAGS, "-k"); break; case 'm': @@ -545,35 +545,35 @@ MainParseArg(char c, const char *argvalue) /* XXX: why no Var_Append? */ break; case 'n': - opts.noExecute = TRUE; + opts.noExecute = true; Global_Append(MAKEFLAGS, "-n"); break; case 'q': - opts.queryFlag = TRUE; + opts.queryFlag = true; /* Kind of nonsensical, wot? */ Global_Append(MAKEFLAGS, "-q"); break; case 'r': - opts.noBuiltins = TRUE; + opts.noBuiltins = true; Global_Append(MAKEFLAGS, "-r"); break; case 's': - opts.beSilent = TRUE; + opts.beSilent = true; Global_Append(MAKEFLAGS, "-s"); break; case 't': - opts.touchFlag = TRUE; + opts.touchFlag = true; Global_Append(MAKEFLAGS, "-t"); break; case 'w': - opts.enterFlag = TRUE; + opts.enterFlag = true; Global_Append(MAKEFLAGS, "-w"); break; default: case '?': usage(); } - return TRUE; + return true; } /* @@ -592,13 +592,13 @@ MainParseArgs(int argc, char **argv) int arginc; char *argvalue; char *optscan; - Boolean inOption, dashDash = FALSE; + bool inOption, dashDash = false; const char *optspecs = "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"; /* Can't actually use getopt(3) because rescanning is not portable */ rearg: - inOption = FALSE; + inOption = false; optscan = NULL; while (argc > 1) { const char *optspec; @@ -610,20 +610,20 @@ rearg: if (c == '\0') { argv++; argc--; - inOption = FALSE; + inOption = false; continue; } } else { if (c != '-' || dashDash) break; - inOption = TRUE; + inOption = true; c = *optscan++; } /* '-' found at some earlier point */ optspec = strchr(optspecs, c); if (c != '\0' && optspec != NULL && optspec[1] == ':') { /* -<something> found, and <something> should have an arg */ - inOption = FALSE; + inOption = false; arginc = 1; argvalue = optscan; if (*argvalue == '\0') { @@ -638,10 +638,10 @@ rearg: switch (c) { case '\0': arginc = 1; - inOption = FALSE; + inOption = false; break; case '-': - dashDash = TRUE; + dashDash = true; break; default: if (!MainParseArg(c, argvalue)) @@ -659,7 +659,7 @@ rearg: for (; argc > 1; argv++, argc--) { VarAssign var; if (Parse_IsVar(argv[1], &var)) { - Parse_DoVar(&var, SCOPE_CMDLINE); + Parse_Var(&var, SCOPE_CMDLINE); } else { if (argv[1][0] == '\0') Punt("illegal (null) argument."); @@ -716,7 +716,7 @@ Main_ParseArgLine(const char *line) FStr_Done(&argv0); } - words = Str_Words(buf, TRUE); + words = Str_Words(buf, true); if (words.words == NULL) { Error("Unterminated quoted string [%s]", buf); free(buf); @@ -728,14 +728,14 @@ Main_ParseArgLine(const char *line) Words_Free(words); } -Boolean -Main_SetObjdir(Boolean writable, const char *fmt, ...) +bool +Main_SetObjdir(bool writable, const char *fmt, ...) { struct stat sb; char *path; char buf[MAXPATHLEN + 1]; char buf2[MAXPATHLEN + 1]; - Boolean rc = FALSE; + bool rc = false; va_list ap; va_start(ap, fmt); @@ -759,24 +759,24 @@ Main_SetObjdir(Boolean writable, const char *fmt, ...) setenv("PWD", objdir, 1); Dir_InitDot(); purge_relative_cached_realpaths(); - rc = TRUE; + rc = true; if (opts.enterFlag && strcmp(objdir, curdir) != 0) - enterFlagObj = TRUE; + enterFlagObj = true; } } return rc; } -static Boolean -SetVarObjdir(Boolean writable, const char *var, const char *suffix) +static bool +SetVarObjdir(bool writable, const char *var, const char *suffix) { FStr path = Var_Value(SCOPE_CMDLINE, var); FStr xpath; if (path.str == NULL || path.str[0] == '\0') { FStr_Done(&path); - return FALSE; + return false; } /* expand variable substitutions */ @@ -792,7 +792,7 @@ SetVarObjdir(Boolean writable, const char *var, const char *suffix) FStr_Done(&xpath); FStr_Done(&path); - return TRUE; + return true; } /* @@ -841,8 +841,8 @@ MakeMode(void) if (mode[0] != '\0') { if (strstr(mode, "compat") != NULL) { - opts.compatMake = TRUE; - forceJobs = FALSE; + opts.compatMake = true; + forceJobs = false; } #if USE_META if (strstr(mode, "meta") != NULL) @@ -854,7 +854,7 @@ MakeMode(void) } static void -PrintVar(const char *varname, Boolean expandVars) +PrintVar(const char *varname, bool expandVars) { if (strchr(varname, '$') != NULL) { char *evalue; @@ -880,24 +880,22 @@ PrintVar(const char *varname, Boolean expandVars) } /* - * Return a Boolean based on a variable. + * Return a bool 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. + * is false, otherwise true. */ -Boolean -GetBooleanVar(const char *varname, Boolean fallback) +bool +GetBooleanExpr(const char *expr, bool fallback) { - char *expr = str_concat3("${", varname, ":U}"); char *value; - Boolean res; + bool res; (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &value); /* TODO: handle errors */ res = ParseBoolean(value, fallback); free(value); - free(expr); return res; } @@ -905,14 +903,15 @@ static void doPrintVars(void) { StringListNode *ln; - Boolean expandVars; + bool expandVars; if (opts.printVars == PVM_EXPANDED) - expandVars = TRUE; + expandVars = true; else if (opts.debugVflag) - expandVars = FALSE; + expandVars = false; else - expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE); + expandVars = GetBooleanExpr("${.MAKE.EXPAND_VARIABLES}", + false); for (ln = opts.variables.first; ln != NULL; ln = ln->next) { const char *varname = ln->datum; @@ -920,11 +919,11 @@ doPrintVars(void) } } -static Boolean +static bool runTargets(void) { GNodeList targs = LST_INIT; /* target nodes to create */ - Boolean outOfDate; /* FALSE if all targets up to date */ + bool outOfDate; /* false if all targets up to date */ /* * Have now read the entire graph and need to make a list of @@ -947,7 +946,7 @@ runTargets(void) */ if (!opts.queryFlag) { Job_Init(); - jobsRunning = TRUE; + jobsRunning = true; } /* Traverse the graph, checking on all the targets */ @@ -958,7 +957,7 @@ runTargets(void) * targets as well as initializing the module. */ Compat_Run(&targs); - outOfDate = FALSE; + outOfDate = false; } Lst_Done(&targs); /* Don't free the targets themselves. */ return outOfDate; @@ -1110,11 +1109,11 @@ ignore_pwd: static void InitObjdir(const char *machine, const char *machine_arch) { - Boolean writable; + bool writable; Dir_InitCur(curdir); - writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE); - (void)Main_SetObjdir(FALSE, "%s", curdir); + writable = GetBooleanExpr("${MAKE_OBJDIR_CHECK_WRITABLE}", true); + (void)Main_SetObjdir(false, "%s", curdir); if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) && !SetVarObjdir(writable, "MAKEOBJDIR", "") && @@ -1141,27 +1140,27 @@ UnlimitFiles(void) static void CmdOpts_Init(void) { - opts.compatMake = FALSE; + opts.compatMake = false; opts.debug = DEBUG_NONE; /* opts.debug_file has already been initialized earlier */ - opts.strict = FALSE; - opts.debugVflag = FALSE; - opts.checkEnvFirst = FALSE; + opts.strict = false; + opts.debugVflag = false; + opts.checkEnvFirst = false; Lst_Init(&opts.makefiles); - opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + opts.ignoreErrors = false; /* Pay attention to non-zero returns */ opts.maxJobs = 1; - opts.keepgoing = FALSE; /* Stop on error */ - opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ - opts.noExecute = FALSE; /* Execute all commands */ - opts.queryFlag = FALSE; - opts.noBuiltins = FALSE; /* Read the built-in rules */ - opts.beSilent = FALSE; /* Print commands as executed */ - opts.touchFlag = FALSE; + opts.keepgoing = false; /* Stop on error */ + opts.noRecursiveExecute = false; /* Execute all .MAKE targets */ + opts.noExecute = false; /* Execute all commands */ + opts.queryFlag = false; + opts.noBuiltins = false; /* Read the built-in rules */ + opts.beSilent = false; /* Print commands as executed */ + opts.touchFlag = false; opts.printVars = PVM_NONE; Lst_Init(&opts.variables); - opts.parseWarnFatal = FALSE; - opts.enterFlag = FALSE; - opts.varNoExportEnv = FALSE; + opts.parseWarnFatal = false; + opts.enterFlag = false; + opts.varNoExportEnv = false; Lst_Init(&opts.create); } @@ -1286,7 +1285,7 @@ InitMaxJobs(void) opts.maxJobs = n; maxJobTokens = opts.maxJobs; - forceJobs = TRUE; + forceJobs = true; free(value); } @@ -1427,12 +1426,12 @@ main_Init(int argc, char **argv) Global_Set(MAKE_DEPENDFILE, ".depend"); CmdOpts_Init(); - allPrecious = FALSE; /* Remove targets when interrupted */ - deleteOnError = FALSE; /* Historical default behavior */ - jobsRunning = FALSE; + allPrecious = false; /* Remove targets when interrupted */ + deleteOnError = false; /* Historical default behavior */ + jobsRunning = false; maxJobTokens = opts.maxJobs; - ignorePWD = FALSE; + ignorePWD = false; /* * Initialize the parsing, directory and variable modules to prepare @@ -1575,9 +1574,9 @@ main_PrepareMaking(void) SCOPE_CMDLINE, VARE_WANTRES, &makeDependfile); if (makeDependfile[0] != '\0') { /* TODO: handle errors */ - doing_depend = TRUE; + doing_depend = true; (void)ReadMakefile(makeDependfile); - doing_depend = FALSE; + doing_depend = false; } } @@ -1599,7 +1598,7 @@ main_PrepareMaking(void) * turn compatibility on. */ if (!opts.compatMake && !forceJobs) - opts.compatMake = TRUE; + opts.compatMake = true; if (!opts.compatMake) Job_ServerStart(maxJobTokens, jp_0, jp_1); @@ -1607,7 +1606,7 @@ main_PrepareMaking(void) jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); if (opts.printVars == PVM_NONE) - Main_ExportMAKEFLAGS(TRUE); /* initial export */ + Main_ExportMAKEFLAGS(true); /* initial export */ InitVpath(); @@ -1615,7 +1614,7 @@ main_PrepareMaking(void) * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ - Suff_DoPaths(); + Suff_ExtendPaths(); /* * Propagate attributes through :: dependency lists. @@ -1632,13 +1631,13 @@ main_PrepareMaking(void) * If the -v or -V options are given, print variables instead. * Return whether any of the targets is out-of-date. */ -static Boolean +static bool main_Run(void) { if (opts.printVars != PVM_NONE) { /* print the values of any variables requested by the user */ doPrintVars(); - return FALSE; + return false; } else { return runTargets(); } @@ -1684,7 +1683,7 @@ main_CleanUp(void) /* Determine the exit code. */ static int -main_Exit(Boolean outOfDate) +main_Exit(bool outOfDate) { if (opts.strict && (main_errors > 0 || Parse_GetFatals() > 0)) return 2; /* Not 1 so -q can distinguish error */ @@ -1694,7 +1693,7 @@ main_Exit(Boolean outOfDate) int main(int argc, char **argv) { - Boolean outOfDate; + bool outOfDate; main_Init(argc, argv); main_ReadFiles(); @@ -1862,7 +1861,7 @@ Cmd_Exec(const char *cmd, const char **errfmt) /* Wait for the process to exit. */ while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) - JobReapChild(pid, status, FALSE); + JobReapChild(pid, status, false); res_len = buf.len; res = Buf_DoneData(&buf); @@ -2107,13 +2106,14 @@ cached_realpath(const char *pathname, char *resolved) * Return true if we should die without noise. * For example our failing child was a sub-make or failure happened elsewhere. */ -Boolean +bool shouldDieQuietly(GNode *gn, int bf) { static int quietly = -1; if (quietly < 0) { - if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE)) + if (DEBUG(JOB) || + !GetBooleanExpr("${.MAKE.DIE_QUIETLY}", true)) quietly = 0; else if (bf >= 0) quietly = bf; @@ -2193,15 +2193,15 @@ PrintOnError(GNode *gn, const char *msg) } void -Main_ExportMAKEFLAGS(Boolean first) +Main_ExportMAKEFLAGS(bool first) { - static Boolean once = TRUE; + static bool once = true; const char *expr; char *s; if (once != first) return; - once = FALSE; + once = false; expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; (void)Var_Subst(expr, SCOPE_CMDLINE, VARE_WANTRES, &s); @@ -2225,7 +2225,7 @@ getTmpdir(void) return tmpdir; /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */ - (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", SCOPE_GLOBAL, VARE_WANTRES, &tmpdir); /* TODO: handle errors */ @@ -2272,18 +2272,18 @@ mkTempFile(const char *pattern, char *tfile, size_t tfile_sz) /* * 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. + * Anything that looks like "No", "False", "Off", "0" etc. is false, + * the empty string is the fallback, everything else is true. */ -Boolean -ParseBoolean(const char *s, Boolean fallback) +bool +ParseBoolean(const char *s, bool fallback) { char ch = ch_tolower(s[0]); if (ch == '\0') return fallback; if (ch == '0' || ch == 'f' || ch == 'n') - return FALSE; + return false; if (ch == 'o') return ch_tolower(s[1]) != 'f'; - return TRUE; + return true; } @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.242 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: make.c,v 1.244 2021/04/04 10:05:08 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -72,7 +72,7 @@ * Examination of targets and their suitability for creation. * * Interface: - * Make_Run Initialize things for the module. Returns TRUE if + * Make_Run Initialize things for the module. Returns true if * work was (or would have been) done. * * Make_Update After a target is made, update all its parents. @@ -85,7 +85,8 @@ * Update the node's youngestChild field based on the * child's modification time. * - * Make_DoAllVar Set up the various local variables for a + * GNode_SetLocalVars + * 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. @@ -103,7 +104,7 @@ #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.242 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.244 2021/04/04 10:05:08 rillig Exp $"); /* Sequence # to detect recursion. */ static unsigned int checked_seqno = 1; @@ -168,7 +169,7 @@ GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, suffix); } -Boolean +bool GNode_ShouldExecute(GNode *gn) { return !((gn->type & OP_MAKE) @@ -184,7 +185,7 @@ GNode_UpdateYoungestChild(GNode *gn, GNode *cgn) gn->youngestChild = cgn; } -static Boolean +static bool IsOODateRegular(GNode *gn) { /* These rules are inherited from the original Make. */ @@ -193,22 +194,22 @@ IsOODateRegular(GNode *gn) if (gn->mtime < gn->youngestChild->mtime) { DEBUG1(MAKE, "modified before source \"%s\"...", GNode_Path(gn->youngestChild)); - return TRUE; + return true; } - return FALSE; + return false; } if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) { DEBUG0(MAKE, "nonexistent and no sources..."); - return TRUE; + return true; } if (gn->type & OP_DOUBLEDEP) { DEBUG0(MAKE, ":: operator and no sources..."); - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -223,17 +224,17 @@ IsOODateRegular(GNode *gn) * The mtime field of the node and the youngestChild field of its parents * may be changed. */ -Boolean +bool GNode_IsOODate(GNode *gn) { - Boolean oodate; + bool oodate; /* * Certain types of targets needn't even be sought as their datedness * doesn't depend on their modification time... */ if (!(gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC))) { - Dir_UpdateMTime(gn, TRUE); + Dir_UpdateMTime(gn, true); if (DEBUG(MAKE)) { if (gn->mtime != 0) debug_printf("modified %s...", @@ -267,7 +268,7 @@ GNode_IsOODate(GNode *gn) * no matter *what*. */ DEBUG0(MAKE, ".USE node..."); - oodate = FALSE; + oodate = false; } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) { DEBUG0(MAKE, "library..."); @@ -302,9 +303,9 @@ GNode_IsOODate(GNode *gn) debug_printf(".EXEC node..."); } } - oodate = TRUE; + oodate = true; } else if (IsOODateRegular(gn)) { - oodate = TRUE; + oodate = true; } else { /* * When a nonexistent child with no sources @@ -351,7 +352,7 @@ PretendAllChildrenAreMade(GNode *pgn) GNode *cgn = ln->datum; /* This may also update cgn->path. */ - Dir_UpdateMTime(cgn, FALSE); + Dir_UpdateMTime(cgn, false); GNode_UpdateYoungestChild(pgn, cgn); pgn->unmade--; } @@ -443,7 +444,7 @@ Make_HandleUse(GNode *cgn, GNode *pgn) static void MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln) { - Boolean unmarked; + bool unmarked; unmarked = !(cgn->type & OP_MARK); cgn->type |= OP_MARK; @@ -485,7 +486,7 @@ Make_Recheck(GNode *gn) { time_t mtime; - Dir_UpdateMTime(gn, TRUE); + Dir_UpdateMTime(gn, true); mtime = gn->mtime; #ifndef RECHECK @@ -576,7 +577,7 @@ UpdateImplicitParentsVars(GNode *cgn, const char *cname) } /* See if a .ORDER rule stops us from building this node. */ -static Boolean +static bool IsWaitingForOrder(GNode *gn) { GNodeListNode *ln; @@ -590,9 +591,9 @@ IsWaitingForOrder(GNode *gn) DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n", ogn->name, ogn->cohort_num); - return TRUE; + return true; } - return FALSE; + return false; } static void MakeBuildParent(GNode *, GNodeListNode *); @@ -868,7 +869,7 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn) * match its ALLSRC variable. */ void -Make_DoAllVar(GNode *gn) +GNode_SetLocalVars(GNode *gn) { GNodeListNode *ln; @@ -889,7 +890,7 @@ Make_DoAllVar(GNode *gn) gn->flags |= DONE_ALLSRC; } -static Boolean +static bool MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext) { @@ -899,13 +900,13 @@ MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext) GNode_FprintDetails(opts.debug_file, "", cn, "\n"); } if (GNode_IsReady(cn)) - return FALSE; + return false; /* If this node is on the RHS of a .ORDER, check LHSs. */ if (IsWaitingForOrder(cn)) { /* Can't build this (or anything else in this child list) yet */ cn->made = DEFERRED; - return FALSE; /* but keep looking */ + return false; /* but keep looking */ } DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", @@ -961,13 +962,13 @@ MakeChildren(GNode *gn) * * 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. In all other cases, this function returns FALSE. + * returns true. In all other cases, this function returns false. */ -static Boolean +static bool MakeStartJobs(void) { GNode *gn; - Boolean have_token = FALSE; + bool have_token = false; while (!Lst_IsEmpty(&toBeMade)) { /* @@ -976,7 +977,7 @@ MakeStartJobs(void) */ if (!have_token && !Job_TokenWithdraw()) break; - have_token = TRUE; + have_token = true; gn = Lst_Dequeue(&toBeMade); DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); @@ -1022,10 +1023,10 @@ MakeStartJobs(void) if (GNode_IsOODate(gn)) { DEBUG0(MAKE, "out-of-date\n"); if (opts.queryFlag) - return TRUE; - Make_DoAllVar(gn); + return true; + GNode_SetLocalVars(gn); Job_Make(gn); - have_token = FALSE; + have_token = false; } else { DEBUG0(MAKE, "up-to-date\n"); gn->made = UPTODATE; @@ -1037,7 +1038,7 @@ MakeStartJobs(void) * for .TARGET when building up the local * variables of its parent(s)... */ - Make_DoAllVar(gn); + GNode_SetLocalVars(gn); } Make_Update(gn); } @@ -1046,7 +1047,7 @@ MakeStartJobs(void) if (have_token) Job_TokenReturn(); - return FALSE; + return false; } /* Print the status of a .ORDER node. */ @@ -1081,7 +1082,7 @@ static void MakePrintStatusList(GNodeList *, int *); * Print the status of a top-level node, viz. it being up-to-date already * or not created due to an error in a lower level. */ -static Boolean +static bool MakePrintStatus(GNode *gn, int *errors) { if (gn->flags & DONECYCLE) { @@ -1089,7 +1090,7 @@ MakePrintStatus(GNode *gn, int *errors) * We've completely processed this node before, don't do * it again. */ - return FALSE; + return false; } if (gn->unmade == 0) { @@ -1128,7 +1129,7 @@ MakePrintStatus(GNode *gn, int *errors) gn->name, gn->cohort_num); break; } - return FALSE; + return false; } DEBUG3(MAKE, "MakePrintStatus: %s%s has %d unmade children\n", @@ -1143,7 +1144,7 @@ MakePrintStatus(GNode *gn, int *errors) MakePrintStatusList(&gn->children, errors); /* Mark that this node needn't be processed again */ gn->flags |= DONECYCLE; - return FALSE; + return false; } /* Only output the error once per node */ @@ -1151,11 +1152,11 @@ MakePrintStatus(GNode *gn, int *errors) Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); if ((*errors)++ > 100) /* Abandon the whole error report */ - return TRUE; + return true; /* Reporting for our children will give the rest of the loop */ MakePrintStatusList(&gn->children, errors); - return FALSE; + return false; } static void @@ -1243,7 +1244,7 @@ Make_ExpandUse(GNodeList *targs) *eon = ')'; } - Dir_UpdateMTime(gn, FALSE); + Dir_UpdateMTime(gn, false); Var_Set(gn, TARGET, GNode_Path(gn)); UnmarkChildren(gn); HandleUseNodes(gn); @@ -1364,14 +1365,14 @@ Make_ProcessWait(GNodeList *targs) * targs the initial list of targets * * Results: - * TRUE if work was done. FALSE otherwise. + * True if work was done, false otherwise. * * Side Effects: * The make field of all nodes involved in the creation of the given * targets is set to 1. The toBeMade list is set to contain all the * 'leaves' of these subgraphs. */ -Boolean +bool Make_Run(GNodeList *targs) { int errors; /* Number of errors the Job module reports */ @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.256 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: make.h,v 1.263 2021/06/21 10:33:11 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -136,55 +136,30 @@ #endif #define MAKE_INLINE static inline MAKE_ATTR_UNUSED +#define MAKE_STATIC static 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). - */ -#if defined(lint) || defined(USE_C99_BOOLEAN) +#if __STDC_VERSION__ >= 199901L || defined(lint) || defined(USE_C99_BOOLEAN) #include <stdbool.h> -typedef bool Boolean; -#define FALSE false -#define TRUE true -#elif defined(USE_DOUBLE_BOOLEAN) -/* During development, to find type mismatches in function declarations. */ -typedef double Boolean; -#define TRUE 1.0 -#define FALSE 0.0 -#elif defined(USE_UCHAR_BOOLEAN) -/* - * During development, to find code that depends on the exact value of TRUE or - * that stores other values in Boolean variables. - */ -typedef unsigned char Boolean; -#define TRUE ((unsigned char)0xFF) -#define FALSE ((unsigned char)0x00) -#elif defined(USE_CHAR_BOOLEAN) -/* - * During development, to find code that uses a boolean as array index, via - * -Wchar-subscripts. - */ -typedef char Boolean; -#define TRUE ((char)-1) -#define FALSE ((char)0x00) -#elif defined(USE_ENUM_BOOLEAN) -typedef enum Boolean { FALSE, TRUE } Boolean; #else -typedef int Boolean; -#ifndef TRUE -#define TRUE 1 +#ifndef bool +typedef unsigned int Boolean; +#define bool Boolean +#endif +#ifndef true +#define true 1 #endif -#ifndef FALSE -#define FALSE 0 +#ifndef false +#define false 0 #endif #endif #include "lst.h" #include "enum.h" +#include "make_malloc.h" +#include "str.h" #include "hash.h" #include "make-conf.h" #include "buf.h" -#include "make_malloc.h" /* * some vendors don't have this --sjg @@ -247,6 +222,8 @@ typedef enum GNodeMade { * should be made. * * Some of the OP_ constants can be combined, others cannot. + * + * See the tests depsrc-*.mk and deptgt-*.mk. */ typedef enum GNodeType { OP_NONE = 0, @@ -503,11 +480,11 @@ typedef enum CondEvalResult { */ /* True if every target is precious */ -extern Boolean allPrecious; +extern bool allPrecious; /* True if failed targets should be deleted */ -extern Boolean deleteOnError; -/* TRUE while processing .depend */ -extern Boolean doing_depend; +extern bool deleteOnError; +/* true while processing .depend */ +extern bool doing_depend; /* .DEFAULT rule */ extern GNode *defaultNode; @@ -606,7 +583,7 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); do { \ if (DEBUG(module)) \ debug_printf args; \ - } while (/*CONSTCOND*/FALSE) + } while (/*CONSTCOND*/false) #define DEBUG0(module, text) \ DEBUG_IMPL(module, ("%s", text)) @@ -630,7 +607,7 @@ typedef enum PrintVarsMode { /* Command line options */ typedef struct CmdOpts { /* -B: whether we are make compatible */ - Boolean compatMake; + bool compatMake; /* -d: debug control: There is one bit per module. It is up to the * module what debug information to print. */ @@ -643,19 +620,19 @@ typedef struct CmdOpts { * * Runs make in strict mode, with additional checks and better error * handling. */ - Boolean strict; + bool strict; /* -dV: for the -V option, print unexpanded variable values */ - Boolean debugVflag; + bool debugVflag; /* -e: check environment variables before global variables */ - Boolean checkEnvFirst; + bool checkEnvFirst; /* -f: the makefiles to read */ StringList makefiles; /* -i: if true, ignore all errors from shell commands */ - Boolean ignoreErrors; + bool ignoreErrors; /* -j: the maximum number of jobs that can run in parallel; * this is coordinated with the submakes */ @@ -663,29 +640,29 @@ typedef struct CmdOpts { /* -k: if true and an error occurs while making a node, continue * making nodes that do not depend on the erroneous node */ - Boolean keepgoing; + bool keepgoing; /* -N: execute no commands from the targets */ - Boolean noRecursiveExecute; + bool noRecursiveExecute; /* -n: execute almost no commands from the targets */ - Boolean noExecute; + bool noExecute; /* * -q: if true, do not really make anything, just see if the targets * are out-of-date */ - Boolean queryFlag; + bool queryFlag; /* -r: raw mode, do not load the builtin rules. */ - Boolean noBuiltins; + bool noBuiltins; /* -s: don't echo the shell commands before executing them */ - Boolean beSilent; + bool beSilent; /* -t: touch the targets if they are out-of-date, but don't actually * make them */ - Boolean touchFlag; + bool touchFlag; /* -[Vv]: print expanded or unexpanded selected variables */ PrintVarsMode printVars; @@ -693,14 +670,14 @@ typedef struct CmdOpts { StringList variables; /* -W: if true, makefile parsing warnings are treated as errors */ - Boolean parseWarnFatal; + bool parseWarnFatal; /* -w: print 'Entering' and 'Leaving' for submakes */ - Boolean enterFlag; + bool enterFlag; /* -X: if true, do not export variables set on the command line to the * environment. */ - Boolean varNoExportEnv; + bool varNoExportEnv; /* The target names specified on the command line. * Used to resolve .if make(...) statements. */ @@ -713,24 +690,24 @@ extern CmdOpts opts; #include "nonints.h" void GNode_UpdateYoungestChild(GNode *, GNode *); -Boolean GNode_IsOODate(GNode *); +bool 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 *); -Boolean shouldDieQuietly(GNode *, int); +void GNode_SetLocalVars(GNode *); +bool Make_Run(GNodeList *); +bool shouldDieQuietly(GNode *, int); void PrintOnError(GNode *, const char *); -void Main_ExportMAKEFLAGS(Boolean); -Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +void Main_ExportMAKEFLAGS(bool); +bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); int mkTempFile(const char *, char *, size_t); int str2Lst_Append(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); -Boolean GNode_ShouldExecute(GNode *gn); +bool GNode_ShouldExecute(GNode *gn); /* See if the node was seen on the left-hand side of a dependency operator. */ -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsTarget(const GNode *gn) { return (gn->type & OP_OPMASK) != 0; @@ -742,25 +719,25 @@ GNode_Path(const GNode *gn) return gn->path != NULL ? gn->path : gn->name; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsWaitingFor(const GNode *gn) { return (gn->flags & REMAKE) && gn->made <= REQUESTED; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsReady(const GNode *gn) { return gn->made > DEFERRED; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsDone(const GNode *gn) { return gn->made >= MADE; } -MAKE_INLINE Boolean +MAKE_INLINE bool GNode_IsError(const GNode *gn) { return gn->made == ERROR || gn->made == ABORTED; @@ -781,7 +758,7 @@ GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); } MAKE_INLINE const char * GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } -#ifdef __GNUC__ +#if defined(__GNUC__) && __STDC_VERSION__ >= 199901L #define UNCONST(ptr) ({ \ union __unconst { \ const void *__cp; \ @@ -809,15 +786,15 @@ GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } #define KILLPG(pid, sig) killpg((pid), (sig)) #endif -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } -MAKE_INLINE Boolean +MAKE_INLINE bool ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } MAKE_INLINE char ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.177 2021/02/05 19:19:17 sjg Exp $ */ +/* $NetBSD: meta.c,v 1.181 2021/04/04 10:05:08 rillig Exp $ */ /* * Implement 'meta' mode. @@ -70,20 +70,20 @@ static char *metaIgnorePathsStr; /* string storage for the list */ #define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER" #endif -Boolean useMeta = FALSE; -static Boolean useFilemon = FALSE; -static Boolean writeMeta = FALSE; -static Boolean metaMissing = FALSE; /* oodate if missing */ -static Boolean filemonMissing = FALSE; /* oodate if missing */ -static Boolean metaEnv = FALSE; /* don't save env unless asked */ -static Boolean metaVerbose = FALSE; -static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */ -static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */ -static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */ -static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ -static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ - -extern Boolean forceJobs; +bool useMeta = false; +static bool useFilemon = false; +static bool writeMeta = false; +static bool metaMissing = false; /* oodate if missing */ +static bool filemonMissing = false; /* oodate if missing */ +static bool metaEnv = false; /* don't save env unless asked */ +static bool metaVerbose = false; +static bool metaIgnoreCMDs = false; /* ignore CMDs in .meta files */ +static bool metaIgnorePatterns = false; /* do we need to do pattern matches */ +static bool metaIgnoreFilter = false; /* do we have more complex filtering? */ +static bool metaCurdirOk = false; /* write .meta in .CURDIR Ok? */ +static bool metaSilent = false; /* if we have a .meta be SILENT */ + +extern bool forceJobs; extern char **environ; #define MAKE_META_PREFIX ".MAKE.META.PREFIX" @@ -133,7 +133,7 @@ meta_open_filemon(BuildMon *pbm) pbm->filemon = filemon_open(); if (pbm->filemon == NULL) { - useFilemon = FALSE; + useFilemon = false; warn("Could not open filemon %s", filemon_path()); return; } @@ -319,7 +319,7 @@ meta_name(char *mname, size_t mnamelen, * Return true if running ${.MAKE} * Bypassed if target is flagged .MAKE */ -static Boolean +static bool is_submake(const char *cmd, GNode *gn) { static const char *p_make = NULL; @@ -327,7 +327,7 @@ is_submake(const char *cmd, GNode *gn) char *mp = NULL; char *cp; char *cp2; - Boolean rc = FALSE; + bool rc = false; if (p_make == NULL) { p_make = Var_Value(gn, ".MAKE").str; @@ -346,7 +346,7 @@ is_submake(const char *cmd, GNode *gn) case ' ': case '\t': case '\n': - rc = TRUE; + rc = true; break; } if (cp2 > cmd && rc) { @@ -356,7 +356,7 @@ is_submake(const char *cmd, GNode *gn) case '\n': break; default: - rc = FALSE; /* no match */ + rc = false; /* no match */ break; } } @@ -365,29 +365,31 @@ is_submake(const char *cmd, GNode *gn) return rc; } -static Boolean +static bool any_is_submake(GNode *gn) { StringListNode *ln; for (ln = gn->commands.first; ln != NULL; ln = ln->next) if (is_submake(ln->datum, gn)) - return TRUE; - return FALSE; + return true; + return false; } static void -printCMD(const char *cmd, FILE *fp, GNode *gn) +printCMD(const char *ucmd, FILE *fp, GNode *gn) { - char *cmd_freeIt = NULL; + FStr xcmd = FStr_InitRefer(ucmd); - if (strchr(cmd, '$') != NULL) { - (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmd_freeIt); + if (strchr(ucmd, '$') != NULL) { + char *expanded; + (void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded); /* TODO: handle errors */ - cmd = cmd_freeIt; + xcmd = FStr_InitOwn(expanded); } - fprintf(fp, "CMD %s\n", cmd); - free(cmd_freeIt); + + fprintf(fp, "CMD %s\n", xcmd.str); + FStr_Done(&xcmd); } static void @@ -408,17 +410,17 @@ printCMDs(GNode *gn, FILE *fp) debug_printf("Skipping meta for %s: .%s\n", \ gn->name, __STRING(_type)); \ } \ - return FALSE; \ + return false; \ } \ -} while (/*CONSTCOND*/FALSE) +} while (/*CONSTCOND*/false) /* * Do we need/want a .meta file ? */ -static Boolean +static bool meta_needed(GNode *gn, const char *dname, - char *objdir_realpath, Boolean verbose) + char *objdir_realpath, bool verbose) { struct cached_stat cst; @@ -440,13 +442,13 @@ meta_needed(GNode *gn, const char *dname, if (Lst_IsEmpty(&gn->commands)) { if (verbose) debug_printf("Skipping meta for %s: no commands\n", gn->name); - return FALSE; + return false; } if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { /* OP_SUBMAKE is a bit too aggressive */ if (any_is_submake(gn)) { DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name); - return FALSE; + return false; } } @@ -454,7 +456,7 @@ meta_needed(GNode *gn, const char *dname, if (cached_stat(dname, &cst) != 0) { if (verbose) debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); - return FALSE; + return false; } /* make sure these are canonical */ @@ -466,9 +468,9 @@ meta_needed(GNode *gn, const char *dname, if (verbose) debug_printf("Skipping meta for %s: .OBJDIR == .CURDIR\n", gn->name); - return FALSE; + return false; } - return TRUE; + return true; } @@ -490,7 +492,7 @@ meta_create(BuildMon *pbm, GNode *gn) tname = GNode_VarTarget(gn); /* if this succeeds objdir_realpath is realpath of dname */ - if (!meta_needed(gn, dname.str, objdir_realpath, TRUE)) + if (!meta_needed(gn, dname.str, objdir_realpath, true)) goto out; dname.str = objdir_realpath; @@ -554,7 +556,7 @@ meta_create(BuildMon *pbm, GNode *gn) return fp; } -static Boolean +static bool boolValue(char *s) { switch(*s) { @@ -563,9 +565,9 @@ boolValue(char *s) case 'n': case 'F': case 'f': - return FALSE; + return false; } - return TRUE; + return true; } /* @@ -591,25 +593,25 @@ meta_init(void) void meta_mode_init(const char *make_mode) { - static Boolean once = FALSE; + static bool once = false; char *cp; FStr value; - useMeta = TRUE; - useFilemon = TRUE; - writeMeta = TRUE; + useMeta = true; + useFilemon = true; + writeMeta = true; if (make_mode != NULL) { if (strstr(make_mode, "env") != NULL) - metaEnv = TRUE; + metaEnv = true; if (strstr(make_mode, "verb") != NULL) - metaVerbose = TRUE; + metaVerbose = true; if (strstr(make_mode, "read") != NULL) - writeMeta = FALSE; + writeMeta = false; if (strstr(make_mode, "nofilemon") != NULL) - useFilemon = FALSE; + useFilemon = false; if (strstr(make_mode, "ignore-cmd") != NULL) - metaIgnoreCMDs = TRUE; + metaIgnoreCMDs = true; if (useFilemon) get_mode_bf(filemonMissing, "missing-filemon="); get_mode_bf(metaCurdirOk, "curdirok="); @@ -628,7 +630,7 @@ meta_mode_init(const char *make_mode) } if (once) return; - once = TRUE; + once = true; memset(&Mybm, 0, sizeof Mybm); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} @@ -652,12 +654,12 @@ meta_mode_init(const char *make_mode) */ value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS); if (value.str != NULL) { - metaIgnorePatterns = TRUE; + metaIgnorePatterns = true; FStr_Done(&value); } value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER); if (value.str != NULL) { - metaIgnoreFilter = TRUE; + metaIgnoreFilter = true; FStr_Done(&value); } } @@ -774,7 +776,7 @@ meta_job_event(Job *job) } void -meta_job_error(Job *job, GNode *gn, Boolean ignerr, int status) +meta_job_error(Job *job, GNode *gn, bool ignerr, int status) { char cwd[MAXPATHLEN]; BuildMon *pbm; @@ -944,7 +946,7 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) return 0; } -static Boolean +static bool prefix_match(const char *prefix, const char *path) { size_t n = strlen(prefix); @@ -952,35 +954,35 @@ prefix_match(const char *prefix, const char *path) return strncmp(path, prefix, n) == 0; } -static Boolean +static bool has_any_prefix(const char *path, StringList *prefixes) { StringListNode *ln; for (ln = prefixes->first; ln != NULL; ln = ln->next) if (prefix_match(ln->datum, path)) - return TRUE; - return FALSE; + return true; + return false; } /* See if the path equals prefix or starts with "prefix/". */ -static Boolean +static bool path_starts_with(const char *path, const char *prefix) { size_t n = strlen(prefix); if (strncmp(path, prefix, n) != 0) - return FALSE; + return false; return path[n] == '\0' || path[n] == '/'; } -static Boolean +static bool meta_ignore(GNode *gn, const char *p) { char fname[MAXPATHLEN]; if (p == NULL) - return TRUE; + return true; if (*p == '/') { cached_realpath(p, fname); /* clean it up */ @@ -988,7 +990,7 @@ meta_ignore(GNode *gn, const char *p) #ifdef DEBUG_META_MODE DEBUG1(META, "meta_oodate: ignoring path: %s\n", p); #endif - return TRUE; + return true; } } @@ -1011,7 +1013,7 @@ meta_ignore(GNode *gn, const char *p) DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p); #endif free(pm); - return TRUE; + return true; } free(pm); } @@ -1030,11 +1032,11 @@ meta_ignore(GNode *gn, const char *p) DEBUG1(META, "meta_oodate: ignoring filtered: %s\n", p); #endif free(fm); - return TRUE; + return true; } free(fm); } - return FALSE; + return false; } /* @@ -1048,11 +1050,11 @@ meta_ignore(GNode *gn, const char *p) /* * It is possible that a .meta file is corrupted, * if we detect this we want to reproduce it. - * Setting oodate TRUE will have that effect. + * Setting oodate true will have that effect. */ #define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \ warnx("%s: %d: malformed", fname, lineno); \ - oodate = TRUE; \ + oodate = true; \ continue; \ } @@ -1074,8 +1076,8 @@ append_if_new(StringList *list, const char *str) Lst_Append(list, bmake_strdup(str)); } -Boolean -meta_oodate(GNode *gn, Boolean oodate) +bool +meta_oodate(GNode *gn, bool oodate) { static char *tmpdir = NULL; static char cwd[MAXPATHLEN]; @@ -1095,9 +1097,9 @@ meta_oodate(GNode *gn, Boolean oodate) static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; - Boolean needOODATE = FALSE; + bool needOODATE = false; StringList missingFiles; - Boolean have_filemon = FALSE; + bool have_filemon = false; if (oodate) return oodate; /* we're done */ @@ -1106,7 +1108,7 @@ meta_oodate(GNode *gn, Boolean oodate) tname = GNode_VarTarget(gn); /* if this succeeds fname3 is realpath of dname */ - if (!meta_needed(gn, dname.str, fname3, FALSE)) + if (!meta_needed(gn, dname.str, fname3, false)) goto oodate_out; dname.str = fname3; @@ -1118,7 +1120,7 @@ meta_oodate(GNode *gn, Boolean oodate) * requires that all variables are set in the same way that they * would be if the target needs to be re-built. */ - Make_DoAllVar(gn); + GNode_SetLocalVars(gn); meta_name(fname, sizeof fname, dname.str, tname, dname.str); @@ -1164,7 +1166,7 @@ meta_oodate(GNode *gn, Boolean oodate) buf[x - 1] = '\0'; else { warnx("%s: %d: line truncated at %u", fname, lineno, x); - oodate = TRUE; + oodate = true; break; } link_src = NULL; @@ -1172,11 +1174,11 @@ meta_oodate(GNode *gn, Boolean oodate) /* Find the start of the build monitor section. */ if (!have_filemon) { if (strncmp(buf, "-- filemon", 10) == 0) { - have_filemon = TRUE; + have_filemon = true; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { - have_filemon = TRUE; + have_filemon = true; continue; } } @@ -1424,7 +1426,7 @@ meta_oodate(GNode *gn, Boolean oodate) char *sdirs[4]; char **sdp; int sdx = 0; - Boolean found = FALSE; + bool found = false; if (*p == '/') { sdirs[sdx++] = p; /* done */ @@ -1455,7 +1457,7 @@ meta_oodate(GNode *gn, Boolean oodate) fname, lineno, *sdp); #endif if (cached_stat(*sdp, &cst) == 0) { - found = TRUE; + found = true; p = *sdp; } } @@ -1468,7 +1470,7 @@ meta_oodate(GNode *gn, Boolean oodate) cst.cst_mtime > gn->mtime) { DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); - oodate = TRUE; + oodate = true; } else if (S_ISDIR(cst.cst_mode)) { /* Update the latest directory. */ cached_realpath(p, latestdir); @@ -1500,25 +1502,25 @@ meta_oodate(GNode *gn, Boolean oodate) if (cmdNode == NULL) { DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); - oodate = TRUE; + oodate = true; } else { const char *cp; char *cmd = cmdNode->datum; - Boolean hasOODATE = FALSE; + bool hasOODATE = false; if (strstr(cmd, "$?") != NULL) - hasOODATE = TRUE; + hasOODATE = true; else if ((cp = strstr(cmd, ".OODATE")) != NULL) { /* check for $[{(].OODATE[:)}] */ if (cp > cmd + 2 && cp[-2] == '$') - hasOODATE = TRUE; + hasOODATE = true; } if (hasOODATE) { - needOODATE = TRUE; + needOODATE = true; DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); } - (void)Var_Subst(cmd, gn, VARE_WANTRES|VARE_UNDEFERR, &cmd); + (void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd); /* TODO: handle errors */ if ((cp = strchr(cmd, '\n')) != NULL) { @@ -1553,7 +1555,7 @@ meta_oodate(GNode *gn, Boolean oodate) DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); if (!metaIgnoreCMDs) - oodate = TRUE; + oodate = true; } free(cmd); cmdNode = cmdNode->next; @@ -1566,13 +1568,13 @@ meta_oodate(GNode *gn, Boolean oodate) if (!oodate && cmdNode != NULL) { DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); - oodate = TRUE; + oodate = true; } CHECK_VALID_META(p); if (strcmp(p, cwd) != 0) { DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); - oodate = TRUE; + oodate = true; } } } @@ -1581,11 +1583,11 @@ meta_oodate(GNode *gn, Boolean oodate) if (!Lst_IsEmpty(&missingFiles)) { DEBUG2(META, "%s: missing files: %s...\n", fname, (char *)missingFiles.first->datum); - oodate = TRUE; + oodate = true; } if (!oodate && !have_filemon && filemonMissing) { DEBUG1(META, "%s: missing filemon data\n", fname); - oodate = TRUE; + oodate = true; } } else { if (writeMeta && (metaMissing || (gn->type & OP_META))) { @@ -1600,8 +1602,8 @@ meta_oodate(GNode *gn, Boolean oodate) } if (cp == NULL) { DEBUG1(META, "%s: required but missing\n", fname); - oodate = TRUE; - needOODATE = TRUE; /* assume the worst */ + oodate = true; + needOODATE = true; /* assume the worst */ } } } @@ -1708,7 +1710,7 @@ meta_compat_parent(pid_t child) fflush(stdout); buf[nread] = '\0'; meta_job_output(NULL, buf, ""); - } while (/*CONSTCOND*/FALSE); + } while (/*CONSTCOND*/false); if (metafd != -1 && FD_ISSET(metafd, &readfds) != 0) { if (meta_job_event(NULL) <= 0) metafd = -1; @@ -1,4 +1,4 @@ -/* $NetBSD: meta.h,v 1.9 2020/12/10 20:49:11 rillig Exp $ */ +/* $NetBSD: meta.h,v 1.10 2021/04/03 11:08:40 rillig Exp $ */ /* * Things needed for 'meta' mode. @@ -48,13 +48,13 @@ void meta_job_child(struct Job *); void meta_job_parent(struct Job *, pid_t); int meta_job_fd(struct Job *); int meta_job_event(struct Job *); -void meta_job_error(struct Job *, GNode *, Boolean, int); +void meta_job_error(struct Job *, GNode *, bool, int); void meta_job_output(struct Job *, char *, const char *); int meta_cmd_finish(void *); int meta_job_finish(struct Job *); -Boolean meta_oodate(GNode *, Boolean); +bool meta_oodate(GNode *, bool); void meta_compat_start(void); void meta_compat_child(void); void meta_compat_parent(pid_t); -extern Boolean useMeta; +extern bool useMeta; diff --git a/metachar.h b/metachar.h index 04f967229109..1fd1397cfe63 100644 --- a/metachar.h +++ b/metachar.h @@ -1,4 +1,4 @@ -/* $NetBSD: metachar.h,v 1.15 2021/01/19 20:51:46 rillig Exp $ */ +/* $NetBSD: metachar.h,v 1.16 2021/04/03 11:08:40 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] != 0) -MAKE_INLINE Boolean +MAKE_INLINE bool needshell(const char *cmd) { while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') diff --git a/mk/ChangeLog b/mk/ChangeLog index fa6ea9b9d337..f73c4fb68c6b 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,38 @@ +2021-06-16 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20210616 + + * dirdeps.mk: when using .MAKE.DEPENDFILE_PREFERENCE to find + depend files to read, anchor MACHINE at , or end of string + to avoid prefix match. + +2021-05-04 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20210504 + + * dirdeps.mk: re-implement ALL_MACHINES support to better + cater for local complexities, when ONLY_TARGET_SPEC_LIST + is not set. local.dirdeps.mk can set + DIRDEPS_ALL_MACHINES_FILTER and/or + DIRDEPS_ALL_MACHINES_FILTER_XTRAS to filter the results we get + from listing all existing Makefile.depend.* + +2021-04-20 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20210420 + + * dirdeps.mk: revert previous - not always safe. + +2021-03-20 Simon J Gerraty <sjg@beast.crufty.net> + + * install-mk (MK_VERSION): 20210321 + + * dirdeps.mk: when generating dirdeps.cache + we only need to hook the initial DIRDEPS to the + dirdeps target. That and any _build_xtra_dirs (like tests which + should not be hooked directly to the dependency graph - to avoid + cycles) + 2021-01-30 Simon J Gerraty <sjg@beast.crufty.net> * install-mk (MK_VERSION): 20210130 diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 4eb20cedfec7..38ead3de37cd 100644 --- a/mk/dirdeps.mk +++ b/mk/dirdeps.mk @@ -1,4 +1,4 @@ -# $Id: dirdeps.mk,v 1.133 2021/01/31 04:39:22 sjg Exp $ +# $Id: dirdeps.mk,v 1.140 2021/06/20 23:42:38 sjg Exp $ # Copyright (c) 2010-2021, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. @@ -409,23 +409,72 @@ _DIRDEP_USE: .USE .MAKE done .ifdef ALL_MACHINES -# this is how you limit it to only the machines we have been built for -# previously. .if empty(ONLY_TARGET_SPEC_LIST) && empty(ONLY_MACHINE_LIST) -.if !empty(ALL_MACHINE_LIST) -# ALL_MACHINE_LIST is the list of all legal machines - ignore anything else -_machine_list != cd ${_CURDIR} && 'ls' -1 ${ALL_MACHINE_LIST:O:u:@m@${.MAKE.DEPENDFILE:T:R}.$m@} 2> /dev/null; echo +# we start with everything +_machine_list != echo; 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}* 2> /dev/null + +# some things we know we want to ignore +DIRDEPS_TARGETS_SKIP_LIST += \ + *~ \ + *.bak \ + *.inc \ + *.old \ + *.options \ + *.orig \ + *.rej \ + +# first trim things we know we want to skip +# and provide canonical form +_machine_list := ${_machine_list:${DIRDEPS_TARGETS_SKIP_LIST:${M_ListToSkip}}:T:E} + +# cater for local complexities +# local.dirdeps.mk can set +# DIRDEPS_ALL_MACHINES_FILTER and +# DIRDEPS_ALL_MACHINES_FILTER_XTRAS for final tweaks + +.if !empty(ALL_TARGET_SPEC_LIST) +.if ${_debug_reldir} +.info ALL_TARGET_SPEC_LIST=${ALL_TARGET_SPEC_LIST} +.endif +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_TARGET_SPEC_LIST:@s@$${x:M$$s}@}@ +.elif !empty(ALL_MACHINE_LIST) +.if ${_debug_reldir} +.info ALL_MACHINE_LIST=${ALL_MACHINE_LIST} +.endif +.if ${TARGET_SPEC_VARS:[#]} > 1 +# the space below can result in extraneous ':' +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m,*} $${x:M$$m}@}@ .else -_machine_list != 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.* 2> /dev/null; echo +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m}@}@ .endif -_only_machines := ${_machine_list:${NIgnoreFiles:UN*.bak}:E:O:u} +.endif +# add local XTRAS - default to something benign +DIRDEPS_ALL_MACHINES_FILTER += \ + ${DIRDEPS_ALL_MACHINES_FILTER_XTRAS:UNbak} + +.if ${_debug_reldir} +.info _machine_list=${_machine_list} +.info DIRDEPS_ALL_MACHINES_FILTER=${DIRDEPS_ALL_MACHINES_FILTER} +.endif + +_only_machines := ${_machine_list:${DIRDEPS_ALL_MACHINES_FILTER:ts:}:S,:, ,g} .else _only_machines := ${ONLY_TARGET_SPEC_LIST:U} ${ONLY_MACHINE_LIST:U} .endif .if empty(_only_machines) # we must be boot-strapping -_only_machines := ${TARGET_MACHINE:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}} +_only_machines := ${TARGET_MACHINE:U${ALL_TARGET_SPEC_LIST:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}}} +.endif + +# cleanup the result +_only_machines := ${_only_machines:O:u} + +.if ${_debug_reldir} +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: ALL_MACHINES _only_machines=${_only_machines} .endif .else # ! ALL_MACHINES @@ -452,6 +501,10 @@ _only_machines := ${_only_machines:${NOT_TARGET_SPEC_LIST:${M_ListToSkip}}} # clean up _only_machines := ${_only_machines:O:u} +.if ${_debug_reldir} +.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _only_machines=${_only_machines} +.endif + # make sure we have a starting place? DIRDEPS ?= ${RELDIR} .endif # target @@ -697,6 +750,7 @@ _cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m} .export _cache_deps x!= echo; for x in $$_cache_deps; do echo " $$x \\"; done >&3 .endif +# anything in _build_xtra_dirs is hooked to dirdeps: only x!= echo; { echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ echo; echo 'dirdeps: ${_this_dir}.$m \'; \ for x in $$_build_xtra_dirs; do echo " $$x \\"; done; \ @@ -735,7 +789,7 @@ DEP_MACHINE := ${_DEP_MACHINE} # Warning: there is an assumption here that MACHINE is always # the first entry in TARGET_SPEC_VARS. # If TARGET_SPEC and MACHINE are insufficient, you have a problem. -_m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:S;${MACHINE};${d:E:C/,.*//};:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} +_m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:C;${MACHINE}((,.+)?)$;${d:E:C/,.*//}\1;:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} .if !empty(_m) # M_dep_qual_fixes isn't geared to Makefile.depend _qm := ${_m:C;(\.depend)$;\1.${d:E};:${M_dep_qual_fixes:ts:}} @@ -801,6 +855,7 @@ _reldir_failed: .NOMETA .endif # bootstrapping new dependencies made easy? +.if !target(bootstrap-empty) .if !target(bootstrap) && (make(bootstrap) || \ make(bootstrap-this) || \ make(bootstrap-recurse) || \ @@ -856,3 +911,4 @@ bootstrap-empty: .NOTMAIN .NOMETA @{ echo DIRDEPS=; echo ".include <dirdeps.mk>"; } > ${_want} .endif +.endif diff --git a/mk/dpadd.mk b/mk/dpadd.mk index 45585ce76d9b..aef12528f163 100644 --- a/mk/dpadd.mk +++ b/mk/dpadd.mk @@ -1,4 +1,4 @@ -# $Id: dpadd.mk,v 1.28 2020/08/19 17:51:53 sjg Exp $ +# $Id: dpadd.mk,v 1.29 2021/04/20 02:30:44 sjg Exp $ # # @(#) Copyright (c) 2004, Simon J. Gerraty # @@ -211,8 +211,9 @@ OBJ_${__lib:T:R} ?= ${__lib:H:S,${OBJTOP},${RELOBJTOP},} # If INCLUDES_libfoo is not set, then we'll use ${SRC_libfoo}/h if it exists, # else just ${SRC_libfoo}. # -INCLUDES_${__lib:T:R}?= -I${exists(${SRC_${__lib:T:R}}/h):?${SRC_${__lib:T:R}}/h:${SRC_${__lib:T:R}}} - +.if !empty(SRC_${__lib:T:R}) +INCLUDES_${__lib:T:R} ?= -I${exists(${SRC_${__lib:T:R}}/h):?${SRC_${__lib:T:R}}/h:${SRC_${__lib:T:R}}} +.endif .endfor # even for staged libs we sometimes diff --git a/mk/install-mk b/mk/install-mk index b014728ca450..96c35b1052ec 100755 --- a/mk/install-mk +++ b/mk/install-mk @@ -55,7 +55,7 @@ # Simon J. Gerraty <sjg@crufty.net> # RCSid: -# $Id: install-mk,v 1.191 2021/01/30 23:16:42 sjg Exp $ +# $Id: install-mk,v 1.196 2021/06/19 15:30:41 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # @@ -70,7 +70,7 @@ # sjg@crufty.net # -MK_VERSION=20210130 +MK_VERSION=20210616 OWNER= GROUP= MODE=444 diff --git a/mk/meta.autodep.mk b/mk/meta.autodep.mk index b5cb39a30855..5d09dbd88e81 100644 --- a/mk/meta.autodep.mk +++ b/mk/meta.autodep.mk @@ -1,4 +1,4 @@ -# $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 sjg Exp $ +# $Id: meta.autodep.mk,v 1.54 2021/03/06 17:03:18 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty @@ -298,16 +298,20 @@ start_utc := ${now_utc} meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \ created=${empty(.MAKE.META.CREATED):?0:${.MAKE.META.CREATED:[#]}} +.if !target(_reldir_finish) #.END: _reldir_finish .if target(gendirdeps) _reldir_finish: gendirdeps .endif _reldir_finish: .NOMETA @echo "${TIME_STAMP} Finished ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" +.endif +.if !target(_reldir_failed) #.ERROR: _reldir_failed _reldir_failed: .NOMETA @echo "${TIME_STAMP} Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" +.endif .if !defined(WITHOUT_META_STATS) && ${.MAKE.LEVEL} > 0 .END: _reldir_finish diff --git a/mk/meta2deps.py b/mk/meta2deps.py index 4627e08d7c11..193e303de3da 100755 --- a/mk/meta2deps.py +++ b/mk/meta2deps.py @@ -37,7 +37,7 @@ We only pay attention to a subset of the information in the """ RCSid: - $Id: meta2deps.py,v 1.34 2020/10/02 03:11:17 sjg Exp $ + $Id: meta2deps.py,v 1.38 2021/06/17 05:20:08 sjg Exp $ Copyright (c) 2011-2020, Simon J. Gerraty Copyright (c) 2011-2017, Juniper Networks, Inc. @@ -68,12 +68,6 @@ RCSid: import os, re, sys -def getv(dict, key, d=None): - """Lookup key in dict and return value or the supplied default.""" - if key in dict: - return dict[key] - return d - def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. @@ -152,7 +146,10 @@ def abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): return path def sort_unique(list, cmp=None, key=None, reverse=False): - list.sort(cmp, key, reverse) + if sys.version_info[0] == 2: + list.sort(cmp, key, reverse) + else: + list.sort(reverse=reverse) nl = [] le = None for e in list: @@ -224,22 +221,22 @@ class MetaFile: """ self.name = name - self.debug = getv(conf, 'debug', 0) - self.debug_out = getv(conf, 'debug_out', sys.stderr) - - self.machine = getv(conf, 'MACHINE', '') - self.machine_arch = getv(conf, 'MACHINE_ARCH', '') - self.target_spec = getv(conf, 'TARGET_SPEC', '') - self.curdir = getv(conf, 'CURDIR') - self.reldir = getv(conf, 'RELDIR') - self.dpdeps = getv(conf, 'DPDEPS') + self.debug = conf.get('debug', 0) + self.debug_out = conf.get('debug_out', sys.stderr) + + self.machine = conf.get('MACHINE', '') + self.machine_arch = conf.get('MACHINE_ARCH', '') + self.target_spec = conf.get('TARGET_SPEC', '') + self.curdir = conf.get('CURDIR') + self.reldir = conf.get('RELDIR') + self.dpdeps = conf.get('DPDEPS') self.line = 0 if not self.conf: # some of the steps below we want to do only once self.conf = conf - self.host_target = getv(conf, 'HOST_TARGET') - for srctop in getv(conf, 'SRCTOPS', []): + self.host_target = conf.get('HOST_TARGET') + for srctop in conf.get('SRCTOPS', []): if srctop[-1] != '/': srctop += '/' if not srctop in self.srctops: @@ -256,7 +253,7 @@ class MetaFile: if self.target_spec: trim_list += add_trims(self.target_spec) - for objroot in getv(conf, 'OBJROOTS', []): + for objroot in conf.get('OBJROOTS', []): for e in trim_list: if objroot.endswith(e): # this is not what we want - fix it @@ -276,7 +273,7 @@ class MetaFile: self.srctops.sort(reverse=True) self.objroots.sort(reverse=True) - self.excludes = getv(conf, 'EXCLUDES', []) + self.excludes = conf.get('EXCLUDES', []) if self.debug: print("host_target=", self.host_target, file=self.debug_out) @@ -468,8 +465,8 @@ class MetaFile: if pid != last_pid: if last_pid: pid_last_dir[last_pid] = self.last_dir - cwd = getv(pid_cwd, pid, self.cwd) - self.last_dir = getv(pid_last_dir, pid, self.cwd) + cwd = pid_cwd.get(pid, self.cwd) + self.last_dir = pid_last_dir.get(pid, self.cwd) last_pid = pid # process operations @@ -557,7 +554,10 @@ class MetaFile: # to the src dir, we may need to add dependencies for each rdir = dir dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) - rdir = os.path.realpath(dir) + if dir: + rdir = os.path.realpath(dir) + else: + dir = rdir if rdir == dir: rdir = None # now put path back together @@ -725,7 +725,7 @@ def main(argv, klass=MetaFile, xopts='', xoptf=None): for a in eaten: args.remove(a) - debug_out = getv(conf, 'debug_out', sys.stderr) + debug_out = conf.get('debug_out', sys.stderr) if debug: print("config:", file=debug_out) @@ -752,9 +752,9 @@ def main(argv, klass=MetaFile, xopts='', xoptf=None): print(m.src_dirdeps('\nsrc:')) - dpdeps = getv(conf, 'DPDEPS') + dpdeps = conf.get('DPDEPS') if dpdeps: - m.file_depends(open(dpdeps, 'wb')) + m.file_depends(open(dpdeps, 'w')) return m diff --git a/mk/rst2htm.mk b/mk/rst2htm.mk index 1db9792f4127..66eb8552f875 100644 --- a/mk/rst2htm.mk +++ b/mk/rst2htm.mk @@ -1,4 +1,4 @@ -# $Id: rst2htm.mk,v 1.11 2020/08/19 17:51:53 sjg Exp $ +# $Id: rst2htm.mk,v 1.12 2021/05/26 04:20:31 sjg Exp $ # # @(#) Copyright (c) 2009, Simon J. Gerraty # @@ -21,7 +21,9 @@ TXTSRCS != 'ls' -1t ${.CURDIR}/*.txt ${.CURDIR}/*.rst 2>/dev/null; echo .endif RSTSRCS ?= ${TXTSRCS} HTMFILES ?= ${RSTSRCS:R:T:O:u:%=%.htm} -RST2HTML ?= rst2html.py +# can be empty, 4 or 5 +HTML_VERSION ?= +RST2HTML ?= rst2html${HTML_VERSION}.py RST2PDF ?= rst2pdf RST2S5 ?= rst2s5.py # the following will run RST2S5 if the target name contains the word 'slides' diff --git a/nonints.h b/nonints.h index 38ac0c85518b..7119d798432b 100644 --- a/nonints.h +++ b/nonints.h @@ -1,4 +1,4 @@ -/* $NetBSD: nonints.h,v 1.202 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: nonints.h,v 1.213 2021/04/11 13:35:56 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -76,14 +76,14 @@ void Arch_Init(void); void Arch_End(void); -Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); +bool Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); void Arch_UpdateMTime(GNode *gn); void Arch_UpdateMemberMTime(GNode *gn); void Arch_FindLib(GNode *, SearchPath *); -Boolean Arch_LibOODate(GNode *); -Boolean Arch_IsLib(GNode *); +bool Arch_LibOODate(GNode *); +bool Arch_IsLib(GNode *); /* compat.c */ int Compat_RunCommand(const char *, GNode *, StringListNode *); @@ -91,7 +91,7 @@ void Compat_Run(GNodeList *); void Compat_Make(GNode *, GNode *); /* cond.c */ -CondEvalResult Cond_EvalCondition(const char *, Boolean *); +CondEvalResult Cond_EvalCondition(const char *, bool *); CondEvalResult Cond_EvalLine(const char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); @@ -117,16 +117,16 @@ void SearchPath_Free(SearchPath *); /* for.c */ int For_Eval(const char *); -Boolean For_Accum(const char *); +bool For_Accum(const char *); void For_Run(int); /* job.c */ #ifdef WAIT_T -void JobReapChild(pid_t, WAIT_T, Boolean); +void JobReapChild(pid_t, WAIT_T, bool); #endif /* main.c */ -Boolean GetBooleanVar(const char *, Boolean); +bool GetBooleanExpr(const char *, bool); void Main_ParseArgLine(const char *); char *Cmd_Exec(const char *, const char **); void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); @@ -137,7 +137,7 @@ void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); void execDie(const char *, const char *); char *getTmpdir(void); -Boolean ParseBoolean(const char *, Boolean); +bool ParseBoolean(const char *, bool); char *cached_realpath(const char *, char *); /* parse.c */ @@ -161,86 +161,14 @@ typedef struct VarAssign { typedef char *(*ReadMoreProc)(void *, size_t *); void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); -Boolean Parse_IsVar(const char *, VarAssign *out_var); -void Parse_DoVar(VarAssign *, GNode *); +bool Parse_IsVar(const char *, VarAssign *out_var); +void Parse_Var(VarAssign *, GNode *); void Parse_AddIncludeDir(const char *); void Parse_File(const char *, int); void Parse_SetInput(const char *, int, int, ReadMoreProc, void *); void Parse_MainName(GNodeList *); int Parse_GetFatals(void); -/* str.c */ - -/* A read-only string that may need to be freed after use. */ -typedef struct FStr { - const char *str; - void *freeIt; -} FStr; - -/* A modifiable string that may need to be freed after use. */ -typedef struct MFStr { - char *str; - void *freeIt; -} MFStr; - -typedef struct Words { - char **words; - size_t len; - void *freeIt; -} Words; - -/* Return a string that is the sole owner of str. */ -MAKE_INLINE FStr -FStr_InitOwn(char *str) -{ - return (FStr){ str, str }; -} - -/* Return a string that refers to the shared str. */ -MAKE_INLINE FStr -FStr_InitRefer(const char *str) -{ - return (FStr){ str, NULL }; -} - -MAKE_INLINE void -FStr_Done(FStr *fstr) -{ - free(fstr->freeIt); -} - -/* Return a string that is the sole owner of str. */ -MAKE_INLINE MFStr -MFStr_InitOwn(char *str) -{ - return (MFStr){ str, str }; -} - -/* Return a string that refers to the shared str. */ -MAKE_INLINE MFStr -MFStr_InitRefer(char *str) -{ - return (MFStr){ str, NULL }; -} - -MAKE_INLINE void -MFStr_Done(MFStr *mfstr) -{ - free(mfstr->freeIt); -} - -Words Str_Words(const char *, Boolean); -MAKE_INLINE void -Words_Free(Words w) -{ - free(w.words); - free(w.freeIt); -} - -char *str_concat2(const char *, const char *); -char *str_concat3(const char *, const char *, const char *); -char *str_concat4(const char *, const char *, const char *, const char *); -Boolean Str_Match(const char *, const char *); #ifndef HAVE_STRLCPY /* strlcpy.c */ @@ -252,12 +180,12 @@ void Suff_Init(void); void Suff_End(void); void Suff_ClearSuffixes(void); -Boolean Suff_IsTransform(const char *); +bool Suff_IsTransform(const char *); GNode *Suff_AddTransform(const char *); void Suff_EndTransform(GNode *); void Suff_AddSuffix(const char *, GNode **); SearchPath *Suff_GetPath(const char *); -void Suff_DoPaths(void); +void Suff_ExtendPaths(void); void Suff_AddInclude(const char *); void Suff_AddLib(const char *); void Suff_FindDeps(GNode *); @@ -277,7 +205,7 @@ GNode *Targ_GetNode(const char *); GNode *Targ_NewInternalNode(const char *); GNode *Targ_GetEndNode(void); void Targ_FindList(GNodeList *, StringList *); -Boolean Targ_Precious(const GNode *); +bool Targ_Precious(const GNode *); void Targ_SetMain(GNode *); void Targ_PrintCmds(GNode *); void Targ_PrintNode(GNode *, int); @@ -292,36 +220,40 @@ const char *GNodeMade_Name(GNodeMade); void Var_Init(void); void Var_End(void); -typedef enum VarEvalFlags { - VARE_NONE = 0, +typedef enum VarEvalMode { /* - * Expand and evaluate variables during parsing. + * Only parse the expression but don't evaluate any part of it. * - * TODO: Document what Var_Parse and Var_Subst return when this flag - * is not set. + * TODO: Document what Var_Parse and Var_Subst return in this mode. + * As of 2021-03-15, they return unspecified, inconsistent results. */ - VARE_WANTRES = 1 << 0, + VARE_PARSE_ONLY, + + /* Parse and evaluate the expression. */ + VARE_WANTRES, /* - * Treat undefined variables as errors. - * Must only be used in combination with VARE_WANTRES. + * Parse and evaluate the expression. It is an error if a + * subexpression evaluates to undefined. */ - VARE_UNDEFERR = 1 << 1, + VARE_UNDEFERR, /* - * Keep '$$' as '$$' instead of reducing it to a single '$'. + * Parse and evaluate the expression. Keep '$$' as '$$' instead of + * reducing it to a single '$'. Subexpressions that evaluate to + * undefined expand to an empty string. * * 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. */ - VARE_KEEP_DOLLAR = 1 << 2, + VARE_EVAL_KEEP_DOLLAR, /* - * Keep undefined variables as-is instead of expanding them to an - * empty string. + * Parse and evaluate the expression. Keep undefined variables as-is + * instead of expanding them to an empty string. * * Example for a ':=' assignment: * CFLAGS = $(.INCLUDES) @@ -330,8 +262,14 @@ typedef enum VarEvalFlags { * # way) is still undefined, the updated CFLAGS becomes * # "-I.. $(.INCLUDES)". */ - VARE_KEEP_UNDEF = 1 << 3 -} VarEvalFlags; + VARE_EVAL_KEEP_UNDEF, + + /* + * Parse and evaluate the expression. Keep '$$' as '$$' and preserve + * undefined subexpressions. + */ + VARE_KEEP_DOLLAR_UNDEF +} VarEvalMode; typedef enum VarSetFlags { VAR_SET_NONE = 0, @@ -361,7 +299,8 @@ typedef enum VarParseResult { * Some callers handle this case differently, so return this * information to them, for now. * - * TODO: Replace this with a new flag VARE_KEEP_UNDEFINED. + * TODO: Instead of having this special return value, rather ensure + * that VARE_EVAL_KEEP_UNDEF is processed properly. */ VPR_UNDEF @@ -385,18 +324,18 @@ void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags); void Var_Append(GNode *, const char *, const char *); void Var_AppendExpand(GNode *, const char *, const char *); -Boolean Var_Exists(GNode *, const char *); -Boolean Var_ExistsExpand(GNode *, const char *); +bool Var_Exists(GNode *, const char *); +bool Var_ExistsExpand(GNode *, const char *); FStr Var_Value(GNode *, const char *); const char *GNode_ValueDirect(GNode *, const char *); -VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, FStr *); -VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **); +VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *); +VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **); void Var_Stats(void); void Var_Dump(GNode *); void Var_ReexportVars(void); void Var_Export(VarExportMode, const char *); void Var_ExportVars(const char *); -void Var_UnExport(Boolean, const char *); +void Var_UnExport(bool, const char *); void Global_Set(const char *, const char *); void Global_SetExpand(const char *, const char *); @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.549 2021/02/05 05:46:27 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.560 2021/06/21 10:42:06 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -86,7 +86,7 @@ * Parse_File Parse a top-level makefile. Included files are * handled by IncludeFile instead. * - * Parse_IsVar Return TRUE if the given line is a variable + * Parse_IsVar Return true if the given line is a variable * assignment. Used by MainParseArgs to determine if * an argument is a target or a variable assignment. * Used internally for pretty much the same thing. @@ -124,7 +124,7 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.549 2021/02/05 05:46:27 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.560 2021/06/21 10:42:06 rillig Exp $"); /* types and constants */ @@ -133,11 +133,11 @@ MAKE_RCSID("$NetBSD: parse.c,v 1.549 2021/02/05 05:46:27 rillig Exp $"); */ typedef struct IFile { char *fname; /* name of file (relative? absolute?) */ - Boolean fromForLoop; /* simulated .include by the .for loop */ + bool fromForLoop; /* simulated .include by the .for loop */ int lineno; /* current line number in file */ int first_lineno; /* line number of start of text */ unsigned int cond_depth; /* 'if' nesting when file opened */ - Boolean depending; /* state of doing_depend on EOF */ + bool depending; /* state of doing_depend on EOF */ /* The buffer from which the file's content is read. */ char *buf_freeIt; @@ -333,7 +333,7 @@ struct loadedfile { const char *path; /* name, for error reports */ char *buf; /* contents buffer */ size_t len; /* length of contents */ - Boolean used; /* XXX: have we used the data yet */ + bool used; /* XXX: have we used the data yet */ }; /* XXX: What is the lifetime of the path? Who manages the memory? */ @@ -346,7 +346,7 @@ loadedfile_create(const char *path, char *buf, size_t buflen) lf->path = path == NULL ? "(stdin)" : path; lf->buf = buf; lf->len = buflen; - lf->used = FALSE; + lf->used = false; return lf; } @@ -369,7 +369,7 @@ loadedfile_readMore(void *x, size_t *len) if (lf->used) return NULL; - lf->used = TRUE; + lf->used = true; *len = lf->len; return lf->buf; } @@ -377,16 +377,16 @@ loadedfile_readMore(void *x, size_t *len) /* * Try to get the size of a file. */ -static Boolean +static bool load_getsize(int fd, size_t *ret) { struct stat st; if (fstat(fd, &st) < 0) - return FALSE; + return false; if (!S_ISREG(st.st_mode)) - return FALSE; + return false; /* * st_size is an off_t, which is 64 bits signed; *ret is @@ -398,10 +398,10 @@ 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 > 0x3fffffff) - return FALSE; + return false; *ret = (size_t)st.st_size; - return TRUE; + return true; } /* @@ -507,7 +507,7 @@ PrintStackTrace(void) for (i = n; i-- > 0;) { const IFile *entry = entries + i; const char *fname = entry->fname; - Boolean printLineno; + bool printLineno; char dirbuf[MAXPATHLEN + 1]; if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) @@ -527,10 +527,10 @@ PrintStackTrace(void) } /* Check if the current character is escaped on the current line. */ -static Boolean +static bool ParseIsEscaped(const char *line, const char *c) { - Boolean active = FALSE; + bool active = false; for (;;) { if (line == c) return active; @@ -612,7 +612,7 @@ static void ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, ParseErrorLevel type, const char *fmt, va_list ap) { - static Boolean fatal_warning_error_printed = FALSE; + static bool fatal_warning_error_printed = false; (void)fprintf(f, "%s: ", progname); @@ -631,7 +631,7 @@ ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, fatals++; if (type == PARSE_WARNING && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); - fatal_warning_error_printed = TRUE; + fatal_warning_error_printed = true; } print_stack_trace: @@ -730,7 +730,7 @@ ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg) * which does not need to be informed once the child target has been made. */ static void -LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial) +LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) { if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&pgn->cohorts)) pgn = pgn->cohorts.last->datum; @@ -752,7 +752,7 @@ LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial) /* Add the node to each target from the current dependency group. */ static void -LinkToTargets(GNode *gn, Boolean isSpecial) +LinkToTargets(GNode *gn, bool isSpecial) { GNodeListNode *ln; @@ -760,7 +760,7 @@ LinkToTargets(GNode *gn, Boolean isSpecial) LinkSource(ln->datum, gn, isSpecial); } -static Boolean +static bool TryApplyDependencyOperator(GNode *gn, GNodeType op) { /* @@ -771,7 +771,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); - return FALSE; + return false; } if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { @@ -821,7 +821,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) gn->type |= op; } - return TRUE; + return true; } static void @@ -843,7 +843,7 @@ ApplyDependencyOperator(GNodeType op) * We give each .WAIT node a unique name (mainly for diagnostics). */ static void -ParseDependencySourceWait(Boolean isSpecial) +ParseDependencySourceWait(bool isSpecial) { static int wait_number = 0; char wait_src[16]; @@ -858,29 +858,29 @@ ParseDependencySourceWait(Boolean isSpecial) } -static Boolean +static bool ParseDependencySourceKeyword(const char *src, ParseSpecial specType) { int keywd; GNodeType op; if (*src != '.' || !ch_isupper(src[1])) - return FALSE; + return false; keywd = ParseFindKeyword(src); if (keywd == -1) - return FALSE; + return false; op = parseKeywords[keywd].op; if (op != OP_NONE) { ApplyDependencyOperator(op); - return TRUE; + return true; } if (parseKeywords[keywd].spec == SP_WAIT) { ParseDependencySourceWait(specType != SP_NOT); - return TRUE; + return true; } - return FALSE; + return false; } static void @@ -891,7 +891,7 @@ ParseDependencySourceMain(const char *src) * list of things to create, but only if the user didn't specify a * target on the command line and .MAIN occurs for the first time. * - * See ParseDoDependencyTargetSpecial, branch SP_MAIN. + * See ParseDependencyTargetSpecial, branch SP_MAIN. * See unit-tests/cond-func-make-main.mk. */ Lst_Append(&opts.create, bmake_strdup(src)); @@ -1057,8 +1057,8 @@ ParseDependencyTargetWord(const char **pp, const char *lstart) const char *nested_p = cp; FStr nested_val; - (void)Var_Parse(&nested_p, SCOPE_CMDLINE, VARE_NONE, - &nested_val); + (void)Var_Parse(&nested_p, SCOPE_CMDLINE, + VARE_PARSE_ONLY, &nested_val); /* TODO: handle errors */ FStr_Done(&nested_val); cp += nested_p - cp; @@ -1069,11 +1069,15 @@ ParseDependencyTargetWord(const char **pp, const char *lstart) *pp = cp; } -/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */ +/* + * Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. + * + * See the tests deptgt-*.mk. + */ static void -ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, - const char *targetName, - SearchPathList **inout_paths) +ParseDependencyTargetSpecial(ParseSpecial *inout_specType, + const char *targetName, + SearchPathList **inout_paths) { switch (*inout_specType) { case SP_PATH: @@ -1116,13 +1120,13 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, break; } case SP_DELETE_ON_ERROR: - deleteOnError = TRUE; + deleteOnError = true; break; case SP_NOTPARALLEL: opts.maxJobs = 1; break; case SP_SINGLESHELL: - opts.compatMake = TRUE; + opts.compatMake = true; break; case SP_ORDER: order_pred = NULL; @@ -1136,9 +1140,9 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, * .PATH<suffix> has to be handled specially. * Call on the suffix module to give us a path to modify. */ -static Boolean -ParseDoDependencyTargetPath(const char *suffixName, - SearchPathList **inout_paths) +static bool +ParseDependencyTargetPath(const char *suffixName, + SearchPathList **inout_paths) { SearchPath *path; @@ -1146,28 +1150,28 @@ ParseDoDependencyTargetPath(const char *suffixName, if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", suffixName); - return FALSE; + return false; } if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, path); - return TRUE; + return true; } /* * See if it's a special target and if so set specType to match it. */ -static Boolean -ParseDoDependencyTarget(const char *targetName, - ParseSpecial *inout_specType, - GNodeType *out_tOp, SearchPathList **inout_paths) +static bool +ParseDependencyTarget(const char *targetName, + ParseSpecial *inout_specType, + GNodeType *out_tOp, SearchPathList **inout_paths) { int keywd; if (!(targetName[0] == '.' && ch_isupper(targetName[1]))) - return TRUE; + return true; /* * See if the target is a special target that must have it @@ -1178,25 +1182,25 @@ ParseDoDependencyTarget(const char *targetName, if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); - return FALSE; + return false; } *inout_specType = parseKeywords[keywd].spec; *out_tOp = parseKeywords[keywd].op; - ParseDoDependencyTargetSpecial(inout_specType, targetName, + ParseDependencyTargetSpecial(inout_specType, targetName, inout_paths); } else if (strncmp(targetName, ".PATH", 5) == 0) { *inout_specType = SP_PATH; - if (!ParseDoDependencyTargetPath(targetName + 5, inout_paths)) - return FALSE; + if (!ParseDependencyTargetPath(targetName + 5, inout_paths)) + return false; } - return TRUE; + return true; } static void -ParseDoDependencyTargetMundane(char *targetName, StringList *curTargs) +ParseDependencyTargetMundane(char *targetName, StringList *curTargs) { if (Dir_HasWildcards(targetName)) { /* @@ -1233,16 +1237,16 @@ ParseDoDependencyTargetMundane(char *targetName, StringList *curTargs) } static void -ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) +ParseDependencyTargetExtraWarn(char **pp, const char *lstart) { - Boolean warning = FALSE; + bool warning = false; char *cp = *pp; while (*cp != '\0') { if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) break; if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) - warning = TRUE; + warning = true; cp++; } if (warning) @@ -1252,7 +1256,7 @@ ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) } static void -ParseDoDependencyCheckSpec(ParseSpecial specType) +ParseDependencyCheckSpec(ParseSpecial specType) { switch (specType) { default: @@ -1276,15 +1280,19 @@ ParseDoDependencyCheckSpec(ParseSpecial specType) } } -static Boolean -ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) +/* + * In a dependency line like 'targets: sources' or 'targets! sources', parse + * the operator ':', '::' or '!' from between the targets and the sources. + */ +static bool +ParseDependencyOp(char **pp, const char *lstart, GNodeType *out_op) { const char *cp = *pp; if (*cp == '!') { *out_op = OP_FORCE; (*pp)++; - return TRUE; + return true; } if (*cp == ':') { @@ -1295,14 +1303,14 @@ ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) *out_op = OP_DEPENDS; (*pp)++; } - return TRUE; + return true; } { const char *msg = lstart[0] == '.' ? "Unknown directive" : "Missing dependency operator"; Parse_Error(PARSE_FATAL, "%s", msg); - return FALSE; + return false; } } @@ -1318,21 +1326,30 @@ ClearPaths(SearchPathList *paths) Dir_SetPATH(); } +/* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * a .PATH removes all directories from the search path(s). + */ static void -ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) +ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) { switch (specType) { case SP_SUFFIXES: Suff_ClearSuffixes(); break; case SP_PRECIOUS: - allPrecious = TRUE; + allPrecious = true; break; case SP_IGNORE: - opts.ignoreErrors = TRUE; + opts.ignoreErrors = true; break; case SP_SILENT: - opts.beSilent = TRUE; + opts.beSilent = true; break; case SP_PATH: ClearPaths(paths); @@ -1385,8 +1402,8 @@ AddToPaths(const char *dir, SearchPathList *paths) * and will cause make to do a new chdir to that path. */ static void -ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, - SearchPathList *paths) +ParseDependencySourceSpecial(ParseSpecial specType, char *word, + SearchPathList *paths) { switch (specType) { case SP_SUFFIXES: @@ -1405,21 +1422,21 @@ ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, Suff_SetNull(word); break; case SP_OBJDIR: - Main_SetObjdir(FALSE, "%s", word); + Main_SetObjdir(false, "%s", word); break; default: break; } } -static Boolean -ParseDoDependencyTargets(char **inout_cp, - char **inout_line, - const char *lstart, - ParseSpecial *inout_specType, - GNodeType *inout_tOp, - SearchPathList **inout_paths, - StringList *curTargs) +static bool +ParseDependencyTargets(char **inout_cp, + char **inout_line, + const char *lstart, + ParseSpecial *inout_specType, + GNodeType *inout_tOp, + SearchPathList **inout_paths, + StringList *curTargs) { char *cp; char *tgt = *inout_line; @@ -1452,7 +1469,7 @@ ParseDoDependencyTargets(char **inout_cp, * Arch_ParseArchive will set 'line' to be the first * non-blank after the archive-spec. It creates/finds * nodes for the members and places them on the given - * list, returning TRUE if all went well and FALSE if + * list, returning true if all went well and false if * there was an error in the specification. On error, * line should remain untouched. */ @@ -1460,7 +1477,7 @@ ParseDoDependencyTargets(char **inout_cp, Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", tgt); - return FALSE; + return false; } cp = tgt; @@ -1469,23 +1486,23 @@ ParseDoDependencyTargets(char **inout_cp, if (*cp == '\0') { ParseErrorNoDependency(lstart); - return FALSE; + return false; } /* Insert a null terminator. */ savec = *cp; *cp = '\0'; - if (!ParseDoDependencyTarget(tgt, inout_specType, inout_tOp, + if (!ParseDependencyTarget(tgt, inout_specType, inout_tOp, inout_paths)) - return FALSE; + return false; /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ if (*inout_specType == SP_NOT && *tgt != '\0') - ParseDoDependencyTargetMundane(tgt, curTargs); + ParseDependencyTargetMundane(tgt, curTargs); else if (*inout_specType == SP_PATH && *tgt != '.' && *tgt != '\0') Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", @@ -1499,7 +1516,7 @@ ParseDoDependencyTargets(char **inout_cp, * we allow on this line. */ if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) - ParseDoDependencyTargetExtraWarn(&cp, lstart); + ParseDependencyTargetExtraWarn(&cp, lstart); else pp_skip_whitespace(&cp); @@ -1513,12 +1530,12 @@ ParseDoDependencyTargets(char **inout_cp, *inout_cp = cp; *inout_line = tgt; - return TRUE; + return true; } static void -ParseDoDependencySourcesSpecial(char *start, char *end, - ParseSpecial specType, SearchPathList *paths) +ParseDependencySourcesSpecial(char *start, char *end, + ParseSpecial specType, SearchPathList *paths) { char savec; @@ -1527,7 +1544,7 @@ ParseDoDependencySourcesSpecial(char *start, char *end, end++; savec = *end; *end = '\0'; - ParseDoDependencySourceSpecial(specType, start, paths); + ParseDependencySourceSpecial(specType, start, paths); *end = savec; if (savec != '\0') end++; @@ -1536,9 +1553,9 @@ ParseDoDependencySourcesSpecial(char *start, char *end, } } -static Boolean -ParseDoDependencySourcesMundane(char *start, char *end, - ParseSpecial specType, GNodeType tOp) +static bool +ParseDependencySourcesMundane(char *start, char *end, + ParseSpecial specType, GNodeType tOp) { while (*start != '\0') { /* @@ -1566,7 +1583,7 @@ ParseDoDependencySourcesMundane(char *start, char *end, Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", start); - return FALSE; + return false; } while (!Lst_IsEmpty(&sources)) { @@ -1586,7 +1603,60 @@ ParseDoDependencySourcesMundane(char *start, char *end, pp_skip_whitespace(&end); start = end; } - return TRUE; + return true; +} + +/* + * In a dependency line like 'targets: sources', parse the sources. + * + * See the tests depsrc-*.mk. + */ +static void +ParseDependencySources(char *const line, char *const cp, + GNodeType const tOp, + ParseSpecial const specType, + SearchPathList ** inout_paths) +{ + if (line[0] == '\0') { + ParseDependencySourcesEmpty(specType, *inout_paths); + } else if (specType == SP_MFLAGS) { + Main_ParseArgLine(line); + /* + * Set the initial character to a null-character so the loop + * to get sources won't get anything. + */ + *line = '\0'; + } else if (specType == SP_SHELL) { + if (!Job_ParseShell(line)) { + Parse_Error(PARSE_FATAL, + "improper shell specification"); + return; + } + *line = '\0'; + } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || + specType == SP_DELETE_ON_ERROR) { + *line = '\0'; + } + + /* Now go for the sources. */ + if (specType == SP_SUFFIXES || specType == SP_PATH || + specType == SP_INCLUDES || specType == SP_LIBS || + specType == SP_NULL || specType == SP_OBJDIR) { + ParseDependencySourcesSpecial(line, cp, specType, + *inout_paths); + if (*inout_paths != NULL) { + Lst_Free(*inout_paths); + *inout_paths = NULL; + } + if (specType == SP_PATH) + Dir_SetPATH(); + } else { + assert(*inout_paths == NULL); + if (!ParseDependencySourcesMundane(line, cp, specType, tOp)) + return; + } + + FindMainTarget(); } /* @@ -1598,7 +1668,7 @@ ParseDoDependencySourcesMundane(char *start, char *end, * * The operator is applied to each node in the global 'targets' list, * which is where the nodes found for the targets are kept, by means of - * the ParseDoOp function. + * the ParseOp function. * * 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, @@ -1617,7 +1687,7 @@ ParseDoDependencySourcesMundane(char *start, char *end, * Upon return, the value of the line is unspecified. */ static void -ParseDoDependency(char *line) +ParseDependency(char *line) { char *cp; /* our current position */ GNodeType op; /* the operator on the line */ @@ -1635,7 +1705,7 @@ ParseDoDependency(char *line) */ ParseSpecial specType = SP_NOT; - DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); + DEBUG1(PARSE, "ParseDependency(%s)\n", line); tOp = OP_NONE; paths = NULL; @@ -1643,8 +1713,8 @@ ParseDoDependency(char *line) /* * First, grind through the targets. */ - /* XXX: don't use line as an iterator variable */ - if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, + /* XXX: don't use 'line' as an iterator variable */ + if (!ParseDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths, &curTargs)) goto out; @@ -1656,12 +1726,12 @@ ParseDoDependency(char *line) Lst_Init(&curTargs); if (!Lst_IsEmpty(targets)) - ParseDoDependencyCheckSpec(specType); + ParseDependencyCheckSpec(specType); /* * Have now parsed all the target names. Must parse the operator next. */ - if (!ParseDoDependencyParseOp(&cp, lstart, &op)) + if (!ParseDependencyOp(&cp, lstart, &op)) goto out; /* @@ -1680,55 +1750,7 @@ ParseDoDependency(char *line) pp_skip_whitespace(&cp); line = cp; /* XXX: 'line' is an inappropriate name */ - /* - * Several special targets take different actions if present with no - * sources: - * a .SUFFIXES line with no sources clears out all old suffixes - * a .PRECIOUS line makes all targets precious - * a .IGNORE line ignores errors for all targets - * a .SILENT line creates silence when making all targets - * a .PATH removes all directories from the search path(s). - */ - if (line[0] == '\0') { - ParseDoDependencySourcesEmpty(specType, paths); - } else if (specType == SP_MFLAGS) { - /* - * Call on functions in main.c to deal with these arguments and - * set the initial character to a null-character so the loop to - * get sources won't get anything - */ - Main_ParseArgLine(line); - *line = '\0'; - } else if (specType == SP_SHELL) { - if (!Job_ParseShell(line)) { - Parse_Error(PARSE_FATAL, - "improper shell specification"); - goto out; - } - *line = '\0'; - } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || - specType == SP_DELETE_ON_ERROR) { - *line = '\0'; - } - - /* Now go for the sources. */ - if (specType == SP_SUFFIXES || specType == SP_PATH || - specType == SP_INCLUDES || specType == SP_LIBS || - specType == SP_NULL || specType == SP_OBJDIR) { - ParseDoDependencySourcesSpecial(line, cp, specType, paths); - if (paths != NULL) { - Lst_Free(paths); - paths = NULL; - } - if (specType == SP_PATH) - Dir_SetPATH(); - } else { - assert(paths == NULL); - if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) - goto out; - } - - FindMainTarget(); + ParseDependencySources(line, cp, tOp, specType, &paths); out: if (paths != NULL) @@ -1805,7 +1827,7 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, * * Used for both lines in a file and command line arguments. */ -Boolean +bool Parse_IsVar(const char *p, VarAssign *out_var) { VarAssignParsed pvar; @@ -1861,7 +1883,7 @@ Parse_IsVar(const char *p, VarAssign *out_var) pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; cpp_skip_whitespace(&p); AdjustVarassignOp(&pvar, p, out_var); - return TRUE; + return true; } if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { @@ -1870,13 +1892,13 @@ Parse_IsVar(const char *p, VarAssign *out_var) p++; cpp_skip_whitespace(&p); AdjustVarassignOp(&pvar, p, out_var); - return TRUE; + return true; } if (firstSpace != NULL) - return FALSE; + return false; } - return FALSE; + return false; } /* @@ -1889,7 +1911,7 @@ VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { char *expandedValue; - (void)Var_Subst(uvalue, scope, VARE_NONE, + (void)Var_Subst(uvalue, scope, VARE_PARSE_ONLY, &expandedValue); /* TODO: handle errors */ free(expandedValue); @@ -1913,8 +1935,7 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); - (void)Var_Subst(uvalue, scope, - VARE_WANTRES | VARE_KEEP_DOLLAR | VARE_KEEP_UNDEF, &evalue); + (void)Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF, &evalue); /* TODO: handle errors */ Var_SetExpand(scope, name, evalue); @@ -1933,8 +1954,8 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, cmd = FStr_InitRefer(uvalue); if (strchr(cmd.str, '$') != NULL) { char *expanded; - (void)Var_Subst(cmd.str, SCOPE_CMDLINE, - VARE_WANTRES | VARE_UNDEFERR, &expanded); + (void)Var_Subst(cmd.str, SCOPE_CMDLINE, VARE_UNDEFERR, + &expanded); /* TODO: handle errors */ cmd = FStr_InitOwn(expanded); } @@ -1952,7 +1973,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, /* * Perform a variable assignment. * - * The actual value of the variable is returned in *out_TRUE_avalue. + * The actual value of the variable is returned in *out_true_avalue. * Especially for VAR_SUBST and VAR_SHELL this can differ from the literal * value. * @@ -1960,9 +1981,9 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, * the case. It is only skipped if the operator is '?=' and the variable * already exists. */ -static Boolean +static bool VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, - GNode *scope, FStr *out_TRUE_avalue) + GNode *scope, FStr *out_true_avalue) { FStr avalue = FStr_InitRefer(uvalue); @@ -1974,21 +1995,21 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, VarAssign_EvalShell(name, uvalue, scope, &avalue); else { if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name)) - return FALSE; + return false; /* Normal assignment -- just do it. */ Var_SetExpand(scope, name, uvalue); } - *out_TRUE_avalue = avalue; - return TRUE; + *out_true_avalue = avalue; + return true; } static void VarAssignSpecial(const char *name, const char *avalue) { if (strcmp(name, MAKEOVERRIDES) == 0) - Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ + Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */ else if (strcmp(name, ".CURDIR") == 0) { /* * Someone is being (too?) clever... @@ -2005,7 +2026,7 @@ VarAssignSpecial(const char *name, const char *avalue) /* Perform the variable variable assignment in the given scope. */ void -Parse_DoVar(VarAssign *var, GNode *scope) +Parse_Var(VarAssign *var, GNode *scope) { FStr avalue; /* actual value (maybe expanded) */ @@ -2023,7 +2044,7 @@ Parse_DoVar(VarAssign *var, GNode *scope) * See if the command possibly calls a sub-make by using the variable * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ -static Boolean +static bool MaybeSubMake(const char *cmd) { const char *start; @@ -2036,7 +2057,7 @@ MaybeSubMake(const char *cmd) 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; + return true; if (*p != '$') continue; @@ -2055,9 +2076,9 @@ MaybeSubMake(const char *cmd) if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E') if (p[4] == endc) - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -2119,7 +2140,7 @@ Parse_AddIncludeDir(const char *dir) * line options. */ static void -IncludeFile(char *file, Boolean isSystem, Boolean depinc, Boolean silent) +IncludeFile(char *file, bool isSystem, bool depinc, bool silent) { struct loadedfile *lf; char *fullname; /* full pathname of file */ @@ -2225,11 +2246,11 @@ IncludeFile(char *file, Boolean isSystem, Boolean depinc, Boolean silent) } static void -ParseDoInclude(char *directive) +ParseInclude(char *directive) { char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ - Boolean silent = directive[0] != 'i'; + bool silent = directive[0] != 'i'; char *file = directive + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ @@ -2281,25 +2302,24 @@ ParseDoInclude(char *directive) static void SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { - const char *slash, *dirname, *basename; - void *freeIt; + const char *slash, *basename; + FStr dirname; slash = strrchr(filename, '/'); if (slash == NULL) { - dirname = curdir; + dirname = FStr_InitRefer(curdir); basename = filename; - freeIt = NULL; } else { - dirname = freeIt = bmake_strsedup(filename, slash); + dirname = FStr_InitOwn(bmake_strsedup(filename, slash)); basename = slash + 1; } - Global_SetExpand(dirvar, dirname); + Global_SetExpand(dirvar, dirname.str); Global_SetExpand(filevar, basename); DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", - __func__, dirvar, dirname, filevar, basename); - free(freeIt); + __func__, dirvar, dirname.str, filevar, basename); + FStr_Done(&dirname); } /* @@ -2338,7 +2358,7 @@ ParseSetParseFile(const char *filename) } } -static Boolean +static bool StrContainsWord(const char *str, const char *word) { size_t strLen = strlen(str); @@ -2346,20 +2366,20 @@ StrContainsWord(const char *str, const char *word) const char *p, *end; if (strLen < wordLen) - return FALSE; /* str is too short to contain word */ + return false; /* str is too short to contain word */ end = str + strLen - wordLen; for (p = str; p != NULL; p = strchr(p, ' ')) { if (*p == ' ') p++; if (p > end) - return FALSE; /* cannot contain word */ + return false; /* cannot contain word */ if (memcmp(p, word, wordLen) == 0 && (p[wordLen] == '\0' || p[wordLen] == ' ')) - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -2368,11 +2388,11 @@ StrContainsWord(const char *str, const char *word) * * XXX: The paths in this list don't seem to be normalized in any way. */ -static Boolean +static bool VarContainsWord(const char *varname, const char *word) { FStr val = Var_Value(SCOPE_GLOBAL, varname); - Boolean found = val.str != NULL && StrContainsWord(val.str, word); + bool found = val.str != NULL && StrContainsWord(val.str, word); FStr_Done(&val); return found; } @@ -2404,7 +2424,7 @@ Parse_SetInput(const char *name, int lineno, int fd, IFile *curFile; char *buf; size_t len; - Boolean fromForLoop = name == NULL; + bool fromForLoop = name == NULL; if (fromForLoop) name = CurFile()->fname; @@ -2449,14 +2469,14 @@ Parse_SetInput(const char *name, int lineno, int fd, } /* Check if the directive is an include directive. */ -static Boolean -IsInclude(const char *dir, Boolean sysv) +static bool +IsInclude(const char *dir, bool sysv) { if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) dir++; if (strncmp(dir, "include", 7) != 0) - return FALSE; + return false; /* Space is not mandatory for BSD .include */ return !sysv || ch_isspace(dir[7]); @@ -2465,26 +2485,26 @@ IsInclude(const char *dir, Boolean sysv) #ifdef SYSVINCLUDE /* Check if the line is a SYSV include directive. */ -static Boolean +static bool IsSysVInclude(const char *line) { const char *p; - if (!IsInclude(line, TRUE)) - return FALSE; + if (!IsInclude(line, true)) + return false; /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { /* end of line -> it's a dependency */ if (*++p == '\0') - return FALSE; + return false; /* '::' operator or ': ' -> it's a dependency */ if (*p == ':' || ch_isspace(*p)) - return FALSE; + return false; } - return TRUE; + return true; } /* Push to another file. The line points to the word "include". */ @@ -2492,8 +2512,8 @@ static void ParseTraditionalInclude(char *line) { char *cp; /* current position in file spec */ - Boolean done = FALSE; - Boolean silent = line[0] != 'i'; + bool done = false; + bool silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); char *all_files; @@ -2521,9 +2541,9 @@ ParseTraditionalInclude(char *line) if (*cp != '\0') *cp = '\0'; else - done = TRUE; + done = true; - IncludeFile(file, FALSE, FALSE, silent); + IncludeFile(file, false, false, silent); } out: free(all_files); @@ -2569,10 +2589,10 @@ ParseGmakeExport(char *line) * 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 - * included file, FALSE if the main file has been parsed completely. + * true to continue parsing, i.e. it had only reached the end of an + * included file, false if the main file has been parsed completely. */ -static Boolean +static bool ParseEOF(void) { char *ptr; @@ -2589,7 +2609,7 @@ ParseEOF(void) curFile->buf_end = ptr == NULL ? NULL : ptr + len; curFile->lineno = curFile->first_lineno; if (ptr != NULL) - return TRUE; /* Iterate again */ + return true; /* Iterate again */ /* Ensure the makefile (or loop) didn't have mismatched conditionals */ Cond_restore_depth(curFile->cond_depth); @@ -2610,7 +2630,7 @@ ParseEOF(void) Global_Delete(".PARSEFILE"); Global_Delete(".INCLUDEDFROMDIR"); Global_Delete(".INCLUDEDFROMFILE"); - return FALSE; + return false; } curFile = CurFile(); @@ -2618,7 +2638,7 @@ ParseEOF(void) curFile->fname, curFile->lineno); ParseSetParseFile(curFile->fname); - return TRUE; + return true; } typedef enum ParseRawLineResult { @@ -2839,7 +2859,7 @@ ParseGetLine(GetLineMode mode) return line; } -static Boolean +static bool ParseSkippedBranches(void) { char *line; @@ -2854,7 +2874,7 @@ ParseSkippedBranches(void) * This check will probably duplicate some of * the code in ParseLine. Most of the code * there cannot apply, only ParseVarassign and - * ParseDependency can, and to prevent code + * ParseDependencyLine can, and to prevent code * duplication, these would need to be called * with a flag called onlyCheckSyntax. * @@ -2865,7 +2885,7 @@ ParseSkippedBranches(void) return line != NULL; } -static Boolean +static bool ParseForLoop(const char *line) { int rval; @@ -2873,9 +2893,9 @@ ParseForLoop(const char *line) rval = For_Eval(line); if (rval == 0) - return FALSE; /* Not a .for line */ + return false; /* Not a .for line */ if (rval < 0) - return TRUE; /* Syntax error - error printed, ignore line */ + return true; /* Syntax error - error printed, ignore line */ firstLineno = CurFile()->lineno; @@ -2891,7 +2911,7 @@ ParseForLoop(const char *line) For_Run(firstLineno); /* Stash each iteration as a new 'input file' */ - return TRUE; /* Read next line from for-loop buffer */ + return true; /* Read next line from for-loop buffer */ } /* @@ -2992,7 +3012,7 @@ ParseLine_ShellCommand(const char *p) } } -MAKE_INLINE Boolean +MAKE_INLINE bool IsDirective(const char *dir, size_t dirlen, const char *name) { return dirlen == strlen(name) && memcmp(dir, name, dirlen) == 0; @@ -3002,7 +3022,7 @@ IsDirective(const char *dir, size_t dirlen, const char *name) * See if the line starts with one of the known directives, and if so, handle * the directive. */ -static Boolean +static bool ParseDirective(char *line) { char *cp = line + 1; @@ -3010,9 +3030,9 @@ ParseDirective(char *line) size_t dirlen; pp_skip_whitespace(&cp); - if (IsInclude(cp, FALSE)) { - ParseDoInclude(cp); - return TRUE; + if (IsInclude(cp, false)) { + ParseInclude(cp); + return true; } dir = cp; @@ -3021,53 +3041,53 @@ ParseDirective(char *line) dirlen = (size_t)(cp - dir); if (*cp != '\0' && !ch_isspace(*cp)) - return FALSE; + return false; pp_skip_whitespace(&cp); arg = cp; if (IsDirective(dir, dirlen, "undef")) { Var_Undef(cp); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export")) { Var_Export(VEM_PLAIN, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export-env")) { Var_Export(VEM_ENV, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "export-literal")) { Var_Export(VEM_LITERAL, arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "unexport")) { - Var_UnExport(FALSE, arg); - return TRUE; + Var_UnExport(false, arg); + return true; } else if (IsDirective(dir, dirlen, "unexport-env")) { - Var_UnExport(TRUE, arg); - return TRUE; + Var_UnExport(true, arg); + return true; } else if (IsDirective(dir, dirlen, "info")) { ParseMessage(PARSE_INFO, "info", arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "warning")) { ParseMessage(PARSE_WARNING, "warning", arg); - return TRUE; + return true; } else if (IsDirective(dir, dirlen, "error")) { ParseMessage(PARSE_FATAL, "error", arg); - return TRUE; + return true; } - return FALSE; + return false; } -static Boolean +static bool ParseVarassign(const char *line) { VarAssign var; if (!Parse_IsVar(line, &var)) - return FALSE; + return false; FinishDependencyGroup(); - Parse_DoVar(&var, SCOPE_GLOBAL); - return TRUE; + Parse_Var(&var, SCOPE_GLOBAL); + return true; } static char * @@ -3092,13 +3112,13 @@ FindSemicolon(char *p) } /* - * dependency -> target... op [source...] + * dependency -> target... op [source...] [';' command] * op -> ':' | '::' | '!' */ static void -ParseDependency(char *line) +ParseDependencyLine(char *line) { - VarEvalFlags eflags; + VarEvalMode emode; char *expanded_line; const char *shellcmd = NULL; @@ -3147,8 +3167,8 @@ 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 = opts.strict ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR; - (void)Var_Subst(line, SCOPE_CMDLINE, eflags, &expanded_line); + emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; + (void)Var_Subst(line, SCOPE_CMDLINE, emode, &expanded_line); /* TODO: handle errors */ /* Need a fresh list for the target nodes */ @@ -3156,7 +3176,7 @@ ParseDependency(char *line) Lst_Free(targets); targets = Lst_New(); - ParseDoDependency(expanded_line); + ParseDependency(expanded_line); free(expanded_line); if (shellcmd != NULL) @@ -3207,7 +3227,7 @@ ParseLine(char *line) FinishDependencyGroup(); - ParseDependency(line); + ParseDependencyLine(line); } /* @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.81 2021/02/01 22:36:28 rillig Exp $ */ +/* $NetBSD: str.c,v 1.85 2021/05/30 21:16:54 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.81 2021/02/01 22:36:28 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.85 2021/05/30 21:16:54 rillig Exp $"); /* Return the concatenation of s1 and s2, freshly allocated. */ char * @@ -99,39 +99,23 @@ str_concat3(const char *s1, const char *s2, const char *s3) return result; } -/* Return the concatenation of s1, s2, s3 and s4, freshly allocated. */ -char * -str_concat4(const char *s1, const char *s2, const char *s3, const char *s4) -{ - size_t len1 = strlen(s1); - size_t len2 = strlen(s2); - size_t len3 = strlen(s3); - size_t len4 = strlen(s4); - char *result = bmake_malloc(len1 + len2 + len3 + len4 + 1); - memcpy(result, s1, len1); - memcpy(result + len1, s2, len2); - memcpy(result + len1 + len2, s3, len3); - memcpy(result + len1 + len2 + len3, s4, len4 + 1); - return result; -} - /* * Fracture a string into an array of words (as delineated by tabs or spaces) * taking quotation marks into account. * - * If expand is TRUE, quotes are removed and escape sequences such as \r, \t, + * If expand is true, quotes are removed and escape sequences such as \r, \t, * etc... are expanded. In this case, return NULL on parse errors. * * 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) +SubstringWords +Substring_Words(const char *str, bool expand) { size_t str_len; char *words_buf; size_t words_cap; - char **words; + Substring *words; size_t words_len; char inquote; char *word_start; @@ -146,7 +130,7 @@ Str_Words(const char *str, Boolean expand) words_buf = bmake_malloc(str_len + 1); words_cap = str_len / 5 > 50 ? str_len / 5 : 50; - words = bmake_malloc((words_cap + 1) * sizeof(char *)); + words = bmake_malloc((words_cap + 1) * sizeof(words[0])); /* * copy the string; at the same time, parse backslashes, @@ -205,17 +189,24 @@ Str_Words(const char *str, Boolean expand) *word_end++ = '\0'; if (words_len == words_cap) { size_t new_size; - words_cap *= 2; /* ramp up fast */ - new_size = (words_cap + 1) * sizeof(char *); + words_cap *= 2; + new_size = (words_cap + 1) * sizeof(words[0]); words = bmake_realloc(words, new_size); } - words[words_len++] = word_start; + words[words_len++] = + Substring_Init(word_start, word_end - 1); word_start = NULL; if (ch == '\n' || ch == '\0') { if (expand && inquote != '\0') { + SubstringWords res; + free(words); free(words_buf); - return (Words){ NULL, 0, NULL }; + + res.words = NULL; + res.len = 0; + res.freeIt = NULL; + return res; } goto done; } @@ -262,8 +253,40 @@ Str_Words(const char *str, Boolean expand) *word_end++ = ch; } done: - words[words_len] = NULL; /* useful for argv */ - return (Words){ words, words_len, words_buf }; + words[words_len] = Substring_Init(NULL, NULL); /* useful for argv */ + + { + SubstringWords result; + + result.words = words; + result.len = words_len; + result.freeIt = words_buf; + return result; + } +} + +Words +Str_Words(const char *str, bool expand) +{ + SubstringWords swords; + Words words; + size_t i; + + swords = Substring_Words(str, expand); + if (swords.words == NULL) { + words.words = NULL; + words.len = 0; + words.freeIt = NULL; + return words; + } + + words.words = bmake_malloc((swords.len + 1) * sizeof(words.words[0])); + words.len = swords.len; + words.freeIt = swords.freeIt; + for (i = 0; i < swords.len + 1; i++) + words.words[i] = UNCONST(swords.words[i].start); + free(swords.words); + return words; } /* @@ -272,7 +295,7 @@ done: * * XXX: this function does not detect or report malformed patterns. */ -Boolean +bool Str_Match(const char *str, const char *pat) { for (;;) { @@ -284,7 +307,7 @@ Str_Match(const char *str, const char *pat) if (*pat == '\0') return *str == '\0'; if (*str == '\0' && *pat != '*') - return FALSE; + return false; /* * A '*' in the pattern matches any substring. We handle this @@ -295,13 +318,13 @@ Str_Match(const char *str, const char *pat) while (*pat == '*') pat++; if (*pat == '\0') - return TRUE; + return true; while (*str != '\0') { if (Str_Match(str, pat)) - return TRUE; + return true; str++; } - return FALSE; + return false; } /* A '?' in the pattern matches any single character. */ @@ -315,14 +338,14 @@ Str_Match(const char *str, const char *pat) * character lists, the backslash is an ordinary character. */ if (*pat == '[') { - Boolean neg = pat[1] == '^'; + bool neg = pat[1] == '^'; pat += neg ? 2 : 1; for (;;) { if (*pat == ']' || *pat == '\0') { if (neg) break; - return FALSE; + return false; } /* * XXX: This naive comparison makes the @@ -347,7 +370,7 @@ Str_Match(const char *str, const char *pat) pat++; } if (neg && *pat != ']' && *pat != '\0') - return FALSE; + return false; while (*pat != ']' && *pat != '\0') pat++; if (*pat == '\0') @@ -362,11 +385,11 @@ Str_Match(const char *str, const char *pat) if (*pat == '\\') { pat++; if (*pat == '\0') - return FALSE; + return false; } if (*pat != *str) - return FALSE; + return false; thisCharOK: pat++; diff --git a/str.h b/str.h new file mode 100644 index 000000000000..ce0bb5ad82bc --- /dev/null +++ b/str.h @@ -0,0 +1,366 @@ +/* $NetBSD: str.h,v 1.9 2021/05/30 21:16:54 rillig Exp $ */ + +/* + Copyright (c) 2021 Roland Illig <rillig@NetBSD.org> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. + */ + + +/* + * Memory-efficient string handling. + */ + + +/* A read-only string that may need to be freed after use. */ +typedef struct FStr { + const char *str; + void *freeIt; +} FStr; + +/* A modifiable string that may need to be freed after use. */ +typedef struct MFStr { + char *str; + void *freeIt; +} MFStr; + +/* A read-only range of a character array, NOT null-terminated. */ +typedef struct Substring { + const char *start; + const char *end; +} Substring; + +/* + * Builds a string, only allocating memory if the string is different from the + * expected string. + */ +typedef struct LazyBuf { + char *data; + size_t len; + size_t cap; + const char *expected; + void *freeIt; +} LazyBuf; + +/* The result of splitting a string into words. */ +typedef struct Words { + char **words; + size_t len; + void *freeIt; +} Words; + +/* The result of splitting a string into words. */ +typedef struct SubstringWords { + Substring *words; + size_t len; + void *freeIt; +} SubstringWords; + + +MAKE_INLINE FStr +FStr_Init(const char *str, void *freeIt) +{ + FStr fstr; + fstr.str = str; + fstr.freeIt = freeIt; + return fstr; +} + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE FStr +FStr_InitOwn(char *str) +{ + return FStr_Init(str, str); +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE FStr +FStr_InitRefer(const char *str) +{ + return FStr_Init(str, NULL); +} + +MAKE_INLINE void +FStr_Done(FStr *fstr) +{ + free(fstr->freeIt); +#ifdef CLEANUP + fstr->str = NULL; + fstr->freeIt = NULL; +#endif +} + + +MAKE_INLINE MFStr +MFStr_Init(char *str, void *freeIt) +{ + MFStr mfstr; + mfstr.str = str; + mfstr.freeIt = freeIt; + return mfstr; +} + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE MFStr +MFStr_InitOwn(char *str) +{ + return MFStr_Init(str, str); +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE MFStr +MFStr_InitRefer(char *str) +{ + return MFStr_Init(str, NULL); +} + +MAKE_INLINE void +MFStr_Done(MFStr *mfstr) +{ + free(mfstr->freeIt); +#ifdef CLEANUP + mfstr->str = NULL; + mfstr->freeIt = NULL; +#endif +} + + +MAKE_STATIC Substring +Substring_Init(const char *start, const char *end) +{ + Substring sub; + + sub.start = start; + sub.end = end; + return sub; +} + +MAKE_INLINE Substring +Substring_InitStr(const char *str) +{ + return Substring_Init(str, str + strlen(str)); +} + +MAKE_STATIC size_t +Substring_Length(Substring sub) +{ + return (size_t)(sub.end - sub.start); +} + +MAKE_STATIC bool +Substring_IsEmpty(Substring sub) +{ + return sub.start == sub.end; +} + +MAKE_INLINE bool +Substring_Equals(Substring sub, const char *str) +{ + size_t len = strlen(str); + return Substring_Length(sub) == len && + memcmp(sub.start, str, len) == 0; +} + +MAKE_STATIC Substring +Substring_Sub(Substring sub, size_t start, size_t end) +{ + assert(start <= Substring_Length(sub)); + assert(end <= Substring_Length(sub)); + return Substring_Init(sub.start + start, sub.start + end); +} + +MAKE_STATIC bool +Substring_HasPrefix(Substring sub, Substring prefix) +{ + return Substring_Length(sub) >= Substring_Length(prefix) && + memcmp(sub.start, prefix.start, Substring_Length(prefix)) == 0; +} + +MAKE_STATIC bool +Substring_HasSuffix(Substring sub, Substring suffix) +{ + size_t suffixLen = Substring_Length(suffix); + return Substring_Length(sub) >= suffixLen && + memcmp(sub.end - suffixLen, suffix.start, suffixLen) == 0; +} + +/* Returns an independent, null-terminated copy of the substring. */ +MAKE_STATIC FStr +Substring_Str(Substring sub) +{ + if (Substring_IsEmpty(sub)) + return FStr_InitRefer(""); + return FStr_InitOwn(bmake_strsedup(sub.start, sub.end)); +} + +MAKE_STATIC const char * +Substring_SkipFirst(Substring sub, char ch) +{ + const char *p; + + for (p = sub.start; p != sub.end; p++) + if (*p == ch) + return p + 1; + return sub.start; +} + +MAKE_STATIC const char * +Substring_LastIndex(Substring sub, char ch) +{ + const char *p; + + for (p = sub.end; p != sub.start; p--) + if (p[-1] == ch) + return p - 1; + return NULL; +} + +MAKE_STATIC Substring +Substring_Dirname(Substring pathname) +{ + const char *p; + + for (p = pathname.end; p != pathname.start; p--) + if (p[-1] == '/') + return Substring_Init(pathname.start, p - 1); + return Substring_InitStr("."); +} + +MAKE_STATIC Substring +Substring_Basename(Substring pathname) +{ + const char *p; + + for (p = pathname.end; p != pathname.start; p--) + if (p[-1] == '/') + return Substring_Init(p, pathname.end); + return pathname; +} + + +MAKE_STATIC void +LazyBuf_Init(LazyBuf *buf, const char *expected) +{ + buf->data = NULL; + buf->len = 0; + buf->cap = 0; + buf->expected = expected; + buf->freeIt = NULL; +} + +MAKE_INLINE void +LazyBuf_Done(LazyBuf *buf) +{ + free(buf->freeIt); +} + +MAKE_STATIC void +LazyBuf_Add(LazyBuf *buf, char ch) +{ + + if (buf->data != NULL) { + if (buf->len == buf->cap) { + buf->cap *= 2; + buf->data = bmake_realloc(buf->data, buf->cap); + } + buf->data[buf->len++] = ch; + + } else if (ch == buf->expected[buf->len]) { + buf->len++; + return; + + } else { + buf->cap = buf->len + 16; + buf->data = bmake_malloc(buf->cap); + memcpy(buf->data, buf->expected, buf->len); + buf->data[buf->len++] = ch; + } +} + +MAKE_STATIC void +LazyBuf_AddStr(LazyBuf *buf, const char *str) +{ + const char *p; + + for (p = str; *p != '\0'; p++) + LazyBuf_Add(buf, *p); +} + +MAKE_STATIC void +LazyBuf_AddBytesBetween(LazyBuf *buf, const char *start, const char *end) +{ + const char *p; + + for (p = start; p != end; p++) + LazyBuf_Add(buf, *p); +} + +MAKE_INLINE void +LazyBuf_AddSubstring(LazyBuf *buf, Substring sub) +{ + LazyBuf_AddBytesBetween(buf, sub.start, sub.end); +} + +MAKE_STATIC Substring +LazyBuf_Get(const LazyBuf *buf) +{ + const char *start = buf->data != NULL ? buf->data : buf->expected; + return Substring_Init(start, start + buf->len); +} + +MAKE_STATIC FStr +LazyBuf_DoneGet(LazyBuf *buf) +{ + if (buf->data != NULL) { + LazyBuf_Add(buf, '\0'); + return FStr_InitOwn(buf->data); + } + return Substring_Str(LazyBuf_Get(buf)); +} + + +Words Str_Words(const char *, bool); + +MAKE_INLINE void +Words_Free(Words w) +{ + free(w.words); + free(w.freeIt); +} + + +SubstringWords Substring_Words(const char *, bool); + +MAKE_INLINE void +SubstringWords_Free(SubstringWords w) +{ + free(w.words); + free(w.freeIt); +} + + +char *str_concat2(const char *, const char *); +char *str_concat3(const char *, const char *, const char *); + +bool Str_Match(const char *, const char *); @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.345 2021/02/05 05:15:12 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.350 2021/04/04 10:05:08 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -77,7 +77,8 @@ * * Suff_End Clean up the module. * - * Suff_DoPaths Extend the search path of each suffix to include the + * Suff_ExtendPaths + * Extend the search path of each suffix to include the * default search path. * * Suff_ClearSuffixes @@ -114,7 +115,7 @@ #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.345 2021/02/05 05:15:12 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.350 2021/04/04 10:05:08 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; @@ -328,7 +329,7 @@ Suffix_TrimSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) suff->name, suff->nameLen); } -static Boolean +static bool Suffix_IsSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { return Suffix_TrimSuffix(suff, nameLen, nameEnd) != NULL; @@ -509,9 +510,9 @@ Suff_ClearSuffixes(void) * suffixes (the source ".c" and the target ".o"). If there are no such * suffixes, try a single-suffix transformation as well. * - * Return TRUE if the string is a valid transformation. + * Return true if the string is a valid transformation. */ -static Boolean +static bool ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) { SuffixListNode *ln; @@ -536,7 +537,7 @@ ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) if (targ != NULL) { *out_src = src; *out_targ = targ; - return TRUE; + return true; } } } @@ -554,17 +555,17 @@ ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) */ *out_src = single; *out_targ = nullSuff; - return TRUE; + return true; } - return FALSE; + return false; } /* - * Return TRUE if the given string is a transformation rule, that is, a + * Return true if the given string is a transformation rule, that is, a * concatenation of two known suffixes such as ".c.o" or a single suffix * such as ".o". */ -Boolean +bool Suff_IsTransform(const char *str) { Suffix *src, *targ; @@ -616,7 +617,7 @@ Suff_AddTransform(const char *name) { /* TODO: Avoid the redundant parsing here. */ - Boolean ok = ParseTransform(name, &srcSuff, &targSuff); + bool ok = ParseTransform(name, &srcSuff, &targSuff); assert(ok); (void)ok; } @@ -725,11 +726,11 @@ RebuildGraph(GNode *transform, Suffix *suff) * becomes the main target. * * Results: - * TRUE iff a new main target has been selected. + * true iff a new main target has been selected. */ -static Boolean +static bool UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, - Boolean *inout_removedMain) + bool *inout_removedMain) { Suffix *srcSuff, *targSuff; char *ptr; @@ -740,20 +741,20 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, *inout_main = target; Targ_SetMain(target); /* - * XXX: Why could it be a good idea to return TRUE here? + * XXX: Why could it be a good idea to return true here? * The main task of this function is to turn ordinary nodes * into transformations, no matter whether or not a new .MAIN * node has been found. */ /* - * XXX: Even when changing this to FALSE, none of the existing + * XXX: Even when changing this to false, none of the existing * unit tests fails. */ - return TRUE; + return true; } if (target->type == OP_TRANSFORM) - return FALSE; + return false; /* * XXX: What about a transformation ".cpp.c"? If ".c" is added as @@ -762,7 +763,7 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, */ ptr = strstr(target->name, suff->name); if (ptr == NULL) - return FALSE; + return false; /* * XXX: In suff-rebuild.mk, in the line '.SUFFIXES: .c .b .a', this @@ -773,14 +774,14 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, * amounts of memory. */ if (ptr == target->name) - return FALSE; + return false; if (ParseTransform(target->name, &srcSuff, &targSuff)) { if (*inout_main == target) { DEBUG1(MAKE, "Setting main node from \"%s\" back to null\n", target->name); - *inout_removedMain = TRUE; + *inout_removedMain = true; *inout_main = NULL; Targ_SetMain(NULL); } @@ -795,7 +796,7 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, srcSuff->name, targSuff->name); Relate(srcSuff, targSuff); } - return FALSE; + return false; } /* @@ -808,7 +809,7 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, static void UpdateTargets(GNode **inout_main, Suffix *suff) { - Boolean removedMain = FALSE; + bool removedMain = false; GNodeListNode *ln; for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { @@ -876,7 +877,7 @@ Suff_GetPath(const char *sname) * ".LIBS" and the flag is '-L'. */ void -Suff_DoPaths(void) +Suff_ExtendPaths(void) { SuffixListNode *ln; char *flags; @@ -1061,7 +1062,7 @@ CandidateList_AddCandidatesFor(CandidateList *list, Candidate *cand) * Free the first candidate in the list that is not referenced anymore. * Return whether a candidate was removed. */ -static Boolean +static bool RemoveCandidate(CandidateList *srcs) { CandidateListNode *ln; @@ -1097,7 +1098,7 @@ RemoveCandidate(CandidateList *srcs) Lst_Remove(srcs, ln); free(src->file); free(src); - return TRUE; + return true; } #ifdef DEBUG_SRC else { @@ -1108,7 +1109,7 @@ RemoveCandidate(CandidateList *srcs) #endif } - return FALSE; + return false; } /* Find the first existing file/target in srcs. */ @@ -1282,7 +1283,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) * add those nodes to the members list. * * Unfortunately, we can't use Str_Words because it doesn't understand about - * variable specifications with spaces in them. + * variable expressions with spaces in them. */ static void ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) @@ -1309,7 +1310,7 @@ ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) const char *nested_p = cp; FStr junk; - (void)Var_Parse(&nested_p, pgn, VARE_NONE, &junk); + (void)Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY, &junk); /* TODO: handle errors */ if (junk.str == var_Error) { Parse_Error(PARSE_FATAL, @@ -1380,7 +1381,7 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn) } DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); - (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp); + (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR, &cp); /* TODO: handle errors */ { @@ -1494,9 +1495,9 @@ Suff_FindPath(GNode *gn) * the sources for the transformation rule. * * Results: - * TRUE if successful, FALSE if not. + * true if successful, false if not. */ -static Boolean +static bool ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) { GNodeListNode *ln; @@ -1515,7 +1516,7 @@ ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ if (gn == NULL) - return FALSE; + return false; DEBUG3(SUFF, "\tapplying %s -> %s to \"%s\"\n", ssuff->name, tsuff->name, tgn->name); @@ -1540,7 +1541,7 @@ ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) */ Lst_Append(&sgn->implicitParents, tgn); - return TRUE; + return true; } /* @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.165 2021/02/04 21:42:46 rillig Exp $ */ +/* $NetBSD: targ.c,v 1.168 2021/04/03 12:01:00 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -93,7 +93,7 @@ * Targ_FindList Given a list of names, find nodes for all * of them, creating them as necessary. * - * Targ_Precious Return TRUE if the target is precious and + * Targ_Precious Return true if the target is precious and * should not be removed if we are interrupted. * * Targ_Propagate Propagate information between related nodes. @@ -113,7 +113,7 @@ #include "dir.h" /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: targ.c,v 1.165 2021/02/04 21:42:46 rillig Exp $"); +MAKE_RCSID("$NetBSD: targ.c,v 1.168 2021/04/03 12:01:00 rillig Exp $"); /* * All target nodes that appeared on the left-hand side of one of the @@ -246,7 +246,7 @@ GNode_Free(void *gnp) * SCOPE_GLOBAL), it should be safe to free the variables as well, * since each node manages the memory for all its variables itself. * - * XXX: The GNodes that are only used as variable scopes (VAR_CMD, + * XXX: The GNodes that are only used as variable scopes (SCOPE_CMD, * SCOPE_GLOBAL, SCOPE_INTERNAL) are not freed at all (see Var_End, * where they are not mentioned). These might be freed at all, if * their variable values are indeed not used anywhere else (see @@ -283,7 +283,7 @@ Targ_FindNode(const char *name) GNode * Targ_GetNode(const char *name) { - Boolean isNew; + bool isNew; HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew); if (!isNew) return HashEntry_Get(he); @@ -347,7 +347,7 @@ Targ_FindList(GNodeList *gns, StringList *names) } /* See if the given target is precious. */ -Boolean +bool Targ_Precious(const GNode *gn) { /* XXX: Why are '::' targets precious? */ @@ -410,7 +410,7 @@ Targ_FmtTime(time_t tm) static char buf[128]; struct tm *parts = localtime(&tm); - (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); + (void)strftime(buf, sizeof buf, "%H:%M:%S %b %d, %Y", parts); return buf; } diff --git a/unit-tests/Makefile b/unit-tests/Makefile index d649c552a03a..784223a56652 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.143 2021/02/06 18:31:30 sjg Exp $ +# $Id: Makefile,v 1.148 2021/06/16 19:18:56 sjg Exp $ # -# $NetBSD: Makefile,v 1.269 2021/02/06 18:26:03 sjg Exp $ +# $NetBSD: Makefile,v 1.279 2021/06/16 09:39:48 rillig Exp $ # # Unit tests for make(1) # @@ -203,7 +203,9 @@ TESTS+= impsrc TESTS+= include-main TESTS+= job-flags #TESTS+= job-output-long-lines +TESTS+= job-output-null TESTS+= jobs-empty-commands +TESTS+= jobs-empty-commands-error TESTS+= jobs-error-indirect TESTS+= jobs-error-nested TESTS+= jobs-error-nested-make @@ -228,6 +230,7 @@ TESTS+= opt-debug-curdir TESTS+= opt-debug-cond TESTS+= opt-debug-dir TESTS+= opt-debug-errors +TESTS+= opt-debug-errors-jobs TESTS+= opt-debug-file TESTS+= opt-debug-for TESTS+= opt-debug-graph1 @@ -321,6 +324,7 @@ TESTS+= var-class-env TESTS+= var-class-global TESTS+= var-class-local TESTS+= var-class-local-legacy +TESTS+= var-eval-short TESTS+= var-op TESTS+= var-op-append TESTS+= var-op-assign @@ -347,6 +351,7 @@ TESTS+= varmod-indirect TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop +TESTS+= varmod-loop-varname TESTS+= varmod-match TESTS+= varmod-match-escape TESTS+= varmod-no-match @@ -363,6 +368,7 @@ TESTS+= varmod-select-words TESTS+= varmod-shell TESTS+= varmod-subst TESTS+= varmod-subst-regex +TESTS+= varmod-sun-shell TESTS+= varmod-sysv TESTS+= varmod-tail TESTS+= varmod-to-abs @@ -484,6 +490,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.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} @@ -494,11 +501,12 @@ SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,' SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: <shell>,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: <shell>\) -q,\1,' +SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} -# For Compat_RunCommand, useShell == FALSE. +# For Compat_RunCommand, useShell == false. SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<not found: ...>,' -# For Compat_RunCommand, useShell == TRUE. +# For Compat_RunCommand, useShell == true. SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,<not found: \1>,' SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1<nonzero>,' SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj} @@ -509,8 +517,7 @@ SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1} SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell} SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,' SED_CMDS.vardebug+= -e 's,${.SHELL},</path/to/shell>,' -SED_CMDS.varmod-subst-regex+= \ - -e 's,\(Regex compilation error:\).*,\1 (details omitted),' +SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex} 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' @@ -590,6 +597,11 @@ STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,' +# The actual error messages for a failed regcomp or regexec differ between the +# implementations. +STD_SED_CMDS.regex= \ + -e 's,\(Regex compilation error:\).*,\1 (details omitted),' + # End of the configuration helpers section. .-include "Makefile.inc" @@ -639,8 +651,10 @@ _MKMSG_TEST= : .if ${.OBJDIR} != ${.CURDIR} # easy TMPDIR:= ${.OBJDIR}/tmp +.elif defined(TMPDIR) +TMPDIR:= ${TMPDIR}/uid${.MAKE.UID} .else -TMPDIR:= ${TMPDIR:U/tmp}/uid${.MAKE.UID} +TMPDIR:= /tmp/uid${.MAKE.UID} .endif # make sure it exists .if !exist(${TMPDIR}) diff --git a/unit-tests/archive.mk b/unit-tests/archive.mk index f8815cf40a40..2cd43a99e9ad 100644 --- a/unit-tests/archive.mk +++ b/unit-tests/archive.mk @@ -1,4 +1,4 @@ -# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $ +# $NetBSD: archive.mk,v 1.12 2021/04/09 14:42:00 christos Exp $ # # Very basic demonstration of handling archives, based on the description # in PSD.doc/tutorial.ms. @@ -8,11 +8,11 @@ # several other tests. ARCHIVE= libprog.a -FILES= archive.mk modmisc.mk varmisc.mk +FILES= archive.mk archive-suffix.mk modmisc.mk ternary.mk varmisc.mk all: .if ${.PARSEDIR:tA} != ${.CURDIR:tA} - @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} + @cd ${MAKEFILE:H} && cp ${FILES} ${.CURDIR} .endif # The following targets create and remove files. The filesystem cache in # dir.c would probably not handle this correctly, therefore each of the diff --git a/unit-tests/cmd-errors-jobs.exp b/unit-tests/cmd-errors-jobs.exp index 6d9c6bb7f890..9ed0557975b3 100644 --- a/unit-tests/cmd-errors-jobs.exp +++ b/unit-tests/cmd-errors-jobs.exp @@ -3,7 +3,7 @@ make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier eol : end eol exit status 0 diff --git a/unit-tests/cmd-errors-lint.exp b/unit-tests/cmd-errors-lint.exp index 09924c538de0..90b63bbcb08e 100644 --- a/unit-tests/cmd-errors-lint.exp +++ b/unit-tests/cmd-errors-lint.exp @@ -3,7 +3,7 @@ make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier : end exit status 2 diff --git a/unit-tests/cmd-errors.exp b/unit-tests/cmd-errors.exp index 6d9c6bb7f890..9ed0557975b3 100644 --- a/unit-tests/cmd-errors.exp +++ b/unit-tests/cmd-errors.exp @@ -3,7 +3,7 @@ make: Unclosed variable "UNCLOSED" : unclosed-variable make: Unclosed variable expression (expecting '}') for "UNCLOSED" : unclosed-modifier -make: Unknown modifier 'Z' +make: Unknown modifier "Z" : unknown-modifier eol : end eol exit status 0 diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index 5094924f1c8d..11a990cbbce1 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-empty.mk,v 1.11 2020/11/28 14:08:37 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.14 2021/04/11 13:35:56 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests a variable # expression for emptiness. @@ -42,7 +42,7 @@ WORD= word .endif # The :U modifier modifies expressions based on undefined variables -# (VAR_JUNK) by adding the VAR_KEEP flag, which marks the expression +# (DEF_UNDEF) by adding the DEF_DEFINED flag, which marks the expression # as "being interesting enough to be further processed". # .if empty(UNDEF:S,^$,value,W:Ufallback) @@ -93,8 +93,8 @@ WORD= word # neither leading nor trailing spaces are trimmed in the argument of the # function. If the spaces were trimmed, the variable name would be "" and # that variable is indeed undefined. Since ParseEmptyArg calls Var_Parse -# without VARE_UNDEFERR, the value of the undefined variable is returned as -# an empty string. +# without VARE_UNDEFERR, the value of the undefined variable is +# returned as an empty string. ${:U }= space .if empty( ) . error @@ -168,7 +168,7 @@ ${:U WORD }= variable name with spaces # parsing it, this unrealistic variable name should have done no harm. # # The variable expression was expanded though, and this was wrong. The -# expansion was done without the VARE_WANTRES flag (called VARF_WANTRES back +# expansion was done without VARE_WANTRES (called VARF_WANTRES back # then) though. This had the effect that the ${:U1} from the value of VARNAME # expanded to an empty string. This in turn created the seemingly recursive # definition VARNAME=${VARNAME}, and that definition was never meant to be diff --git a/unit-tests/cond-func-make-main.mk b/unit-tests/cond-func-make-main.mk index 31b370afabde..97b91f869991 100644 --- a/unit-tests/cond-func-make-main.mk +++ b/unit-tests/cond-func-make-main.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-make-main.mk,v 1.1 2020/11/22 19:37:27 rillig Exp $ +# $NetBSD: cond-func-make-main.mk,v 1.2 2021/04/04 10:13:09 rillig Exp $ # # Test how accurately the make() function in .if conditions reflects # what is actually made. @@ -33,7 +33,7 @@ first-main-target: # the line. This implies that several main targets can be set at the name # time, but they have to be in the same dependency group. # -# See ParseDoDependencyTargetSpecial, branch SP_MAIN. +# See ParseDependencyTargetSpecial, branch SP_MAIN. .MAIN: dot-main-target-1a dot-main-target-1b .if !make(dot-main-target-1a) @@ -47,7 +47,7 @@ dot-main-target-{1,2}{a,b}: : Making ${.TARGET}. # At this point, the list of targets to be made (opts.create) is not empty -# anymore. ParseDoDependencyTargetSpecial therefore treats the .MAIN as if +# anymore. ParseDependencyTargetSpecial therefore treats the .MAIN as if # it were an ordinary target. Since .MAIN is not listed as a dependency # anywhere, it is not made. .if target(.MAIN) diff --git a/unit-tests/cond-late.exp b/unit-tests/cond-late.exp index 46c4aa2f4230..e179e8c74cc4 100644 --- a/unit-tests/cond-late.exp +++ b/unit-tests/cond-late.exp @@ -1,4 +1,4 @@ -make: Bad conditional expression ` != "no"' in != "no"?: +make: Bad conditional expression ' != "no"' in ' != "no"?:' yes no exit status 0 diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk index 46c7ea26a97b..113c3fd08fed 100644 --- a/unit-tests/cond-short.mk +++ b/unit-tests/cond-short.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-short.mk,v 1.15 2020/12/01 19:37:23 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.16 2021/03/14 11:49:37 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. @@ -13,8 +13,11 @@ # parse them. They were still evaluated though, the only difference to # relevant variable expressions was that in the irrelevant variable # expressions, undefined variables were allowed. +# +# See also: +# var-eval-short.mk, for short-circuited variable modifiers -# The && operator. +# The && operator: .if 0 && ${echo "unexpected and" 1>&2 :L:sh} .endif @@ -86,7 +89,7 @@ VAR= # empty again, for the following tests . warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN} .endif -# The || operator. +# The || operator: .if 1 || ${echo "unexpected or" 1>&2 :L:sh} .endif @@ -208,9 +211,4 @@ x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo . 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-string.exp b/unit-tests/cond-token-string.exp index 07b318caa81a..45f9993457d3 100644 --- a/unit-tests/cond-token-string.exp +++ b/unit-tests/cond-token-string.exp @@ -1,4 +1,4 @@ -make: "cond-token-string.mk" line 13: Unknown modifier 'Z' +make: "cond-token-string.mk" line 13: Unknown modifier "Z" make: "cond-token-string.mk" line 13: Malformed conditional ("" != "${:Uvalue:Z}") make: "cond-token-string.mk" line 22: xvalue is not defined. make: "cond-token-string.mk" line 28: Malformed conditional (x${:Uvalue} == "") diff --git a/unit-tests/cond-token-var.mk b/unit-tests/cond-token-var.mk index 30eba87ad4d2..168c63c46ac1 100644 --- a/unit-tests/cond-token-var.mk +++ b/unit-tests/cond-token-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.6 2021/04/25 21:05:38 rillig Exp $ # # Tests for variable expressions in .if conditions. # @@ -46,3 +46,24 @@ DEF= defined # Since the expression is defined now, it doesn't generate any parse error. .if ${UNDEF:U} .endif + +# If the value of the variable expression is a number, it is compared against +# zero. +.if ${:U0} +. error +.endif +.if !${:U1} +. error +.endif + +# If the value of the variable expression is not a number, any non-empty +# value evaluates to true, even if there is only whitespace. +.if ${:U} +. error +.endif +.if !${:U } +. error +.endif +.if !${:Uanything} +. error +.endif diff --git a/unit-tests/cond1.exp b/unit-tests/cond1.exp index 0acd935780a0..8b65d782524d 100644 --- a/unit-tests/cond1.exp +++ b/unit-tests/cond1.exp @@ -17,7 +17,7 @@ Passed: 5 is prime make: String comparison operator must be either == or != -make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No +make: Bad conditional expression '"0" > 0' in '"0" > 0?OK:No' OK exit status 0 diff --git a/unit-tests/counter-append.mk b/unit-tests/counter-append.mk index 1c4e00d6118c..d234835e5ec3 100755 --- a/unit-tests/counter-append.mk +++ b/unit-tests/counter-append.mk @@ -1,4 +1,4 @@ -# $NetBSD: counter-append.mk,v 1.4 2020/10/17 16:57:17 rillig Exp $ +# $NetBSD: counter-append.mk,v 1.5 2021/04/04 10:13:09 rillig Exp $ # # Demonstrates how to let make count the number of times a variable # is actually accessed, using the ::+= variable modifier. @@ -15,7 +15,7 @@ COUNTER= # zero NEXT= ${COUNTER::+=a}${COUNTER:[#]} # This variable is first set to empty and then expanded. -# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +# See parse.c, function Parse_Var, keyword "!Var_Exists". A:= ${NEXT} B:= ${NEXT} C:= ${NEXT} diff --git a/unit-tests/counter.mk b/unit-tests/counter.mk index 3c75d7a5032a..7cf8fba72876 100644 --- a/unit-tests/counter.mk +++ b/unit-tests/counter.mk @@ -1,4 +1,4 @@ -# $NetBSD: counter.mk,v 1.5 2020/10/17 16:57:17 rillig Exp $ +# $NetBSD: counter.mk,v 1.6 2021/04/04 10:13:09 rillig Exp $ # # Demonstrates how to let make count the number of times a variable # is actually accessed, using the ::= variable modifier. @@ -15,7 +15,7 @@ COUNTER= # zero NEXT= ${COUNTER::=${COUNTER} a}${COUNTER:[#]} # This variable is first set to empty and then expanded. -# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +# See parse.c, function Parse_Var, keyword "!Var_Exists". A:= ${NEXT} B:= ${NEXT} C:= ${NEXT} diff --git a/unit-tests/dep-var.mk b/unit-tests/dep-var.mk index 438a8a84a60d..4503424e31ab 100755 --- a/unit-tests/dep-var.mk +++ b/unit-tests/dep-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-var.mk,v 1.5 2020/09/13 20:04:26 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.6 2021/04/04 10:13:09 rillig Exp $ # # Tests for variable references in dependency declarations. # @@ -79,7 +79,7 @@ all: $$$$) # undefined. # # Since 2020-09-13, this generates a parse error in lint mode (-dL), but not -# in normal mode since ParseDoDependency does not handle any errors after +# in normal mode since ParseDependency does not handle any errors after # calling Var_Parse. undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: @echo ${.TARGET:Q} diff --git a/unit-tests/deptgt-makeflags.exp b/unit-tests/deptgt-makeflags.exp index 7eb54eba7f30..11043bc5110c 100644 --- a/unit-tests/deptgt-makeflags.exp +++ b/unit-tests/deptgt-makeflags.exp @@ -1,10 +1,10 @@ Global:delete DOLLAR (not found) -Command:DOLLAR = $$$$ -Global:.MAKEOVERRIDES = VAR DOLLAR +Command: DOLLAR = $$$$ +Global: .MAKEOVERRIDES = VAR DOLLAR CondParser_Eval: ${DOLLAR} != "\$\$" -Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${DOLLAR} != "\$\$" (eval-defined) 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 +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-order.exp b/unit-tests/deptgt-order.exp index 39a9383953dd..5f7dde0ac69d 100644 --- a/unit-tests/deptgt-order.exp +++ b/unit-tests/deptgt-order.exp @@ -1 +1,4 @@ +: 'Making two out of one.' +: 'Making three out of two.' +: 'Making all out of three.' exit status 0 diff --git a/unit-tests/deptgt-order.mk b/unit-tests/deptgt-order.mk index 003552f57a49..f241331ae1e1 100644 --- a/unit-tests/deptgt-order.mk +++ b/unit-tests/deptgt-order.mk @@ -1,8 +1,18 @@ -# $NetBSD: deptgt-order.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: deptgt-order.mk,v 1.3 2021/06/17 15:25:33 rillig Exp $ # # Tests for the special target .ORDER in dependency declarations. -# TODO: Implementation +all one two three: .PHONY -all: - @:; +two: one + : 'Making $@ out of $>.' +three: two + : 'Making $@ out of $>.' + +# This .ORDER creates a circular dependency since 'three' depends on 'one' +# but 'one' is supposed to be built after 'three'. +.ORDER: three one + +# XXX: The circular dependency should be detected here. +all: three + : 'Making $@ out of $>.' diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp index b2aeaa5a2850..bdac2aee3e6c 100644 --- a/unit-tests/deptgt.exp +++ b/unit-tests/deptgt.exp @@ -1,14 +1,14 @@ 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) +ParseDependency(: empty-source) ParseReadLine (35): ' : command for empty targets list' ParseReadLine (36): ': empty-source' -ParseDoDependency(: empty-source) +ParseDependency(: empty-source) ParseReadLine (37): ' : command for empty targets list' ParseReadLine (38): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -make: "deptgt.mk" line 46: Unknown modifier 'Z' +ParseDependency(.MAKEFLAGS: -d0) +make: "deptgt.mk" line 46: Unknown modifier "Z" 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 09f381715e6d..15d7e59aeced 100644 --- a/unit-tests/deptgt.mk +++ b/unit-tests/deptgt.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt.mk,v 1.10 2020/12/27 18:20:26 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.11 2021/04/04 10:13:09 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. @@ -12,7 +12,7 @@ # The following lines demonstrate how 'targets' is set and reset during # parsing of dependencies. To see it in action, set breakpoints in: # -# ParseDoDependency at the beginning +# ParseDependency at the beginning # FinishDependencyGroup at "targets = NULL" # Parse_File at "Lst_Free(targets)" # Parse_File at "targets = Lst_New()" diff --git a/unit-tests/directive-export-impl.exp b/unit-tests/directive-export-impl.exp index 1a5cf34dbfb8..740daa605129 100644 --- a/unit-tests/directive-export-impl.exp +++ b/unit-tests/directive-export-impl.exp @@ -1,56 +1,56 @@ ParseReadLine (21): 'UT_VAR= <${REF}>' -Global:UT_VAR = <${REF}> +Global: UT_VAR = <${REF}> ParseReadLine (28): '.export UT_VAR' -Global:.MAKE.EXPORTED = UT_VAR +Global: .MAKE.EXPORTED = UT_VAR ParseReadLine (32): ': ${UT_VAR:N*}' -Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES -Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -Pattern[UT_VAR] for [<>] is [*] +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Evaluating modifier ${UT_VAR:N...} on value "<>" +Pattern for ':N' is "*" ModifyWords: split "<>" into 1 words -Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -ParseDoDependency(: ) +Result of ${UT_VAR:N*} is "" +ParseDependency(: ) CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>" -Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) +Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined) +Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none) -Var_Parse: ${UT_VAR} with VARE_WANTRES -Var_Parse: ${REF}> with VARE_WANTRES -Result of ${:!echo "\$UT_VAR"!} is "<>" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined) lhs = "<>", rhs = "<>", op = != -ParseReadLine (49): ': ${UT_VAR:N*}' -Var_Parse: ${UT_VAR:N*} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${REF}> with VARE_UNDEFERR|VARE_WANTRES -Applying ${UT_VAR:N...} to "<>" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -Pattern[UT_VAR] for [<>] is [*] +ParseReadLine (50): ': ${UT_VAR:N*}' +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Evaluating modifier ${UT_VAR:N...} on value "<>" +Pattern for ':N' is "*" ModifyWords: split "<>" into 1 words -Result of ${UT_VAR:N*} is "" (VARE_UNDEFERR|VARE_WANTRES, VAR_EXPORTED|VAR_REEXPORT, none) -ParseDoDependency(: ) -ParseReadLine (53): 'REF= defined' -Global:REF = defined +Result of ${UT_VAR:N*} is "" +ParseDependency(: ) +ParseReadLine (54): 'REF= defined' +Global: REF = defined CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<defined>" -Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:!...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) +Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" (eval-defined) +Evaluating modifier ${:!...} on value "" (eval-defined, undefined) Modifier part: "echo "$UT_VAR"" -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (VARE_WANTRES, none, none) -Var_Parse: ${UT_VAR} with VARE_WANTRES -Var_Parse: ${REF}> with VARE_WANTRES -Result of ${:!echo "\$UT_VAR"!} is "<defined>" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "<defined>" (eval-defined, defined) lhs = "<defined>", rhs = "<defined>", op = != -ParseReadLine (61): 'all:' -ParseDoDependency(all:) -Global:.ALLTARGETS = all -ParseReadLine (62): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -Global:.MAKEFLAGS = -r -k -d cpv -d -Global:.MAKEFLAGS = -r -k -d cpv -d 0 +ParseReadLine (62): 'all:' +ParseDependency(all:) +Global: .ALLTARGETS = all +ParseReadLine (63): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/unit-tests/directive-export-impl.mk b/unit-tests/directive-export-impl.mk index 556e5352d1c3..0ad290f653d4 100644 --- a/unit-tests/directive-export-impl.mk +++ b/unit-tests/directive-export-impl.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export-impl.mk,v 1.1 2020/12/29 01:45:06 rillig Exp $ +# $NetBSD: directive-export-impl.mk,v 1.3 2021/04/03 23:08:30 rillig Exp $ # # Test for the implementation of exporting variables to child processes. # This involves marking variables for export, actually exporting them, @@ -8,8 +8,8 @@ # Var_Export # ExportVar # VarExportedMode (global) -# VAR_EXPORTED (per variable) -# VAR_REEXPORT (per variable) +# VarFlags.exported (per variable) +# VarFlags.reexport (per variable) # VarExportMode (per call of Var_Export and ExportVar) : ${:U:sh} # side effect: initialize .SHELL @@ -22,13 +22,13 @@ UT_VAR= <${REF}> # At this point, ExportVar("UT_VAR", VEM_PLAIN) is called. Since the # variable value refers to another variable, ExportVar does not actually -# export the variable but only marks it as VAR_EXPORTED and VAR_REEXPORT. -# After that, ExportVars registers the variable name in .MAKE.EXPORTED. -# That's all for now. +# export the variable but only marks it as VarFlags.exported and +# VarFlags.reexport. After that, ExportVars registers the variable name in +# .MAKE.EXPORTED. That's all for now. .export UT_VAR -# Evaluating this expression shows the variable flags in the debug log, -# which are VAR_EXPORTED|VAR_REEXPORT. +# The following expression has both flags 'exported' and 'reexport' set. +# These flags do not show up anywhere, not even in the debug log. : ${UT_VAR:N*} # At the last moment before actually forking off the child process for the @@ -43,9 +43,10 @@ UT_VAR= <${REF}> . error .endif -# Evaluating this expression shows the variable flags in the debug log, -# which are still VAR_EXPORTED|VAR_REEXPORT, which means that the variable -# is still marked as being re-exported for each child process. +# The following expression still has 'exported' and 'reexport' set. +# These flags do not show up anywhere though, not even in the debug log. +# These flags means that the variable is still marked as being re-exported +# for each child process. : ${UT_VAR:N*} # Now the referenced variable gets defined. This does not influence anything diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk index 40fda0968cb0..942d4b371bbd 100644 --- a/unit-tests/directive-export.mk +++ b/unit-tests/directive-export.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export.mk,v 1.6 2020/12/13 01:07:54 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.8 2021/02/16 19:01:18 rillig Exp $ # # Tests for the .export directive. # @@ -28,8 +28,17 @@ VAR= value $$ ${INDIRECT} . error .endif -# No argument means to export all variables. +# No syntactical argument means to export all variables. .export +# An empty argument means no additional variables to export. +.export ${:U} + + +# Trigger the "This isn't going to end well" in ExportVarEnv. +EMPTY_SHELL= ${:sh} +.export EMPTY_SHELL # only marked for export at this point +_!= :;: # Force the variable to be actually exported. + + all: - @:; diff --git a/unit-tests/directive-for-errors.exp b/unit-tests/directive-for-errors.exp index 6088a93c9a4a..da5eee473ec2 100644 --- a/unit-tests/directive-for-errors.exp +++ b/unit-tests/directive-for-errors.exp @@ -13,7 +13,7 @@ make: "directive-for-errors.mk" line 53: Wrong number of words (5) in .for subst make: "directive-for-errors.mk" line 64: missing `in' in for make: "directive-for-errors.mk" line 66: warning: Should not be reached. make: "directive-for-errors.mk" line 67: for-less endfor -make: "directive-for-errors.mk" line 73: Unknown modifier 'Z' +make: "directive-for-errors.mk" line 73: Unknown modifier "Z" make: "directive-for-errors.mk" line 74: warning: Should not be reached. make: "directive-for-errors.mk" line 74: warning: Should not be reached. make: "directive-for-errors.mk" line 74: warning: Should not be reached. diff --git a/unit-tests/directive-for-errors.mk b/unit-tests/directive-for-errors.mk index 7890e2375af4..602ecbf32e4e 100644 --- a/unit-tests/directive-for-errors.mk +++ b/unit-tests/directive-for-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-errors.mk,v 1.1 2020/12/31 03:05:12 rillig Exp $ +# $NetBSD: directive-for-errors.mk,v 1.3 2021/04/04 10:13:09 rillig Exp $ # # Tests for error handling in .for loops. @@ -13,8 +13,8 @@ # XXX: The error message is misleading though. As of 2020-12-31, it says # "Unknown directive "for"", but that directive is actually known. This is # because ForEval does not detect the .for loop as such, so parsing -# continues in ParseLine > ParseDependency > ParseDoDependency > -# ParseDoDependencyTargets > ParseErrorNoDependency, and there the directive +# continues in ParseLine > ParseDependencyLine > ParseDependency > +# ParseDependencyTargets > ParseErrorNoDependency, and there the directive # name is parsed a bit differently. .for/i in 1 2 3 . warning ${i} diff --git a/unit-tests/directive-for-escape.exp b/unit-tests/directive-for-escape.exp index 89a8cbc2e229..59d4c2324f15 100644 --- a/unit-tests/directive-for-escape.exp +++ b/unit-tests/directive-for-escape.exp @@ -1,12 +1,12 @@ For: end for 1 For: loop body: . info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable specification (expecting '}') for "" (value "!"") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!"" make: "directive-for-escape.mk" line 19: !" For: end for 1 For: loop body: . info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable specification (expecting '}') for "" (value "!"\\") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\" make: "directive-for-escape.mk" line 29: !"\\ For: end for 1 For: loop body: @@ -37,19 +37,19 @@ make: "directive-for-escape.mk" line 55: end} For: end for 1 For: loop body: . info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end} -make: "directive-for-escape.mk" line 66: begin<fallback>end +make: "directive-for-escape.mk" line 67: begin<fallback>end For: end for 1 For: loop body: . info ${:U\$} -make: "directive-for-escape.mk" line 74: $ +make: "directive-for-escape.mk" line 75: $ For: end for 1 For: loop body: . info ${NUMBERS} ${:Ureplaced} -make: "directive-for-escape.mk" line 82: one two three replaced +make: "directive-for-escape.mk" line 83: one two three replaced For: end for 1 For: loop body: . info ${:Ureplaced} -make: "directive-for-escape.mk" line 92: replaced +make: "directive-for-escape.mk" line 93: replaced For: end for 1 For: loop body: . info . $$i: ${:Uinner} @@ -62,14 +62,14 @@ For: loop body: . info . $${i2}: ${i2} . info . $${i,}: ${i,} . info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner} -make: "directive-for-escape.mk" line 100: . $i: inner -make: "directive-for-escape.mk" line 101: . ${i}: inner -make: "directive-for-escape.mk" line 102: . ${i:M*}: inner -make: "directive-for-escape.mk" line 103: . $(i): inner -make: "directive-for-escape.mk" line 104: . $(i:M*): inner -make: "directive-for-escape.mk" line 105: . ${i${:U}}: outer -make: "directive-for-escape.mk" line 106: . ${i\}}: inner} -make: "directive-for-escape.mk" line 107: . ${i2}: two -make: "directive-for-escape.mk" line 108: . ${i,}: comma -make: "directive-for-escape.mk" line 109: . adjacent: innerinnerinnerinner +make: "directive-for-escape.mk" line 101: . $i: inner +make: "directive-for-escape.mk" line 102: . ${i}: inner +make: "directive-for-escape.mk" line 103: . ${i:M*}: inner +make: "directive-for-escape.mk" line 104: . $(i): inner +make: "directive-for-escape.mk" line 105: . $(i:M*): inner +make: "directive-for-escape.mk" line 106: . ${i${:U}}: outer +make: "directive-for-escape.mk" line 107: . ${i\}}: inner} +make: "directive-for-escape.mk" line 108: . ${i2}: two +make: "directive-for-escape.mk" line 109: . ${i,}: comma +make: "directive-for-escape.mk" line 110: . adjacent: innerinnerinnerinner exit status 0 diff --git a/unit-tests/directive-for-escape.mk b/unit-tests/directive-for-escape.mk index d61f05cc53cc..babc4b8c6e88 100644 --- a/unit-tests/directive-for-escape.mk +++ b/unit-tests/directive-for-escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-escape.mk,v 1.6 2021/01/25 19:05:39 rillig Exp $ +# $NetBSD: directive-for-escape.mk,v 1.7 2021/02/15 07:58:19 rillig Exp $ # # Test escaping of special characters in the iteration values of a .for loop. # These values get expanded later using the :U variable modifier, and this @@ -7,8 +7,8 @@ .MAKEFLAGS: -df -# Even though the .for loops takes quotes into account when splitting the -# string into words, the quotes don't need to be balances, as of 2020-12-31. +# Even though the .for loops take quotes into account when splitting the +# string into words, the quotes don't need to be balanced, as of 2020-12-31. # This could be considered a bug. ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ @@ -33,7 +33,7 @@ ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ # # XXX: It is unexpected that the variable V gets expanded in the loop body. # The double '$$' should prevent exactly this. Probably nobody was -# adventurous enough to use literal dollar signs in the values for a .for +# adventurous enough to use literal dollar signs in the values of a .for # loop. V= value VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) @@ -43,14 +43,14 @@ VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) # Try to cover the code for nested '{}' in for_var_len, without success. # -# The value of VALUES is not meant to be a variable expression. Instead, it -# is meant to represent dollar, lbrace, "UNDEF:U", backslash, dollar, -# backslash, dollar, space, nested braces, space, "end}". +# The value of the variable VALUES is not meant to be a variable expression. +# Instead, it is meant to represent literal text, the only escaping mechanism +# being that each '$' is written as '$$'. # # The .for loop splits ${VALUES} into 3 words, at the space characters, since # these are not escaped. VALUES= $${UNDEF:U\$$\$$ {{}} end} -# XXX: Where does the '\$$\$$' get converted into a single '\$'? +# XXX: Where in the code does the '\$\$' get converted into a single '\$'? .for i in ${VALUES} . info $i .endfor @@ -59,8 +59,9 @@ VALUES= $${UNDEF:U\$$\$$ {{}} end} # # XXX: It is wrong that for_var_len requires the braces to be balanced. # Each variable modifier has its own inconsistent way of parsing nested -# variable expressions, braces and parentheses. The only sensible thing -# to do is therefore to let Var_Parse do all the parsing work. +# variable expressions, braces and parentheses. (Compare ':M', ':S', and +# ':D' for details.) The only sensible thing to do is therefore to let +# Var_Parse do all the parsing work. VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end .for i in ${VALUES} . info $i diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp index bdaf4492baf0..4e882aad7b68 100755 --- a/unit-tests/directive-for.exp +++ b/unit-tests/directive-for.exp @@ -16,7 +16,7 @@ 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\" -make: "directive-for.mk" line 154: Unknown modifier 'Z' +make: "directive-for.mk" line 154: Unknown modifier "Z" make: "directive-for.mk" line 155: XXX: Not reached word1 make: "directive-for.mk" line 155: XXX: Not reached word3 make: Fatal errors encountered -- cannot continue diff --git a/unit-tests/directive-undef.exp b/unit-tests/directive-undef.exp index d64cb8b5afe0..56c871429397 100644 --- a/unit-tests/directive-undef.exp +++ b/unit-tests/directive-undef.exp @@ -1,5 +1,6 @@ make: "directive-undef.mk" line 29: The .undef directive requires an argument -make: "directive-undef.mk" line 86: Unknown modifier 'Z' +make: "directive-undef.mk" line 86: Unknown modifier "Z" +make: "directive-undef.mk" line 103: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. 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 b9a69f733517..41ea6b5bf8fa 100644 --- a/unit-tests/directive-undef.mk +++ b/unit-tests/directive-undef.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-undef.mk,v 1.9 2020/12/22 20:10:21 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.10 2021/02/16 18:02:19 rillig Exp $ # # Tests for the .undef directive. # @@ -86,5 +86,22 @@ ${DOLLAR}= dollar .undef ${VARNAMES:L:Z} +UT_EXPORTED= exported-value +.export UT_EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "exported-value" +. error +.endif +.if !${.MAKE.EXPORTED:MUT_EXPORTED} +. error +.endif +.undef UT_EXPORTED # XXX: does not update .MAKE.EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "not-exported" +. error +.endif +.if ${.MAKE.EXPORTED:MUT_EXPORTED} +. warning UT_EXPORTED is still listed in .MAKE.EXPORTED even though $\ + it is not exported anymore. +.endif + + all: - @:; diff --git a/unit-tests/directive-unexport-env.exp b/unit-tests/directive-unexport-env.exp index 677596ea4aa8..6d653e65fd32 100644 --- a/unit-tests/directive-unexport-env.exp +++ b/unit-tests/directive-unexport-env.exp @@ -1,18 +1,18 @@ make: "directive-unexport-env.mk" line 13: Unknown directive "unexport-en" make: "directive-unexport-env.mk" line 15: Unknown directive "unexport-environment" -Global:UT_EXPORTED = value -Global:UT_UNEXPORTED = value -Global:.MAKE.EXPORTED = UT_EXPORTED +Global: UT_EXPORTED = value +Global: UT_UNEXPORTED = value +Global: .MAKE.EXPORTED = UT_EXPORTED make: "directive-unexport-env.mk" line 21: The directive .unexport-env does not take arguments -Var_Parse: ${.MAKE.EXPORTED:O:u} with VARE_WANTRES -Applying ${.MAKE.EXPORTED:O} to "UT_EXPORTED" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" (VARE_WANTRES, none, none) -Applying ${.MAKE.EXPORTED:u} to "UT_EXPORTED" (VARE_WANTRES, none, none) -Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" (VARE_WANTRES, none, none) +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_EXPORTED" +Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" +Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_EXPORTED" +Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" Unexporting "UT_EXPORTED" Global:delete .MAKE.EXPORTED -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +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.exp b/unit-tests/directive.exp index b93d768169ab..ee866b7ee2b3 100644 --- a/unit-tests/directive.exp +++ b/unit-tests/directive.exp @@ -2,11 +2,11 @@ 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 +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 +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/include-main.exp b/unit-tests/include-main.exp index 61e716ad8ad7..c8a670a1c14a 100644 --- a/unit-tests/include-main.exp +++ b/unit-tests/include-main.exp @@ -9,7 +9,7 @@ make: "include-subsub.mk" line 5: subsub-ok in .for loop from include-sub.mk:29 in .include from include-main.mk:27 ParseReadLine (6): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) +ParseDependency(.MAKEFLAGS: -d0) make: "include-sub.mk" line 38: sub-after-ok make: "include-sub.mk" line 45: sub-after-for-ok make: "include-main.mk" line 30: main-after-ok diff --git a/unit-tests/job-output-null.exp b/unit-tests/job-output-null.exp new file mode 100644 index 000000000000..af9b4e64dba3 --- /dev/null +++ b/unit-tests/job-output-null.exp @@ -0,0 +1,4 @@ +hello +hello +hello world without newline, hello world without newline, hello world without newline. +exit status 0 diff --git a/unit-tests/job-output-null.mk b/unit-tests/job-output-null.mk new file mode 100644 index 000000000000..7620bdf6a7ba --- /dev/null +++ b/unit-tests/job-output-null.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-output-null.mk,v 1.1 2021/04/15 19:02:29 rillig Exp $ +# +# Test how null bytes in the output of a command are handled. Make processes +# them using null-terminated strings, which may cut off some of the output. +# +# As of 2021-04-15, make handles null bytes from the child process +# inconsistently. It's an edge case though since typically the child +# processes output text. + +.MAKEFLAGS: -j1 # force jobs mode + +all: .PHONY + # The null byte from the command output is kept as-is. + # See CollectOutput, which looks like it intended to replace these + # null bytes with simple spaces. + @printf 'hello\0world%s\n' '' + + # Give the parent process a chance to see the above output, but not + # yet the output from the next printf command. + @sleep 1 + + # All null bytes from the command output are kept as-is. + @printf 'hello\0world%s\n' '' '' '' '' '' '' + + @sleep 1 + + # The null bytes are replaced with spaces since they are not followed + # by a newline. + # + # The three null bytes in a row test whether this output is + # compressed to a single space like in DebugFailedTarget. It isn't. + @printf 'hello\0world\0without\0\0\0newline%s' ', ' ', ' '.' diff --git a/unit-tests/jobs-empty-commands-error.exp b/unit-tests/jobs-empty-commands-error.exp new file mode 100644 index 000000000000..1639425d9013 --- /dev/null +++ b/unit-tests/jobs-empty-commands-error.exp @@ -0,0 +1,5 @@ +: 'Making existing-target out of nothing.' +make: don't know how to make nonexistent-target (continuing) + +make: stopped in unit-tests +exit status 2 diff --git a/unit-tests/jobs-empty-commands-error.mk b/unit-tests/jobs-empty-commands-error.mk new file mode 100644 index 000000000000..b9ba4403078e --- /dev/null +++ b/unit-tests/jobs-empty-commands-error.mk @@ -0,0 +1,19 @@ +# $NetBSD: jobs-empty-commands-error.mk,v 1.1 2021/06/16 09:39:48 rillig Exp $ +# +# In jobs mode, the shell commands for creating a target are written to a +# temporary file first, which is then run by the shell. In chains of +# dependencies, these files would end up empty. Since job.c 1.399 from +# 2021-01-29, these empty files are no longer created. +# +# After 2021-01-29, before job.c 1.435 2021-06-16, targets that could not be +# made led to longer error messages than necessary. + +.MAKEFLAGS: -j1 + +all: existing-target + +existing-target: + : 'Making $@ out of nothing.' + +all: nonexistent-target + : 'Not reached' diff --git a/unit-tests/moderrs.exp b/unit-tests/moderrs.exp index 0ca1aa2aedd5..9d8bd308c36c 100644 --- a/unit-tests/moderrs.exp +++ b/unit-tests/moderrs.exp @@ -1,143 +1,136 @@ mod-unknown-direct: want: Unknown modifier 'Z' -make: Unknown modifier 'Z' +make: Unknown modifier "Z" VAR:Z=before--after mod-unknown-indirect: want: Unknown modifier 'Z' -make: Unknown modifier 'Z' +make: Unknown modifier "Z" VAR:Z=before-inner}-after unclosed-direct: -want: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S -make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S +want: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" +make: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" VAR:S,V,v,=Thevariable unclosed-indirect: -want: Unclosed variable specification after complex modifier (expecting '}') for VAR -make: Unclosed variable specification after complex modifier (expecting '}') for VAR +want: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" +make: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" VAR:S,V,v,=Thevariable unfinished-indirect: want: Unfinished modifier for VAR (',' missing) -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) VAR:S,V,v= unfinished-loop: want: Unfinished modifier for UNDEF ('@' missing) -make: Unfinished modifier for UNDEF ('@' missing) +make: Unfinished modifier for "UNDEF" ('@' missing) want: Unfinished modifier for UNDEF ('@' missing) -make: Unfinished modifier for UNDEF ('@' missing) +make: Unfinished modifier for "UNDEF" ('@' missing) 1 2 3 loop-close: -make: Unclosed variable specification (expecting '}') for "UNDEF" (value "1}... 2}... 3}...") modifier @ +make: Unclosed variable expression, expecting '}' for modifier "@var@${var}}...@" of variable "UNDEF" with value "1}... 2}... 3}..." 1}... 2}... 3}... 1}... 2}... 3}... words: want: Unfinished modifier for UNDEF (']' missing) -make: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) want: Unfinished modifier for UNDEF (']' missing) -make: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) 13= -make: Bad modifier `:[123451234512345123451234512345]' for UNDEF +make: Bad modifier ":[123451234512345123451234512345]" for variable "UNDEF" 12345=S,^ok,:S,^3ok,} exclam: want: Unfinished modifier for VARNAME ('!' missing) -make: Unfinished modifier for VARNAME ('!' missing) +make: Unfinished modifier for "VARNAME" ('!' missing) want: Unfinished modifier for ! ('!' missing) -make: Unfinished modifier for ! ('!' missing) +make: Unfinished modifier for "!" ('!' missing) mod-subst-delimiter: -make: Missing delimiter for :S modifier +make: Missing delimiter for modifier ':S' 1: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 2: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 3: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 4: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier S +make: Unclosed variable expression, expecting '}' for modifier "S,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable mod-regex-delimiter: make: Missing delimiter for :C modifier 1: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 2: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 3: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 4: -make: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable specification (expecting '}') for "VAR" (value "TheVariable") modifier C +make: Unclosed variable expression, expecting '}' for modifier "C,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable -mod-regex-undefined-subexpression: -one one 2 3 5 8 one3 2one 34 -make: No match for subexpression \2 -make: No match for subexpression \2 -make: No match for subexpression \1 -make: No match for subexpression \2 -make: No match for subexpression \1 -()+() ()+() ()+() 3 5 8 (3)+() ()+(1) 34 - mod-ts-parse: 112358132134 15152535558513521534 -make: Bad modifier `:ts\65oct' for FIB +make: Bad modifier ":ts\65oct" for variable "FIB" +65oct} +make: Bad modifier ":ts\65oct" for variable "" 65oct} -make: Bad modifier `:tsxy' for FIB +make: Bad modifier ":tsxy" for variable "FIB" xy} mod-t-parse: -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" -make: Bad modifier `:txy' for FIB +make: Bad modifier ":txy" for variable "FIB" y} -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" -make: Bad modifier `:t' for FIB +make: Bad modifier ":t" for variable "FIB" M*} mod-ifelse-parse: -make: Unfinished modifier for FIB (':' missing) +make: Unfinished modifier for "FIB" (':' missing) -make: Unfinished modifier for FIB (':' missing) +make: Unfinished modifier for "FIB" (':' missing) -make: Unfinished modifier for FIB ('}' missing) +make: Unfinished modifier for "FIB" ('}' missing) -make: Unfinished modifier for FIB ('}' missing) +make: Unfinished modifier for "FIB" ('}' missing) then mod-remember-parse: 1 1 2 3 5 8 13 21 34 -make: Unknown modifier '_' +make: Unknown modifier "__" mod-sysv-parse: -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3" +make: Unclosed variable expression, expecting '}' for modifier "3" of variable "FIB" with value "" -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3=" +make: Unclosed variable expression, expecting '}' for modifier "3=" of variable "FIB" with value "" -make: Unknown modifier '3' -make: Unclosed variable specification (expecting '}') for "FIB" (value "") modifier 3 +make: Unknown modifier "3=x3" +make: Unclosed variable expression, expecting '}' for modifier "3=x3" of variable "FIB" with value "" 1 1 2 x3 5 8 1x3 21 34 diff --git a/unit-tests/moderrs.mk b/unit-tests/moderrs.mk index 8fdcb496ee29..ffd920314c5d 100644 --- a/unit-tests/moderrs.mk +++ b/unit-tests/moderrs.mk @@ -1,4 +1,4 @@ -# $NetBSD: moderrs.mk,v 1.25 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.30 2021/06/21 08:28:37 rillig Exp $ # # various modifier error tests @@ -19,7 +19,6 @@ all: words all: exclam all: mod-subst-delimiter all: mod-regex-delimiter -all: mod-regex-undefined-subexpression all: mod-ts-parse all: mod-t-parse all: mod-ifelse-parse @@ -35,11 +34,11 @@ mod-unknown-indirect: print-header print-footer @echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after' unclosed-direct: print-header print-footer - @echo 'want: Unclosed variable specification (expecting $'}$') for "VAR" (value "Thevariable") modifier S' + @echo 'want: Unclosed variable expression, expecting $'}$' for modifier "S,V,v," of variable "VAR" with value "Thevariable"' @echo VAR:S,V,v,=${VAR:S,V,v, unclosed-indirect: print-header print-footer - @echo 'want: Unclosed variable specification after complex modifier (expecting $'}$') for VAR' + @echo 'want: Unclosed variable expression after indirect modifier, expecting $'}$' for variable "VAR"' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} unfinished-indirect: print-header print-footer @@ -119,27 +118,11 @@ mod-regex-delimiter: print-header print-footer @echo 6: ${VAR:C,from,to, @echo 7: ${VAR:C,from,to,} -# In regular expressions with alternatives, not all capturing groups are -# 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 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 -# syntactical ambiguity since the :S and :C modifiers are open-ended (see -# mod-subst-chain). Luckily the modifier :U does not make sense after :C, -# therefore this case does not happen in practice. -# The sub-modifier for the :S and :C modifiers would have to be chosen -# wisely, to not create ambiguities while parsing. -mod-regex-undefined-subexpression: print-header print-footer - @echo ${FIB:C,1(.*),one\1,} # all ok - @echo ${FIB:C,1(.*)|2(.*),(\1)+(\2),:Q} # no match for subexpression - mod-ts-parse: print-header print-footer @echo ${FIB:ts} @echo ${FIB:ts\65} # octal 065 == U+0035 == '5' @echo ${FIB:ts\65oct} # bad modifier + @echo ${:U${FIB}:ts\65oct} # bad modifier, variable name is "" @echo ${FIB:tsxy} # modifier too long mod-t-parse: print-header print-footer diff --git a/unit-tests/modts.exp b/unit-tests/modts.exp index 5db79fc96586..18837016add4 100644 --- a/unit-tests/modts.exp +++ b/unit-tests/modts.exp @@ -1,6 +1,6 @@ -make: Bad modifier `:tx' for LIST +make: Bad modifier ":tx" for variable "LIST" LIST:tx="}" -make: Bad modifier `:ts\X' for LIST +make: Bad modifier ":ts\X" for variable "LIST" LIST:ts/x:tu="\X:tu}" FU_mod-ts="a/b/cool" FU_mod-ts:ts:T="cool" == cool? diff --git a/unit-tests/modword.exp b/unit-tests/modword.exp index 9fd7f1b494fe..02e9974c02d6 100644 --- a/unit-tests/modword.exp +++ b/unit-tests/modword.exp @@ -1,4 +1,4 @@ -make: Bad modifier `:[]' for LIST +make: Bad modifier ":[]" for variable "LIST" LIST:[]="" is an error LIST:[0]="one two three four five six" LIST:[0x0]="one two three four five six" @@ -37,17 +37,17 @@ REALLYSPACE=" " REALLYSPACE:[1]="" == "" ? REALLYSPACE:[*]:[1]=" " == " " ? LIST:[1]="one" -make: Bad modifier `:[1.]' for LIST +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1].' for LIST +make: Bad modifier ":[1]." for variable "LIST" LIST:[1].="}" is an error LIST:[2]="two" LIST:[6]="six" LIST:[7]="" LIST:[999]="" -make: Bad modifier `:[-]' for LIST +make: Bad modifier ":[-]" for variable "LIST" LIST:[-]="" is an error -make: Bad modifier `:[--]' for LIST +make: Bad modifier ":[--]" for variable "LIST" LIST:[--]="" is an error LIST:[-1]="six" LIST:[-2]="five" @@ -67,20 +67,22 @@ LIST:[*]:C/ /,/:[2]="" LIST:[*]:C/ /,/:[*]:[2]="" LIST:[*]:C/ /,/:[@]:[2]="three" LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" -make: Bad modifier `:[1.]' for LIST +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1..]' for LIST +make: Bad modifier ":[1..]" for variable "LIST" LIST:[1..]="" is an error +make: Bad modifier ":[1.. ]" for variable "LIST" +LIST:[1.. ]="" is an error LIST:[1..1]="one" -make: Bad modifier `:[1..1.]' for LIST +make: Bad modifier ":[1..1.]" for variable "LIST" LIST:[1..1.]="" is an error LIST:[1..2]="one two" LIST:[2..1]="two one" LIST:[3..-2]="three four five" LIST:[-4..4]="three four" -make: Bad modifier `:[0..1]' for LIST +make: Bad modifier ":[0..1]" for variable "LIST" LIST:[0..1]="" is an error -make: Bad modifier `:[-1..0]' for LIST +make: Bad modifier ":[-1..0]" for variable "LIST" LIST:[-1..0]="" is an error LIST:[-1..1]="six five four three two one" LIST:[0..0]="one two three four five six" @@ -95,7 +97,7 @@ LIST:[${ONE}]="one" LIST:[${MINUSONE}]="six" LIST:[${STAR}]="one two three four five six" LIST:[${AT}]="one two three four five six" -make: Bad modifier `:[${EMPTY' for LIST +make: Bad modifier ":[${EMPTY" for variable "LIST" LIST:[${EMPTY}]="" is an error LIST:[${LONGLIST:[21]:S/2//}]="one" LIST:[${LIST:[#]}]="six" diff --git a/unit-tests/modword.mk b/unit-tests/modword.mk index 383c9dca975b..95bb1fec78c3 100644 --- a/unit-tests/modword.mk +++ b/unit-tests/modword.mk @@ -1,4 +1,4 @@ -# $NetBSD: modword.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: modword.mk,v 1.6 2021/03/14 16:00:07 rillig Exp $ # # Test behaviour of new :[] modifier # TODO: When was this modifier new? @@ -99,6 +99,7 @@ mod-squarebrackets-n: mod-squarebrackets-start-end: @echo 'LIST:[1.]="${LIST:[1.]}" is an error' @echo 'LIST:[1..]="${LIST:[1..]}" is an error' + @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error' @echo 'LIST:[1..1]="${LIST:[1..1]}"' @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' @echo 'LIST:[1..2]="${LIST:[1..2]}"' diff --git a/unit-tests/opt-chdir.mk b/unit-tests/opt-chdir.mk index 20241f02740e..a8806149f31c 100644 --- a/unit-tests/opt-chdir.mk +++ b/unit-tests/opt-chdir.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $ +# $NetBSD: opt-chdir.mk,v 1.6 2021/05/18 17:05:45 sjg Exp $ # # Tests for the -C command line option, which changes the directory at the # beginning. @@ -23,5 +23,7 @@ chdir-root: .PHONY .IGNORE @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -C / -V 'cwd: $${.CURDIR}' # Trying to change to a nonexistent directory exits immediately. +# Note: just because the whole point of /nonexistent is that it should +# not exist - doesn't mean it doesn't. chdir-nonexistent: .PHONY .IGNORE - @${MAKE} -C /nonexistent + @${MAKE} -C /nonexistent.${.MAKE.PID} diff --git a/unit-tests/opt-debug-errors-jobs.exp b/unit-tests/opt-debug-errors-jobs.exp new file mode 100644 index 000000000000..25eb2b470b72 --- /dev/null +++ b/unit-tests/opt-debug-errors-jobs.exp @@ -0,0 +1,48 @@ +echo '3 spaces'; false +3 spaces + +*** Failed target: fail-spaces +*** Failed commands: + echo '3 spaces'; false +*** [fail-spaces] Error code 1 + +make: stopped in unit-tests +echo \ indented; false + indented + +*** Failed target: fail-escaped-space +*** Failed commands: + echo \ indented; false +*** [fail-escaped-space] Error code 1 + +make: stopped in unit-tests +echo 'line1 +line2'; false +line1 +line2 + +*** Failed target: fail-newline +*** Failed commands: + echo 'line1${.newline}line2'; false +*** [fail-newline] Error code 1 + +make: stopped in unit-tests +echo 'line1 line2'; false +line1 line2 + +*** Failed target: fail-multiline +*** Failed commands: + echo 'line1 line2'; false +*** [fail-multiline] Error code 1 + +make: stopped in unit-tests +echo 'word1' 'word2'; false +word1 word2 + +*** Failed target: fail-multiline-intention +*** Failed commands: + echo 'word1' 'word2'; false +*** [fail-multiline-intention] Error code 1 + +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/opt-debug-errors-jobs.mk b/unit-tests/opt-debug-errors-jobs.mk new file mode 100644 index 000000000000..83b50987a752 --- /dev/null +++ b/unit-tests/opt-debug-errors-jobs.mk @@ -0,0 +1,36 @@ +# $NetBSD: opt-debug-errors-jobs.mk,v 1.1 2021/04/27 16:20:06 rillig Exp $ +# +# Tests for the -de command line option, which adds debug logging for +# failed commands and targets; since 2021-04-27 also in jobs mode. + +.MAKEFLAGS: -de -j1 + +all: fail-spaces +all: fail-escaped-space +all: fail-newline +all: fail-multiline +all: fail-multiline-intention + +fail-spaces: + echo '3 spaces'; false + +fail-escaped-space: + echo \ indented; false + +fail-newline: + echo 'line1${.newline}line2'; false + +# The line continuations in multiline commands are turned into an ordinary +# space before the command is actually run. +fail-multiline: + echo 'line1\ + line2'; false + +# It is a common style to align the continuation backslashes at the right +# of the lines, usually at column 73. All spaces before the continuation +# backslash are preserved and are usually outside a shell word and thus +# irrelevant. Since "usually" is not "always", these space characters are +# not merged into a single space. +fail-multiline-intention: + echo 'word1' \ + 'word2'; false diff --git a/unit-tests/opt-debug-lint.exp b/unit-tests/opt-debug-lint.exp index f2123f20e37f..05b341b30dae 100644 --- a/unit-tests/opt-debug-lint.exp +++ b/unit-tests/opt-debug-lint.exp @@ -2,7 +2,7 @@ make: "opt-debug-lint.mk" line 19: Variable "X" is undefined make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L" make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P" -make: "opt-debug-lint.mk" line 69: Unknown modifier '$' +make: "opt-debug-lint.mk" line 69: Unknown modifier "${" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/opt-debug-lint.mk b/unit-tests/opt-debug-lint.mk index bb1b38feb717..155e1a3de3be 100644 --- a/unit-tests/opt-debug-lint.mk +++ b/unit-tests/opt-debug-lint.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-lint.mk,v 1.12 2020/12/20 19:10:53 rillig Exp $ +# $NetBSD: opt-debug-lint.mk,v 1.14 2021/03/14 10:57:12 rillig Exp $ # # Tests for the -dL command line option, which runs additional checks # to catch common mistakes, such as unclosed variable expressions. @@ -77,5 +77,19 @@ ${UNDEF}: ${UNDEF} . error .endif -all: - @:; +# In lint mode, the whole variable text is evaluated to check for unclosed +# expressions and unknown operators. During this check, the subexpression +# '${:U2}' is not expanded, instead it is copied verbatim into the regular +# expression, leading to '.*=.{1,${:U2}}$'. +# +# Before var.c 1.856 from 2021-03-14, this regular expression was then +# compiled even though that was not necessary for checking the syntax at the +# level of variable expressions. The unexpanded '$' then resulted in a wrong +# error message. +# +# This only happened in lint mode since in default mode the early check for +# unclosed expressions and unknown modifiers is skipped. +# +# See VarCheckSyntax, ApplyModifier_Regex. +# +VARMOD_REGEX= ${:UA=111 B=222 C=33:C/.*=.{1,${:U2}}$//g} diff --git a/unit-tests/opt-debug.exp b/unit-tests/opt-debug.exp index 52a36c71b4ee..6a5f7b4cb3e7 100644 --- a/unit-tests/opt-debug.exp +++ b/unit-tests/opt-debug.exp @@ -1,4 +1,4 @@ -Global:VAR = value -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Global: VAR = value +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/opt-file.mk b/unit-tests/opt-file.mk index 3ab8ef4e3c7d..b7a1c09e6d16 100644 --- a/unit-tests/opt-file.mk +++ b/unit-tests/opt-file.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-file.mk,v 1.11 2020/12/22 08:57:23 rillig Exp $ +# $NetBSD: opt-file.mk,v 1.12 2021/04/04 10:13:09 rillig Exp $ # # Tests for the -f command line option. @@ -28,10 +28,10 @@ all: file-containing-null-byte # ParseReadLine (1): 'VAR=value\<A5><A5><A5><A5><A5><A5>' # Global:VAR = value\<A5><A5><A5><A5><A5><A5>value\<A5><A5><A5><A5><A5><A5> # ParseReadLine (2): 'alue\<A5><A5><A5><A5><A5><A5>' -# ParseDoDependency(alue\<A5><A5><A5><A5><A5><A5>) +# ParseDependency(alue\<A5><A5><A5><A5><A5><A5>) # make-2014.01.01.00.00.00: "(stdin)" line 2: Need an operator # ParseReadLine (3): '<A5><A5><A5>ZZZZZZZZZZZZZZZZ' -# ParseDoDependency(<A5><A5><A5>ZZZZZZZZZZZZZZZZ) +# ParseDependency(<A5><A5><A5>ZZZZZZZZZZZZZZZZ) # file-ending-in-backslash: .PHONY @printf '%s' 'VAR=value\' \ diff --git a/unit-tests/opt-jobs-no-action.mk b/unit-tests/opt-jobs-no-action.mk index a75fc38cf2fa..19d82c5bf4b8 100644 --- a/unit-tests/opt-jobs-no-action.mk +++ b/unit-tests/opt-jobs-no-action.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-jobs-no-action.mk,v 1.8 2020/12/10 23:54:41 rillig Exp $ +# $NetBSD: opt-jobs-no-action.mk,v 1.9 2021/04/04 09:58:51 rillig Exp $ # # Tests for the combination of the options -j and -n, which prints the # commands instead of actually running them. @@ -23,7 +23,7 @@ # this is handled by the [0] != '\0' checks in Job_ParseShell. # The '\#' is handled by ParseGetLine. # The '\n' is handled by Str_Words in Job_ParseShell. -# The '$$' is handled by Var_Subst in ParseDependency. +# The '$$' is handled by Var_Subst in ParseDependencyLine. .SHELL: \ name=sh \ path=${.SHELL} \ diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk index 73a8409fe030..5265cec59a2d 100644 --- a/unit-tests/recursive.mk +++ b/unit-tests/recursive.mk @@ -1,12 +1,12 @@ -# $NetBSD: recursive.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# $NetBSD: recursive.mk,v 1.5 2021/03/15 12:15:03 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 # is fatal and exits immediately. # # The purpose of evaluating that variable early was just to detect -# whether there are unclosed variables. It might be enough to parse the -# variable value without VARE_WANTRES for that purpose. +# whether there are unclosed variables. The variable value is therefore +# parsed with VARE_PARSE_ONLY for that purpose. # # Seen in pkgsrc/x11/libXfixes, and probably many more package that use # GNU Automake. @@ -36,4 +36,3 @@ MISSING_BRACE_INDIRECT:= ${:U\${MISSING_BRACE} UNCLOSED= $(MISSING_PAREN UNCLOSED= ${MISSING_BRACE UNCLOSED= ${MISSING_BRACE_INDIRECT} - diff --git a/unit-tests/sh-jobs.mk b/unit-tests/sh-jobs.mk index e8d4f976109a..de80de56040c 100644 --- a/unit-tests/sh-jobs.mk +++ b/unit-tests/sh-jobs.mk @@ -1,4 +1,4 @@ -# $NetBSD: sh-jobs.mk,v 1.3 2020/12/11 01:06:10 rillig Exp $ +# $NetBSD: sh-jobs.mk,v 1.4 2021/04/16 16:49:27 rillig Exp $ # # Tests for the "run in jobs mode" part of the "Shell Commands" section # from the manual page. @@ -14,14 +14,14 @@ all: .PHONY comment .WAIT comment-with-followup-line .WAIT no-comment # would lead to a syntax error in the generated shell file, at least for # bash and dash, but not for NetBSD sh and ksh. # -# See JobPrintCommand, cmdTemplate, runIgnTmpl +# See JobWriteCommand, cmdTemplate, runIgnTmpl comment: .PHONY @# comment # If a shell command starts with a comment character after stripping the # leading '@', it is run in ignore-errors mode. # -# See JobPrintCommand, cmdTemplate, runIgnTmpl +# See JobWriteCommand, cmdTemplate, runIgnTmpl comment-with-followup-line: .PHONY @# comment${.newline}echo '$@: This is printed.'; false @true @@ -29,7 +29,7 @@ comment-with-followup-line: .PHONY # Without the comment, the commands are run in the default mode, which checks # the exit status of every makefile line. # -# See JobPrintCommand, cmdTemplate, runChkTmpl +# See JobWriteCommand, cmdTemplate, runChkTmpl no-comment: .PHONY @echo '$@: This is printed.'; false @true diff --git a/unit-tests/shell-csh.mk b/unit-tests/shell-csh.mk index 99852e33ce16..47313563d22b 100644 --- a/unit-tests/shell-csh.mk +++ b/unit-tests/shell-csh.mk @@ -1,4 +1,4 @@ -# $NetBSD: shell-csh.mk,v 1.7 2020/12/13 02:09:55 sjg Exp $ +# $NetBSD: shell-csh.mk,v 1.8 2021/04/04 09:58:51 rillig Exp $ # # Tests for using a C shell for running the commands. @@ -12,7 +12,7 @@ CSH!= which csh 2> /dev/null || true .endif # In parallel mode, the shell->noPrint command is filtered from -# the output, rather naively (in JobOutput). +# the output, rather naively (in PrintOutput). # # Until 2020-10-03, the output in parallel mode was garbled because # the definition of the csh had been wrong since 1993 at least. diff --git a/unit-tests/suff-incomplete.exp b/unit-tests/suff-incomplete.exp index 23b959d4b4e5..2331436d378e 100644 --- a/unit-tests/suff-incomplete.exp +++ b/unit-tests/suff-incomplete.exp @@ -1,19 +1,19 @@ ParseReadLine (9): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (11): '.SUFFIXES: .a .b .c' -ParseDoDependency(.SUFFIXES: .a .b .c) +ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" ParseReadLine (17): '.a.b:' -ParseDoDependency(.a.b:) +ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list ParseReadLine (21): '.a.c: ${.PREFIX}.dependency' deleting incomplete transformation from `.a' to `.b' -ParseDoDependency(.a.c: ${.PREFIX}.dependency) +ParseDependency(.a.c: ${.PREFIX}.dependency) defining transformation from `.a' to `.c' inserting ".a" (1) at end of list inserting ".c" (3) at end of list @@ -22,7 +22,7 @@ inserting ".c" (3) at end of list # ${.PREFIX}.dependency, unmade, type none, flags none ParseReadLine (23): '.DEFAULT:' transformation .a.c complete -ParseDoDependency(.DEFAULT:) +ParseDependency(.DEFAULT:) ParseReadLine (24): ' : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.' transformation .DEFAULT complete Wildcard expanding "all"... diff --git a/unit-tests/suff-main-several.exp b/unit-tests/suff-main-several.exp index a494ddc68545..09fa6d63bffa 100644 --- a/unit-tests/suff-main-several.exp +++ b/unit-tests/suff-main-several.exp @@ -1,12 +1,12 @@ ParseReadLine (8): '.1.2 .1.3 .1.4:' -ParseDoDependency(.1.2 .1.3 .1.4:) +ParseDependency(.1.2 .1.3 .1.4:) Setting main node to ".1.2" ParseReadLine (9): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (14): 'next-main:' -ParseDoDependency(next-main:) +ParseDependency(next-main:) ParseReadLine (15): ' : Making ${.TARGET}' ParseReadLine (19): '.SUFFIXES: .1 .2 .3 .4' -ParseDoDependency(.SUFFIXES: .1 .2 .3 .4) +ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Setting main node from ".1.2" back to null @@ -27,42 +27,42 @@ inserting ".1" (1) at end of list inserting ".4" (4) at end of list Setting main node to "next-main" ParseReadLine (24): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (32): '.SUFFIXES: .4 .3 .2 .1' -ParseDoDependency(.SUFFIXES: .4 .3 .2 .1) +ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" ParseReadLine (33): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (34): '.SUFFIXES: .1 .2 .3 .4' -ParseDoDependency(.SUFFIXES: .1 .2 .3 .4) +ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Adding suffix ".3" Adding suffix ".4" ParseReadLine (35): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (36): '.SUFFIXES: .4 .3 .2 .1' -ParseDoDependency(.SUFFIXES: .4 .3 .2 .1) +ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" ParseReadLine (38): 'suff-main-several.1:' -ParseDoDependency(suff-main-several.1:) +ParseDependency(suff-main-several.1:) ParseReadLine (39): ' : Making ${.TARGET} out of nothing.' ParseReadLine (40): 'next-main: suff-main-several.{2,3,4}' -ParseDoDependency(next-main: suff-main-several.{2,3,4}) +ParseDependency(next-main: suff-main-several.{2,3,4}) # LinkSource: added child next-main - suff-main-several.{2,3,4} # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none ParseReadLine (42): '.MAKEFLAGS: -d0 -dg1' -ParseDoDependency(.MAKEFLAGS: -d0 -dg1) +ParseDependency(.MAKEFLAGS: -d0 -dg1) #*** Input graph: # .1.2, unmade, type OP_TRANSFORM, flags none # .1.3, unmade, type OP_TRANSFORM, flags none diff --git a/unit-tests/suff-rebuild.exp b/unit-tests/suff-rebuild.exp index ccb423a6086a..7ef53ae2e151 100644 --- a/unit-tests/suff-rebuild.exp +++ b/unit-tests/suff-rebuild.exp @@ -1,38 +1,38 @@ ParseReadLine (10): '.SUFFIXES:' -ParseDoDependency(.SUFFIXES:) +ParseDependency(.SUFFIXES:) Clearing all suffixes ParseReadLine (12): '.SUFFIXES: .a .b .c' -ParseDoDependency(.SUFFIXES: .a .b .c) +ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" ParseReadLine (14): 'suff-rebuild-example.a:' -ParseDoDependency(suff-rebuild-example.a:) +ParseDependency(suff-rebuild-example.a:) Adding "suff-rebuild-example.a" to all targets. ParseReadLine (15): ' : Making ${.TARGET} out of nothing.' ParseReadLine (17): '.a.b:' -ParseDoDependency(.a.b:) +ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list ParseReadLine (18): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (19): '.b.c:' transformation .a.b complete -ParseDoDependency(.b.c:) +ParseDependency(.b.c:) defining transformation from `.b' to `.c' inserting ".b" (2) at end of list inserting ".c" (3) at end of list ParseReadLine (20): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (21): '.c:' transformation .b.c complete -ParseDoDependency(.c:) +ParseDependency(.c:) defining transformation from `.c' to `' inserting ".c" (3) at end of list inserting "" (0) at end of list ParseReadLine (22): ' : Making ${.TARGET} from ${.IMPSRC}.' ParseReadLine (44): '.SUFFIXES: .c .b .a' transformation .c complete -ParseDoDependency(.SUFFIXES: .c .b .a) +ParseDependency(.SUFFIXES: .c .b .a) Adding ".END" to all targets. Wildcard expanding "all"... SuffFindDeps "all" diff --git a/unit-tests/var-class-cmdline.exp b/unit-tests/var-class-cmdline.exp index 39a9383953dd..6df2155ca7eb 100644 --- a/unit-tests/var-class-cmdline.exp +++ b/unit-tests/var-class-cmdline.exp @@ -1 +1,4 @@ +make: "var-class-cmdline.mk" line 67: global +make: "var-class-cmdline.mk" line 76: makeflags +makeflags exit status 0 diff --git a/unit-tests/var-class-cmdline.mk b/unit-tests/var-class-cmdline.mk index c43b5351c329..679e051bb242 100644 --- a/unit-tests/var-class-cmdline.mk +++ b/unit-tests/var-class-cmdline.mk @@ -1,8 +1,80 @@ -# $NetBSD: var-class-cmdline.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# $NetBSD: var-class-cmdline.mk,v 1.5 2021/02/23 21:59:31 rillig Exp $ # # Tests for variables specified on the command line. +# +# Variables that are specified on the command line override those from the +# global scope. +# +# For performance reasons, the actual implementation is more complex than the +# above single-sentence rule, in order to avoid unnecessary lookups in scopes, +# which before var.c 1.586 from 2020-10-25 calculated the hash value of the +# variable name once for each lookup. Instead, when looking up the value of +# a variable, the search often starts in the global scope since that is where +# most of the variables are stored. This conflicts with the statement that +# variables from the cmdline scope override global variables, since after the +# common case of finding a variable in the global scope, another lookup would +# be needed in the cmdline scope to ensure that there is no overriding +# variable there. +# +# Instead of this costly lookup scheme, make implements it in a different +# way: +# +# Whenever a global variable is created, this creation is ignored if +# there is a cmdline variable of the same name. +# +# Whenever a cmdline variable is created, any global variable of the +# same name is deleted. +# +# Whenever a global variable is deleted, nothing special happens. +# +# Deleting a cmdline variable is not possible. +# +# These 4 rules provide the guarantee that whenever a global variable exists, +# there cannot be a cmdline variable of the same name. Therefore, after +# finding a variable in the global scope, no additional lookup is needed in +# the cmdline scope. +# +# The above ruleset provides the same guarantees as the simple rule "cmdline +# overrides global". Due to an implementation mistake, the actual behavior +# was not entirely equivalent to the simple rule though. The mistake was +# that when a cmdline variable with '$$' in its name was added, a global +# variable was deleted, but not with the exact same name as the cmdline +# variable. Instead, the name of the global variable was expanded one more +# time than the name of the cmdline variable. For variable names that didn't +# have a '$$' in their name, it was implemented correctly all the time. +# +# The bug was added in var.c 1.183 on 2013-07-16, when Var_Set called +# Var_Delete to delete the global variable. Just two months earlier, in var.c +# 1.174 from 2013-05-18, Var_Delete had started to expand the variable name. +# Together, these two changes made the variable name be expanded twice in a +# row. This bug was fixed in var.c 1.835 from 2021-02-22. +# +# Another bug was the wrong assumption that "deleting a cmdline variable is +# not possible". Deleting such a variable has been possible since var.c 1.204 +# from 2016-02-19, when the variable modifier ':@' started to delete the +# temporary loop variable after finishing the loop. It was probably not +# intended back then that a side effect of this seemingly simple change was +# that both global and cmdline variables could now be undefined at will as a +# side effect of evaluating a variable expression. As of 2021-02-23, this is +# still possible. +# +# Most cmdline variables are set at the very beginning, when parsing the +# command line arguments. Using the special target '.MAKEFLAGS', it is +# possible to set cmdline variables at any later time. + +# A normal global variable, without any cmdline variable nearby. +VAR= global +.info ${VAR} -# TODO: Implementation +# The global variable is "overridden" by simply deleting it and then +# installing the cmdline variable instead. Since there is no obvious way to +# undefine a cmdline variable, there is no need to remember the old value +# of the global variable could become visible again. +# +# See varmod-loop.mk for a non-obvious way to undefine a cmdline variable. +.MAKEFLAGS: VAR=makeflags +.info ${VAR} -all: - @:; +# If Var_SetWithFlags should ever forget to delete the global variable, +# the below line would print "global" instead of the current "makeflags". +.MAKEFLAGS: -V VAR diff --git a/unit-tests/var-eval-short.exp b/unit-tests/var-eval-short.exp new file mode 100644 index 000000000000..ae0aff7d7c2c --- /dev/null +++ b/unit-tests/var-eval-short.exp @@ -0,0 +1,29 @@ +make: "var-eval-short.mk" line 41: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar. +make: "var-eval-short.mk" line 41: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@}) +make: "var-eval-short.mk" line 79: Invalid time value: ${FAIL}} +make: "var-eval-short.mk" line 79: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}}) +make: "var-eval-short.mk" line 93: Invalid time value: ${FAIL}} +make: "var-eval-short.mk" line 93: Malformed conditional (0 && ${:Uword:localtime=${FAIL}}) +CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else} +Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only) +Parsing modifier ${0:?...} +Modifier part: "${FAIL}then" +Modifier part: "${FAIL}else" +Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined) +ParseReadLine (158): 'DEFINED= defined' +Global: DEFINED = defined +CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} +Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only) +Parsing modifier ${DEFINED:L} +Result of ${DEFINED:L} is "defined" (parse-only, regular) +Parsing modifier ${DEFINED:?...} +Modifier part: "${FAIL}then" +Modifier part: "${FAIL}else" +Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular) +ParseReadLine (161): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/var-eval-short.mk b/unit-tests/var-eval-short.mk new file mode 100644 index 000000000000..41782f0d7823 --- /dev/null +++ b/unit-tests/var-eval-short.mk @@ -0,0 +1,163 @@ +# $NetBSD: var-eval-short.mk,v 1.5 2021/04/04 13:35:26 rillig Exp $ +# +# Tests for each variable modifier to ensure that they only do the minimum +# necessary computations. If the result of the expression is not needed, they +# should only parse the modifier but not actually evaluate it. +# +# See also: +# var.c, the comment starting with 'The ApplyModifier functions' +# ApplyModifier, for the order of the modifiers +# ParseModifierPart, for evaluating nested expressions +# cond-short.mk + +FAIL= ${:!echo unexpected 1>&2!} + +# The following tests only ensure that nested expressions are not evaluated. +# They cannot ensure that any unexpanded text returned from ParseModifierPart +# is ignored as well. To do that, it is necessary to step through the code of +# each modifier. + +.if 0 && ${FAIL} +.endif + +.if 0 && ${VAR::=${FAIL}} +.elif defined(VAR) +. error +.endif + +.if 0 && ${${FAIL}:?then:else} +.endif + +.if 0 && ${1:?${FAIL}:${FAIL}} +.endif + +.if 0 && ${0:?${FAIL}:${FAIL}} +.endif + +# Before var.c 1.870 from 2021-03-14, the expression ${FAIL} was evaluated +# after the loop, when undefining the temporary global loop variable. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if 0 && ${:Uword:@${FAIL}@expr@} +.endif + +.if 0 && ${:Uword:@var@${FAIL}@} +.endif + +# Before var.c,v 1.877 from 2021-03-14, the modifier ':[...]' did not expand +# the nested expression ${FAIL} and then tried to parse the unexpanded text, +# which failed since '$' is not a valid range character. +.if 0 && ${:Uword:[${FAIL}]} +.endif + +# Before var.c,v 1.867 from 2021-03-14, the modifier ':_' defined the variable +# even though the whole expression should have only been parsed, not +# evaluated. +.if 0 && ${:Uword:_=VAR} +.elif defined(VAR) +. error +.endif + +# Before var.c,v 1.856 from 2021-03-14, the modifier ':C' did not expand the +# nested expression ${FAIL} and then tried to compile the unexpanded text as a +# regular expression, which failed both because of the '{FAIL}', which is not +# a valid repetition, and because of the '****', which are repeated +# repetitions as well. +# '${FAIL}' +.if 0 && ${:Uword:C,${FAIL}****,,} +.endif + +DEFINED= # defined +.if 0 && ${DEFINED:D${FAIL}} +.endif + +.if 0 && ${:Uword:E} +.endif + +# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since +# ':gmtime' does not expand its argument. +.if 0 && ${:Uword:gmtime=${FAIL}} +.endif + +.if 0 && ${:Uword:H} +.endif + +.if 0 && ${:Uword:hash} +.endif + +.if 0 && ${value:L} +.endif + +# As of 2021-03-14, the error 'Invalid time value: ${FAIL}}' is ok since +# ':localtime' does not expand its argument. +.if 0 && ${:Uword:localtime=${FAIL}} +.endif + +.if 0 && ${:Uword:M${FAIL}} +.endif + +.if 0 && ${:Uword:N${FAIL}} +.endif + +.if 0 && ${:Uword:O} +.endif + +.if 0 && ${:Uword:Ox} +.endif + +.if 0 && ${:Uword:P} +.endif + +.if 0 && ${:Uword:Q} +.endif + +.if 0 && ${:Uword:q} +.endif + +.if 0 && ${:Uword:R} +.endif + +.if 0 && ${:Uword:range} +.endif + +.if 0 && ${:Uword:S,${FAIL},${FAIL},} +.endif + +.if 0 && ${:Uword:sh} +.endif + +.if 0 && ${:Uword:T} +.endif + +.if 0 && ${:Uword:ts/} +.endif + +.if 0 && ${:U${FAIL}} +.endif + +.if 0 && ${:Uword:u} +.endif + +.if 0 && ${:Uword:word=replacement} +.endif + +# Before var.c 1.875 from 2021-03-14, Var_Parse returned "${FAIL}else" for the +# irrelevant right-hand side of the condition, even though this was not +# necessary. Since the return value from Var_Parse is supposed to be ignored +# anyway, and since it is actually ignored in an overly complicated way, +# an empty string suffices. +.MAKEFLAGS: -dcpv +.if 0 && ${0:?${FAIL}then:${FAIL}else} +.endif + +# The ':L' is applied before the ':?' modifier, giving the expression a name +# and a value, just to see whether this value gets passed through or whether +# the parse-only mode results in an empty string (only visible in the debug +# log). As of var.c 1.875 from 2021-03-14, the value of the variable gets +# through, even though an empty string would suffice. +DEFINED= defined +.if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} +.endif +.MAKEFLAGS: -d0 + +all: diff --git a/unit-tests/var-op-append.exp b/unit-tests/var-op-append.exp index 424ad37ccf63..32134be75a3d 100644 --- a/unit-tests/var-op-append.exp +++ b/unit-tests/var-op-append.exp @@ -1,7 +1,7 @@ -Var_Parse: ${:U\$\$\$\$\$\$\$\$} with VARE_WANTRES -Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (VARE_WANTRES, none, VES_DEF) -Global:VAR.$$$$$$$$ = dollars -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Var_Parse: ${:U\$\$\$\$\$\$\$\$} (eval) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:U\$\$\$\$\$\$\$\$} is "$$$$$$$$" (eval, defined) +Global: VAR.$$$$$$$$ = dollars +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-append.mk b/unit-tests/var-op-append.mk index deb4af6a7384..420ee376b75d 100644 --- a/unit-tests/var-op-append.mk +++ b/unit-tests/var-op-append.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-append.mk,v 1.8 2021/02/03 08:40:47 rillig Exp $ +# $NetBSD: var-op-append.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $ # # Tests for the += variable assignment operator, which appends to a variable, # creating it if necessary. @@ -26,7 +26,7 @@ VAR+= # empty # '+=' assignment operator. As far as possible, the '+' is interpreted as # part of the assignment operator. # -# See Parse_DoVar +# See Parse_Var C++= value .if ${C+} != "value" || defined(C++) . error diff --git a/unit-tests/var-op-assign.mk b/unit-tests/var-op-assign.mk index 3bcc3de0ba0e..18ecf8d0d5ed 100644 --- a/unit-tests/var-op-assign.mk +++ b/unit-tests/var-op-assign.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-assign.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: var-op-assign.mk,v 1.8 2021/03/15 19:15:04 rillig Exp $ # # Tests for the = variable assignment operator, which overwrites an existing # variable or creates it. @@ -42,7 +42,7 @@ VAR= new value and \# some $$ special characters # comment # 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. +# the call to Expr_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. diff --git a/unit-tests/var-op-sunsh.mk b/unit-tests/var-op-sunsh.mk index 0e16b2b42d34..0d15b8c88b92 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.6 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: var-op-sunsh.mk,v 1.8 2021/04/04 10:13:09 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 != @@ -50,7 +50,7 @@ VAR:shoe:shore= echo two-colons # The variable modifier ':sh' and the assignment operator modifier ':sh'. # Intuitively this variable name contains the variable modifier, but until # 2020-10-04, the parser regarded it as an assignment operator modifier, in -# Parse_DoVar. +# Parse_Var. VAR.${:Uecho 123:sh}= ok-123 .if ${VAR.123} != "ok-123" . error @@ -75,11 +75,11 @@ VAR.key:shift= Shift # the ':sh' assignment operator modifier. Let's see what happens ... # # Well, the end result is correct but the way until there is rather -# adventurous. This only works because the parser replaces each an every -# whitespace character that is not nested with '\0' (see Parse_DoVar). +# adventurous. This only works because the parser replaces each and every +# whitespace character that is not nested with '\0' (see Parse_Var). # The variable name therefore ends before the first ':sh', and the last # ':sh' turns the assignment operator into the shell command evaluation. -# Parse_DoVar completely trusts Parse_IsVar to properly verify the syntax. +# Parse_Var completely trusts Parse_IsVar to properly verify the syntax. # # The ':sh' is the only word that may occur between the variable name and # the assignment operator at nesting level 0. All other words would lead @@ -102,7 +102,7 @@ VAR :sh(Put a comment here)= comment in parentheses # The unintended comment can include multiple levels of nested braces and # parentheses, they don't even need to be balanced since they are only -# counted by Parse_IsVar and ignored by Parse_DoVar. +# counted by Parse_IsVar and ignored by Parse_Var. VAR :sh{Put}((((a}{comment}}}}{here}= comment in braces .if ${VAR} != "comment in braces" . error diff --git a/unit-tests/varcmd.mk b/unit-tests/varcmd.mk index 9ec4f4f9a21a..12739df30926 100644 --- a/unit-tests/varcmd.mk +++ b/unit-tests/varcmd.mk @@ -1,6 +1,17 @@ -# $NetBSD: varcmd.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: varcmd.mk,v 1.6 2021/02/16 19:43:09 rillig Exp $ # # Test behaviour of recursive make and vars set on command line. +# +# FIXME: The purpose of this test is unclear. The test uses six levels of +# sub-makes, which makes it incredibly hard to understand. There must be at +# least an introductory explanation about what _should_ happen here. +# The variable names are terrible, as well as their values. +# +# This test produces different results if the large block with the condition +# "scope == SCOPE_GLOBAL" in Var_SetWithFlags is removed. This test should +# be rewritten to make it clear why there is a difference and why this is +# actually intended. Removing that large block of code makes only this test +# and vardebug.mk fail, which is not enough. FU= fu FOO?= foo @@ -57,4 +68,3 @@ five: show show-v six: show-v @${.MAKE} -f ${MAKEFILE} V=override show-v - diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp index a9a00a11f5dd..6d00acc977af 100644 --- a/unit-tests/vardebug.exp +++ b/unit-tests/vardebug.exp @@ -1,86 +1,69 @@ Global:delete FROM_CMDLINE (not found) -Command:FROM_CMDLINE = -Global:.MAKEOVERRIDES = FROM_CMDLINE -Global:VAR = added -Global:VAR = overwritten +Command: FROM_CMDLINE = +Global: .MAKEOVERRIDES = FROM_CMDLINE +Global: VAR = added +Global: VAR = overwritten Global:delete VAR Global:delete VAR (not found) -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "empty name", ...) name expands to empty string - ignored -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Append("${:U}", "empty name", ...) name expands to empty string - ignored -Global:FROM_CMDLINE = overwritten ignored! -Global:VAR = 1 -Global:VAR = 1 2 -Global:VAR = 1 2 3 -Var_Parse: ${VAR:M[2]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:M...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Pattern[VAR] for [1 2 3] is [[2]] +Var_SetExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored +Var_AppendExpand: variable name "${:U}" expands to empty string, with value "empty name" - ignored +Global: FROM_CMDLINE = overwritten ignored! +Global: VAR = 1 +Global: VAR = 1 2 +Global: VAR = 1 2 3 +Var_Parse: ${VAR:M[2]} (eval-defined) +Evaluating modifier ${VAR:M...} on value "1 2 3" +Pattern for ':M' is "[2]" ModifyWords: split "1 2 3" into 3 words -VarMatch [1] [[2]] -VarMatch [2] [[2]] -VarMatch [3] [[2]] -Result of ${VAR:M[2]} is "2" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VAR:N[2]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:N...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Pattern[VAR] for [1 2 3] is [[2]] +Result of ${VAR:M[2]} is "2" +Var_Parse: ${VAR:N[2]} (eval-defined) +Evaluating modifier ${VAR:N...} on value "1 2 3" +Pattern for ':N' is "[2]" ModifyWords: split "1 2 3" into 3 words -Result of ${VAR:N[2]} is "1 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VAR:S,2,two,} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:S...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) +Result of ${VAR:N[2]} is "1 3" +Var_Parse: ${VAR:S,2,two,} (eval-defined) +Evaluating modifier ${VAR:S...} on value "1 2 3" Modifier part: "2" Modifier part: "two" ModifyWords: split "1 2 3" into 3 words -Result of ${VAR:S,2,two,} is "1 two 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VAR:Q} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:Q} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Result of ${VAR:Q} is "1\ 2\ 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VAR:tu:tl:Q} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VAR:t...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Result of ${VAR:tu} is "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Applying ${VAR:t...} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Result of ${VAR:tl} is "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Applying ${VAR:Q} to "1 2 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Result of ${VAR:Q} is "1\ 2\ 3" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Uvalue} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:UM*e}:Mvalu[e]} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:UM*e} is "M*e" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Result of ${VAR:S,2,two,} is "1 two 3" +Var_Parse: ${VAR:Q} (eval-defined) +Evaluating modifier ${VAR:Q} on value "1 2 3" +Result of ${VAR:Q} is "1\ 2\ 3" +Var_Parse: ${VAR:tu:tl:Q} (eval-defined) +Evaluating modifier ${VAR:t...} on value "1 2 3" +Result of ${VAR:tu} is "1 2 3" +Evaluating modifier ${VAR:t...} on value "1 2 3" +Result of ${VAR:tl} is "1 2 3" +Evaluating modifier ${VAR:Q} on value "1 2 3" +Result of ${VAR:Q} is "1\ 2\ 3" +Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:Uvalue} is "value" (eval-defined, defined) Indirect modifier "M*e" from "${:UM*e}" -Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[] for [value] is [*e] +Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Pattern for ':M' is "*e" ModifyWords: split "value" into 1 words -VarMatch [value] [*e] -Result of ${:M*e} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Applying ${:M...} to "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[] for [value] is [valu[e]] +Result of ${:M*e} is "value" (eval-defined, defined) +Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Pattern for ':M' is "valu[e]" ModifyWords: split "value" into 1 words -VarMatch [value] [valu[e]] -Result of ${:Mvalu[e]} is "value" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:UVAR} with VARE_WANTRES -Applying ${:U...} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:UVAR} is "VAR" (VARE_WANTRES, none, VES_DEF) +Result of ${:Mvalu[e]} is "value" (eval-defined, defined) Global:delete VAR -Var_Parse: ${:Uvariable:unknown} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Uvariable} is "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Applying ${:u...} to "variable" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -make: "vardebug.mk" line 44: Unknown modifier 'u' -Result of ${:unknown} is error (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) +Var_Parse: ${:Uvariable:unknown} (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:Uvariable} is "variable" (eval-defined, defined) +Evaluating modifier ${:u...} on value "variable" (eval-defined, defined) +make: "vardebug.mk" line 44: Unknown modifier "unknown" +Result of ${:unknown} is error (eval-defined, defined) make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown}) -Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${UNDEFINED} (eval-defined) make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED}) Global:delete .SHELL (not found) -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 +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 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmisc.exp b/unit-tests/varmisc.exp index e8f88d9ca51f..f56f72d0ab9c 100644 --- a/unit-tests/varmisc.exp +++ b/unit-tests/varmisc.exp @@ -54,7 +54,7 @@ make: Unclosed variable "UNCLOSED" make: Unclosed variable "UNCLOSED" make: Unclosed variable "PATTERN" -make: Unclosed variable specification (expecting '}') for "UNCLOSED" (value "") modifier M +make: Unclosed variable expression, expecting '}' for modifier "M${PATTERN" of variable "UNCLOSED" with value "" make: Unclosed variable "param" make: Unclosed variable "UNCLOSED." diff --git a/unit-tests/varmod-assign.exp b/unit-tests/varmod-assign.exp index 743ef2fb4082..1e43714d500b 100644 --- a/unit-tests/varmod-assign.exp +++ b/unit-tests/varmod-assign.exp @@ -1,3 +1,17 @@ +Global: param = twice +Global: VARNAME = VAR.$${param} +Var_Parse: ${VARNAME} (eval) +Global: VAR.${param} = initial-value +Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined) +Var_Parse: ${VARNAME}::=assigned-value} (eval-defined) +Evaluating modifier ${VAR.${param}::...} on value "initial-value" +Modifier part: "assigned-value" +Global: VAR.${param} = assigned-value +Result of ${VAR.${param}::=assigned-value} is "" +Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined) +Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined) +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 mod-assign: first=1. mod-assign: last=3. mod-assign: appended=1 2 3. @@ -10,15 +24,15 @@ mod-assign-nested: then1t1 mod-assign-nested: else2e2 mod-assign-nested: then3t3 mod-assign-nested: else4e4 -make: Bad modifier `:' for +make: Bad modifier ":" for variable "" mod-assign-empty: value} -make: Bad modifier `:' for +make: Bad modifier ":" for variable "" mod-assign-empty: overwritten} mod-assign-empty: VAR=overwritten -make: Unknown modifier ':' +make: Unknown modifier ":x" sysv:y -make: Unfinished modifier for ASSIGN ('}' missing) +make: Unfinished modifier for "ASSIGN" ('}' missing) ok=word make: " echo word; false " returned non-zero status diff --git a/unit-tests/varmod-assign.mk b/unit-tests/varmod-assign.mk index e4cbc249df88..f50c654f5bcf 100644 --- a/unit-tests/varmod-assign.mk +++ b/unit-tests/varmod-assign.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-assign.mk,v 1.9 2021/01/22 22:54:53 rillig Exp $ +# $NetBSD: varmod-assign.mk,v 1.12 2021/03/15 18:56:38 rillig Exp $ # # Tests for the obscure ::= variable modifiers, which perform variable # assignments during evaluation, just like the = operator in C. @@ -91,7 +91,7 @@ mod-assign-shell-error: @${SH_ERR::=previous} @${SH_ERR::!= echo word; false } echo err=${SH_ERR} -# XXX: The ::= modifier expands its right-hand side, exactly once. +# XXX: The ::= modifier expands its right-hand side exactly once. # This differs subtly from normal assignments such as '+=' or '=', which copy # their right-hand side literally. APPEND.prev= previous @@ -104,3 +104,38 @@ APPEND.dollar= $${APPEND.indirect} .if ${APPEND.var} != "previous indirect \${:Unot expanded}" . error .endif + + +# The assignment modifier can be used in a variable expression that is +# enclosed in parentheses. In such a case, parsing stops at the first ')', +# not at the first '}'. +VAR= previous +_:= $(VAR::=current}) +.if ${VAR} != "current}" +. error +.endif + + +# Before var.c 1.888 from 2021-03-15, an expression using the modifier '::=' +# expanded its variable name once too often during evaluation. This was only +# relevant for variable names containing a '$' sign in their actual name, not +# the usual VAR.${param}. +.MAKEFLAGS: -dv +param= twice +VARNAME= VAR.$${param} # Indirect variable name because of the '$', + # to avoid difficult escaping rules. + +${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'. +.if defined(VAR.twice) # At this point, the '$$' is not expanded. +. error +.endif +.if ${${VARNAME}::=assigned-value} # Here the variable name gets expanded once +. error # too often. +.endif +.if defined(VAR.twice) +. error The variable name in the '::=' modifier is expanded once too often. +.endif +.if ${${VARNAME}} != "assigned-value" +. error +.endif +.MAKEFLAGS: -d0 diff --git a/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp index 15f40226f1db..2f7d4dbf4baa 100644 --- a/unit-tests/varmod-defined.exp +++ b/unit-tests/varmod-defined.exp @@ -1,23 +1,23 @@ -Global:8_DOLLARS = $$$$$$$$ -Global:VAR = -Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Global:VAR = $$$$$$$$ -Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Global:VAR = $$$$$$$$ -Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) +Global: 8_DOLLARS = $$$$$$$$ +Global: VAR = +Var_Parse: ${8_DOLLARS} (eval-keep-dollar-and-undefined) +Global: VAR = $$$$$$$$ +Var_Parse: ${VAR:D${8_DOLLARS}} (eval-keep-dollar-and-undefined) +Evaluating modifier ${VAR:D...} on value "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) +Var_Parse: ${8_DOLLARS}} (eval-keep-dollar-and-undefined) +Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) +Global: VAR = $$$$$$$$ +Var_Parse: ${VAR:@var@${8_DOLLARS}@} (eval-keep-dollar-and-undefined) +Evaluating modifier ${VAR:@...} on value "$$$$$$$$" (eval-keep-dollar-and-undefined, regular) Modifier part: "var" Modifier part: "${8_DOLLARS}" ModifyWords: split "$$$$$$$$" into 1 words -Global:var = $$$$$$$$ -Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_UNDEF +Global: var = $$$$$$$$ +Var_Parse: ${8_DOLLARS} (eval-keep-undefined) ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$" Global:delete var -Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, none) -Global:VAR = $$$$ -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (eval-keep-dollar-and-undefined, regular) +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 59b9d79d754b..a44b9f993146 100644 --- a/unit-tests/varmod-defined.mk +++ b/unit-tests/varmod-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-defined.mk,v 1.9 2020/11/12 00:40:55 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.11 2021/04/11 13:35:56 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. @@ -89,9 +89,9 @@ DEF= 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_Defined passes the emode unmodified to Var_Parse, unlike # ApplyModifier_Loop, which uses ParseModifierPart, which in turn removes -# VARE_KEEP_DOLLAR from eflags. +# the keepDollar flag from emode. # # XXX: This inconsistency is documented nowhere. .MAKEFLAGS: -dv diff --git a/unit-tests/varmod-edge.exp b/unit-tests/varmod-edge.exp index c90eef2756c6..d9db72b2e2ef 100644 --- a/unit-tests/varmod-edge.exp +++ b/unit-tests/varmod-edge.exp @@ -1,7 +1,7 @@ make: "varmod-edge.mk" line 166: ok M-paren make: "varmod-edge.mk" line 166: ok M-mixed make: "varmod-edge.mk" line 166: ok M-unescape -make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U +make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" make: "varmod-edge.mk" line 166: ok M-nest-mix make: "varmod-edge.mk" line 166: ok M-nest-brk make: "varmod-edge.mk" line 166: ok M-pat-err @@ -12,12 +12,16 @@ make: "varmod-edge.mk" line 166: ok M-128 make: "varmod-edge.mk" line 166: ok eq-ext make: "varmod-edge.mk" line 166: ok eq-q make: "varmod-edge.mk" line 166: ok eq-bs -make: Unfinished modifier for INP.eq-esc ('=' missing) +make: Unfinished modifier for "INP.eq-esc" ('=' missing) make: "varmod-edge.mk" line 166: ok eq-esc make: "varmod-edge.mk" line 166: ok colon -make: "varmod-edge.mk" line 165: Unknown modifier ':' -make: "varmod-edge.mk" line 165: Unknown modifier ':' +make: "varmod-edge.mk" line 165: Unknown modifier ":" +make: "varmod-edge.mk" line 165: Unknown modifier ":" make: "varmod-edge.mk" line 166: ok colons +make: "varmod-edge.mk" line 175: Unknown modifier "Z" +make: "varmod-edge.mk" line 175: Malformed conditional (${:Z}) +make: Unfinished modifier for "" (',' missing) +make: "varmod-edge.mk" line 188: Malformed conditional (${:S,}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-edge.mk b/unit-tests/varmod-edge.mk index a0b6d9342ef6..762053d281a3 100644 --- a/unit-tests/varmod-edge.mk +++ b/unit-tests/varmod-edge.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-edge.mk,v 1.13 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-edge.mk,v 1.16 2021/02/23 15:56:30 rillig Exp $ # # Tests for edge cases in variable modifiers. # @@ -51,7 +51,7 @@ TESTS+= M-nest-mix INP.M-nest-mix= (parentheses) MOD.M-nest-mix= ${INP.M-nest-mix:M${:U*)}} EXP.M-nest-mix= (parentheses)} -# make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U +# make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" # In contrast to parentheses and braces, the brackets are not counted # when the :M modifier is parsed since Makefile variables only take the @@ -169,5 +169,27 @@ EXP.colons= # empty . endif .endfor +# Even in expressions based on an unnamed variable, there may be errors. +# XXX: The error message should mention the variable name of the expression, +# even though that name is empty in this case. +.if ${:Z} +. error +.else +. error +.endif + +# Even in expressions based on an unnamed variable, there may be errors. +# +# Before var.c 1.842 from 2021-02-23, the error message did not surround the +# variable name with quotes, leading to the rather confusing "Unfinished +# modifier for (',' missing)", having two spaces in a row. +# +# XXX: The error message should report the filename:lineno. +.if ${:S,} +. error +.else +. error +.endif + all: @echo ok diff --git a/unit-tests/varmod-hash.exp b/unit-tests/varmod-hash.exp index f16f30903539..1286b456c6c2 100644 --- a/unit-tests/varmod-hash.exp +++ b/unit-tests/varmod-hash.exp @@ -1,9 +1,9 @@ -make: Unknown modifier 'h' +make: Unknown modifier "has" 26bb0f5f 12345 -make: Unknown modifier 'h' +make: Unknown modifier "hasX" -make: Unknown modifier 'h' +make: Unknown modifier "hashed" exit status 0 diff --git a/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp index 17d4d8afcbeb..e42e39525f1c 100644 --- a/unit-tests/varmod-ifelse.exp +++ b/unit-tests/varmod-ifelse.exp @@ -1,20 +1,32 @@ -make: Bad conditional expression `variable expression == "literal"' in variable expression == "literal"?bad:bad +make: Bad conditional expression 'variable expression == "literal"' in 'variable expression == "literal"?bad:bad' make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad}) -make: Bad conditional expression ` == ""' in == ""?bad-assign:bad-assign -make: Bad conditional expression ` == ""' in == ""?bad-cond:bad-cond +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: 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 +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. CondParser_Eval: ${ ${:U\$}{VAR} == value :?ok:bad} != "ok" CondParser_Eval: ${VAR} == value lhs = "value", rhs = "value", op = == lhs = "ok", rhs = "ok", op = != +make: "varmod-ifelse.mk" line 153: no. +make: "varmod-ifelse.mk" line 154: String comparison operator must be either == or != +make: Bad conditional expression 'string == "literal" || no >= 10' in 'string == "literal" || no >= 10?yes:no' +make: "varmod-ifelse.mk" line 154: . +make: Bad conditional expression 'string == "literal" && >= 10' in 'string == "literal" && >= 10?yes:no' +make: "varmod-ifelse.mk" line 159: . +make: Bad conditional expression 'string == "literal" || >= 10' in 'string == "literal" || >= 10?yes:no' +make: "varmod-ifelse.mk" line 160: . +make: "varmod-ifelse.mk" line 167: true +make: "varmod-ifelse.mk" line 169: false +make: Bad conditional expression ' ' in ' ?true:false' +make: "varmod-ifelse.mk" line 171: 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 ec6acdb2ee2f..0e16032a6543 100644 --- a/unit-tests/varmod-ifelse.mk +++ b/unit-tests/varmod-ifelse.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-ifelse.mk,v 1.9 2021/01/25 19:05:39 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.17 2021/06/11 13:01:28 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. @@ -74,7 +74,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # 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 +# being called without VARE_UNDEFERR. 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 @@ -111,5 +111,61 @@ VAR= value .endif .MAKEFLAGS: -d0 -all: - @:; +# On 2021-04-19, when building external/bsd/tmux with HAVE_LLVM=yes and +# HAVE_GCC=no, the following conditional generated this error message: +# +# make: Bad conditional expression 'string == "literal" && no >= 10' +# in 'string == "literal" && no >= 10?yes:no' +# +# Despite the error message (which was not clearly marked with "error:"), +# the build continued, for historical reasons, see main_Exit. +# +# The tricky detail here is that the condition that looks so obvious in the +# form written in the makefile becomes tricky when it is actually evaluated. +# This is because the condition is written in the place of the variable name +# of the expression, and in an expression, the variable name is always +# expanded first, before even looking at the modifiers. This happens for the +# modifier ':?' as well, so when CondEvalExpression gets to see the +# expression, it already looks like this: +# +# string == "literal" && no >= 10 +# +# When parsing such an expression, the parser used to be strict. It first +# evaluated the left-hand side of the operator '&&' and then started parsing +# the right-hand side 'no >= 10'. The word 'no' is obviously a string +# literal, not enclosed in quotes, which is ok, even on the left-hand side of +# the comparison operator, but only because this is a condition in the +# modifier ':?'. In an ordinary directive '.if', this would be a parse error. +# For strings, only the comparison operators '==' and '!=' are defined, +# therefore parsing stopped at the '>', producing the 'Bad conditional +# expression'. +# +# Ideally, the conditional expression would not be expanded before parsing +# it. This would allow to write the conditions exactly as seen below. That +# change has a high chance of breaking _some_ existing code and would need +# to be thoroughly tested. +# +# Since cond.c 1.262 from 2021-04-20, make reports a more specific error +# message in situations like these, pointing directly to the specific problem +# instead of just saying that the whole condition is bad. +STRING= string +NUMBER= no # not really a number +.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. +.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. + +# The following situation occasionally occurs with MKINET6 or similar +# variables. +NUMBER= # empty, not really a number either +.info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. +.info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. + +# CondParser_LeafToken handles [0-9-+] specially, treating them as a number. +PLUS= + +ASTERISK= * +EMPTY= # empty +# "true" since "+" is not the empty string. +.info ${${PLUS} :?true:false} +# "false" since the variable named "*" is not defined. +.info ${${ASTERISK} :?true:false} +# syntax error since the condition is completely blank. +.info ${${EMPTY} :?true:false} diff --git a/unit-tests/varmod-indirect.exp b/unit-tests/varmod-indirect.exp index 860da7781979..63ed988d0c0e 100644 --- a/unit-tests/varmod-indirect.exp +++ b/unit-tests/varmod-indirect.exp @@ -1,59 +1,43 @@ -make: "varmod-indirect.mk" line 13: Unknown modifier '$' -make: "varmod-indirect.mk" line 108: before -make: "varmod-indirect.mk" line 108: after -make: "varmod-indirect.mk" line 114: before -make: "varmod-indirect.mk" line 114: after -make: "varmod-indirect.mk" line 120: before -make: "varmod-indirect.mk" line 120: after -make: "varmod-indirect.mk" line 124: Unknown modifier 'Z' -make: "varmod-indirect.mk" line 125: before -make: "varmod-indirect.mk" line 125: after -ParseReadLine (134): '_:= before ${UNDEF} after' -Global:_ = -Var_Parse: ${UNDEF} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Global:_ = before ${UNDEF} after -ParseReadLine (137): '_:= before ${UNDEF:${:US,a,a,}} after' -Var_Parse: ${UNDEF:${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +make: "varmod-indirect.mk" line 19: Unknown modifier "${" +make: "varmod-indirect.mk" line 52: Unknown modifier "${" +make: "varmod-indirect.mk" line 55: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. +make: "varmod-indirect.mk" line 140: before +make: "varmod-indirect.mk" line 140: after +make: "varmod-indirect.mk" line 146: before +make: "varmod-indirect.mk" line 146: after +make: "varmod-indirect.mk" line 152: before +make: "varmod-indirect.mk" line 152: after +make: "varmod-indirect.mk" line 156: Unknown modifier "Z" +make: "varmod-indirect.mk" line 157: before +make: "varmod-indirect.mk" line 157: after +ParseReadLine (166): '_:= before ${UNDEF} after' +Global: _ = +Var_Parse: ${UNDEF} after (eval-keep-dollar-and-undefined) +Global: _ = before ${UNDEF} after +ParseReadLine (169): '_:= before ${UNDEF:${:US,a,a,}} after' +Var_Parse: ${UNDEF:${:US,a,a,}} after (eval-keep-dollar-and-undefined) Indirect modifier "S,a,a," from "${:US,a,a,}" -Applying ${UNDEF:S...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) +Evaluating modifier ${UNDEF:S...} on value "" (eval-keep-dollar-and-undefined, undefined) Modifier part: "a" Modifier part: "a" ModifyWords: split "" into 1 words -Result of ${UNDEF:S,a,a,} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Var_Parse: ${:US,a,a,}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:US,a,a,} is "S,a,a," (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:S,a,a,} after -ParseReadLine (147): '_:= before ${UNDEF:${:U}} after' -Var_Parse: ${UNDEF:${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +Result of ${UNDEF:S,a,a,} is "" (eval-keep-dollar-and-undefined, undefined) +Global: _ = before ${UNDEF:S,a,a,} after +ParseReadLine (179): '_:= before ${UNDEF:${:U}} after' +Var_Parse: ${UNDEF:${:U}} after (eval-keep-dollar-and-undefined) Indirect modifier "" from "${:U}" -Var_Parse: ${:U}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:} after -ParseReadLine (152): '_:= before ${UNDEF:${:UZ}} after' -Var_Parse: ${UNDEF:${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) +Global: _ = before ${UNDEF:} after +ParseReadLine (184): '_:= before ${UNDEF:${:UZ}} after' +Var_Parse: ${UNDEF:${:UZ}} after (eval-keep-dollar-and-undefined) Indirect modifier "Z" from "${:UZ}" -Applying ${UNDEF:Z} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -make: "varmod-indirect.mk" line 152: Unknown modifier 'Z' -Result of ${UNDEF:Z} is error (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Var_Parse: ${:UZ}} after with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF -Applying ${:U...} to "" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_UNDEF) -Result of ${:UZ} is "Z" (VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF, none, VES_DEF) -Global:_ = before ${UNDEF:Z} after -ParseReadLine (154): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -Global:.MAKEFLAGS = -r -k -d 0 -d pv -d -Global:.MAKEFLAGS = -r -k -d 0 -d pv -d 0 +Evaluating modifier ${UNDEF:Z} on value "" (eval-keep-dollar-and-undefined, undefined) +make: "varmod-indirect.mk" line 184: Unknown modifier "Z" +Result of ${UNDEF:Z} is error (eval-keep-dollar-and-undefined, undefined) +Global: _ = before ${UNDEF:Z} after +ParseReadLine (186): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d 0 -d pv -d +Global: .MAKEFLAGS = -r -k -d 0 -d pv -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-indirect.mk b/unit-tests/varmod-indirect.mk index d130c7cae76d..fa58997cc849 100644 --- a/unit-tests/varmod-indirect.mk +++ b/unit-tests/varmod-indirect.mk @@ -1,15 +1,21 @@ -# $NetBSD: varmod-indirect.mk,v 1.5 2020/12/27 17:32:25 rillig Exp $ +# $NetBSD: varmod-indirect.mk,v 1.9 2021/03/15 20:00:50 rillig Exp $ # # Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}. # These can be used for very basic purposes like converting a string to either # uppercase or lowercase, as well as for fairly advanced modifiers that first # look like line noise and are hard to decipher. # -# TODO: Since when are indirect modifiers supported? +# Initial support for indirect modifiers was added in var.c 1.101 from +# 2006-02-18. Since var.c 1.108 from 2006-05-11 it is possible to use +# indirect modifiers for all but the very first modifier as well. # To apply a modifier indirectly via another variable, the whole # modifier must be put into a single variable expression. +# The following expression generates a parse error since its indirect +# modifier contains more than a sole variable expression. +# +# expect+1: Unknown modifier '$' .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" . warning unexpected .endif @@ -28,13 +34,39 @@ .endif -# An indirect variable that evaluates to the empty string is allowed though. +# An indirect variable that evaluates to the empty string is allowed. +# It is even allowed to write another modifier directly afterwards. +# There is no practical use case for this feature though, as demonstrated +# in the test case directly below. +.if ${value:L:${:Dempty}S,value,replaced,} != "replaced" +. warning unexpected +.endif + +# If an expression for an indirect modifier evaluates to anything else than an +# empty string and is neither followed by a ':' nor '}', this produces a parse +# error. Because of this parse error, this feature cannot be used reasonably +# in practice. +# +# expect+1: Unknown modifier '$' +#.MAKEFLAGS: -dvc +.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}" +. warning FIXME: this expression should have resulted in a parse $\ + error rather than returning the unparsed portion of the $\ + expression. +.else +. error +.endif +#.MAKEFLAGS: -d0 + +# An indirect modifier can be followed by other modifiers, no matter if the +# indirect modifier evaluates to an empty string or not. +# # This makes it possible to define conditional modifiers, like this: # # M.little-endian= S,1234,4321, # M.big-endian= # none -.if ${value:L:${:Dempty}S,a,A,} != "vAlue" -. warning unexpected +.if ${value:L:${:D empty }:S,value,replaced,} != "replaced" +. error .endif @@ -154,4 +186,62 @@ _:= before ${UNDEF:${:UZ}} after .MAKEFLAGS: -d0 .undef _ + +# When evaluating indirect modifiers, these modifiers may expand to ':tW', +# which modifies the interpretation of the expression value. This modified +# interpretation only lasts until the end of the indirect modifier, it does +# not influence the outer variable expression. +.if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] +. error +.endif +.if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#] +. error +.endif + + +# When evaluating indirect modifiers, these modifiers may expand to ':ts*', +# which modifies the interpretation of the expression value. This modified +# interpretation only lasts until the end of the indirect modifier, it does +# not influence the outer variable expression. +# +# In this first expression, the direct ':ts*' has no effect since ':U' does not +# treat the expression value as a list of words but as a single word. It has +# to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no +# variable of that name. +#.MAKEFLAGS: -dcpv +.if ${1 2 3:L:ts*:Ua b c} != "a b c" +. error +.endif +# In this expression, the direct ':ts*' affects the ':M' at the end. +.if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c" +. error +.endif +# In this expression, the ':ts*' is indirect, therefore the changed separator +# only applies to the modifiers from the indirect text. It does not affect +# the ':M' since that is not part of the text from the indirect modifier. +# +# Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers +# (which creates a new ModChain containing a fresh separator), +# the outer separator character is not passed by reference to the inner +# evaluation, therefore the scope of the inner separator ends after applying +# the modifier ':ts*'. +.if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c" +. error +.endif + +# A direct modifier ':U' turns the expression from undefined to defined. +# An indirect modifier ':U' has the same effect, unlike the separator from +# ':ts*' or the single-word marker from ':tW'. +# +# This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes +# the definedness of the outer expression by reference. If that weren't the +# case, the first condition below would result in a parse error because its +# left-hand side would be undefined. +.if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback" +. error +.endif +.if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback" +. error +.endif + all: diff --git a/unit-tests/varmod-loop-varname.exp b/unit-tests/varmod-loop-varname.exp new file mode 100644 index 000000000000..9170307bd2a0 --- /dev/null +++ b/unit-tests/varmod-loop-varname.exp @@ -0,0 +1,11 @@ +make: "varmod-loop-varname.mk" line 13: In the :@ modifier of "", the variable name "${:Ubar:S,b,v,}" must not contain a dollar. +make: "varmod-loop-varname.mk" line 13: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+") +make: "varmod-loop-varname.mk" line 80: In the :@ modifier of "1 2 3", the variable name "v$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 80: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)") +make: "varmod-loop-varname.mk" line 85: In the :@ modifier of "1 2 3", the variable name "v$$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 85: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()") +make: "varmod-loop-varname.mk" line 90: In the :@ modifier of "1 2 3", the variable name "v$$$" must not contain a dollar. +make: "varmod-loop-varname.mk" line 90: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()") +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/unit-tests/varmod-loop-varname.mk b/unit-tests/varmod-loop-varname.mk new file mode 100644 index 000000000000..d51e2ba76a42 --- /dev/null +++ b/unit-tests/varmod-loop-varname.mk @@ -0,0 +1,127 @@ +# $NetBSD: varmod-loop-varname.mk,v 1.2 2021/04/04 13:35:26 rillig Exp $ +# +# Tests for the first part of the variable modifier ':@var@...@', which +# contains the variable name to use during the loop. + +.MAKE.SAVE_DOLLARS= yes + + +# Before 2021-04-04, the name of the loop variable could be generated +# dynamically. There was no practical use-case for this. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if ${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+" +. error +.endif + + +# ":::" is a very creative variable name, unlikely to occur in practice. +# The expression ${\:\:\:} would not work since backslashes can only +# be escaped in the modifiers, but not in the variable name, therefore +# the extra indirection via the modifier ':U'. +.if ${:U1 2 3:@:::@x${${:U\:\:\:}}y@} != "x1y x2y x3y" +. error +.endif + + +# "@@" is another creative variable name. +.if ${:U1 2 3:@\@\@@x${@@}y@} != "x1y x2y x3y" +. error +.endif + + +# In extreme cases, even the backslash can be used as variable name. +# It needs to be doubled though. +.if ${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@} != "x1y x2y x3y" +. error +.endif + + +# The variable name can technically be empty, and in this situation +# the variable value cannot be accessed since the empty "variable" +# is protected to always return an empty string. +.if ${:U1 2 3:@@x${}y@} != "xy xy xy" +. error +.endif + + +# The :@ modifier resolves the variables from the replacement text once more +# than expected. In particular, it resolves _all_ variables from the scope, +# and not only the loop variable (in this case v). +SRCS= source +CFLAGS.source= before +ALL_CFLAGS:= ${SRCS:@src@${CFLAGS.${src}}@} # note the ':=' +CFLAGS.source+= after +.if ${ALL_CFLAGS} != "before" +. error +.endif + + +# In the following example, the modifier ':@' expands the '$$' to '$'. This +# means that when the resulting expression is evaluated, these resulting '$' +# will be interpreted as starting a subexpression. +# +# The d means direct reference, the i means indirect reference. +RESOLVE= ${RES1} $${RES1} +RES1= 1d${RES2} 1i$${RES2} +RES2= 2d${RES3} 2i$${RES3} +RES3= 3 + +.if ${RESOLVE:@v@w${v}w@} != "w1d2d3w w2i3w w1i2d3 2i\${RES3}w w1d2d3 2i\${RES3} 1i\${RES2}w" +. error +.endif + + +# Until 2020-07-20, the variable name of the :@ modifier could end with one +# or two dollar signs, which were silently ignored. +# There's no point in allowing a dollar sign in that position. +# Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the +# variable name. +.if ${1 2 3:L:@v$@($v)@} != "(1) (2) (3)" +. error +.else +. error +.endif +.if ${1 2 3:L:@v$$@($v)@} != "() () ()" +. error +.else +. error +.endif +.if ${1 2 3:L:@v$$$@($v)@} != "() () ()" +. error +.else +. error +.endif + + +# It may happen that there are nested :@ modifiers that use the same name for +# for the loop variable. These modifiers influence each other. +# +# As of 2020-10-18, the :@ modifier is implemented by actually setting a +# variable in the scope of the expression and deleting it again after the +# loop. This is different from the .for loops, which substitute the variable +# expression with ${:Uvalue}, leading to different unwanted side effects. +# +# To make the behavior more predictable, the :@ modifier should restore the +# loop variable to the value it had before the loop. This would result in +# the string "1a b c1 2a b c2 3a b c3", making the two loops independent. +.if ${:U1 2 3:@i@$i${:Ua b c:@i@$i@}${i:Uu}@} != "1a b cu 2a b cu 3a b cu" +. error +.endif + +# During the loop, the variable is actually defined and nonempty. +# If the loop were implemented in the same way as the .for loop, the variable +# would be neither defined nor nonempty since all expressions of the form +# ${var} would have been replaced with ${:Uword} before evaluating them. +.if defined(var) +. error +.endif +.if ${:Uword:@var@${defined(var):?def:undef} ${empty(var):?empty:nonempty}@} \ + != "def nonempty" +. error +.endif +.if defined(var) +. error +.endif + +all: .PHONY diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp index 66cfd6f51e16..a4704973f6e2 100644 --- a/unit-tests/varmod-loop.exp +++ b/unit-tests/varmod-loop.exp @@ -1,21 +1,12 @@ -ParseReadLine (117): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$' +ParseReadLine (75): '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}' +ParseReadLine (80): '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: -:mod-loop-varname: :x1y x2y x3y: :: -:x1y x2y x3y: -empty: :xy xy xy: -mod-loop-resolve:w1d2d3w w2i3w w1i2d3 2i${RES3}w w1d2d3 2i${RES3} 1i${RES2}w: -mod-loop-varname-dollar:(1) (2) (3). -mod-loop-varname-dollar:() () (). -mod-loop-varname-dollar:() () (). +ParseReadLine (105): '.MAKEFLAGS: -d0' +ParseDependency(.MAKEFLAGS: -d0) +:varname-overwriting-target: :x1y x2y x3y: :: mod-loop-dollar:1: mod-loop-dollar:${word}$: mod-loop-dollar:$3$: diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk index c109c775a492..4fdaa3ff4e61 100644 --- a/unit-tests/varmod-loop.mk +++ b/unit-tests/varmod-loop.mk @@ -1,63 +1,21 @@ -# $NetBSD: varmod-loop.mk,v 1.9 2021/02/04 21:42:47 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.15 2021/04/11 13:35:56 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 +all: varname-overwriting-target all: mod-loop-dollar -# In the :@ modifier, the name of the loop variable can even be generated -# dynamically. There's no practical use-case for this, and hopefully nobody -# will ever depend on this, but technically it's possible. -# Therefore, in -dL mode, this is forbidden, see lint.mk. -mod-loop-varname: - @echo :${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@:Q}: - - # ":::" is a very creative variable name, unlikely in practice. - # The expression ${\:\:\:} would not work since backslashes can only - # be escaped in the modifiers, but not in the variable name. - @echo :${:U1 2 3:@:::@x${${:U\:\:\:}}y@}: - - # "@@" is another creative variable name. - @echo :${:U1 2 3:@\@\@@x${@@}y@}: - +varname-overwriting-target: # Even "@" works as a variable name since the variable is installed # in the "current" scope, which in this case is the one from the - # target. + # target. Because of this, after the loop has finished, '$@' is + # undefined. This is something that make doesn't expect, this may + # even trigger an assertion failure somewhere. @echo :$@: :${:U1 2 3:@\@@x${@}y@}: :$@: - # In extreme cases, even the backslash can be used as variable name. - # It needs to be doubled though. - @echo :${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@}: - - # The variable name can technically be empty, and in this situation - # the variable value cannot be accessed since the empty variable is - # protected to always return an empty string. - @echo empty: :${:U1 2 3:@@x${}y@}: -# The :@ modifier resolves the variables a little more often than expected. -# In particular, it resolves _all_ variables from the scope, and not only -# the loop variable (in this case v). -# -# The d means direct reference, the i means indirect reference. -RESOLVE= ${RES1} $${RES1} -RES1= 1d${RES2} 1i$${RES2} -RES2= 2d${RES3} 2i$${RES3} -RES3= 3 - -mod-loop-resolve: - @echo $@:${RESOLVE:@v@w${v}w@:Q}: - -# Until 2020-07-20, the variable name of the :@ modifier could end with one -# or two dollar signs, which were silently ignored. -# There's no point in allowing a dollar sign in that position. -mod-loop-varname-dollar: - @echo $@:${1 2 3:L:@v$@($v)@:Q}. - @echo $@:${1 2 3:L:@v$$@($v)@:Q}. - @echo $@:${1 2 3:L:@v$$$@($v)@:Q}. # Demonstrate that it is possible to generate dollar signs using the # :@ modifier. @@ -109,9 +67,9 @@ mod-loop-dollar: # 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. +# prevented by VARE_EVAL_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} $$$$$$$$ @@ -120,20 +78,20 @@ USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ .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 ':=' assignment operator evaluates the variable value using the mode +# VARE_KEEP_DOLLAR_UNDEF, 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 +# in ApplyModifier_Loop, the flag keepDollar 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. +# modifier and are thus expanded with the flag keepDollar in action. # The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value # "$$$$ $$$$$$$$ $$$$$$$$". # @@ -145,3 +103,87 @@ SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} . error .endif .MAKEFLAGS: -d0 + +# After looping over the words of the expression, the loop variable gets +# undefined. The modifier ':@' uses an ordinary global variable for this, +# which is different from the '.for' loop, which replaces ${var} with +# ${:Uvalue} in the body of the loop. This choice of implementation detail +# can be used for a nasty side effect. The expression ${:U:@VAR@@} evaluates +# to an empty string, plus it undefines the variable 'VAR'. This is the only +# possibility to undefine a global variable during evaluation. +GLOBAL= before-global +RESULT:= ${:U${GLOBAL} ${:U:@GLOBAL@@} ${GLOBAL:Uundefined}} +.if ${RESULT} != "before-global undefined" +. error +.endif + +# The above side effect of undefining a variable from a certain scope can be +# further combined with the otherwise undocumented implementation detail that +# the argument of an '.if' directive is evaluated in cmdline scope. Putting +# these together makes it possible to undefine variables from the cmdline +# scope, something that is not possible in a straight-forward way. +.MAKEFLAGS: CMDLINE=cmdline +.if ${:U${CMDLINE}${:U:@CMDLINE@@}} != "cmdline" +. error +.endif +# Now the cmdline variable got undefined. +.if ${CMDLINE} != "cmdline" +. error +.endif +# At this point, it still looks as if the cmdline variable were defined, +# since the value of CMDLINE is still "cmdline". That impression is only +# superficial though, the cmdline variable is actually deleted. To +# demonstrate this, it is now possible to override its value using a global +# variable, something that was not possible before: +CMDLINE= global +.if ${CMDLINE} != "global" +. error +.endif +# Now undefine that global variable again, to get back to the original value. +.undef CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# What actually happened is that when CMDLINE was set by the '.MAKEFLAGS' +# target in the cmdline scope, that same variable was exported to the +# environment, see Var_SetWithFlags. +.unexport CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# The above '.unexport' has no effect since UnexportVar requires a global +# variable of the same name to be defined, otherwise nothing is unexported. +CMDLINE= global +.unexport CMDLINE +.undef CMDLINE +.if ${CMDLINE} != "cmdline" +. error +.endif +# This still didn't work since there must not only be a global variable, the +# variable must be marked as exported as well, which it wasn't before. +CMDLINE= global +.export CMDLINE +.unexport CMDLINE +.undef CMDLINE +.if ${CMDLINE:Uundefined} != "undefined" +. error +.endif +# Finally the variable 'CMDLINE' from the cmdline scope is gone, and all its +# traces from the environment are gone as well. To do that, a global variable +# had to be defined and exported, something that is far from obvious. To +# recap, here is the essence of the above story: +.MAKEFLAGS: CMDLINE=cmdline # have a cmdline + environment variable +.if ${:U:@CMDLINE@@}} # undefine cmdline, keep environment +.endif +CMDLINE= global # needed for deleting the environment +.export CMDLINE # needed for deleting the environment +.unexport CMDLINE # delete the environment +.undef CMDLINE # delete the global helper variable +.if ${CMDLINE:Uundefined} != "undefined" +. error # 'CMDLINE' is gone now from all scopes +.endif + + +# TODO: Actually trigger the undefined behavior (use after free) that was +# already suspected in Var_Parse, in the comment 'the value of the variable +# must not change'. diff --git a/unit-tests/varmod-match-escape.exp b/unit-tests/varmod-match-escape.exp index 30c148075524..42cdd7a87ac9 100755 --- a/unit-tests/varmod-match-escape.exp +++ b/unit-tests/varmod-match-escape.exp @@ -1,60 +1,38 @@ -Global:SPECIALS = \: : \\ * \* +Global: SPECIALS = \: : \\ * \* CondParser_Eval: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} -Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} with VARE_UNDEFERR|VARE_WANTRES -Applying ${SPECIALS:M...} to "\: : \\ * \*" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U}\: with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[SPECIALS] for [\: : \\ * \*] is [\:] +Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined) +Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" +Pattern for ':M' is "\:" ModifyWords: split "\: : \\ * \*" into 5 words -VarMatch [\:] [\:] -VarMatch [:] [\:] -VarMatch [\\] [\:] -VarMatch [*] [\:] -VarMatch [\*] [\:] -Result of ${SPECIALS:M${:U}\:} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${SPECIALS:M\:${:U}} with VARE_UNDEFERR|VARE_WANTRES -Applying ${SPECIALS:M...} to "\: : \\ * \*" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[SPECIALS] for [\: : \\ * \*] is [:] +Result of ${SPECIALS:M${:U}\:} is ":" +Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined) +Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" +Pattern for ':M' is ":" ModifyWords: split "\: : \\ * \*" into 5 words -VarMatch [\:] [:] -VarMatch [:] [:] -VarMatch [\\] [:] -VarMatch [*] [:] -VarMatch [\*] [:] -Result of ${SPECIALS:M\:${:U}} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) +Result of ${SPECIALS:M\:${:U}} is ":" lhs = ":", rhs = ":", op = != -Global:VALUES = : :: :\: +Global: VALUES = : :: :\: CondParser_Eval: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} -Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VALUES:M...} to ": :: :\:" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[VALUES] for [: :: :\:] is [:] +Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined) +Evaluating modifier ${VALUES:M...} on value ": :: :\:" +Var_Parse: ${:U:} (eval-defined) +Evaluating modifier ${:U} on value "" (eval-defined, undefined) +Result of ${:U} is "" (eval-defined, defined) +Pattern for ':M' is ":" ModifyWords: split ": :: :\:" into 3 words -VarMatch [:] [:] -VarMatch [::] [:] -VarMatch [:\:] [:] -Result of ${VALUES:M\:${:U\:}} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${VALUES:M${:U\:}\:} with VARE_UNDEFERR|VARE_WANTRES -Applying ${VALUES:M...} to ": :: :\:" (VARE_UNDEFERR|VARE_WANTRES, none, none) -Var_Parse: ${:U\:}\: with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:U\:} is ":" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Pattern[VALUES] for [: :: :\:] is [:\:] +Result of ${VALUES:M\:${:U\:}} is ":" +Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined) +Evaluating modifier ${VALUES:M...} on value ": :: :\:" +Var_Parse: ${:U\:}\: (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:U\:} is ":" (eval-defined, defined) +Pattern for ':M' is ":\:" ModifyWords: split ": :: :\:" into 3 words -VarMatch [:] [:\:] -VarMatch [::] [:\:] -VarMatch [:\:] [:\:] -Result of ${VALUES:M${:U\:}\:} is "::" (VARE_UNDEFERR|VARE_WANTRES, none, none) +Result of ${VALUES:M${:U\:}\:} is "::" lhs = ":", rhs = "::", op = != make: "varmod-match-escape.mk" line 42: warning: XXX: Oops -Global:.MAKEFLAGS = -r -k -d cv -d -Global:.MAKEFLAGS = -r -k -d cv -d 0 +Global: .MAKEFLAGS = -r -k -d cv -d +Global: .MAKEFLAGS = -r -k -d cv -d 0 make: "varmod-match-escape.mk" line 67: Dollar followed by nothing make: Fatal errors encountered -- cannot continue make: stopped in unit-tests diff --git a/unit-tests/varmod-match-escape.mk b/unit-tests/varmod-match-escape.mk index e62fbe8352b7..5ac69f964a68 100755 --- a/unit-tests/varmod-match-escape.mk +++ b/unit-tests/varmod-match-escape.mk @@ -1,8 +1,8 @@ -# $NetBSD: varmod-match-escape.mk,v 1.6 2021/02/01 22:36:28 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.7 2021/04/03 11:08:40 rillig Exp $ # # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently, # depending on whether there was a variable expression somewhere before the -# first backslash or not. See ApplyModifier_Match, "copy = TRUE". +# first backslash or not. See ApplyModifier_Match, "copy = true". # # Apart from the different and possibly confusing debug output, there is no # difference in behavior. When parsing the modifier text, only \{, \} and \: @@ -29,8 +29,8 @@ SPECIALS= \: : \\ * \* # # XXX: As of 2020-11-01, the modifier on the right-hand side of the # comparison is parsed differently though. First, the variable expression -# is parsed, resulting in ':' and needSubst=TRUE. After that, the escaped -# ':' is seen, and this time, copy=TRUE is not executed but stays copy=FALSE. +# is parsed, resulting in ':' and needSubst=true. After that, the escaped +# ':' is seen, and this time, copy=true is not executed but stays copy=false. # Therefore the escaped ':' is kept as-is, and the final pattern becomes # ':\:'. # diff --git a/unit-tests/varmod-order.exp b/unit-tests/varmod-order.exp index 99d1d6ef164c..94c3cb694886 100644 --- a/unit-tests/varmod-order.exp +++ b/unit-tests/varmod-order.exp @@ -1,6 +1,6 @@ -make: Bad modifier `:OX' for NUMBERS +make: Bad modifier ":OX" for variable "NUMBERS" make: "varmod-order.mk" line 13: Undefined variable "${NUMBERS:OX" -make: Bad modifier `:OxXX' for NUMBERS +make: Bad modifier ":OxXX" for variable "NUMBERS" make: "varmod-order.mk" line 16: Undefined variable "${NUMBERS:Ox" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests diff --git a/unit-tests/varmod-range.exp b/unit-tests/varmod-range.exp index 3a9d4d032c3a..f4ada11ebde6 100644 --- a/unit-tests/varmod-range.exp +++ b/unit-tests/varmod-range.exp @@ -1,12 +1,12 @@ -make: "varmod-range.mk" line 53: Invalid number: x}Rest" != "Rest" +make: "varmod-range.mk" line 53: Invalid number "x}Rest" != "Rest"" for ':range' modifier make: "varmod-range.mk" line 53: Malformed conditional ("${:U:range=x}Rest" != "Rest") -make: "varmod-range.mk" line 62: Unknown modifier 'x' +make: "varmod-range.mk" line 62: Unknown modifier "x0" make: "varmod-range.mk" line 62: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") -make: "varmod-range.mk" line 78: Unknown modifier 'r' +make: "varmod-range.mk" line 78: Unknown modifier "rang" make: "varmod-range.mk" line 78: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") -make: "varmod-range.mk" line 85: Unknown modifier 'r' +make: "varmod-range.mk" line 85: Unknown modifier "rango" make: "varmod-range.mk" line 85: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") -make: "varmod-range.mk" line 92: Unknown modifier 'r' +make: "varmod-range.mk" line 92: Unknown modifier "ranger" make: "varmod-range.mk" line 92: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests diff --git a/unit-tests/varmod-remember.exp b/unit-tests/varmod-remember.exp index 448f817d8969..39a9383953dd 100644 --- a/unit-tests/varmod-remember.exp +++ b/unit-tests/varmod-remember.exp @@ -1,3 +1 @@ -1 2 3 1 2 3 1 2 3 -1 2 3, SAVED=3 exit status 0 diff --git a/unit-tests/varmod-remember.mk b/unit-tests/varmod-remember.mk index 68eb96a122c4..403811759672 100644 --- a/unit-tests/varmod-remember.mk +++ b/unit-tests/varmod-remember.mk @@ -1,12 +1,35 @@ -# $NetBSD: varmod-remember.mk,v 1.3 2020/08/23 15:18:43 rillig Exp $ +# $NetBSD: varmod-remember.mk,v 1.6 2021/03/14 17:27:27 rillig Exp $ # # Tests for the :_ modifier, which saves the current variable value # in the _ variable or another, to be used later again. +.if ${1 2 3:L:_:@var@${_}@} != "1 2 3 1 2 3 1 2 3" +. error +.endif + # In the parameterized form, having the variable name on the right side of # the = assignment operator is confusing. In almost all other situations # the variable name is on the left-hand side of the = operator. Luckily # this modifier is only rarely needed. +.if ${1 2 3:L:@var@${var:_=SAVED:}@} != "1 2 3" +. error +.elif ${SAVED} != "3" +. error +.endif + +# The ':_' modifier takes a variable name as optional argument. This variable +# name can refer to other variables, though this was rather an implementation +# oversight than an intended feature. The variable name stops at the first +# '}' or ')' and thus cannot use the usual form ${VARNAME} of long variable +# names. +# +# Because of all these edge-casey conditions, this "feature" has been removed +# in var.c 1.867 from 2021-03-14. +S= INDIRECT_VARNAME +.if ${value:L:@var@${var:_=$S}@} != "value" +. error +.elif defined(INDIRECT_VARNAME) +. error +.endif + all: - @echo ${1 2 3:L:_:@var@${_}@} - @echo ${1 2 3:L:@var@${var:_=SAVED:}@}, SAVED=${SAVED} diff --git a/unit-tests/varmod-shell.mk b/unit-tests/varmod-shell.mk index db82e302f2a8..c736042f80a0 100644 --- a/unit-tests/varmod-shell.mk +++ b/unit-tests/varmod-shell.mk @@ -1,15 +1,13 @@ -# $NetBSD: varmod-shell.mk,v 1.5 2020/11/17 20:11:02 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.6 2021/02/14 20:16:17 rillig Exp $ # -# Tests for the :sh variable modifier, which runs the shell command -# given by the variable value and returns its output. +# Tests for the ':!cmd!' variable modifier, which runs the shell command +# given by the variable modifier 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. @@ -32,4 +30,3 @@ .endif all: - @:; diff --git a/unit-tests/varmod-subst-regex.exp b/unit-tests/varmod-subst-regex.exp index 207a97fc25e8..a09046ef764c 100644 --- a/unit-tests/varmod-subst-regex.exp +++ b/unit-tests/varmod-subst-regex.exp @@ -20,6 +20,27 @@ mod-regex-limits:22-ok:1 33 556 mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest make: Regex compilation error: (details omitted) mod-regex-errors: -make: Unknown modifier 'Z' +make: Unknown modifier "Z" mod-regex-errors: xy -exit status 0 +unmatched-subexpression.ok: one one 2 3 5 8 one3 2one 34 +make: No match for subexpression \2 +unmatched-subexpression.1: ()() +make: No match for subexpression \2 +unmatched-subexpression.1: ()() +make: No match for subexpression \1 +unmatched-subexpression.2: ()() +unmatched-subexpression.3: 3 +unmatched-subexpression.5: 5 +unmatched-subexpression.8: 8 +make: No match for subexpression \2 +unmatched-subexpression.13: (3)() +make: No match for subexpression \1 +unmatched-subexpression.21: ()(1) +unmatched-subexpression.34: 34 +make: No match for subexpression \2 +make: No match for subexpression \2 +make: No match for subexpression \1 +make: No match for subexpression \2 +make: No match for subexpression \1 +unmatched-subexpression.all: ()() ()() ()() 3 5 8 (3)() ()(1) 34 +exit status 2 diff --git a/unit-tests/varmod-subst-regex.mk b/unit-tests/varmod-subst-regex.mk index 91b2f0e6a2f9..197691d73aad 100644 --- a/unit-tests/varmod-subst-regex.mk +++ b/unit-tests/varmod-subst-regex.mk @@ -1,10 +1,14 @@ -# $NetBSD: varmod-subst-regex.mk,v 1.6 2020/12/05 18:13:44 rillig Exp $ +# $NetBSD: varmod-subst-regex.mk,v 1.7 2021/06/21 08:17:39 rillig Exp $ # # Tests for the :C,from,to, variable modifier. +# report unmatched subexpressions +.MAKEFLAGS: -dL + all: mod-regex-compile-error all: mod-regex-limits all: mod-regex-errors +all: unmatched-subexpression # The variable expression expands to 4 words. Of these words, none matches # the regular expression "a b" since these words don't contain any @@ -107,3 +111,51 @@ mod-regex-errors: # unknown modifier, the parse error is ignored in ParseModifierPart # and the faulty variable expression expands to "". @echo $@: ${word:L:C,.*,x${:U:Z}y,W} + +# In regular expressions with alternatives, not all capturing groups are +# always set; some may be missing. Make calls these "unmatched +# subexpressions". +# +# Between var.c 1.16 from 1996-12-24 until before var.c 1.933 from 2021-06-21, +# unmatched subexpressions produced an "error message" but did not have any +# further effect since the "error handling" didn't influence the exit status. +# +# Before 2021-06-21 there was no way to turn off this warning, thus the +# combination of alternative matches and capturing groups was seldom used, if +# at all. +# +# Since var.c 1.933 from 2021-06-21, the error message is only printed in lint +# mode (-dL), but not in default mode. +# +# As an alternative to the change from var.c 1.933 from 2021-06-21, a possible +# mitigation would have been to add a new modifier 'U' to the already existing +# '1Wg' modifiers of the ':C' modifier. That modifier could have been used in +# the modifier ':C,(a.)|(b.),\1\2,U' to treat unmatched subexpressions as +# empty. This approach would have created a syntactical ambiguity since the +# modifiers ':S' and ':C' are open-ended (see mod-subst-chain), that is, they +# do not need to be followed by a ':' to separate them from the next modifier. +# Luckily the modifier :U does not make sense after :C, therefore this case +# does not happen in practice. +unmatched-subexpression: + # In each of the following cases, if the regular expression matches at + # all, the subexpression \1 matches as well. + @echo $@.ok: ${:U1 1 2 3 5 8 13 21 34:C,1(.*),one\1,} + + # In the following cases: + # * The subexpression \1 is only defined for 1 and 13. + # * The subexpression \2 is only defined for 2 and 21. + # * If the regular expression does not match at all, the + # replacement string is not analyzed, thus no error messages. + # In total, there are 5 error messages about unmatched subexpressions. + @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.2: ${:U 2:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 + @echo $@.3: ${:U 3:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.5: ${:U 5:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.8: ${:U 8:C,1(.*)|2(.*),(\1)(\2),:Q} + @echo $@.13: ${:U 13:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 + @echo $@.21: ${:U 21:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 + @echo $@.34: ${:U 34:C,1(.*)|2(.*),(\1)(\2),:Q} + + # And now all together: 5 error messages for 1, 1, 2, 13, 21. + @echo $@.all: ${:U1 1 2 3 5 8 13 21 34:C,1(.*)|2(.*),(\1)(\2),:Q} diff --git a/unit-tests/varmod-subst.exp b/unit-tests/varmod-subst.exp index 3122c17b1ed3..97fa2e4f1491 100644 --- a/unit-tests/varmod-subst.exp +++ b/unit-tests/varmod-subst.exp @@ -45,7 +45,7 @@ mod-subst-delimiter: 1 two 3 tilde mod-subst-chain: A B c. -make: Unknown modifier 'i' +make: Unknown modifier "i" . mod-subst-dollar:$1: mod-subst-dollar:$2: diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk index 3c3ee673c07a..85f41e499ab7 100644 --- a/unit-tests/varmod-subst.mk +++ b/unit-tests/varmod-subst.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.8 2021/05/14 19:37:16 rillig Exp $ # # Tests for the :S,from,to, variable modifier. @@ -78,6 +78,14 @@ WORDS= sequences of letters . warning The :S modifier matches a too long suffix anchored at both ends. .endif +.if ${WORDS:S,*,replacement,} != ${WORDS} +. error The '*' seems to be interpreted as a wildcard of some kind. +.endif + +.if ${WORDS:S,.,replacement,} != ${WORDS} +. error The '.' seems to be interpreted as a wildcard of some kind. +.endif + mod-subst: @echo $@: @echo :${:Ua b b c:S,a b,,:Q}: diff --git a/unit-tests/varmod-sun-shell.exp b/unit-tests/varmod-sun-shell.exp new file mode 100644 index 000000000000..5087bc66d943 --- /dev/null +++ b/unit-tests/varmod-sun-shell.exp @@ -0,0 +1,2 @@ +make: "echo word; false" returned non-zero status +exit status 0 diff --git a/unit-tests/varmod-sun-shell.mk b/unit-tests/varmod-sun-shell.mk new file mode 100644 index 000000000000..712b36bc7030 --- /dev/null +++ b/unit-tests/varmod-sun-shell.mk @@ -0,0 +1,21 @@ +# $NetBSD: varmod-sun-shell.mk,v 1.1 2021/02/14 20:16:17 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 1996-05-29. +# +# See also: +# ApplyModifier_SunShell + +.if ${echo word:L:sh} != "word" +. error +.endif + +# If the command exits with non-zero, an error message is printed. +# XXX: Processing continues as usual though. +.if ${echo word; false:L:sh} != "word" +. error +.endif + +all: diff --git a/unit-tests/varmod-sysv.exp b/unit-tests/varmod-sysv.exp index 57e69a667281..59275857f98a 100644 --- a/unit-tests/varmod-sysv.exp +++ b/unit-tests/varmod-sysv.exp @@ -1,5 +1,150 @@ -make: Unfinished modifier for word214 ('=' missing) +make: Unfinished modifier for "word214" ('=' missing) make: "varmod-sysv.mk" line 214: Malformed conditional (${word214:L:from${:D=}to}) +word modifier result +'' = "" +suffix = "suffix" +prefix = "prefix" +pre-middle-suffix = "pre-middle-suffix" +'' =NS "" +suffix =NS "suffixNS" +prefix =NS "prefixNS" +pre-middle-suffix =NS "pre-middle-suffixNS" +'' =% "" +suffix =% "suffix%" +prefix =% "prefix%" +pre-middle-suffix =% "pre-middle-suffix%" +'' =%NS "" +suffix =%NS "suffix%NS" +prefix =%NS "prefix%NS" +pre-middle-suffix =%NS "pre-middle-suffix%NS" +'' =NPre% "" +suffix =NPre% "suffixNPre%" +prefix =NPre% "prefixNPre%" +pre-middle-suffix =NPre% "pre-middle-suffixNPre%" +'' =NPre%NS "" +suffix =NPre%NS "suffixNPre%NS" +prefix =NPre%NS "prefixNPre%NS" +pre-middle-suffix =NPre%NS "pre-middle-suffixNPre%NS" +'' ffix= "" +suffix ffix= "su" +prefix ffix= "prefix" +pre-middle-suffix ffix= "pre-middle-su" +'' ffix=NS "" +suffix ffix=NS "suNS" +prefix ffix=NS "prefix" +pre-middle-suffix ffix=NS "pre-middle-suNS" +'' ffix=% "" +suffix ffix=% "su%" +prefix ffix=% "prefix" +pre-middle-suffix ffix=% "pre-middle-su%" +'' ffix=%NS "" +suffix ffix=%NS "su%NS" +prefix ffix=%NS "prefix" +pre-middle-suffix ffix=%NS "pre-middle-su%NS" +'' ffix=NPre% "" +suffix ffix=NPre% "suNPre%" +prefix ffix=NPre% "prefix" +pre-middle-suffix ffix=NPre% "pre-middle-suNPre%" +'' ffix=NPre%NS "" +suffix ffix=NPre%NS "suNPre%NS" +prefix ffix=NPre%NS "prefix" +pre-middle-suffix ffix=NPre%NS "pre-middle-suNPre%NS" +'' %= "" +suffix %= "" +prefix %= "" +pre-middle-suffix %= "" +'' %=NS "" +suffix %=NS "NS" +prefix %=NS "NS" +pre-middle-suffix %=NS "NS" +'' %=% "" +suffix %=% "suffix" +prefix %=% "prefix" +pre-middle-suffix %=% "pre-middle-suffix" +'' %=%NS "" +suffix %=%NS "suffixNS" +prefix %=%NS "prefixNS" +pre-middle-suffix %=%NS "pre-middle-suffixNS" +'' %=NPre% "" +suffix %=NPre% "NPresuffix" +prefix %=NPre% "NPreprefix" +pre-middle-suffix %=NPre% "NPrepre-middle-suffix" +'' %=NPre%NS "" +suffix %=NPre%NS "NPresuffixNS" +prefix %=NPre%NS "NPreprefixNS" +pre-middle-suffix %=NPre%NS "NPrepre-middle-suffixNS" +'' pre%= "" +suffix pre%= "suffix" +prefix pre%= "" +pre-middle-suffix pre%= "" +'' pre%=NS "" +suffix pre%=NS "suffix" +prefix pre%=NS "NS" +pre-middle-suffix pre%=NS "NS" +'' pre%=% "" +suffix pre%=% "suffix" +prefix pre%=% "fix" +pre-middle-suffix pre%=% "-middle-suffix" +'' pre%=%NS "" +suffix pre%=%NS "suffix" +prefix pre%=%NS "fixNS" +pre-middle-suffix pre%=%NS "-middle-suffixNS" +'' pre%=NPre% "" +suffix pre%=NPre% "suffix" +prefix pre%=NPre% "NPrefix" +pre-middle-suffix pre%=NPre% "NPre-middle-suffix" +'' pre%=NPre%NS "" +suffix pre%=NPre%NS "suffix" +prefix pre%=NPre%NS "NPrefixNS" +pre-middle-suffix pre%=NPre%NS "NPre-middle-suffixNS" +'' %ffix= "" +suffix %ffix= "" +prefix %ffix= "prefix" +pre-middle-suffix %ffix= "" +'' %ffix=NS "" +suffix %ffix=NS "NS" +prefix %ffix=NS "prefix" +pre-middle-suffix %ffix=NS "NS" +'' %ffix=% "" +suffix %ffix=% "su" +prefix %ffix=% "prefix" +pre-middle-suffix %ffix=% "pre-middle-su" +'' %ffix=%NS "" +suffix %ffix=%NS "suNS" +prefix %ffix=%NS "prefix" +pre-middle-suffix %ffix=%NS "pre-middle-suNS" +'' %ffix=NPre% "" +suffix %ffix=NPre% "NPresu" +prefix %ffix=NPre% "prefix" +pre-middle-suffix %ffix=NPre% "NPrepre-middle-su" +'' %ffix=NPre%NS "" +suffix %ffix=NPre%NS "NPresuNS" +prefix %ffix=NPre%NS "prefix" +pre-middle-suffix %ffix=NPre%NS "NPrepre-middle-suNS" +'' pre%ffix= "" +suffix pre%ffix= "suffix" +prefix pre%ffix= "prefix" +pre-middle-suffix pre%ffix= "" +'' pre%ffix=NS "" +suffix pre%ffix=NS "suffix" +prefix pre%ffix=NS "prefix" +pre-middle-suffix pre%ffix=NS "NS" +'' pre%ffix=% "" +suffix pre%ffix=% "suffix" +prefix pre%ffix=% "prefix" +pre-middle-suffix pre%ffix=% "-middle-su" +'' pre%ffix=%NS "" +suffix pre%ffix=%NS "suffix" +prefix pre%ffix=%NS "prefix" +pre-middle-suffix pre%ffix=%NS "-middle-suNS" +'' pre%ffix=NPre% "" +suffix pre%ffix=NPre% "suffix" +prefix pre%ffix=NPre% "prefix" +pre-middle-suffix pre%ffix=NPre% "NPre-middle-su" +'' pre%ffix=NPre%NS "" +suffix pre%ffix=NPre%NS "suffix" +prefix pre%ffix=NPre%NS "prefix" +pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-sysv.mk b/unit-tests/varmod-sysv.mk index 751736ceaf74..712c1731717b 100644 --- a/unit-tests/varmod-sysv.mk +++ b/unit-tests/varmod-sysv.mk @@ -1,13 +1,13 @@ -# $NetBSD: varmod-sysv.mk,v 1.12 2020/12/05 13:01:33 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.14 2021/04/12 16:09:57 rillig Exp $ # -# Tests for the ${VAR:from=to} variable modifier, which replaces the suffix +# Tests for the variable modifier ':from=to', which replaces the suffix # "from" with "to". It can also use '%' as a wildcard. # # This modifier is applied when the other modifiers don't match exactly. # # See ApplyModifier_SysV. -# A typical use case for the :from=to modifier is conversion of filename +# A typical use case for the modifier ':from=to' is conversion of filename # extensions. .if ${src.c:L:.c=.o} != "src.o" . error @@ -23,43 +23,43 @@ . error .endif -# The :from=to modifier is therefore often combined with the :M modifier. +# The modifier ':from=to' is therefore often combined with the modifier ':M'. .if ${src.c src.h:L:M*.c:.c=.o} != "src.o" . error .endif -# Another use case for the :from=to modifier is to append a suffix to each +# Another use case for the modifier ':from=to' is to append a suffix to each # word. In this case, the "from" string is empty, therefore it always -# matches. The same effect can be achieved with the :S,$,teen, modifier. +# matches. The same effect can be achieved with the modifier ':S,$,teen,'. .if ${four six seven nine:L:=teen} != "fourteen sixteen seventeen nineteen" . error .endif -# The :from=to modifier can also be used to surround each word by strings. +# The modifier ':from=to' can also be used to surround each word by strings. # It might be tempting to use this for enclosing a string in quotes for the -# shell, but that's the job of the :Q modifier. +# shell, but that's the job of the modifier ':Q'. .if ${one two three:L:%=(%)} != "(one) (two) (three)" . error .endif -# When the :from=to modifier is parsed, it lasts until the closing brace -# or parenthesis. The :Q in the below expression may look like a modifier -# but isn't. It is part of the replacement string. +# When the modifier ':from=to' is parsed, it lasts until the closing brace +# or parenthesis. The ':Q' in the below expression may look like a modifier +# but it isn't. It is part of the replacement string. .if ${a b c d e:L:%a=x:Q} != "x:Q b c d e" . error .endif -# In the :from=to modifier, both parts can contain variable expressions. +# In the modifier ':from=to', both parts can contain variable expressions. .if ${one two:L:${:Uone}=${:U1}} != "1 two" . error .endif -# In the :from=to modifier, the "from" part is expanded exactly once. +# In the modifier ':from=to', the "from" part is expanded exactly once. .if ${:U\$ \$\$ \$\$\$\$:${:U\$\$\$\$}=4} != "\$ \$\$ 4" . error .endif -# In the :from=to modifier, the "to" part is expanded exactly twice. +# In the modifier ':from=to', the "to" part is expanded exactly twice. # XXX: The right-hand side should be expanded only once. # XXX: It's hard to get the escaping correct here, and to read that. # XXX: It's not intuitive why the closing brace must be escaped but not @@ -75,7 +75,7 @@ .endif # If the variable value is empty, it is debatable whether it consists of a -# single empty word, or no word at all. The :from=to modifier treats it as +# single empty word, or no word at all. The modifier ':from=to' treats it as # no word at all. # # See SysVMatch, which doesn't handle w_len == p_len specially. @@ -93,10 +93,10 @@ # Before 2020-07-19, an ampersand could be used in the replacement part # of a SysV substitution modifier, and it was replaced with the whole match, -# just like in the :S modifier. +# just like in the modifier ':S'. # # This was probably a copy-and-paste mistake since the code for the SysV -# modifier looked a lot like the code for the :S and :C modifiers. +# modifier looked a lot like the code for the modifiers ':S' and ':C'. # The ampersand is not mentioned in the manual page. .if ${a.bcd.e:L:a.%=%} != "bcd.e" . error @@ -109,14 +109,14 @@ # Before 2020-07-20, when a SysV modifier was parsed, a single dollar # before the '=' was parsed (but not interpreted) as an anchor. # Parsing something without then evaluating it accordingly doesn't make -# sense. +# sense, so this has been fixed. .if ${value:L:e$=x} != "value" . error .endif -# Before 2020-07-20, the modifier ":e$=x" was parsed as having a left-hand -# side "e" and a right-hand side "x". The dollar was parsed (but not +# Before 2020-07-20, the modifier ':e$=x' was parsed as having a left-hand +# side 'e' and a right-hand side 'x'. The dollar was parsed (but not # interpreted) as 'anchor at the end'. Therefore the modifier was equivalent -# to ":e=x", which doesn't match the string "value$". Therefore the whole +# to ':e=x', which doesn't match the string "value$". Therefore the whole # expression evaluated to "value$". .if ${${:Uvalue\$}:L:e$=x} != "valux" . error @@ -198,7 +198,7 @@ . error .endif -# The :from=to modifier can be used to replace both the prefix and a suffix +# The modifier ':from=to' can be used to replace both the prefix and a suffix # of a word with other strings. This is not possible with a single :S # modifier, and using a :C modifier for the same task looks more complicated # in many cases. @@ -238,4 +238,17 @@ INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE} . error .endif +# Test all relevant combinations of prefix, '%' and suffix in both the pattern +# and the replacement. +!=1>&2 printf '%-24s %-24s %-24s\n' 'word' 'modifier' 'result' +.for from in '' ffix % pre% %ffix pre%ffix +. for to in '' NS % %NS NPre% NPre%NS +. for word in '' suffix prefix pre-middle-suffix +. for mod in ${from:N''}=${to:N''} +!=1>&2 printf '%-24s %-24s "%s"\n' ''${word:Q} ''${mod:Q} ''${word:N'':${mod}:Q} +. endfor +. endfor +. endfor +.endfor + all: diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp index 44c9f0973ed9..c6e8ce98a21a 100644 --- a/unit-tests/varmod-to-separator.exp +++ b/unit-tests/varmod-to-separator.exp @@ -2,17 +2,17 @@ make: "varmod-to-separator.mk" line 107: Invalid character number: 400:tu} make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) make: "varmod-to-separator.mk" line 121: Invalid character number: 100:tu} make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) -make: Bad modifier `:ts\-300' for WORDS +make: Bad modifier ":ts\-300" for variable "WORDS" make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) -make: Bad modifier `:ts\8' for 1 2 3 +make: Bad modifier ":ts\8" for variable "1 2 3" make: "varmod-to-separator.mk" line 136: Malformed conditional (${1 2 3:L:ts\8:tu}) -make: Bad modifier `:ts\100L' for 1 2 3 +make: Bad modifier ":ts\100L" for variable "1 2 3" make: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L}) -make: Bad modifier `:ts\x40g' for 1 2 3 +make: Bad modifier ":ts\x40g" for variable "1 2 3" make: "varmod-to-separator.mk" line 150: Malformed conditional (${1 2 3:L:ts\x40g}) -make: Bad modifier `:tx' for WORDS +make: Bad modifier ":tx" for variable "WORDS" make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything") -make: Bad modifier `:t\X' for WORDS +make: Bad modifier ":t\X" for variable "WORDS" make: "varmod-to-separator.mk" line 165: Malformed conditional (${WORDS:t\X} != "anything") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests diff --git a/unit-tests/varmod-unique.mk b/unit-tests/varmod-unique.mk index ea4698764947..04d04a575af1 100644 --- a/unit-tests/varmod-unique.mk +++ b/unit-tests/varmod-unique.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-unique.mk,v 1.4 2020/08/31 17:41:38 rillig Exp $ +# $NetBSD: varmod-unique.mk,v 1.5 2021/05/30 20:26:41 rillig Exp $ # # Tests for the :u variable modifier, which discards adjacent duplicate # words. @@ -15,10 +15,18 @@ . warning The :u modifier must do nothing with an empty word list. .endif -.if ${:U1:u} != "1" +.if ${:U :u} != "" +. warning The modifier ':u' must normalize the whitespace. +.endif + +.if ${:Uword:u} != "word" . warning The :u modifier must do nothing with a single-element word list. .endif +.if ${:U word :u} != "word" +. warning The modifier ':u' must normalize the whitespace. +.endif + .if ${:U1 1 1 1 1 1 1 1:u} != "1" . warning The :u modifier must merge _all_ adjacent duplicate words. .endif diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp index 46a1b2127c98..bfbcfc960182 100755 --- a/unit-tests/varname-dot-shell.exp +++ b/unit-tests/varname-dot-shell.exp @@ -1,32 +1,32 @@ ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}' -Global:ORIG_SHELL = -Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR|VARE_KEEP_UNDEF +Global: ORIG_SHELL = +Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined) Global:delete .SHELL (not found) -Command:.SHELL = (details omitted) -Global:ORIG_SHELL = (details omitted) +Command: .SHELL = (details omitted) +Global: ORIG_SHELL = (details omitted) ParseReadLine (12): '.SHELL= overwritten' -Global:.SHELL = overwritten +Global: .SHELL = overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended' -ParseDoDependency(.MAKEFLAGS: .SHELL+=appended) +ParseDependency(.MAKEFLAGS: .SHELL+=appended) Ignoring append to .SHELL since it is read-only CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (27): '.undef .SHELL' Global:delete .SHELL ParseReadLine (28): '.SHELL= newly overwritten' -Global:.SHELL = newly overwritten +Global: .SHELL = newly overwritten CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES -Var_Parse: ${ORIG_SHELL} with VARE_UNDEFERR|VARE_WANTRES +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) +Var_Parse: ${ORIG_SHELL} (eval-defined) lhs = "(details omitted)", rhs = "(details omitted)", op = != ParseReadLine (33): '.MAKEFLAGS: -d0' -ParseDoDependency(.MAKEFLAGS: -d0) -Global:.MAKEFLAGS = -r -k -d cpv -d -Global:.MAKEFLAGS = -r -k -d cpv -d 0 +ParseDependency(.MAKEFLAGS: -d0) +Global: .MAKEFLAGS = -r -k -d cpv -d +Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp index 28f55368fd19..ec225c6973c8 100644 --- a/unit-tests/varname-empty.exp +++ b/unit-tests/varname-empty.exp @@ -1,47 +1,27 @@ -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored -Var_Set("", "cmdline-plain", ...) name expands to empty string - ignored -Global:.CURDIR = <curdir> -Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE:U} with VARE_WANTRES -Applying ${MAKE_OBJDIR_CHECK_WRITABLE:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${MAKE_OBJDIR_CHECK_WRITABLE:U} is "" (VARE_WANTRES, none, VES_DEF) -Global:.OBJDIR = <curdir> +Var_SetExpand: variable name "${:U}" expands to empty string, with value "cmdline-u" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "cmdline-plain" - ignored +Global: .CURDIR = <curdir> +Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval) +Global: .OBJDIR = <curdir> Global:delete .PATH (not found) -Global:.PATH = . -Global:.PATH = . <curdir> -Global:.TARGETS = -Internal:MAKEFILE = varname-empty.mk -Global:.MAKE.MAKEFILES = varname-empty.mk -Global:.PARSEFILE = varname-empty.mk +Global: .PATH = . +Global: .PATH = . <curdir> +Global: .TARGETS = +Internal: MAKEFILE = varname-empty.mk +Global: .MAKE.MAKEFILES = varname-empty.mk +Global: .PARSEFILE = varname-empty.mk Global:delete .INCLUDEDFROMDIR (not found) Global:delete .INCLUDEDFROMFILE (not found) -Var_Set("", "default", ...) name expands to empty string - ignored -Var_Set("", "assigned", ...) name expands to empty string - ignored +Var_SetExpand: variable name "" expands to empty string, with value "default" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "assigned" - ignored SetVar: variable name is empty - ignored -Var_Set("", "", ...) name expands to empty string - ignored -Var_Set("", "subst", ...) name expands to empty string - ignored -Var_Set("", "shell-output", ...) name expands to empty string - ignored -Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Set("${:U}", "assigned indirectly", ...) name expands to empty string - ignored -Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Var_Parse: ${:U} with VARE_WANTRES -Applying ${:U} to "" (VARE_WANTRES, none, VES_UNDEF) -Result of ${:U} is "" (VARE_WANTRES, none, VES_DEF) -Var_Append("${:U}", "appended indirectly", ...) name expands to empty string - ignored -Var_Parse: ${:Ufallback} != "fallback" with VARE_UNDEFERR|VARE_WANTRES -Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VES_UNDEF) -Result of ${:Ufallback} is "fallback" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.MAKEFLAGS = -r -d v -d -Global:.MAKEFLAGS = -r -d v -d 0 +Var_SetExpand: variable name "" expands to empty string, with value "" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "subst" - ignored +Var_SetExpand: variable name "" expands to empty string, with value "shell-output" - ignored +Var_SetExpand: variable name "${:U}" expands to empty string, with value "assigned indirectly" - ignored +Var_AppendExpand: variable name "${:U}" expands to empty string, with value "appended indirectly" - ignored +Global: .MAKEFLAGS = -r -d v -d +Global: .MAKEFLAGS = -r -d v -d 0 out: fallback out: 1 2 3 exit status 0 diff --git a/unit-tests/varname-empty.mk b/unit-tests/varname-empty.mk index 492f9f2618ba..f077d2ec07b4 100755 --- a/unit-tests/varname-empty.mk +++ b/unit-tests/varname-empty.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-empty.mk,v 1.8 2021/02/03 08:34:15 rillig Exp $ +# $NetBSD: varname-empty.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $ # # Tests for the special variable with the empty name. # @@ -49,7 +49,7 @@ ${:U}+= appended indirectly .MAKEFLAGS: -d0 # Before 2020-08-22, the simple assignment operator '=' after an empty -# variable name had an off-by-one bug in Parse_DoVar. The code that was +# variable name had an off-by-one bug in Parse_Var. The code that was # supposed to "skip to operator character" started its search _after_ the # assignment operator, assuming that the variable name would be at least # one character long. It then looked for the next occurrence of a '=', which diff --git a/unit-tests/varname.exp b/unit-tests/varname.exp index 84f878a9f742..942532b654d5 100644 --- a/unit-tests/varname.exp +++ b/unit-tests/varname.exp @@ -1,24 +1,21 @@ -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, VES_UNDEF) -Result of ${:UVAR(((} is "VAR(((" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.ALLTARGETS = VAR(((=) +Global: VAR{{{}}} = 3 braces +Var_Parse: ${VAR{{{}}}}" != "3 braces" (eval) +Global: VARNAME = VAR((( +Var_Parse: ${VARNAME} (eval) +Global: VAR((( = 3 open parentheses +Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" (eval) +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, VES_UNDEF) -Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (VARE_UNDEFERR|VARE_WANTRES, none, VES_DEF) -Global:.ALLTARGETS = VAR(((=) VAR\(\(\(= +Var_Parse: ${:UVAR\(\(\(}= try2 (eval-defined) +Evaluating modifier ${:U...} on value "" (eval-defined, undefined) +Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval-defined, defined) +Global: .ALLTARGETS = VAR(((=) VAR\(\(\(= make: "varname.mk" line 35: Invalid line type -Var_Parse: ${VARNAME} with VARE_WANTRES -Global:VAR((( = try3 -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Var_Parse: ${VARNAME} (eval) +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/varparse-dynamic.mk b/unit-tests/varparse-dynamic.mk index c65ba12e6149..d4d165017a7f 100644 --- a/unit-tests/varparse-dynamic.mk +++ b/unit-tests/varparse-dynamic.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-dynamic.mk,v 1.4 2021/02/04 21:42:47 rillig Exp $ +# $NetBSD: varparse-dynamic.mk,v 1.5 2021/02/22 20:38:55 rillig Exp $ # Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped # the last character in the variable name. @@ -15,8 +15,8 @@ # expression is returned as the variable value, hoping that it can be # resolved at a later point. # -# This test covers the code in Var_Parse that deals with VAR_JUNK but not -# VAR_KEEP for dynamic variables. +# This test covers the code in Var_Parse that deals with DEF_UNDEF but not +# DEF_DEFINED for dynamic variables. .if ${.TARGET:S,^,,} != "\${.TARGET:S,^,,}" . error .endif diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp index 50a0766c7d70..27589e0b21af 100644 --- a/unit-tests/varparse-errors.exp +++ b/unit-tests/varparse-errors.exp @@ -1,5 +1,5 @@ -make: "varparse-errors.mk" line 38: Unknown modifier 'Z' -make: "varparse-errors.mk" line 46: Unknown modifier 'Z' +make: "varparse-errors.mk" line 38: Unknown modifier "Z" +make: "varparse-errors.mk" line 46: Unknown modifier "Z" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk index 113c7a292a79..f0947bb9410a 100644 --- a/unit-tests/varparse-errors.mk +++ b/unit-tests/varparse-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-errors.mk,v 1.3 2020/12/20 19:47:34 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.4 2021/03/15 12:15:03 rillig Exp $ # Tests for parsing and evaluating all kinds of variable expressions. # @@ -25,7 +25,7 @@ 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. +# expanded using the mode VARE_UNDEFERR. # The variable itself must be defined. # It may refer to undefined variables though. .if ${REF_UNDEF} != "A reference to an undefined variable." @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.807 2021/02/05 05:42:39 rillig Exp $ */ +/* $NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -120,7 +120,8 @@ * * Var_Dump Print out all variables defined in the given scope. * - * XXX: There's a lot of duplication in these functions. + * XXX: There's a lot of almost duplicate code in these functions that only + * differs in subtle details that are not mentioned in the manual page. */ #include <sys/stat.h> @@ -147,48 +148,7 @@ #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.807 2021/02/05 05:42:39 rillig Exp $"); - -typedef enum VarFlags { - VAR_NONE = 0, - - /* - * The variable's value is currently being used by Var_Parse or - * Var_Subst. This marker is used to avoid endless recursion. - */ - VAR_IN_USE = 0x01, - - /* - * The variable comes from the environment. - * These variables are not registered in any GNode, therefore they - * must be freed as soon as they are not used anymore. - */ - VAR_FROM_ENV = 0x02, - - /* - * The variable is exported to the environment, to be used by child - * processes. - */ - VAR_EXPORTED = 0x10, - - /* - * At the point where this variable was exported, it contained an - * unresolved reference to another variable. Before any child - * process is started, it needs to be exported again, in the hope - * that the referenced variable can then be resolved. - */ - VAR_REEXPORT = 0x20, - - /* The variable came from the command line. */ - VAR_FROM_CMD = 0x40, - - /* - * The variable value cannot be changed anymore, and the variable - * cannot be deleted. Any attempts to do so are silently ignored, - * they are logged with -dv though. - */ - VAR_READONLY = 0x80 -} VarFlags; +MAKE_RCSID("$NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $"); /* * Variables are defined using one of the VAR=value assignments. Their @@ -219,12 +179,53 @@ typedef struct Var { /* The unexpanded value of the variable. */ Buffer val; - /* Miscellaneous status flags. */ - VarFlags flags; + + /* The variable came from the command line. */ + bool fromCmd: 1; + + /* + * The variable comes from the environment. + * These variables are not registered in any GNode, therefore they + * must be freed as soon as they are not used anymore. + */ + bool fromEnv: 1; + + /* + * The variable value cannot be changed anymore, and the variable + * cannot be deleted. Any attempts to do so are silently ignored, + * they are logged with -dv though. + * + * See VAR_SET_READONLY. + */ + bool readOnly: 1; + + /* + * The variable's value is currently being used by Var_Parse or + * Var_Subst. This marker is used to avoid endless recursion. + */ + bool inUse: 1; + + /* + * The variable is exported to the environment, to be used by child + * processes. + */ + bool exported: 1; + + /* + * At the point where this variable was exported, it contained an + * unresolved reference to another variable. Before any child + * process is started, it needs to be exported again, in the hope + * that the referenced variable can then be resolved. + */ + bool reexport: 1; } Var; /* - * Exporting vars is expensive so skip it if we can + * Exporting variables is expensive and may leak memory, so skip it if we + * can. + * + * To avoid this, it might be worth encapsulating the environment variables + * in a separate data structure called EnvVars. */ typedef enum VarExportedMode { VAR_EXPORTED_NONE, @@ -233,37 +234,37 @@ typedef enum VarExportedMode { } VarExportedMode; typedef enum UnexportWhat { + /* Unexport the variables given by name. */ UNEXPORT_NAMED, + /* + * Unexport all globals previously exported, but keep the environment + * inherited from the parent. + */ UNEXPORT_ALL, + /* + * Unexport all globals previously exported and clear the environment + * inherited from the parent. + */ UNEXPORT_ENV } UnexportWhat; /* Flags for pattern matching in the :S and :C modifiers */ -typedef struct VarPatternFlags { - - /* Replace as often as possible ('g') */ - Boolean subGlobal: 1; - /* Replace only once ('1') */ - Boolean subOnce: 1; - /* Match at start of word ('^') */ - Boolean anchorStart: 1; - /* Match at end of word ('$') */ - Boolean anchorEnd: 1; -} VarPatternFlags; - -/* SepBuf is a string being built from words, interleaved with separators. */ +typedef struct PatternFlags { + bool subGlobal: 1; /* 'g': replace as often as possible */ + bool subOnce: 1; /* '1': replace only once */ + bool anchorStart: 1; /* '^': match only at start of word */ + bool anchorEnd: 1; /* '$': match only at end of word */ +} PatternFlags; + +/* SepBuf builds a string from words interleaved with separators. */ typedef struct SepBuf { Buffer buf; - Boolean needSep; + bool needSep; /* Usually ' ', but see the ':ts' modifier. */ char sep; } SepBuf; -ENUM_FLAGS_RTTI_4(VarEvalFlags, - VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR, - VARE_KEEP_UNDEF); - /* * This lets us tell if we have replaced the original environ * (which we cannot free). @@ -282,6 +283,8 @@ char var_Error[] = ""; * a case where VARE_UNDEFERR is not set. This undefined variable is * typically a dynamic variable such as ${.TARGET}, whose expansion needs to * be deferred until it is defined in an actual target. + * + * See VARE_EVAL_KEEP_UNDEF. */ static char varUndefined[] = ""; @@ -289,12 +292,12 @@ static char varUndefined[] = ""; * Traditionally this make consumed $$ during := like any other expansion. * Other make's do not, and this make follows straight since 2016-01-09. * - * This knob allows controlling the behavior. - * FALSE to consume $$ during := assignment. - * TRUE to preserve $$ during := assignment. + * This knob allows controlling the behavior: + * false to consume $$ during := assignment. + * true to preserve $$ during := assignment. */ #define MAKE_SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" -static Boolean save_dollars = FALSE; +static bool save_dollars = false; /* * A scope collects variable names and their values. @@ -321,64 +324,59 @@ GNode *SCOPE_CMDLINE; GNode *SCOPE_GLOBAL; GNode *SCOPE_INTERNAL; -ENUM_FLAGS_RTTI_6(VarFlags, - VAR_IN_USE, VAR_FROM_ENV, - VAR_EXPORTED, VAR_REEXPORT, VAR_FROM_CMD, VAR_READONLY); - static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; +static const char *VarEvalMode_Name[] = { + "parse-only", + "eval", + "eval-defined", + "eval-keep-dollar", + "eval-keep-undefined", + "eval-keep-dollar-and-undefined", +}; + static Var * -VarNew(FStr name, const char *value, VarFlags flags) +VarNew(FStr name, const char *value, bool fromEnv, bool readOnly) { size_t value_len = strlen(value); Var *var = bmake_malloc(sizeof *var); var->name = name; Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); - var->flags = flags; + var->fromCmd = false; + var->fromEnv = fromEnv; + var->readOnly = readOnly; + var->inUse = false; + var->exported = false; + var->reexport = false; return var; } -static const char * -CanonicalVarname(const char *name) -{ - if (*name == '.' && ch_isupper(name[1])) { - switch (name[1]) { - case 'A': - if (strcmp(name, ".ALLSRC") == 0) - name = ALLSRC; - if (strcmp(name, ".ARCHIVE") == 0) - name = ARCHIVE; - break; - case 'I': - if (strcmp(name, ".IMPSRC") == 0) - name = IMPSRC; - break; - case 'M': - if (strcmp(name, ".MEMBER") == 0) - name = MEMBER; - break; - case 'O': - if (strcmp(name, ".OODATE") == 0) - name = OODATE; - break; - case 'P': - if (strcmp(name, ".PREFIX") == 0) - name = PREFIX; - break; - case 'S': - if (strcmp(name, ".SHELL") == 0) { - if (shellPath == NULL) - Shell_Init(); - } - break; - case 'T': - if (strcmp(name, ".TARGET") == 0) - name = TARGET; - break; - } - } +static Substring +CanonicalVarname(Substring name) +{ + + if (!(Substring_Length(name) > 0 && name.start[0] == '.')) + return name; + + if (Substring_Equals(name, ".ALLSRC")) + return Substring_InitStr(ALLSRC); + if (Substring_Equals(name, ".ARCHIVE")) + return Substring_InitStr(ARCHIVE); + if (Substring_Equals(name, ".IMPSRC")) + return Substring_InitStr(IMPSRC); + if (Substring_Equals(name, ".MEMBER")) + return Substring_InitStr(MEMBER); + if (Substring_Equals(name, ".OODATE")) + return Substring_InitStr(OODATE); + if (Substring_Equals(name, ".PREFIX")) + return Substring_InitStr(PREFIX); + if (Substring_Equals(name, ".TARGET")) + return Substring_InitStr(TARGET); + + if (Substring_Equals(name, ".SHELL") && shellPath == NULL) + Shell_Init(); /* GNU make has an additional alias $^ == ${.ALLSRC}. */ @@ -386,9 +384,9 @@ CanonicalVarname(const char *name) } static Var * -GNode_FindVar(GNode *scope, const char *varname, unsigned int hash) +GNode_FindVar(GNode *scope, Substring varname, unsigned int hash) { - return HashTable_FindValueHash(&scope->vars, varname, hash); + return HashTable_FindValueBySubstringHash(&scope->vars, varname, hash); } /* @@ -397,7 +395,7 @@ GNode_FindVar(GNode *scope, const char *varname, unsigned int hash) * Input: * name name to find, is not expanded any further * scope scope in which to look first - * elsewhere TRUE to look in other scopes as well + * elsewhere true to look in other scopes as well * * Results: * The found variable, or NULL if the variable does not exist. @@ -405,29 +403,19 @@ GNode_FindVar(GNode *scope, const char *varname, unsigned int hash) * VarFreeEnv after use. */ static Var * -VarFind(const char *name, GNode *scope, Boolean elsewhere) +VarFindSubstring(Substring name, GNode *scope, bool elsewhere) { Var *var; unsigned int nameHash; - /* - * If the variable name begins with a '.', it could very well be - * one of the local ones. We check the name against all the local - * variables and substitute the short version in for 'name' if it - * matches one of them. - */ + /* Replace '.TARGET' with '@', likewise for other local variables. */ name = CanonicalVarname(name); - nameHash = Hash_Hash(name); + nameHash = Hash_Substring(name); - /* First look for the variable in the given scope. */ var = GNode_FindVar(scope, name, nameHash); if (!elsewhere) return var; - /* - * The variable was not found in the given scope. - * Now look for it in the other scopes as well. - */ if (var == NULL && scope != SCOPE_CMDLINE) var = GNode_FindVar(SCOPE_CMDLINE, name, nameHash); @@ -440,12 +428,19 @@ VarFind(const char *name, GNode *scope, Boolean elsewhere) } if (var == NULL) { - char *env; + FStr envName; + const char *envValue; - if ((env = getenv(name)) != NULL) { - char *varname = bmake_strdup(name); - return VarNew(FStr_InitOwn(varname), env, VAR_FROM_ENV); - } + /* + * TODO: try setting an environment variable with the empty + * name, which should be technically possible, just to see + * how make reacts. All .for loops should be broken then. + */ + envName = Substring_Str(name); + envValue = getenv(envName.str); + if (envValue != NULL) + return VarNew(envName, envValue, true, false); + FStr_Done(&envName); if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) { var = GNode_FindVar(SCOPE_GLOBAL, name, nameHash); @@ -461,43 +456,35 @@ VarFind(const char *name, GNode *scope, Boolean elsewhere) return var; } -/* - * If the variable is an environment variable, free it. - * - * Input: - * v the variable - * freeValue true if the variable value should be freed as well - * - * Results: - * TRUE if it is an environment variable, FALSE otherwise. - */ -static Boolean -VarFreeEnv(Var *v, Boolean freeValue) +/* TODO: Replace these calls with VarFindSubstring, as far as possible. */ +static Var * +VarFind(const char *name, GNode *scope, bool elsewhere) +{ + return VarFindSubstring(Substring_InitStr(name), scope, elsewhere); +} + +/* If the variable is an environment variable, free it, including its value. */ +static void +VarFreeEnv(Var *v) { - if (!(v->flags & VAR_FROM_ENV)) - return FALSE; + if (!v->fromEnv) + return; FStr_Done(&v->name); - if (freeValue) - Buf_Done(&v->val); - else - Buf_DoneData(&v->val); + Buf_Done(&v->val); free(v); - return TRUE; } -/* - * Add a new variable of the given name and value to the given scope. - * The name and val arguments are duplicated so they may safely be freed. - */ -static void -VarAdd(const char *name, const char *val, GNode *scope, VarSetFlags flags) +/* Add a new variable of the given name and value to the given scope. */ +static Var * +VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL); - Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), val, - flags & VAR_SET_READONLY ? VAR_READONLY : VAR_NONE); + Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value, + false, (flags & VAR_SET_READONLY) != 0); HashEntry_Set(he, v); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, val); + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, value); + return v; } /* @@ -516,8 +503,8 @@ Var_Delete(GNode *scope, const char *varname) } DEBUG2(VAR, "%s:delete %s\n", scope->name, varname); - v = HashEntry_Get(he); - if (v->flags & VAR_EXPORTED) + v = he->value; + if (v->exported) unsetenv(v->name.str); if (strcmp(v->name.str, MAKE_EXPORTED) == 0) var_exportedVars = VAR_EXPORTED_NONE; @@ -573,7 +560,7 @@ Var_Undef(const char *arg) return; } - varnames = Str_Words(expanded, FALSE); + varnames = Str_Words(expanded, false); if (varnames.len == 1 && varnames.words[0][0] == '\0') varnames.len = 0; @@ -586,13 +573,13 @@ Var_Undef(const char *arg) free(expanded); } -static Boolean +static bool MayExport(const char *name) { if (name[0] == '.') - return FALSE; /* skip internals */ + return false; /* skip internals */ if (name[0] == '-') - return FALSE; /* skip misnamed variables */ + return false; /* skip misnamed variables */ if (name[1] == '\0') { /* * A single char. @@ -605,34 +592,34 @@ MayExport(const char *name) case '%': case '*': case '!': - return FALSE; + return false; } } - return TRUE; + return true; } -static Boolean +static bool ExportVarEnv(Var *v) { const char *name = v->name.str; char *val = v->val.data; char *expr; - if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) - return FALSE; /* nothing to do */ + if (v->exported && !v->reexport) + return false; /* nothing to do */ if (strchr(val, '$') == NULL) { - if (!(v->flags & VAR_EXPORTED)) + if (!v->exported) setenv(name, val, 1); - return TRUE; + return true; } - if (v->flags & VAR_IN_USE) { + if (v->inUse) { /* * We recursed while exporting in a child. * This isn't going to end well, just skip it. */ - return FALSE; + return false; } /* XXX: name is injected without escaping it */ @@ -642,59 +629,58 @@ ExportVarEnv(Var *v) setenv(name, val, 1); free(val); free(expr); - return TRUE; + return true; } -static Boolean +static bool ExportVarPlain(Var *v) { if (strchr(v->val.data, '$') == NULL) { setenv(v->name.str, v->val.data, 1); - v->flags |= VAR_EXPORTED; - v->flags &= ~(unsigned)VAR_REEXPORT; - return TRUE; + v->exported = true; + v->reexport = false; + return true; } /* * Flag the variable as something we need to re-export. * No point actually exporting it now though, * the child process can do it at the last minute. + * Avoid calling setenv more often than necessary since it can leak. */ - v->flags |= VAR_EXPORTED | VAR_REEXPORT; - return TRUE; + v->exported = true; + v->reexport = true; + return true; } -static Boolean +static bool ExportVarLiteral(Var *v) { - if ((v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) - return FALSE; + if (v->exported && !v->reexport) + return false; - if (!(v->flags & VAR_EXPORTED)) + if (!v->exported) setenv(v->name.str, v->val.data, 1); - return TRUE; + return true; } /* - * Export a single variable. + * Mark a single variable to be exported later for subprocesses. * - * We ignore make internal variables (those which start with '.'). - * Also we jump through some hoops to avoid calling setenv - * more than necessary since it can leak. - * We only manipulate flags of vars if 'parent' is set. + * Internal variables (those starting with '.') are not exported. */ -static Boolean +static bool ExportVar(const char *name, VarExportMode mode) { Var *v; if (!MayExport(name)) - return FALSE; + return false; - v = VarFind(name, SCOPE_GLOBAL, FALSE); + v = VarFind(name, SCOPE_GLOBAL, false); if (v == NULL) - return FALSE; + return false; if (mode == VEM_ENV) return ExportVarEnv(v); @@ -719,7 +705,7 @@ Var_ReexportVars(void) * We allow the makefiles to update MAKELEVEL and ensure * children see a correctly incremented value. */ - char tmp[BUFSIZ]; + char tmp[21]; snprintf(tmp, sizeof tmp, "%d", makelevel + 1); setenv(MAKE_LEVEL_ENV, tmp, 1); @@ -729,7 +715,7 @@ Var_ReexportVars(void) if (var_exportedVars == VAR_EXPORTED_ALL) { HashIter hi; - /* Ouch! Exporting all variables at once is crazy... */ + /* Ouch! Exporting all variables at once is crazy. */ HashIter_Init(&hi, &SCOPE_GLOBAL->vars); while (HashIter_Next(&hi) != NULL) { Var *var = hi.entry->value; @@ -742,7 +728,7 @@ Var_ReexportVars(void) &xvarnames); /* TODO: handle errors */ if (xvarnames[0] != '\0') { - Words varnames = Str_Words(xvarnames, FALSE); + Words varnames = Str_Words(xvarnames, false); size_t i; for (i = 0; i < varnames.len; i++) @@ -753,9 +739,10 @@ Var_ReexportVars(void) } static void -ExportVars(const char *varnames, Boolean isExport, VarExportMode mode) +ExportVars(const char *varnames, bool isExport, VarExportMode mode) +/* TODO: try to combine the parameters 'isExport' and 'mode'. */ { - Words words = Str_Words(varnames, FALSE); + Words words = Str_Words(varnames, false); size_t i; if (words.len == 1 && words.words[0][0] == '\0') @@ -776,7 +763,7 @@ ExportVars(const char *varnames, Boolean isExport, VarExportMode mode) } static void -ExportVarsExpand(const char *uvarnames, Boolean isExport, VarExportMode mode) +ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode) { char *xvarnames; @@ -795,13 +782,13 @@ Var_Export(VarExportMode mode, const char *varnames) return; } - ExportVarsExpand(varnames, TRUE, mode); + ExportVarsExpand(varnames, true, mode); } void Var_ExportVars(const char *varnames) { - ExportVarsExpand(varnames, FALSE, VEM_PLAIN); + ExportVarsExpand(varnames, false, VEM_PLAIN); } @@ -834,7 +821,7 @@ ClearEnv(void) } static void -GetVarnamesToUnexport(Boolean isEnv, const char *arg, +GetVarnamesToUnexport(bool isEnv, const char *arg, FStr *out_varnames, UnexportWhat *out_what) { UnexportWhat what; @@ -845,6 +832,7 @@ GetVarnamesToUnexport(Boolean isEnv, const char *arg, Parse_Error(PARSE_FATAL, "The directive .unexport-env does not take " "arguments"); + /* continue anyway */ } what = UNEXPORT_ENV; @@ -870,17 +858,17 @@ GetVarnamesToUnexport(Boolean isEnv, const char *arg, static void UnexportVar(const char *varname, UnexportWhat what) { - Var *v = VarFind(varname, SCOPE_GLOBAL, FALSE); + Var *v = VarFind(varname, SCOPE_GLOBAL, false); if (v == NULL) { DEBUG1(VAR, "Not unexporting \"%s\" (not found)\n", varname); return; } DEBUG1(VAR, "Unexporting \"%s\"\n", varname); - if (what != UNEXPORT_ENV && - (v->flags & VAR_EXPORTED) && !(v->flags & VAR_REEXPORT)) + if (what != UNEXPORT_ENV && v->exported && !v->reexport) unsetenv(v->name.str); - v->flags &= ~(unsigned)(VAR_EXPORTED | VAR_REEXPORT); + v->exported = false; + v->reexport = false; if (what == UNEXPORT_NAMED) { /* Remove the variable names from .MAKE.EXPORTED. */ @@ -905,7 +893,7 @@ UnexportVars(FStr *varnames, UnexportWhat what) if (what == UNEXPORT_ENV) ClearEnv(); - words = Str_Words(varnames->str, FALSE); + words = Str_Words(varnames->str, false); for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; UnexportVar(varname, what); @@ -922,7 +910,7 @@ UnexportVars(FStr *varnames, UnexportWhat what) * str must have the form "unexport[-env] varname...". */ void -Var_UnExport(Boolean isEnv, const char *arg) +Var_UnExport(bool isEnv, const char *arg) { UnexportWhat what; FStr varnames; @@ -932,6 +920,32 @@ Var_UnExport(Boolean isEnv, const char *arg) FStr_Done(&varnames); } +/* + * When there is a variable of the same name in the command line scope, the + * global variable would not be visible anywhere. Therefore there is no + * point in setting it at all. + * + * See 'scope == SCOPE_CMDLINE' in Var_SetWithFlags. + */ +static bool +ExistsInCmdline(const char *name, const char *val) +{ + Var *v; + + v = VarFind(name, SCOPE_CMDLINE, false); + if (v == NULL) + return false; + + if (v->fromCmd) { + DEBUG3(VAR, "%s: %s = %s ignored!\n", + SCOPE_GLOBAL->name, name, val); + return true; + } + + VarFreeEnv(v); + return false; +} + /* Set the variable to the value; the name is not expanded. */ void Var_SetWithFlags(GNode *scope, const char *name, const char *val, @@ -945,58 +959,48 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, return; } - if (scope == SCOPE_GLOBAL) { - v = VarFind(name, SCOPE_CMDLINE, FALSE); - if (v != NULL) { - if (v->flags & VAR_FROM_CMD) { - DEBUG3(VAR, "%s:%s = %s ignored!\n", - scope->name, name, val); - return; - } - VarFreeEnv(v, TRUE); - } - } + if (scope == SCOPE_GLOBAL && ExistsInCmdline(name, val)) + return; /* * Only look for a variable in the given scope since anything set * here will override anything in a lower scope, so there's not much - * point in searching them all just to save a bit of memory... + * point in searching them all. */ - v = VarFind(name, scope, FALSE); + v = VarFind(name, scope, false); if (v == NULL) { if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) { /* * This var would normally prevent the same name being * added to SCOPE_GLOBAL, so delete it from there if * needed. Otherwise -V name may show the wrong value. + * + * See ExistsInCmdline. */ - /* XXX: name is expanded for the second time */ - Var_DeleteExpand(SCOPE_GLOBAL, name); + Var_Delete(SCOPE_GLOBAL, name); } - VarAdd(name, val, scope, flags); + v = VarAdd(name, val, scope, flags); } else { - if ((v->flags & VAR_READONLY) && !(flags & VAR_SET_READONLY)) { - DEBUG3(VAR, "%s:%s = %s ignored (read-only)\n", + if (v->readOnly && !(flags & VAR_SET_READONLY)) { + DEBUG3(VAR, "%s: %s = %s ignored (read-only)\n", scope->name, name, val); return; } Buf_Empty(&v->val); Buf_AddStr(&v->val, val); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, val); - if (v->flags & VAR_EXPORTED) + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, val); + if (v->exported) ExportVar(name, VEM_PLAIN); } + /* * Any variables given on the command line are automatically exported - * to the environment (as per POSIX standard) - * Other than internals. + * to the environment (as per POSIX standard), except for internals. */ if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') { - if (v == NULL) - v = VarFind(name, scope, FALSE); /* we just added it */ - v->flags |= VAR_FROM_CMD; + v->fromCmd = true; /* * If requested, don't export these in the environment @@ -1006,14 +1010,18 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, */ if (!opts.varNoExportEnv) setenv(name, val, 1); + /* XXX: What about .MAKE.EXPORTED? */ + /* XXX: Why not just mark the variable for needing export, + * as in ExportVarPlain? */ Global_Append(MAKEOVERRIDES, name); } + if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) save_dollars = ParseBoolean(val, save_dollars); if (v != NULL) - VarFreeEnv(v, TRUE); + VarFreeEnv(v); } /* See Var_Set for documentation. */ @@ -1034,8 +1042,9 @@ Var_SetExpandWithFlags(GNode *scope, const char *name, const char *val, } if (varname.str[0] == '\0') { - DEBUG2(VAR, "Var_Set(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", + DEBUG2(VAR, + "Var_SetExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", unexpanded_name, val); } else Var_SetWithFlags(scope, varname.str, val, flags); @@ -1099,23 +1108,23 @@ Var_Append(GNode *scope, const char *name, const char *val) if (v == NULL) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); - } else if (v->flags & VAR_READONLY) { + } else if (v->readOnly) { DEBUG1(VAR, "Ignoring append to %s since it is read-only\n", name); - } else if (scope == SCOPE_CMDLINE || !(v->flags & VAR_FROM_CMD)) { + } else if (scope == SCOPE_CMDLINE || !v->fromCmd) { Buf_AddByte(&v->val, ' '); Buf_AddStr(&v->val, val); - DEBUG3(VAR, "%s:%s = %s\n", scope->name, name, v->val.data); + DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data); - if (v->flags & VAR_FROM_ENV) { + if (v->fromEnv) { /* * If the original variable came from the environment, * we have to install it in the global scope (we * could place it in the environment, but then we * should provide a way to export other variables...) */ - v->flags &= ~(unsigned)VAR_FROM_ENV; + v->fromEnv = false; /* * This is the only place where a variable is * created whose v->name is not the same as @@ -1149,28 +1158,28 @@ Var_Append(GNode *scope, const char *name, const char *val) void Var_AppendExpand(GNode *scope, const char *name, const char *val) { - char *name_freeIt = NULL; + FStr xname = FStr_InitRefer(name); assert(val != NULL); if (strchr(name, '$') != NULL) { - const char *unexpanded_name = name; - (void)Var_Subst(name, scope, VARE_WANTRES, &name_freeIt); + char *expanded; + (void)Var_Subst(name, scope, VARE_WANTRES, &expanded); /* TODO: handle errors */ - name = name_freeIt; - if (name[0] == '\0') { - /* TODO: update function name in the debug message */ - DEBUG2(VAR, "Var_Append(\"%s\", \"%s\", ...) " - "name expands to empty string - ignored\n", - unexpanded_name, val); - free(name_freeIt); + xname = FStr_InitOwn(expanded); + if (expanded[0] == '\0') { + DEBUG2(VAR, + "Var_AppendExpand: variable name \"%s\" expands " + "to empty string, with value \"%s\" - ignored\n", + name, val); + FStr_Done(&xname); return; } } - Var_Append(scope, name, val); + Var_Append(scope, xname.str, val); - free(name_freeIt); + FStr_Done(&xname); } void @@ -1179,15 +1188,15 @@ Global_Append(const char *name, const char *value) Var_Append(SCOPE_GLOBAL, name, value); } -Boolean +bool Var_Exists(GNode *scope, const char *name) { - Var *v = VarFind(name, scope, TRUE); + Var *v = VarFind(name, scope, true); if (v == NULL) - return FALSE; + return false; - (void)VarFreeEnv(v, TRUE); - return TRUE; + VarFreeEnv(v); + return true; } /* @@ -1198,11 +1207,11 @@ Var_Exists(GNode *scope, const char *name) * name Variable to find, is expanded once * scope Scope in which to start search */ -Boolean +bool Var_ExistsExpand(GNode *scope, const char *name) { FStr varname = FStr_InitRefer(name); - Boolean exists; + bool exists; if (strchr(varname.str, '$') != NULL) { char *expanded; @@ -1226,22 +1235,25 @@ Var_ExistsExpand(GNode *scope, const char *name) * * Results: * The value if the variable exists, NULL if it doesn't. - * If the returned value is not NULL, the caller must free - * out_freeIt when the returned value is no longer needed. + * The value is valid until the next modification to any variable. */ FStr Var_Value(GNode *scope, const char *name) { - Var *v = VarFind(name, scope, TRUE); + Var *v = VarFind(name, scope, true); char *value; if (v == NULL) return FStr_InitRefer(NULL); - value = v->val.data; - return VarFreeEnv(v, FALSE) - ? FStr_InitOwn(value) - : FStr_InitRefer(value); + if (!v->fromEnv) + return FStr_InitRefer(v->val.data); + + /* Since environment variables are short-lived, free it now. */ + FStr_Done(&v->name); + value = Buf_DoneData(&v->val); + free(v); + return FStr_InitOwn(value); } /* @@ -1251,23 +1263,59 @@ Var_Value(GNode *scope, const char *name) const char * GNode_ValueDirect(GNode *gn, const char *name) { - Var *v = VarFind(name, gn, FALSE); + Var *v = VarFind(name, gn, false); return v != NULL ? v->val.data : NULL; } +static VarEvalMode +VarEvalMode_WithoutKeepDollar(VarEvalMode emode) +{ + if (emode == VARE_KEEP_DOLLAR_UNDEF) + return VARE_EVAL_KEEP_UNDEF; + if (emode == VARE_EVAL_KEEP_DOLLAR) + return VARE_WANTRES; + return emode; +} + +static VarEvalMode +VarEvalMode_UndefOk(VarEvalMode emode) +{ + return emode == VARE_UNDEFERR ? VARE_WANTRES : emode; +} + +static bool +VarEvalMode_ShouldEval(VarEvalMode emode) +{ + return emode != VARE_PARSE_ONLY; +} + +static bool +VarEvalMode_ShouldKeepUndef(VarEvalMode emode) +{ + return emode == VARE_EVAL_KEEP_UNDEF || + emode == VARE_KEEP_DOLLAR_UNDEF; +} + +static bool +VarEvalMode_ShouldKeepDollar(VarEvalMode emode) +{ + return emode == VARE_EVAL_KEEP_DOLLAR || + emode == VARE_KEEP_DOLLAR_UNDEF; +} + static void SepBuf_Init(SepBuf *buf, char sep) { Buf_InitSize(&buf->buf, 32); - buf->needSep = FALSE; + buf->needSep = false; buf->sep = sep; } static void SepBuf_Sep(SepBuf *buf) { - buf->needSep = TRUE; + buf->needSep = true; } static void @@ -1277,7 +1325,7 @@ SepBuf_AddBytes(SepBuf *buf, const char *mem, size_t mem_size) return; if (buf->needSep && buf->sep != '\0') { Buf_AddByte(&buf->buf, buf->sep); - buf->needSep = FALSE; + buf->needSep = false; } Buf_AddBytes(&buf->buf, mem, mem_size); } @@ -1294,6 +1342,12 @@ SepBuf_AddStr(SepBuf *buf, const char *str) SepBuf_AddBytes(buf, str, strlen(str)); } +static void +SepBuf_AddSubstring(SepBuf *buf, Substring sub) +{ + SepBuf_AddBytesBetween(buf, sub.start, sub.end); +} + static char * SepBuf_DoneData(SepBuf *buf) { @@ -1306,10 +1360,14 @@ SepBuf_DoneData(SepBuf *buf) * and typically adds a modification of this word to the buffer. It may also * do nothing or add several words. * - * For example, in ${:Ua b c:M*2}, the callback is called 3 times, once for - * each word of "a b c". + * For example, when evaluating the modifier ':M*b' in ${:Ua b c:M*b}, the + * callback is called 3 times, once for "a", "b" and "c". + * + * Some ModifyWord functions assume that they are always passed a + * null-terminated substring, which is currently guaranteed but may change in + * the future. */ -typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data); +typedef void (*ModifyWordProc)(Substring word, SepBuf *buf, void *data); /* @@ -1318,13 +1376,9 @@ typedef void (*ModifyWordsCallback)(const char *word, SepBuf *buf, void *data); */ /*ARGSUSED*/ static void -ModifyWord_Head(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *slash = strrchr(word, '/'); - if (slash != NULL) - SepBuf_AddBytesBetween(buf, word, slash); - else - SepBuf_AddStr(buf, "."); + SepBuf_AddSubstring(buf, Substring_Dirname(word)); } /* @@ -1333,9 +1387,9 @@ ModifyWord_Head(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) */ /*ARGSUSED*/ static void -ModifyWord_Tail(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - SepBuf_AddStr(buf, str_basename(word)); + SepBuf_AddSubstring(buf, Substring_Basename(word)); } /* @@ -1344,24 +1398,26 @@ ModifyWord_Tail(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) */ /*ARGSUSED*/ static void -ModifyWord_Suffix(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Suffix(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *lastDot = strrchr(word, '.'); + const char *lastDot = Substring_LastIndex(word, '.'); if (lastDot != NULL) - SepBuf_AddStr(buf, lastDot + 1); + SepBuf_AddBytesBetween(buf, lastDot + 1, word.end); } /* * Callback for ModifyWords to implement the :R modifier. - * Add the basename of the given word to the buffer. + * Add the filename without extension of the given word to the buffer. */ /*ARGSUSED*/ static void -ModifyWord_Root(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) +ModifyWord_Root(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *lastDot = strrchr(word, '.'); - size_t len = lastDot != NULL ? (size_t)(lastDot - word) : strlen(word); - SepBuf_AddBytes(buf, word, len); + const char *lastDot, *end; + + lastDot = Substring_LastIndex(word, '.'); + end = lastDot != NULL ? lastDot : word.end; + SepBuf_AddBytesBetween(buf, word.start, end); } /* @@ -1369,12 +1425,13 @@ ModifyWord_Root(const char *word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) * Place the word in the buffer if it matches the given pattern. */ static void -ModifyWord_Match(const char *word, SepBuf *buf, void *data) +ModifyWord_Match(Substring word, SepBuf *buf, void *data) { const char *pattern = data; - DEBUG2(VAR, "VarMatch [%s] [%s]\n", word, pattern); - if (Str_Match(word, pattern)) - SepBuf_AddStr(buf, word); + + assert(word.end[0] == '\0'); /* assume null-terminated word */ + if (Str_Match(word.start, pattern)) + SepBuf_AddSubstring(buf, word); } /* @@ -1382,194 +1439,146 @@ ModifyWord_Match(const char *word, SepBuf *buf, void *data) * Place the word in the buffer if it doesn't match the given pattern. */ static void -ModifyWord_NoMatch(const char *word, SepBuf *buf, void *data) +ModifyWord_NoMatch(Substring word, SepBuf *buf, void *data) { const char *pattern = data; - if (!Str_Match(word, pattern)) - SepBuf_AddStr(buf, word); -} - -#ifdef SYSVVARSUB - -/* - * Check word against pattern for a match (% is a wildcard). - * - * Input: - * word Word to examine - * pattern Pattern to examine against - * - * Results: - * Returns the start of the match, or NULL. - * out_match_len returns the length of the match, if any. - * out_hasPercent returns whether the pattern contains a percent. - */ -static const char * -SysVMatch(const char *word, const char *pattern, - size_t *out_match_len, Boolean *out_hasPercent) -{ - const char *p = pattern; - const char *w = word; - const char *percent; - size_t w_len; - size_t p_len; - const char *w_tail; - - *out_hasPercent = FALSE; - percent = strchr(p, '%'); - if (percent != NULL) { /* ${VAR:...%...=...} */ - *out_hasPercent = TRUE; - if (w[0] == '\0') - return NULL; /* empty word does not match pattern */ - - /* check that the prefix matches */ - for (; p != percent && *w != '\0' && *w == *p; w++, p++) - continue; - if (p != percent) - return NULL; /* No match */ - - p++; /* Skip the percent */ - if (*p == '\0') { - /* No more pattern, return the rest of the string */ - *out_match_len = strlen(w); - return w; - } - } - - /* Test whether the tail matches */ - w_len = strlen(w); - p_len = strlen(p); - if (w_len < p_len) - return NULL; - - w_tail = w + w_len - p_len; - if (memcmp(p, w_tail, p_len) != 0) - return NULL; - *out_match_len = (size_t)(w_tail - w); - return w; + assert(word.end[0] == '\0'); /* assume null-terminated word */ + if (!Str_Match(word.start, pattern)) + SepBuf_AddSubstring(buf, word); } -struct ModifyWord_SYSVSubstArgs { +#ifdef SYSVVARSUB +struct ModifyWord_SysVSubstArgs { GNode *scope; - const char *lhs; + Substring lhsPrefix; + bool lhsPercent; + Substring lhsSuffix; const char *rhs; }; /* Callback for ModifyWords to implement the :%.from=%.to modifier. */ static void -ModifyWord_SYSVSubst(const char *word, SepBuf *buf, void *data) +ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) { - const struct ModifyWord_SYSVSubstArgs *args = data; - char *rhs_expanded; - const char *rhs; + const struct ModifyWord_SysVSubstArgs *args = data; + FStr rhs; + char *rhsExp; const char *percent; - size_t match_len; - Boolean lhsPercent; - const char *match = SysVMatch(word, args->lhs, &match_len, &lhsPercent); - if (match == NULL) { - SepBuf_AddStr(buf, word); + if (Substring_IsEmpty(word)) return; - } - /* - * Append rhs to the buffer, substituting the first '%' with the - * match, but only if the lhs had a '%' as well. - */ + if (!Substring_HasPrefix(word, args->lhsPrefix)) + goto no_match; + if (!Substring_HasSuffix(word, args->lhsSuffix)) + goto no_match; - (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhs_expanded); - /* TODO: handle errors */ + rhs = FStr_InitRefer(args->rhs); + if (strchr(rhs.str, '$') != NULL) { + (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhsExp); + /* TODO: handle errors */ + rhs = FStr_InitOwn(rhsExp); + } - rhs = rhs_expanded; - percent = strchr(rhs, '%'); + percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; - if (percent != NULL && lhsPercent) { - /* Copy the prefix of the replacement pattern */ - SepBuf_AddBytesBetween(buf, rhs, percent); - rhs = percent + 1; - } - if (percent != NULL || !lhsPercent) - SepBuf_AddBytes(buf, match, match_len); + if (percent != NULL) + SepBuf_AddBytesBetween(buf, rhs.str, percent); + if (percent != NULL || !args->lhsPercent) + SepBuf_AddBytesBetween(buf, + word.start + Substring_Length(args->lhsPrefix), + word.end - Substring_Length(args->lhsSuffix)); + SepBuf_AddStr(buf, percent != NULL ? percent + 1 : rhs.str); - /* Append the suffix of the replacement pattern */ - SepBuf_AddStr(buf, rhs); + FStr_Done(&rhs); + return; - free(rhs_expanded); +no_match: + SepBuf_AddSubstring(buf, word); } #endif struct ModifyWord_SubstArgs { - const char *lhs; - size_t lhsLen; - const char *rhs; - size_t rhsLen; - VarPatternFlags pflags; - Boolean matched; + Substring lhs; + Substring rhs; + PatternFlags pflags; + bool matched; }; +static const char * +Substring_Find(Substring haystack, Substring needle) +{ + size_t len, needleLen, i; + + len = Substring_Length(haystack); + needleLen = Substring_Length(needle); + for (i = 0; i + needleLen <= len; i++) + if (memcmp(haystack.start + i, needle.start, needleLen) == 0) + return haystack.start + i; + return NULL; +} + /* * Callback for ModifyWords to implement the :S,from,to, modifier. * Perform a string substitution on the given word. */ static void -ModifyWord_Subst(const char *word, SepBuf *buf, void *data) +ModifyWord_Subst(Substring word, SepBuf *buf, void *data) { - size_t wordLen = strlen(word); struct ModifyWord_SubstArgs *args = data; - const char *match; + size_t wordLen, lhsLen; + const char *wordEnd, *match; + wordLen = Substring_Length(word); + wordEnd = word.end; if (args->pflags.subOnce && args->matched) goto nosub; + lhsLen = Substring_Length(args->lhs); if (args->pflags.anchorStart) { - if (wordLen < args->lhsLen || - memcmp(word, args->lhs, args->lhsLen) != 0) + if (wordLen < lhsLen || + memcmp(word.start, args->lhs.start, lhsLen) != 0) goto nosub; - if ((args->pflags.anchorEnd) && wordLen != args->lhsLen) + if (args->pflags.anchorEnd && wordLen != lhsLen) goto nosub; /* :S,^prefix,replacement, or :S,^whole$,replacement, */ - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - SepBuf_AddBytes(buf, word + args->lhsLen, - wordLen - args->lhsLen); - args->matched = TRUE; + SepBuf_AddSubstring(buf, args->rhs); + SepBuf_AddBytesBetween(buf, word.start + lhsLen, wordEnd); + args->matched = true; return; } if (args->pflags.anchorEnd) { - const char *start; - - if (wordLen < args->lhsLen) + if (wordLen < lhsLen) goto nosub; - - start = word + (wordLen - args->lhsLen); - if (memcmp(start, args->lhs, args->lhsLen) != 0) + if (memcmp(wordEnd - lhsLen, args->lhs.start, lhsLen) != 0) goto nosub; /* :S,suffix$,replacement, */ - SepBuf_AddBytesBetween(buf, word, start); - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - args->matched = TRUE; + SepBuf_AddBytesBetween(buf, word.start, wordEnd - lhsLen); + SepBuf_AddSubstring(buf, args->rhs); + args->matched = true; return; } - if (args->lhs[0] == '\0') + if (Substring_IsEmpty(args->lhs)) goto nosub; /* unanchored case, may match more than once */ - while ((match = strstr(word, args->lhs)) != NULL) { - SepBuf_AddBytesBetween(buf, word, match); - SepBuf_AddBytes(buf, args->rhs, args->rhsLen); - args->matched = TRUE; - wordLen -= (size_t)(match - word) + args->lhsLen; - word += (size_t)(match - word) + args->lhsLen; - if (wordLen == 0 || !args->pflags.subGlobal) + while ((match = Substring_Find(word, args->lhs)) != NULL) { + SepBuf_AddBytesBetween(buf, word.start, match); + SepBuf_AddSubstring(buf, args->rhs); + args->matched = true; + word.start = match + lhsLen; + if (Substring_IsEmpty(word) || !args->pflags.subGlobal) break; } nosub: - SepBuf_AddBytes(buf, word, wordLen); + SepBuf_AddSubstring(buf, word); } #ifndef NO_REGEX @@ -1587,9 +1596,9 @@ VarREError(int reerr, const regex_t *pat, const char *str) struct ModifyWord_SubstRegexArgs { regex_t re; size_t nsub; - char *replace; - VarPatternFlags pflags; - Boolean matched; + const char *replace; + PatternFlags pflags; + bool matched; }; /* @@ -1597,15 +1606,17 @@ struct ModifyWord_SubstRegexArgs { * Perform a regex substitution on the given word. */ static void -ModifyWord_SubstRegex(const char *word, SepBuf *buf, void *data) +ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data) { struct ModifyWord_SubstRegexArgs *args = data; int xrv; - const char *wp = word; - char *rp; + const char *wp; + const char *rp; int flags = 0; regmatch_t m[10]; + assert(word.end[0] == '\0'); /* assume null-terminated word */ + wp = word.start; if (args->pflags.subOnce && args->matched) goto nosub; @@ -1614,9 +1625,14 @@ tryagain: switch (xrv) { case 0: - args->matched = TRUE; + args->matched = true; SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so); + /* + * Replacement of regular expressions is not specified by + * POSIX, therefore implement it here. + */ + for (rp = args->replace; *rp != '\0'; rp++) { if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) { SepBuf_AddBytes(buf, rp + 1, 1); @@ -1643,9 +1659,11 @@ tryagain: Error("No subexpression \\%u", (unsigned)n); } else if (m[n].rm_so == -1) { - Error( - "No match for subexpression \\%u", - (unsigned)n); + if (opts.strict) { + Error( + "No match for subexpression \\%u", + (unsigned)n); + } } else { SepBuf_AddBytesBetween(buf, wp + m[n].rm_so, wp + m[n].rm_eo); @@ -1680,34 +1698,35 @@ tryagain: struct ModifyWord_LoopArgs { GNode *scope; - char *tvar; /* name of temporary variable */ - char *str; /* string to expand */ - VarEvalFlags eflags; + const char *var; /* name of the temporary variable */ + const char *body; /* string to expand */ + VarEvalMode emode; }; /* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */ static void -ModifyWord_Loop(const char *word, SepBuf *buf, void *data) +ModifyWord_Loop(Substring word, SepBuf *buf, void *data) { const struct ModifyWord_LoopArgs *args; char *s; - if (word[0] == '\0') + if (Substring_IsEmpty(word)) return; args = data; - /* XXX: The variable name should not be expanded here. */ - Var_SetExpandWithFlags(args->scope, args->tvar, word, + assert(word.end[0] == '\0'); /* assume null-terminated word */ + Var_SetWithFlags(args->scope, args->var, word.start, VAR_SET_NO_EXPORT); - (void)Var_Subst(args->str, args->scope, args->eflags, &s); + (void)Var_Subst(args->body, args->scope, args->emode, &s); /* TODO: handle errors */ + assert(word.end[0] == '\0'); /* assume null-terminated word */ DEBUG4(VAR, "ModifyWord_Loop: " "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n", - word, args->tvar, args->str, s); + word.start, args->var, args->body, s); if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) - buf->needSep = FALSE; + buf->needSep = false; SepBuf_AddStr(buf, s); free(s); } @@ -1718,8 +1737,8 @@ ModifyWord_Loop(const char *word, SepBuf *buf, void *data) * It can also reverse the words. */ static char * -VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, - int last) +VarSelectWords(const char *str, int first, int last, + char sep, bool oneBigWord) { Words words; int len, start, end, step; @@ -1737,7 +1756,7 @@ VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, words.words[0] = words.freeIt; words.words[1] = NULL; } else { - words = Str_Words(str, FALSE); + words = Str_Words(str, false); } /* @@ -1779,60 +1798,18 @@ VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first, */ /*ARGSUSED*/ static void -ModifyWord_Realpath(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) +ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { struct stat st; char rbuf[MAXPATHLEN]; + const char *rp; - const char *rp = cached_realpath(word, rbuf); + assert(word.end[0] == '\0'); /* assume null-terminated word */ + rp = cached_realpath(word.start, rbuf); if (rp != NULL && *rp == '/' && stat(rp, &st) == 0) - word = rp; - - SepBuf_AddStr(buf, word); -} - -/* - * Modify each of the words of the passed string using the given function. - * - * Input: - * str String whose words should be modified - * modifyWord Function that modifies a single word - * modifyWord_args Custom arguments for modifyWord - * - * Results: - * A string of all the words modified appropriately. - */ -static char * -ModifyWords(const char *str, - ModifyWordsCallback modifyWord, void *modifyWord_args, - Boolean oneBigWord, char sep) -{ - SepBuf result; - Words words; - size_t i; - - if (oneBigWord) { - SepBuf_Init(&result, sep); - modifyWord(str, &result, modifyWord_args); - return SepBuf_DoneData(&result); - } - - SepBuf_Init(&result, sep); - - words = Str_Words(str, FALSE); - - DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n", - str, (unsigned)words.len); - - for (i = 0; i < words.len; i++) { - modifyWord(words.words[i], &result, modifyWord_args); - if (result.buf.len > 0) - SepBuf_Sep(&result); - } - - Words_Free(words); - - return SepBuf_DoneData(&result); + SepBuf_AddStr(buf, rp); + else + SepBuf_AddSubstring(buf, word); } @@ -1846,7 +1823,7 @@ Words_JoinFree(Words words) for (i = 0; i < words.len; i++) { if (i != 0) { - /* XXX: Use st->sep instead of ' ', for consistency. */ + /* XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddStr(&buf, words.words[i]); @@ -1857,51 +1834,31 @@ Words_JoinFree(Words words) return Buf_DoneData(&buf); } -/* Remove adjacent duplicate words. */ -static char * -VarUniq(const char *str) -{ - Words words = Str_Words(str, FALSE); - - if (words.len > 1) { - size_t i, j; - for (j = 0, i = 1; i < words.len; i++) - if (strcmp(words.words[i], words.words[j]) != 0 && - (++j != i)) - words.words[j] = words.words[i]; - words.len = j + 1; - } - - return Words_JoinFree(words); -} - /* * Quote shell meta-characters and space characters in the string. * If quoteDollar is set, also quote and double any '$' characters. */ -static char * -VarQuote(const char *str, Boolean quoteDollar) +static void +VarQuote(const char *str, bool quoteDollar, LazyBuf *buf) { - Buffer buf; - Buf_Init(&buf); + const char *p; - for (; *str != '\0'; str++) { - if (*str == '\n') { + LazyBuf_Init(buf, str); + for (p = str; *p != '\0'; p++) { + if (*p == '\n') { const char *newline = Shell_GetNewline(); if (newline == NULL) newline = "\\\n"; - Buf_AddStr(&buf, newline); + LazyBuf_AddStr(buf, newline); continue; } - if (ch_isspace(*str) || is_shell_metachar((unsigned char)*str)) - Buf_AddByte(&buf, '\\'); - Buf_AddByte(&buf, *str); - if (quoteDollar && *str == '$') - Buf_AddStr(&buf, "\\$"); + if (ch_isspace(*p) || is_shell_metachar((unsigned char)*p)) + LazyBuf_Add(buf, '\\'); + LazyBuf_Add(buf, *p); + if (quoteDollar && *p == '$') + LazyBuf_AddStr(buf, "\\$"); } - - return Buf_DoneData(&buf); } /* @@ -1969,7 +1926,7 @@ VarHash(const char *str) } static char * -VarStrftime(const char *fmt, Boolean zulu, time_t tim) +VarStrftime(const char *fmt, bool zulu, time_t tim) { char buf[BUFSIZ]; @@ -1985,25 +1942,15 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) /* * 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: - * - * 1. parse the modifier - * 2. evaluate the modifier - * 3. housekeeping + * Their task is to apply a single modifier to the expression. This involves + * parsing the modifier, evaluating it and finally updating the value of the + * expression. * * Parsing the modifier * * 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. The modifier doesn't have to check for this delimiter character, + * ch->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 @@ -2025,18 +1972,21 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * message. Both of these return values will stop processing the variable * expression. (XXX: As of 2020-08-23, evaluation of the whole string * continues nevertheless after skipping a few bytes, which essentially is - * undefined behavior. Not in the sense of C, but still it's impossible to - * predict what happens in the parser.) + * undefined behavior. Not in the sense of C, but still the resulting string + * is garbage.) * * Evaluating the modifier * * After parsing, the modifier is evaluated. The side effects from evaluating * nested variable expressions in the modifier text often already happen - * during parsing though. + * during parsing though. For most modifiers this doesn't matter since their + * only noticeable effect is that the update the value of the expression. + * Some modifiers such as ':sh' or '::=' have noticeable side effects though. * * Evaluating the modifier usually takes the current value of the variable - * expression from st->val, or the variable name from st->var->name and stores - * the result in st->newVal. + * expression from ch->expr->value, or the variable name from ch->var->name + * and stores the result back in expr->value via Expr_SetValueOwn or + * Expr_SetValueRefer. * * If evaluating fails (as of 2020-08-23), an error message is printed using * Error. This function has no side-effects, it really just prints the error @@ -2047,69 +1997,134 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim) * Housekeeping * * Some modifiers such as :D and :U turn undefined expressions into defined - * expressions (see VEF_UNDEF, VEF_DEF). + * expressions (see Expr_Define). * * Some modifiers need to free some memory. */ -typedef enum VarExprStatus { - /* The variable expression is based in a regular, defined variable. */ - VES_NONE, +typedef enum ExprDefined { + /* The variable expression is based on a regular, defined variable. */ + DEF_REGULAR, /* The variable expression is based on an undefined variable. */ - VES_UNDEF, + DEF_UNDEF, /* * The variable expression started as an undefined expression, but one - * of the modifiers (such as :D or :U) has turned the expression from - * undefined to defined. + * of the modifiers (such as ':D' or ':U') has turned the expression + * from undefined to defined. */ - VES_DEF -} VarExprStatus; + DEF_DEFINED +} ExprDefined; -static const char * const VarExprStatus_Name[] = { - "none", - "VES_UNDEF", - "VES_DEF" +static const char *const ExprDefined_Name[] = { + "regular", + "undefined", + "defined" }; -typedef struct ApplyModifiersState { +#if __STDC_VERSION__ >= 199901L +#define const_member const +#else +#define const_member /* no const possible */ +#endif + +/* A variable expression such as $@ or ${VAR:Mpattern:Q}. */ +typedef struct Expr { + const char *name; + FStr value; + VarEvalMode const_member emode; + GNode *const_member scope; + ExprDefined defined; +} Expr; + +/* + * The status of applying a chain of modifiers to an expression. + * + * The modifiers of an expression are broken into chains of modifiers, + * starting a new nested chain whenever an indirect modifier starts. There + * are at most 2 nesting levels: the outer one for the direct modifiers, and + * the inner one for the indirect modifiers. + * + * For example, the expression ${VAR:M*:${IND1}:${IND2}:O:u} has 3 chains of + * modifiers: + * + * Chain 1 starts with the single modifier ':M*'. + * Chain 2 starts with all modifiers from ${IND1}. + * Chain 2 ends at the ':' between ${IND1} and ${IND2}. + * Chain 3 starts with all modifiers from ${IND2}. + * Chain 3 ends at the ':' after ${IND2}. + * Chain 1 continues with the the 2 modifiers ':O' and ':u'. + * Chain 1 ends at the final '}' of the expression. + * + * After such a chain ends, its properties no longer have any effect. + * + * It may or may not have been intended that 'defined' has scope Expr while + * 'sep' and 'oneBigWord' have smaller scope. + * + * See varmod-indirect.mk. + */ +typedef struct ModChain { + Expr *expr; /* '\0' or '{' or '(' */ - const char startc; + char const_member startc; /* '\0' or '}' or ')' */ - const char endc; - Var *const var; - GNode *const scope; - const VarEvalFlags eflags; - /* - * The new value of the expression, after applying the modifier, - * never NULL. - */ - FStr newVal; + char const_member endc; /* Word separator in expansions (see the :ts modifier). */ char sep; /* - * TRUE if some modifiers that otherwise split the variable value + * True if some modifiers that otherwise split the variable value * into words, like :S and :C, treat the variable value as a single * big word, possibly containing spaces. */ - Boolean oneBigWord; - VarExprStatus exprStatus; -} ApplyModifiersState; + bool oneBigWord; +} ModChain; + +static void +Expr_Define(Expr *expr) +{ + if (expr->defined == DEF_UNDEF) + expr->defined = DEF_DEFINED; +} + +static void +Expr_SetValue(Expr *expr, FStr value) +{ + FStr_Done(&expr->value); + expr->value = value; +} + +static void +Expr_SetValueOwn(Expr *expr, char *value) +{ + Expr_SetValue(expr, FStr_InitOwn(value)); +} static void -ApplyModifiersState_Define(ApplyModifiersState *st) +Expr_SetValueRefer(Expr *expr, const char *value) { - if (st->exprStatus == VES_UNDEF) - st->exprStatus = VES_DEF; + Expr_SetValue(expr, FStr_InitRefer(value)); } +static bool +Expr_ShouldEval(const Expr *expr) +{ + return VarEvalMode_ShouldEval(expr->emode); +} + +static bool +ModChain_ShouldEval(const ModChain *ch) +{ + return Expr_ShouldEval(ch->expr); +} + + typedef enum ApplyModifierResult { /* Continue parsing */ AMR_OK, - /* Not a match, try other modifiers as well */ + /* Not a match, try other modifiers as well. */ AMR_UNKNOWN, - /* Error out with "Bad modifier" message */ + /* Error out with "Bad modifier" message. */ AMR_BAD, - /* Error out without error message */ + /* Error out without the standard error message. */ AMR_CLEANUP } ApplyModifierResult; @@ -2117,83 +2132,78 @@ typedef enum ApplyModifierResult { * Allow backslashes to escape the delimiter, $, and \, but don't touch other * backslashes. */ -static Boolean +static bool IsEscapedModifierPart(const char *p, char delim, struct ModifyWord_SubstArgs *subst) { if (p[0] != '\\') - return FALSE; + return false; if (p[1] == delim || p[1] == '\\' || p[1] == '$') - return TRUE; + return true; return p[1] == '&' && subst != NULL; } -/* See ParseModifierPart */ +/* See ParseModifierPart for the documentation. */ static VarParseResult ParseModifierPartSubst( const char **pp, char delim, - VarEvalFlags eflags, - ApplyModifiersState *st, - char **out_part, - /* Optionally stores the length of the returned string, just to save - * another strlen call. */ - size_t *out_length, - /* For the first part of the :S modifier, sets the VARP_ANCHOR_END flag - * if the last character of the pattern is a $. */ - VarPatternFlags *out_pflags, + VarEvalMode emode, + ModChain *ch, + LazyBuf *part, + /* For the first part of the modifier ':S', set anchorEnd if the last + * character of the pattern is a $. */ + PatternFlags *out_pflags, /* For the second part of the :S modifier, allow ampersands to be * escaped and replace unescaped ampersands with subst->lhs. */ struct ModifyWord_SubstArgs *subst ) { - Buffer buf; const char *p; - Buf_Init(&buf); + p = *pp; + LazyBuf_Init(part, p); /* * Skim through until the matching delimiter is found; pick up * variable expressions on the way. */ - p = *pp; while (*p != '\0' && *p != delim) { const char *varstart; if (IsEscapedModifierPart(p, delim, subst)) { - Buf_AddByte(&buf, p[1]); + LazyBuf_Add(part, p[1]); p += 2; continue; } if (*p != '$') { /* Unescaped, simple text */ if (subst != NULL && *p == '&') - Buf_AddBytes(&buf, subst->lhs, subst->lhsLen); + LazyBuf_AddSubstring(part, subst->lhs); else - Buf_AddByte(&buf, *p); + LazyBuf_Add(part, *p); p++; continue; } if (p[1] == delim) { /* Unescaped $ at end of pattern */ if (out_pflags != NULL) - out_pflags->anchorEnd = TRUE; + out_pflags->anchorEnd = true; else - Buf_AddByte(&buf, *p); + LazyBuf_Add(part, *p); p++; continue; } - if (eflags & VARE_WANTRES) { /* Nested variable, evaluated */ + if (VarEvalMode_ShouldEval(emode)) { + /* Nested variable, evaluated */ const char *nested_p = p; FStr nested_val; - VarEvalFlags nested_eflags = - eflags & ~(unsigned)VARE_KEEP_DOLLAR; - (void)Var_Parse(&nested_p, st->scope, nested_eflags, - &nested_val); + (void)Var_Parse(&nested_p, ch->expr->scope, + VarEvalMode_WithoutKeepDollar(emode), &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + LazyBuf_AddStr(part, nested_val.str); FStr_Done(&nested_val); p += nested_p - p; continue; @@ -2201,10 +2211,10 @@ ParseModifierPartSubst( /* * XXX: This whole block is very similar to Var_Parse without - * VARE_WANTRES. There may be subtle edge cases though that - * are not yet covered in the unit tests and that are parsed - * differently, depending on whether they are evaluated or - * not. + * VARE_WANTRES. There may be subtle edge cases + * though that are not yet covered in the unit tests and that + * are parsed differently, depending on whether they are + * evaluated or not. * * This subtle difference is not documented in the manual * page, neither is the difference between parsing :D and @@ -2231,27 +2241,29 @@ ParseModifierPartSubst( depth--; } } - Buf_AddBytesBetween(&buf, varstart, p); + LazyBuf_AddBytesBetween(part, varstart, p); } else { - Buf_AddByte(&buf, *varstart); + LazyBuf_Add(part, *varstart); p++; } } if (*p != delim) { *pp = p; - Error("Unfinished modifier for %s ('%c' missing)", - st->var->name.str, delim); - *out_part = NULL; + Error("Unfinished modifier for \"%s\" ('%c' missing)", + ch->expr->name, delim); + LazyBuf_Done(part); return VPR_ERR; } *pp = p + 1; - if (out_length != NULL) - *out_length = buf.len; - *out_part = Buf_DoneData(&buf); - DEBUG1(VAR, "Modifier part: \"%s\"\n", *out_part); + { + Substring sub = LazyBuf_Get(part); + DEBUG2(VAR, "Modifier part: \"%.*s\"\n", + (int)Substring_Length(sub), sub.start); + } + return VPR_OK; } @@ -2261,10 +2273,9 @@ ParseModifierPartSubst( * including the next unescaped delimiter. The delimiter, as well as the * backslash or the dollar, can be escaped with a backslash. * - * Return the parsed (and possibly expanded) string, or NULL if no delimiter - * was found. On successful return, the parsing position pp points right - * after the delimiter. The delimiter is not included in the returned - * value though. + * Return VPR_OK if parsing succeeded, together with the parsed (and possibly + * expanded) part. In that case, pp points right after the delimiter. The + * delimiter is not included in the part though. */ static VarParseResult ParseModifierPart( @@ -2272,36 +2283,39 @@ ParseModifierPart( const char **pp, /* Parsing stops at this delimiter */ char delim, - /* Flags for evaluating nested variables; if VARE_WANTRES is not set, - * the text is only parsed. */ - VarEvalFlags eflags, - ApplyModifiersState *st, - char **out_part + /* Mode for evaluating nested variables. */ + VarEvalMode emode, + ModChain *ch, + LazyBuf *part ) { - return ParseModifierPartSubst(pp, delim, eflags, st, out_part, - NULL, NULL, NULL); + return ParseModifierPartSubst(pp, delim, emode, ch, part, NULL, NULL); +} + +MAKE_INLINE bool +IsDelimiter(char c, const ModChain *ch) +{ + return c == ':' || c == ch->endc; } /* Test whether mod starts with modname, followed by a delimiter. */ -MAKE_INLINE Boolean -ModMatch(const char *mod, const char *modname, char endc) +MAKE_INLINE bool +ModMatch(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); - return strncmp(mod, modname, n) == 0 && - (mod[n] == endc || mod[n] == ':'); + return strncmp(mod, modname, n) == 0 && IsDelimiter(mod[n], ch); } /* Test whether mod starts with modname, followed by a delimiter or '='. */ -MAKE_INLINE Boolean -ModMatchEq(const char *mod, const char *modname, char endc) +MAKE_INLINE bool +ModMatchEq(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && - (mod[n] == endc || mod[n] == ':' || mod[n] == '='); + (IsDelimiter(mod[n], ch) || mod[n] == '='); } -static Boolean +static bool TryParseIntBase0(const char **pp, int *out_num) { char *end; @@ -2309,127 +2323,180 @@ TryParseIntBase0(const char **pp, int *out_num) errno = 0; n = strtol(*pp, &end, 0); + + if (end == *pp) + return false; if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) - return FALSE; + return false; if (n < INT_MIN || n > INT_MAX) - return FALSE; + return false; *pp = end; *out_num = (int)n; - return TRUE; + return true; } -static Boolean +static bool TryParseSize(const char **pp, size_t *out_num) { char *end; unsigned long n; if (!ch_isdigit(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; if (n > SIZE_MAX) - return FALSE; + return false; *pp = end; *out_num = (size_t)n; - return TRUE; + return true; } -static Boolean +static bool TryParseChar(const char **pp, int base, char *out_ch) { char *end; unsigned long n; if (!ch_isalnum(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, base); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; if (n > UCHAR_MAX) - return FALSE; + return false; *pp = end; *out_ch = (char)n; - return TRUE; + return true; +} + +/* + * Modify each word of the expression using the given function and place the + * result back in the expression. + */ +static void +ModifyWords(ModChain *ch, + ModifyWordProc modifyWord, void *modifyWord_args, + bool oneBigWord) +{ + Expr *expr = ch->expr; + const char *val = expr->value.str; + SepBuf result; + SubstringWords words; + size_t i; + Substring word; + + if (oneBigWord) { + SepBuf_Init(&result, ch->sep); + /* XXX: performance: Substring_InitStr calls strlen */ + word = Substring_InitStr(val); + modifyWord(word, &result, modifyWord_args); + goto done; + } + + words = Substring_Words(val, false); + + DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n", + val, (unsigned)words.len); + + SepBuf_Init(&result, ch->sep); + for (i = 0; i < words.len; i++) { + modifyWord(words.words[i], &result, modifyWord_args); + if (result.buf.len > 0) + SepBuf_Sep(&result); + } + + SubstringWords_Free(words); + +done: + Expr_SetValueOwn(expr, SepBuf_DoneData(&result)); } /* :@var@...${var}...@ */ static ApplyModifierResult -ApplyModifier_Loop(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Loop(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; struct ModifyWord_LoopArgs args; char prev_sep; VarParseResult res; + LazyBuf tvarBuf, strBuf; + FStr tvar, str; - args.scope = st->scope; + args.scope = expr->scope; (*pp)++; /* Skip the first '@' */ - res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.tvar); + res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf); if (res != VPR_OK) return AMR_CLEANUP; - if (opts.strict && strchr(args.tvar, '$') != NULL) { + tvar = LazyBuf_DoneGet(&tvarBuf); + args.var = tvar.str; + if (strchr(args.var, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier of \"%s\", the variable name \"%s\" " "must not contain a dollar.", - st->var->name.str, args.tvar); + expr->name, args.var); return AMR_CLEANUP; } - res = ParseModifierPart(pp, '@', VARE_NONE, st, &args.str); + res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &strBuf); if (res != VPR_OK) return AMR_CLEANUP; - - args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR; - prev_sep = st->sep; - st->sep = ' '; /* XXX: should be st->sep for consistency */ - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Loop, &args, st->oneBigWord, st->sep)); - st->sep = prev_sep; - /* XXX: Consider restoring the previous variable instead of deleting. */ - /* - * XXX: The variable name should not be expanded here, see - * ModifyWord_Loop. - */ - Var_DeleteExpand(st->scope, args.tvar); - free(args.tvar); - free(args.str); + str = LazyBuf_DoneGet(&strBuf); + args.body = str.str; + + if (!Expr_ShouldEval(expr)) + goto done; + + args.emode = VarEvalMode_WithoutKeepDollar(expr->emode); + prev_sep = ch->sep; + ch->sep = ' '; /* XXX: should be ch->sep for consistency */ + ModifyWords(ch, ModifyWord_Loop, &args, ch->oneBigWord); + ch->sep = prev_sep; + /* XXX: Consider restoring the previous value instead of deleting. */ + Var_Delete(expr->scope, args.var); + +done: + FStr_Done(&tvar); + FStr_Done(&str); return AMR_OK; } /* :Ddefined or :Uundefined */ static ApplyModifierResult -ApplyModifier_Defined(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Defined(const char **pp, ModChain *ch) { - Buffer buf; + Expr *expr = ch->expr; + LazyBuf buf; const char *p; - VarEvalFlags eflags = VARE_NONE; - if (st->eflags & VARE_WANTRES) - if ((**pp == 'D') == (st->exprStatus == VES_NONE)) - eflags = st->eflags; + VarEvalMode emode = VARE_PARSE_ONLY; + if (Expr_ShouldEval(expr)) + if ((**pp == 'D') == (expr->defined == DEF_REGULAR)) + emode = expr->emode; - Buf_Init(&buf); p = *pp + 1; - while (*p != st->endc && *p != ':' && *p != '\0') { + LazyBuf_Init(&buf, p); + while (!IsDelimiter(*p, ch) && *p != '\0') { /* XXX: This code is similar to the one in Var_Parse. * See if the code can be merged. - * See also ApplyModifier_Match. */ + * See also ApplyModifier_Match and ParseModifierPart. */ /* Escaped delimiter or other special character */ + /* See Buf_AddEscaped in for.c. */ if (*p == '\\') { char c = p[1]; - if (c == st->endc || c == ':' || c == '$' || - c == '\\') { - Buf_AddByte(&buf, c); + if (IsDelimiter(c, ch) || c == '$' || c == '\\') { + LazyBuf_Add(&buf, c); p += 2; continue; } @@ -2439,173 +2506,197 @@ ApplyModifier_Defined(const char **pp, const char *val, ApplyModifiersState *st) if (*p == '$') { FStr nested_val; - (void)Var_Parse(&p, st->scope, eflags, &nested_val); + (void)Var_Parse(&p, expr->scope, emode, &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + if (Expr_ShouldEval(expr)) + LazyBuf_AddStr(&buf, nested_val.str); FStr_Done(&nested_val); continue; } /* Ordinary text */ - Buf_AddByte(&buf, *p); + LazyBuf_Add(&buf, *p); p++; } *pp = p; - ApplyModifiersState_Define(st); + Expr_Define(expr); + + if (VarEvalMode_ShouldEval(emode)) + Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf))); + else + LazyBuf_Done(&buf); - if (eflags & VARE_WANTRES) { - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); - } else { - st->newVal = FStr_InitRefer(val); - Buf_Done(&buf); - } return AMR_OK; } /* :L */ static ApplyModifierResult -ApplyModifier_Literal(const char **pp, ApplyModifiersState *st) +ApplyModifier_Literal(const char **pp, ModChain *ch) { - ApplyModifiersState_Define(st); - st->newVal = FStr_InitOwn(bmake_strdup(st->var->name.str)); + Expr *expr = ch->expr; + (*pp)++; + + if (Expr_ShouldEval(expr)) { + Expr_Define(expr); + Expr_SetValueOwn(expr, bmake_strdup(expr->name)); + } + return AMR_OK; } -static Boolean +static bool TryParseTime(const char **pp, time_t *out_time) { char *end; unsigned long n; if (!ch_isdigit(**pp)) - return FALSE; + return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) - return FALSE; + return false; *pp = end; *out_time = (time_t)n; /* ignore possible truncation for now */ - return TRUE; + return true; } /* :gmtime */ static ApplyModifierResult -ApplyModifier_Gmtime(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Gmtime(const char **pp, ModChain *ch) { time_t utc; const char *mod = *pp; - if (!ModMatchEq(mod, "gmtime", st->endc)) + if (!ModMatchEq(mod, "gmtime", ch)) return AMR_UNKNOWN; if (mod[6] == '=') { - const char *arg = mod + 7; - if (!TryParseTime(&arg, &utc)) { + const char *p = mod + 7; + if (!TryParseTime(&p, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s", mod + 7); return AMR_CLEANUP; } - *pp = arg; + *pp = p; } else { utc = 0; *pp = mod + 6; } - st->newVal = FStr_InitOwn(VarStrftime(val, TRUE, utc)); + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, + VarStrftime(ch->expr->value.str, true, utc)); + return AMR_OK; } /* :localtime */ static ApplyModifierResult -ApplyModifier_Localtime(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_Localtime(const char **pp, ModChain *ch) { time_t utc; const char *mod = *pp; - if (!ModMatchEq(mod, "localtime", st->endc)) + if (!ModMatchEq(mod, "localtime", ch)) return AMR_UNKNOWN; if (mod[9] == '=') { - const char *arg = mod + 10; - if (!TryParseTime(&arg, &utc)) { + const char *p = mod + 10; + if (!TryParseTime(&p, &utc)) { Parse_Error(PARSE_FATAL, "Invalid time value: %s", mod + 10); return AMR_CLEANUP; } - *pp = arg; + *pp = p; } else { utc = 0; *pp = mod + 9; } - st->newVal = FStr_InitOwn(VarStrftime(val, FALSE, utc)); + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, + VarStrftime(ch->expr->value.str, false, utc)); + return AMR_OK; } /* :hash */ static ApplyModifierResult -ApplyModifier_Hash(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Hash(const char **pp, ModChain *ch) { - if (!ModMatch(*pp, "hash", st->endc)) + if (!ModMatch(*pp, "hash", ch)) return AMR_UNKNOWN; - - st->newVal = FStr_InitOwn(VarHash(val)); *pp += 4; + + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(ch->expr, VarHash(ch->expr->value.str)); + return AMR_OK; } /* :P */ static ApplyModifierResult -ApplyModifier_Path(const char **pp, ApplyModifiersState *st) +ApplyModifier_Path(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; GNode *gn; char *path; - ApplyModifiersState_Define(st); + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; - gn = Targ_FindNode(st->var->name.str); + Expr_Define(expr); + + gn = Targ_FindNode(expr->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->var->name.str, searchPath); + path = Dir_FindFile(expr->name, searchPath); } if (path == NULL) - path = bmake_strdup(st->var->name.str); - st->newVal = FStr_InitOwn(path); + path = bmake_strdup(expr->name); + Expr_SetValueOwn(expr, path); - (*pp)++; return AMR_OK; } /* :!cmd! */ static ApplyModifierResult -ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) +ApplyModifier_ShellCommand(const char **pp, ModChain *ch) { - char *cmd; + Expr *expr = ch->expr; const char *errfmt; VarParseResult res; + LazyBuf cmdBuf; + FStr cmd; (*pp)++; - res = ParseModifierPart(pp, '!', st->eflags, st, &cmd); + res = ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf); if (res != VPR_OK) return AMR_CLEANUP; + cmd = LazyBuf_DoneGet(&cmdBuf); + errfmt = NULL; - if (st->eflags & VARE_WANTRES) - st->newVal = FStr_InitOwn(Cmd_Exec(cmd, &errfmt)); + if (Expr_ShouldEval(expr)) + Expr_SetValueOwn(expr, Cmd_Exec(cmd.str, &errfmt)); else - st->newVal = FStr_InitRefer(""); + Expr_SetValueRefer(expr, ""); if (errfmt != NULL) - Error(errfmt, cmd); /* XXX: why still return AMR_OK? */ - free(cmd); + Error(errfmt, cmd.str); /* XXX: why still return AMR_OK? */ + FStr_Done(&cmd); + Expr_Define(expr); - ApplyModifiersState_Define(st); return AMR_OK; } @@ -2614,21 +2705,22 @@ ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st) * The :range=7 modifier generates an integer sequence from 1 to 7. */ static ApplyModifierResult -ApplyModifier_Range(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Range(const char **pp, ModChain *ch) { size_t n; Buffer buf; size_t i; const char *mod = *pp; - if (!ModMatchEq(mod, "range", st->endc)) + if (!ModMatchEq(mod, "range", ch)) return AMR_UNKNOWN; if (mod[5] == '=') { const char *p = mod + 6; if (!TryParseSize(&p, &n)) { Parse_Error(PARSE_FATAL, - "Invalid number: %s", mod + 6); + "Invalid number \"%s\" for ':range' modifier", + mod + 6); return AMR_CLEANUP; } *pp = p; @@ -2637,8 +2729,11 @@ ApplyModifier_Range(const char **pp, const char *val, ApplyModifiersState *st) *pp = mod + 5; } + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + if (n == 0) { - Words words = Str_Words(val, FALSE); + Words words = Str_Words(ch->expr->value.str, false); n = words.len; Words_Free(words); } @@ -2647,47 +2742,50 @@ ApplyModifier_Range(const char **pp, const char *val, ApplyModifiersState *st) for (i = 0; i < n; i++) { if (i != 0) { - /* XXX: Use st->sep instead of ' ', for consistency. */ + /* XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddInt(&buf, 1 + (int)i); } - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); + Expr_SetValueOwn(ch->expr, Buf_DoneData(&buf)); return AMR_OK; } -/* :Mpattern or :Npattern */ -static ApplyModifierResult -ApplyModifier_Match(const char **pp, const char *val, ApplyModifiersState *st) +/* Parse a ':M' or ':N' modifier. */ +static void +ParseModifier_Match(const char **pp, const ModChain *ch, + char **out_pattern) { const char *mod = *pp; - Boolean copy = FALSE; /* pattern should be, or has been, copied */ - Boolean needSubst = FALSE; + Expr *expr = ch->expr; + bool copy = false; /* pattern should be, or has been, copied */ + bool needSubst = false; const char *endpat; char *pattern; - ModifyWordsCallback callback; /* * 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 code is similar to the one in Var_Parse. + /* + * XXX: This code is similar to the one in Var_Parse. * See if the code can be merged. - * See also ApplyModifier_Defined. */ + * See also ApplyModifier_Defined. + */ int nest = 0; const char *p; for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) { if (*p == '\\' && - (p[1] == ':' || p[1] == st->endc || p[1] == st->startc)) { + (IsDelimiter(p[1], ch) || p[1] == ch->startc)) { if (!needSubst) - copy = TRUE; + copy = true; p++; continue; } if (*p == '$') - needSubst = TRUE; + needSubst = true; if (*p == '(' || *p == '{') nest++; if (*p == ')' || *p == '}') { @@ -2709,8 +2807,8 @@ ApplyModifier_Match(const char **pp, const char *val, ApplyModifiersState *st) src = mod + 1; for (; src < endpat; src++, dst++) { if (src[0] == '\\' && src + 1 < endpat && - /* XXX: st->startc is missing here; see above */ - (src[1] == ':' || src[1] == st->endc)) + /* XXX: ch->startc is missing here; see above */ + IsDelimiter(src[1], ch)) src++; *dst = *src; } @@ -2721,84 +2819,104 @@ ApplyModifier_Match(const char **pp, const char *val, ApplyModifiersState *st) if (needSubst) { char *old_pattern = pattern; - (void)Var_Subst(pattern, st->scope, st->eflags, &pattern); + (void)Var_Subst(pattern, expr->scope, expr->emode, &pattern); /* TODO: handle errors */ free(old_pattern); } - DEBUG3(VAR, "Pattern[%s] for [%s] is [%s]\n", - st->var->name.str, val, pattern); + DEBUG2(VAR, "Pattern for ':%c' is \"%s\"\n", mod[0], pattern); + + *out_pattern = pattern; +} + +/* :Mpattern or :Npattern */ +static ApplyModifierResult +ApplyModifier_Match(const char **pp, ModChain *ch) +{ + const char mod = **pp; + char *pattern; + + ParseModifier_Match(pp, ch, &pattern); + + if (ModChain_ShouldEval(ch)) { + ModifyWordProc modifyWord = + mod == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; + ModifyWords(ch, modifyWord, pattern, ch->oneBigWord); + } - callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch; - st->newVal = FStr_InitOwn(ModifyWords(val, callback, pattern, - st->oneBigWord, st->sep)); free(pattern); return AMR_OK; } +static void +ParsePatternFlags(const char **pp, PatternFlags *pflags, bool *oneBigWord) +{ + for (;; (*pp)++) { + if (**pp == 'g') + pflags->subGlobal = true; + else if (**pp == '1') + pflags->subOnce = true; + else if (**pp == 'W') + *oneBigWord = true; + else + break; + } +} + +MAKE_INLINE PatternFlags +PatternFlags_None(void) +{ + PatternFlags pflags = { false, false, false, false }; + return pflags; +} + /* :S,from,to, */ static ApplyModifierResult -ApplyModifier_Subst(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Subst(const char **pp, ModChain *ch) { struct ModifyWord_SubstArgs args; - char *lhs, *rhs; - Boolean oneBigWord; + bool oneBigWord; VarParseResult res; + LazyBuf lhsBuf, rhsBuf; char delim = (*pp)[1]; if (delim == '\0') { - Error("Missing delimiter for :S modifier"); + Error("Missing delimiter for modifier ':S'"); (*pp)++; return AMR_CLEANUP; } *pp += 2; - args.pflags = (VarPatternFlags){ FALSE, FALSE, FALSE, FALSE }; - args.matched = FALSE; + args.pflags = PatternFlags_None(); + args.matched = false; - /* - * If pattern begins with '^', it is anchored to the - * start of the word -- skip over it and flag pattern. - */ if (**pp == '^') { - args.pflags.anchorStart = TRUE; + args.pflags.anchorStart = true; (*pp)++; } - res = ParseModifierPartSubst(pp, delim, st->eflags, st, &lhs, - &args.lhsLen, &args.pflags, NULL); + res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &lhsBuf, + &args.pflags, NULL); if (res != VPR_OK) return AMR_CLEANUP; - args.lhs = lhs; + args.lhs = LazyBuf_Get(&lhsBuf); - res = ParseModifierPartSubst(pp, delim, st->eflags, st, &rhs, - &args.rhsLen, NULL, &args); - if (res != VPR_OK) + res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &rhsBuf, + NULL, &args); + if (res != VPR_OK) { + LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; - args.rhs = rhs; - - oneBigWord = st->oneBigWord; - for (;; (*pp)++) { - switch (**pp) { - case 'g': - args.pflags.subGlobal = TRUE; - continue; - case '1': - args.pflags.subOnce = TRUE; - continue; - case 'W': - oneBigWord = TRUE; - continue; - } - break; } + args.rhs = LazyBuf_Get(&rhsBuf); + + oneBigWord = ch->oneBigWord; + ParsePatternFlags(pp, &args.pflags, &oneBigWord); - st->newVal = FStr_InitOwn(ModifyWords(val, ModifyWord_Subst, &args, - oneBigWord, st->sep)); + ModifyWords(ch, ModifyWord_Subst, &args, oneBigWord); - free(lhs); - free(rhs); + LazyBuf_Done(&lhsBuf); + LazyBuf_Done(&rhsBuf); return AMR_OK; } @@ -2806,13 +2924,14 @@ ApplyModifier_Subst(const char **pp, const char *val, ApplyModifiersState *st) /* :C,from,to, */ static ApplyModifierResult -ApplyModifier_Regex(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Regex(const char **pp, ModChain *ch) { - char *re; struct ModifyWord_SubstRegexArgs args; - Boolean oneBigWord; + bool oneBigWord; int error; VarParseResult res; + LazyBuf reBuf, replaceBuf; + FStr re, replace; char delim = (*pp)[1]; if (delim == '\0') { @@ -2823,50 +2942,47 @@ ApplyModifier_Regex(const char **pp, const char *val, ApplyModifiersState *st) *pp += 2; - res = ParseModifierPart(pp, delim, st->eflags, st, &re); + res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf); if (res != VPR_OK) return AMR_CLEANUP; + re = LazyBuf_DoneGet(&reBuf); - res = ParseModifierPart(pp, delim, st->eflags, st, &args.replace); - if (args.replace == NULL) { - free(re); + res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf); + if (res != VPR_OK) { + FStr_Done(&re); return AMR_CLEANUP; } + replace = LazyBuf_DoneGet(&replaceBuf); + args.replace = replace.str; - args.pflags = (VarPatternFlags){ FALSE, FALSE, FALSE, FALSE }; - args.matched = FALSE; - oneBigWord = st->oneBigWord; - for (;; (*pp)++) { - switch (**pp) { - case 'g': - args.pflags.subGlobal = TRUE; - continue; - case '1': - args.pflags.subOnce = TRUE; - continue; - case 'W': - oneBigWord = TRUE; - continue; - } - break; + args.pflags = PatternFlags_None(); + args.matched = false; + oneBigWord = ch->oneBigWord; + ParsePatternFlags(pp, &args.pflags, &oneBigWord); + + if (!ModChain_ShouldEval(ch)) { + FStr_Done(&replace); + FStr_Done(&re); + return AMR_OK; } - error = regcomp(&args.re, re, REG_EXTENDED); - free(re); + error = regcomp(&args.re, re.str, REG_EXTENDED); if (error != 0) { VarREError(error, &args.re, "Regex compilation error"); - free(args.replace); + FStr_Done(&replace); + FStr_Done(&re); return AMR_CLEANUP; } args.nsub = args.re.re_nsub + 1; if (args.nsub > 10) args.nsub = 10; - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_SubstRegex, &args, - oneBigWord, st->sep)); + + ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord); + regfree(&args.re); - free(args.replace); + FStr_Done(&replace); + FStr_Done(&re); return AMR_OK; } @@ -2874,40 +2990,59 @@ ApplyModifier_Regex(const char **pp, const char *val, ApplyModifiersState *st) /* :Q, :q */ static ApplyModifierResult -ApplyModifier_Quote(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Quote(const char **pp, ModChain *ch) { - if ((*pp)[1] == st->endc || (*pp)[1] == ':') { - st->newVal = FStr_InitOwn(VarQuote(val, **pp == 'q')); - (*pp)++; - return AMR_OK; - } else + LazyBuf buf; + bool quoteDollar; + + quoteDollar = **pp == 'q'; + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + VarQuote(ch->expr->value.str, quoteDollar, &buf); + if (buf.data != NULL) + Expr_SetValue(ch->expr, LazyBuf_DoneGet(&buf)); + else + LazyBuf_Done(&buf); + + return AMR_OK; } /*ARGSUSED*/ static void -ModifyWord_Copy(const char *word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) +ModifyWord_Copy(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { - SepBuf_AddStr(buf, word); + SepBuf_AddSubstring(buf, word); } /* :ts<separator> */ static ApplyModifierResult -ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_ToSep(const char **pp, ModChain *ch) { const char *sep = *pp + 2; + /* + * Even in parse-only mode, proceed as normal since there is + * neither any observable side effect nor a performance penalty. + * Checking for wantRes for every single piece of code in here + * would make the code in this function too hard to read. + */ + /* ":ts<any><endc>" or ":ts<any>:" */ - if (sep[0] != st->endc && (sep[1] == st->endc || sep[1] == ':')) { - st->sep = sep[0]; + if (sep[0] != ch->endc && IsDelimiter(sep[1], ch)) { *pp = sep + 1; + ch->sep = sep[0]; goto ok; } /* ":ts<endc>" or ":ts:" */ - if (sep[0] == st->endc || sep[0] == ':') { - st->sep = '\0'; /* no separator */ + if (IsDelimiter(sep[0], ch)) { *pp = sep; + ch->sep = '\0'; /* no separator */ goto ok; } @@ -2919,15 +3054,15 @@ ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st) /* ":ts\n" */ if (sep[1] == 'n') { - st->sep = '\n'; *pp = sep + 2; + ch->sep = '\n'; goto ok; } /* ":ts\t" */ if (sep[1] == 't') { - st->sep = '\t'; *pp = sep + 2; + ch->sep = '\t'; goto ok; } @@ -2944,12 +3079,12 @@ ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st) return AMR_BAD; /* ":ts<backslash><unrecognised>". */ } - if (!TryParseChar(&p, base, &st->sep)) { + if (!TryParseChar(&p, base, &ch->sep)) { Parse_Error(PARSE_FATAL, "Invalid character number: %s", p); return AMR_CLEANUP; } - if (*p != ':' && *p != st->endc) { + if (!IsDelimiter(*p, ch)) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } @@ -2958,8 +3093,7 @@ ApplyModifier_ToSep(const char **pp, const char *val, ApplyModifiersState *st) } ok: - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Copy, NULL, st->oneBigWord, st->sep)); + ModifyWords(ch, ModifyWord_Copy, NULL, ch->oneBigWord); return AMR_OK; } @@ -2993,107 +3127,109 @@ str_tolower(const char *str) /* :tA, :tu, :tl, :ts<separator>, etc. */ static ApplyModifierResult -ApplyModifier_To(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_To(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *mod = *pp; assert(mod[0] == 't'); - if (mod[1] == st->endc || mod[1] == ':' || mod[1] == '\0') { + if (IsDelimiter(mod[1], ch) || mod[1] == '\0') { *pp = mod + 1; return AMR_BAD; /* Found ":t<endc>" or ":t:". */ } if (mod[1] == 's') - return ApplyModifier_ToSep(pp, val, st); + return ApplyModifier_ToSep(pp, ch); - if (mod[2] != st->endc && mod[2] != ':') { + if (!IsDelimiter(mod[2], ch)) { /* :t<unrecognized> */ *pp = mod + 1; - return AMR_BAD; /* Found ":t<unrecognised><unrecognised>". */ + return AMR_BAD; } - /* Check for two-character options: ":tu", ":tl" */ - if (mod[1] == 'A') { /* absolute path */ - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_Realpath, NULL, - st->oneBigWord, st->sep)); + if (mod[1] == 'A') { /* :tA */ *pp = mod + 2; + ModifyWords(ch, ModifyWord_Realpath, NULL, ch->oneBigWord); return AMR_OK; } - if (mod[1] == 'u') { /* :tu */ - st->newVal = FStr_InitOwn(str_toupper(val)); + if (mod[1] == 'u') { /* :tu */ *pp = mod + 2; + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(expr, str_toupper(expr->value.str)); return AMR_OK; } - if (mod[1] == 'l') { /* :tl */ - st->newVal = FStr_InitOwn(str_tolower(val)); + if (mod[1] == 'l') { /* :tl */ *pp = mod + 2; + if (ModChain_ShouldEval(ch)) + Expr_SetValueOwn(expr, str_tolower(expr->value.str)); return AMR_OK; } - if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ - st->oneBigWord = mod[1] == 'W'; - st->newVal = FStr_InitRefer(val); + if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ *pp = mod + 2; + ch->oneBigWord = mod[1] == 'W'; return AMR_OK; } /* Found ":t<unrecognised>:" or ":t<unrecognised><endc>". */ - *pp = mod + 1; + *pp = mod + 1; /* XXX: unnecessary but observable */ return AMR_BAD; } /* :[#], :[1], :[-1..1], etc. */ static ApplyModifierResult -ApplyModifier_Words(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Words(const char **pp, ModChain *ch) { - char *estr; + Expr *expr = ch->expr; + const char *estr; int first, last; VarParseResult res; const char *p; + LazyBuf estrBuf; + FStr festr; (*pp)++; /* skip the '[' */ - res = ParseModifierPart(pp, ']', st->eflags, st, &estr); + res = ParseModifierPart(pp, ']', expr->emode, ch, &estrBuf); if (res != VPR_OK) return AMR_CLEANUP; + festr = LazyBuf_DoneGet(&estrBuf); + estr = festr.str; + + if (!IsDelimiter(**pp, ch)) + goto bad_modifier; /* Found junk after ']' */ - /* now *pp points just after the closing ']' */ - if (**pp != ':' && **pp != st->endc) - goto bad_modifier; /* Found junk after ']' */ + if (!ModChain_ShouldEval(ch)) + goto ok; if (estr[0] == '\0') - goto bad_modifier; /* empty square brackets in ":[]". */ + goto bad_modifier; /* Found ":[]". */ - if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ - if (st->oneBigWord) { - st->newVal = FStr_InitRefer("1"); + if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ + if (ch->oneBigWord) { + Expr_SetValueRefer(expr, "1"); } else { Buffer buf; - Words words = Str_Words(val, FALSE); + Words words = Str_Words(expr->value.str, false); size_t ac = words.len; Words_Free(words); /* 3 digits + '\0' is usually enough */ Buf_InitSize(&buf, 4); Buf_AddInt(&buf, (int)ac); - st->newVal = FStr_InitOwn(Buf_DoneData(&buf)); + Expr_SetValueOwn(expr, Buf_DoneData(&buf)); } goto ok; } - if (estr[0] == '*' && estr[1] == '\0') { - /* Found ":[*]" */ - st->oneBigWord = TRUE; - st->newVal = FStr_InitRefer(val); + if (estr[0] == '*' && estr[1] == '\0') { /* Found ":[*]" */ + ch->oneBigWord = true; goto ok; } - if (estr[0] == '@' && estr[1] == '\0') { - /* Found ":[@]" */ - st->oneBigWord = FALSE; - st->newVal = FStr_InitRefer(val); + if (estr[0] == '@' && estr[1] == '\0') { /* Found ":[@]" */ + ch->oneBigWord = false; goto ok; } @@ -3121,8 +3257,7 @@ ApplyModifier_Words(const char **pp, const char *val, ApplyModifiersState *st) */ if (first == 0 && last == 0) { /* ":[0]" or perhaps ":[0..0]" */ - st->oneBigWord = TRUE; - st->newVal = FStr_InitRefer(val); + ch->oneBigWord = true; goto ok; } @@ -3131,15 +3266,16 @@ ApplyModifier_Words(const char **pp, const char *val, ApplyModifiersState *st) goto bad_modifier; /* Normal case: select the words described by first and last. */ - st->newVal = FStr_InitOwn( - VarSelectWords(st->sep, st->oneBigWord, val, first, last)); + Expr_SetValueOwn(expr, + VarSelectWords(expr->value.str, first, last, + ch->sep, ch->oneBigWord)); ok: - free(estr); + FStr_Done(&festr); return AMR_OK; bad_modifier: - free(estr); + FStr_Done(&festr); return AMR_BAD; } @@ -3170,94 +3306,107 @@ ShuffleStrings(char **strs, size_t n) /* :O (order ascending) or :Or (order descending) or :Ox (shuffle) */ static ApplyModifierResult -ApplyModifier_Order(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Order(const char **pp, ModChain *ch) { const char *mod = (*pp)++; /* skip past the 'O' in any case */ + Words words; + enum SortMode { + ASC, DESC, SHUFFLE + } mode; - Words words = Str_Words(val, FALSE); - - if (mod[1] == st->endc || mod[1] == ':') { - /* :O sorts ascending */ - qsort(words.words, words.len, sizeof words.words[0], - str_cmp_asc); - + if (IsDelimiter(mod[1], ch)) { + mode = ASC; } else if ((mod[1] == 'r' || mod[1] == 'x') && - (mod[2] == st->endc || mod[2] == ':')) { + IsDelimiter(mod[2], ch)) { (*pp)++; - - if (mod[1] == 'r') { /* :Or sorts descending */ - qsort(words.words, words.len, sizeof words.words[0], - str_cmp_desc); - } else - ShuffleStrings(words.words, words.len); - } else { - Words_Free(words); + mode = mod[1] == 'r' ? DESC : SHUFFLE; + } else return AMR_BAD; - } - st->newVal = FStr_InitOwn(Words_JoinFree(words)); + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + words = Str_Words(ch->expr->value.str, false); + if (mode == SHUFFLE) + ShuffleStrings(words.words, words.len); + else + qsort(words.words, words.len, sizeof words.words[0], + mode == ASC ? str_cmp_asc : str_cmp_desc); + Expr_SetValueOwn(ch->expr, Words_JoinFree(words)); + return AMR_OK; } /* :? then : else */ static ApplyModifierResult -ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) +ApplyModifier_IfElse(const char **pp, ModChain *ch) { - char *then_expr, *else_expr; + Expr *expr = ch->expr; VarParseResult res; + LazyBuf buf; + FStr then_expr, else_expr; - Boolean value = FALSE; - VarEvalFlags then_eflags = VARE_NONE; - VarEvalFlags else_eflags = VARE_NONE; + bool value = false; + VarEvalMode then_emode = VARE_PARSE_ONLY; + VarEvalMode else_emode = VARE_PARSE_ONLY; int cond_rc = COND_PARSE; /* anything other than COND_INVALID */ - if (st->eflags & VARE_WANTRES) { - cond_rc = Cond_EvalCondition(st->var->name.str, &value); + if (Expr_ShouldEval(expr)) { + cond_rc = Cond_EvalCondition(expr->name, &value); if (cond_rc != COND_INVALID && value) - then_eflags = st->eflags; + then_emode = expr->emode; if (cond_rc != COND_INVALID && !value) - else_eflags = st->eflags; + else_emode = expr->emode; } (*pp)++; /* skip past the '?' */ - res = ParseModifierPart(pp, ':', then_eflags, st, &then_expr); + res = ParseModifierPart(pp, ':', then_emode, ch, &buf); if (res != VPR_OK) return AMR_CLEANUP; + then_expr = LazyBuf_DoneGet(&buf); - res = ParseModifierPart(pp, st->endc, else_eflags, st, &else_expr); - if (res != VPR_OK) + res = ParseModifierPart(pp, ch->endc, else_emode, ch, &buf); + if (res != VPR_OK) { + FStr_Done(&then_expr); return AMR_CLEANUP; + } + else_expr = LazyBuf_DoneGet(&buf); + + (*pp)--; /* Go back to the ch->endc. */ - (*pp)--; if (cond_rc == COND_INVALID) { - Error("Bad conditional expression `%s' in %s?%s:%s", - st->var->name.str, st->var->name.str, then_expr, else_expr); + Error("Bad conditional expression '%s' in '%s?%s:%s'", + expr->name, expr->name, then_expr.str, else_expr.str); return AMR_CLEANUP; } - if (value) { - st->newVal = FStr_InitOwn(then_expr); - free(else_expr); + if (!ModChain_ShouldEval(ch)) { + FStr_Done(&then_expr); + FStr_Done(&else_expr); + } else if (value) { + Expr_SetValue(expr, then_expr); + FStr_Done(&else_expr); } else { - st->newVal = FStr_InitOwn(else_expr); - free(then_expr); + FStr_Done(&then_expr); + Expr_SetValue(expr, else_expr); } - ApplyModifiersState_Define(st); + Expr_Define(expr); return AMR_OK; } /* - * The ::= modifiers actually assign a value to the variable. - * Their main purpose is in supporting modifiers of .for loop - * iterators and other obscure uses. They always expand to - * nothing. In a target rule that would otherwise expand to an - * empty line they can be preceded with @: to keep make happy. - * Eg. + * The ::= modifiers are special in that they do not read the variable value + * but instead assign to that variable. They always expand to an empty + * string. * - * foo: .USE + * Their main purpose is in supporting .for loops that generate shell commands + * since an ordinary variable assignment at that point would terminate the + * dependency group for these targets. For example: + * + * list-targets: .USE * .for i in ${.TARGET} ${.TARGET:R}.gz - * @: ${t::=$i} - * @echo blah ${t:T} + * @${t::=$i} + * @echo 'The target is ${t:T}.' * .endfor * * ::=<str> Assigns <str> as the new value of variable. @@ -3268,12 +3417,13 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st) * variable. */ static ApplyModifierResult -ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) +ApplyModifier_Assign(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; GNode *scope; - char delim; - char *val; + FStr val; VarParseResult res; + LazyBuf buf; const char *mod = *pp; const char *op = mod + 1; @@ -3283,22 +3433,13 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st) if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=') goto ok; return AMR_UNKNOWN; /* "::<unrecognised>" */ -ok: - if (st->var->name.str[0] == '\0') { +ok: + if (expr->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } - scope = st->scope; /* scope where v belongs */ - if (st->exprStatus == VES_NONE && st->scope != SCOPE_GLOBAL) { - Var *gv = VarFind(st->var->name.str, st->scope, FALSE); - if (gv == NULL) - scope = SCOPE_GLOBAL; - else - VarFreeEnv(gv, TRUE); - } - switch (op[0]) { case '+': case '?': @@ -3310,41 +3451,51 @@ ok: break; } - delim = st->startc == '(' ? ')' : '}'; - res = ParseModifierPart(pp, delim, st->eflags, st, &val); + res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf); if (res != VPR_OK) return AMR_CLEANUP; + val = LazyBuf_DoneGet(&buf); - (*pp)--; + (*pp)--; /* Go back to the ch->endc. */ - /* XXX: Expanding the variable name at this point sounds wrong. */ - if (st->eflags & VARE_WANTRES) { - switch (op[0]) { - case '+': - Var_AppendExpand(scope, st->var->name.str, val); - break; - case '!': { - const char *errfmt; - char *cmd_output = Cmd_Exec(val, &errfmt); - if (errfmt != NULL) - Error(errfmt, val); - else - Var_SetExpand(scope, - st->var->name.str, cmd_output); - free(cmd_output); - break; - } - case '?': - if (st->exprStatus == VES_NONE) - break; - /* FALLTHROUGH */ - default: - Var_SetExpand(scope, st->var->name.str, val); + if (!Expr_ShouldEval(expr)) + goto done; + + scope = expr->scope; /* scope where v belongs */ + if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL) { + Var *gv = VarFind(expr->name, expr->scope, false); + if (gv == NULL) + scope = SCOPE_GLOBAL; + else + VarFreeEnv(gv); + } + + switch (op[0]) { + case '+': + Var_Append(scope, expr->name, val.str); + break; + case '!': { + const char *errfmt; + char *cmd_output = Cmd_Exec(val.str, &errfmt); + if (errfmt != NULL) + Error(errfmt, val.str); + else + Var_Set(scope, expr->name, cmd_output); + free(cmd_output); + break; + } + case '?': + if (expr->defined == DEF_REGULAR) break; - } + /* FALLTHROUGH */ + default: + Var_Set(scope, expr->name, val.str); + break; } - free(val); - st->newVal = FStr_InitRefer(""); + Expr_SetValueRefer(expr, ""); + +done: + FStr_Done(&val); return AMR_OK; } @@ -3353,24 +3504,33 @@ ok: * remember current value */ static ApplyModifierResult -ApplyModifier_Remember(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_Remember(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *mod = *pp; - if (!ModMatchEq(mod, "_", st->endc)) + FStr name; + + if (!ModMatchEq(mod, "_", ch)) return AMR_UNKNOWN; + name = FStr_InitRefer("_"); if (mod[1] == '=') { - size_t n = strcspn(mod + 2, ":)}"); - char *name = bmake_strldup(mod + 2, n); - Var_SetExpand(st->scope, name, val); - free(name); - *pp = mod + 2 + n; - } else { - Var_Set(st->scope, "_", val); + /* + * XXX: This ad-hoc call to strcspn deviates from the usual + * behavior defined in ParseModifierPart. This creates an + * unnecessary, undocumented inconsistency in make. + */ + const char *arg = mod + 2; + size_t argLen = strcspn(arg, ":)}"); + *pp = arg + argLen; + name = FStr_InitOwn(bmake_strldup(arg, argLen)); + } else *pp = mod + 1; - } - st->newVal = FStr_InitRefer(val); + + if (Expr_ShouldEval(expr)) + Var_Set(expr->scope, name.str, expr->value.str); + FStr_Done(&name); + return AMR_OK; } @@ -3379,40 +3539,68 @@ ApplyModifier_Remember(const char **pp, const char *val, * for a single-letter modifier such as :H, :T. */ static ApplyModifierResult -ApplyModifier_WordFunc(const char **pp, const char *val, - ApplyModifiersState *st, ModifyWordsCallback modifyWord) +ApplyModifier_WordFunc(const char **pp, ModChain *ch, + ModifyWordProc modifyWord) { - char delim = (*pp)[1]; - if (delim != st->endc && delim != ':') + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; - - st->newVal = FStr_InitOwn(ModifyWords(val, modifyWord, NULL, - st->oneBigWord, st->sep)); (*pp)++; + + if (ModChain_ShouldEval(ch)) + ModifyWords(ch, modifyWord, NULL, ch->oneBigWord); + return AMR_OK; } +/* Remove adjacent duplicate words. */ static ApplyModifierResult -ApplyModifier_Unique(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_Unique(const char **pp, ModChain *ch) { - if ((*pp)[1] == st->endc || (*pp)[1] == ':') { - st->newVal = FStr_InitOwn(VarUniq(val)); - (*pp)++; - return AMR_OK; - } else + Words words; + + if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; + (*pp)++; + + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + words = Str_Words(ch->expr->value.str, false); + + if (words.len > 1) { + size_t si, di; + + di = 0; + for (si = 1; si < words.len; si++) { + if (strcmp(words.words[si], words.words[di]) != 0) { + di++; + if (di != si) + words.words[di] = words.words[si]; + } + } + words.len = di + 1; + } + + Expr_SetValueOwn(ch->expr, Words_JoinFree(words)); + + return AMR_OK; } #ifdef SYSVVARSUB /* :from=to */ static ApplyModifierResult -ApplyModifier_SysV(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier_SysV(const char **pp, ModChain *ch) { - char *lhs, *rhs; + Expr *expr = ch->expr; VarParseResult res; + LazyBuf lhsBuf, rhsBuf; + FStr rhs; + struct ModifyWord_SysVSubstArgs args; + Substring lhs; + const char *lhsSuffix; const char *mod = *pp; - Boolean eqFound = FALSE; + bool eqFound = false; /* * First we make a pass through the string trying to verify it is a @@ -3422,38 +3610,50 @@ ApplyModifier_SysV(const char **pp, const char *val, ApplyModifiersState *st) const char *p = mod; while (*p != '\0' && depth > 0) { if (*p == '=') { /* XXX: should also test depth == 1 */ - eqFound = TRUE; - /* continue looking for st->endc */ - } else if (*p == st->endc) + eqFound = true; + /* continue looking for ch->endc */ + } else if (*p == ch->endc) depth--; - else if (*p == st->startc) + else if (*p == ch->startc) depth++; if (depth > 0) p++; } - if (*p != st->endc || !eqFound) + if (*p != ch->endc || !eqFound) return AMR_UNKNOWN; - res = ParseModifierPart(pp, '=', st->eflags, st, &lhs); + res = ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf); if (res != VPR_OK) return AMR_CLEANUP; /* The SysV modifier lasts until the end of the variable expression. */ - res = ParseModifierPart(pp, st->endc, st->eflags, st, &rhs); - if (res != VPR_OK) + res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf); + if (res != VPR_OK) { + LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; - - (*pp)--; - if (lhs[0] == '\0' && val[0] == '\0') { - st->newVal = FStr_InitRefer(val); /* special case */ - } else { - struct ModifyWord_SYSVSubstArgs args = { st->scope, lhs, rhs }; - st->newVal = FStr_InitOwn( - ModifyWords(val, ModifyWord_SYSVSubst, &args, - st->oneBigWord, st->sep)); } - free(lhs); - free(rhs); + rhs = LazyBuf_DoneGet(&rhsBuf); + + (*pp)--; /* Go back to the ch->endc. */ + + /* Do not turn an empty expression into non-empty. */ + if (lhsBuf.len == 0 && expr->value.str[0] == '\0') + goto done; + + lhs = LazyBuf_Get(&lhsBuf); + lhsSuffix = Substring_SkipFirst(lhs, '%'); + + args.scope = expr->scope; + args.lhsPrefix = Substring_Init(lhs.start, + lhsSuffix != lhs.start ? lhsSuffix - 1 : lhs.start); + args.lhsPercent = lhsSuffix != lhs.start; + args.lhsSuffix = Substring_Init(lhsSuffix, lhs.end); + args.rhs = rhs.str; + + ModifyWords(ch, ModifyWord_SysVSubst, &args, ch->oneBigWord); + +done: + LazyBuf_Done(&lhsBuf); return AMR_OK; } #endif @@ -3461,133 +3661,155 @@ ApplyModifier_SysV(const char **pp, const char *val, ApplyModifiersState *st) #ifdef SUNSHCMD /* :sh */ static ApplyModifierResult -ApplyModifier_SunShell(const char **pp, const char *val, - ApplyModifiersState *st) +ApplyModifier_SunShell(const char **pp, ModChain *ch) { + Expr *expr = ch->expr; const char *p = *pp; - if (p[1] == 'h' && (p[2] == st->endc || p[2] == ':')) { - if (st->eflags & VARE_WANTRES) { - const char *errfmt; - st->newVal = FStr_InitOwn(Cmd_Exec(val, &errfmt)); - if (errfmt != NULL) - Error(errfmt, val); - } else - st->newVal = FStr_InitRefer(""); - *pp = p + 2; - return AMR_OK; - } else + if (!(p[1] == 'h' && IsDelimiter(p[2], ch))) return AMR_UNKNOWN; + *pp = p + 2; + + if (Expr_ShouldEval(expr)) { + const char *errfmt; + char *output = Cmd_Exec(expr->value.str, &errfmt); + if (errfmt != NULL) + Error(errfmt, expr->value.str); + Expr_SetValueOwn(expr, output); + } + + return AMR_OK; } #endif static void -LogBeforeApply(const ApplyModifiersState *st, const char *mod, char endc, - const char *val) +LogBeforeApply(const ModChain *ch, const char *mod) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - Boolean is_single_char = mod[0] != '\0' && - (mod[1] == endc || mod[1] == ':'); + const Expr *expr = ch->expr; + bool is_single_char = mod[0] != '\0' && IsDelimiter(mod[1], ch); + + /* + * At this point, only the first character of the modifier can + * be used since the end of the modifier is not yet known. + */ - /* 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->var->name.str, mod[0], is_single_char ? "" : "...", val, - VarEvalFlags_ToString(eflags_str, st->eflags), - VarFlags_ToString(vflags_str, st->var->flags), - VarExprStatus_Name[st->exprStatus]); + if (!Expr_ShouldEval(expr)) { + debug_printf("Parsing modifier ${%s:%c%s}\n", + expr->name, mod[0], is_single_char ? "" : "..."); + return; + } + + if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) && + expr->defined == DEF_REGULAR) { + debug_printf( + "Evaluating modifier ${%s:%c%s} on value \"%s\"\n", + expr->name, mod[0], is_single_char ? "" : "...", + expr->value.str); + return; + } + + debug_printf( + "Evaluating modifier ${%s:%c%s} on value \"%s\" (%s, %s)\n", + expr->name, mod[0], is_single_char ? "" : "...", expr->value.str, + VarEvalMode_Name[expr->emode], ExprDefined_Name[expr->defined]); } static void -LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod) +LogAfterApply(const ModChain *ch, const char *p, const char *mod) { - char eflags_str[VarEvalFlags_ToStringSize]; - char vflags_str[VarFlags_ToStringSize]; - const char *quot = st->newVal.str == var_Error ? "" : "\""; - const char *newVal = - st->newVal.str == var_Error ? "error" : st->newVal.str; + const Expr *expr = ch->expr; + const char *value = expr->value.str; + const char *quot = value == var_Error ? "" : "\""; + + if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) && + expr->defined == DEF_REGULAR) { + + debug_printf("Result of ${%s:%.*s} is %s%s%s\n", + expr->name, (int)(p - mod), mod, + quot, value == var_Error ? "error" : value, quot); + return; + } - debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n", - st->var->name.str, (int)(p - mod), mod, quot, newVal, quot, - VarEvalFlags_ToString(eflags_str, st->eflags), - VarFlags_ToString(vflags_str, st->var->flags), - VarExprStatus_Name[st->exprStatus]); + debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s)\n", + expr->name, (int)(p - mod), mod, + quot, value == var_Error ? "error" : value, quot, + VarEvalMode_Name[expr->emode], + ExprDefined_Name[expr->defined]); } static ApplyModifierResult -ApplyModifier(const char **pp, const char *val, ApplyModifiersState *st) +ApplyModifier(const char **pp, ModChain *ch) { switch (**pp) { + case '!': + return ApplyModifier_ShellCommand(pp, ch); case ':': - return ApplyModifier_Assign(pp, st); + return ApplyModifier_Assign(pp, ch); + case '?': + return ApplyModifier_IfElse(pp, ch); case '@': - return ApplyModifier_Loop(pp, val, st); + return ApplyModifier_Loop(pp, ch); + case '[': + return ApplyModifier_Words(pp, ch); case '_': - return ApplyModifier_Remember(pp, val, st); + return ApplyModifier_Remember(pp, ch); +#ifndef NO_REGEX + case 'C': + return ApplyModifier_Regex(pp, ch); +#endif case 'D': - case 'U': - return ApplyModifier_Defined(pp, val, st); - case 'L': - return ApplyModifier_Literal(pp, st); - case 'P': - return ApplyModifier_Path(pp, st); - case '!': - return ApplyModifier_ShellCommand(pp, st); - case '[': - return ApplyModifier_Words(pp, val, st); + return ApplyModifier_Defined(pp, ch); + case 'E': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix); case 'g': - return ApplyModifier_Gmtime(pp, val, st); + return ApplyModifier_Gmtime(pp, ch); + case 'H': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head); case 'h': - return ApplyModifier_Hash(pp, val, st); + return ApplyModifier_Hash(pp, ch); + case 'L': + return ApplyModifier_Literal(pp, ch); case 'l': - return ApplyModifier_Localtime(pp, val, st); - case 't': - return ApplyModifier_To(pp, val, st); - case 'N': + return ApplyModifier_Localtime(pp, ch); case 'M': - return ApplyModifier_Match(pp, val, st); - case 'S': - return ApplyModifier_Subst(pp, val, st); - case '?': - return ApplyModifier_IfElse(pp, st); -#ifndef NO_REGEX - case 'C': - return ApplyModifier_Regex(pp, val, st); -#endif - case 'q': + case 'N': + return ApplyModifier_Match(pp, ch); + case 'O': + return ApplyModifier_Order(pp, ch); + case 'P': + return ApplyModifier_Path(pp, ch); case 'Q': - return ApplyModifier_Quote(pp, val, st); - case 'T': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Tail); - case 'H': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Head); - case 'E': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Suffix); + case 'q': + return ApplyModifier_Quote(pp, ch); case 'R': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Root); + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Root); case 'r': - return ApplyModifier_Range(pp, val, st); - case 'O': - return ApplyModifier_Order(pp, val, st); - case 'u': - return ApplyModifier_Unique(pp, val, st); + return ApplyModifier_Range(pp, ch); + case 'S': + return ApplyModifier_Subst(pp, ch); #ifdef SUNSHCMD case 's': - return ApplyModifier_SunShell(pp, val, st); + return ApplyModifier_SunShell(pp, ch); #endif + case 'T': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Tail); + case 't': + return ApplyModifier_To(pp, ch); + case 'U': + return ApplyModifier_Defined(pp, ch); + case 'u': + return ApplyModifier_Unique(pp, ch); default: return AMR_UNKNOWN; } } -static FStr ApplyModifiers(const char **, FStr, char, char, Var *, - VarExprStatus *, GNode *, VarEvalFlags); +static void ApplyModifiers(Expr *, const char **, char, char); typedef enum ApplyModifiersIndirectResult { /* The indirect modifiers have been applied successfully. */ AMIR_CONTINUE, /* Fall back to the SysV modifier. */ - AMIR_APPLY_MODS, + AMIR_SYSV, /* Error out. */ AMIR_OUT } ApplyModifiersIndirectResult; @@ -3602,25 +3824,22 @@ typedef enum ApplyModifiersIndirectResult { * Multiple groups of indirect modifiers can be chained by separating them * with colons. ${VAR:${M1}:${M2}} contains 2 indirect modifiers. * - * If the variable expression is not followed by st->endc or ':', fall + * If the variable expression is not followed by ch->endc or ':', fall * back to trying the SysV modifier, such as in ${VAR:${FROM}=${TO}}. - * - * The expression ${VAR:${M1}${M2}} is not treated as an indirect - * modifier, and it is neither a SysV modifier but a parse error. */ static ApplyModifiersIndirectResult -ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp, - FStr *inout_value) +ApplyModifiersIndirect(ModChain *ch, const char **pp) { + Expr *expr = ch->expr; const char *p = *pp; FStr mods; - (void)Var_Parse(&p, st->scope, st->eflags, &mods); + (void)Var_Parse(&p, expr->scope, expr->emode, &mods); /* TODO: handle errors */ - if (mods.str[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) { + if (mods.str[0] != '\0' && *p != '\0' && !IsDelimiter(*p, ch)) { FStr_Done(&mods); - return AMIR_APPLY_MODS; + return AMIR_SYSV; } DEBUG3(VAR, "Indirect modifier \"%s\" from \"%.*s\"\n", @@ -3628,10 +3847,8 @@ ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp, if (mods.str[0] != '\0') { const char *modsp = mods.str; - FStr newVal = ApplyModifiers(&modsp, *inout_value, '\0', '\0', - st->var, &st->exprStatus, st->scope, st->eflags); - *inout_value = newVal; - if (newVal.str == var_Error || *modsp != '\0') { + ApplyModifiers(expr, &modsp, '\0', '\0'); + if (expr->value.str == var_Error || *modsp != '\0') { FStr_Done(&mods); *pp = p; return AMIR_OUT; /* error already reported */ @@ -3641,10 +3858,10 @@ ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp, if (*p == ':') p++; - else if (*p == '\0' && st->endc != '\0') { - Error("Unclosed variable specification after complex " - "modifier (expecting '%c') for %s", - st->endc, st->var->name.str); + else if (*p == '\0' && ch->endc != '\0') { + Error("Unclosed variable expression after indirect " + "modifier, expecting '%c' for variable \"%s\"", + ch->endc, expr->name); *pp = p; return AMIR_OUT; } @@ -3654,36 +3871,36 @@ ApplyModifiersIndirect(ApplyModifiersState *st, const char **pp, } static ApplyModifierResult -ApplySingleModifier(ApplyModifiersState *st, const char *mod, char endc, - const char **pp, FStr *inout_value) +ApplySingleModifier(const char **pp, ModChain *ch) { ApplyModifierResult res; + const char *mod = *pp; const char *p = *pp; - const char *const val = inout_value->str; if (DEBUG(VAR)) - LogBeforeApply(st, mod, endc, val); + LogBeforeApply(ch, mod); - res = ApplyModifier(&p, val, st); + res = ApplyModifier(&p, ch); #ifdef SYSVVARSUB if (res == AMR_UNKNOWN) { assert(p == mod); - res = ApplyModifier_SysV(&p, val, st); + res = ApplyModifier_SysV(&p, ch); } #endif if (res == AMR_UNKNOWN) { - Parse_Error(PARSE_FATAL, "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++) + for (p++; !IsDelimiter(*p, ch) && *p != '\0'; p++) continue; - st->newVal = FStr_InitRefer(var_Error); + Parse_Error(PARSE_FATAL, "Unknown modifier \"%.*s\"", + (int)(p - mod), mod); + Expr_SetValueRefer(ch->expr, var_Error); } if (res == AMR_CLEANUP || res == AMR_BAD) { *pp = p; @@ -3691,20 +3908,18 @@ ApplySingleModifier(ApplyModifiersState *st, const char *mod, char endc, } if (DEBUG(VAR)) - LogAfterApply(st, p, mod); + LogAfterApply(ch, p, mod); - if (st->newVal.str != val) { - FStr_Done(inout_value); - *inout_value = st->newVal; - } - if (*p == '\0' && st->endc != '\0') { + if (*p == '\0' && ch->endc != '\0') { Error( - "Unclosed variable specification (expecting '%c') " - "for \"%s\" (value \"%s\") modifier %c", - st->endc, st->var->name.str, inout_value->str, *mod); + "Unclosed variable expression, expecting '%c' for " + "modifier \"%.*s\" of variable \"%s\" with value \"%s\"", + ch->endc, + (int)(p - mod), mod, + ch->expr->name, ch->expr->value.str); } else if (*p == ':') { p++; - } else if (opts.strict && *p != '\0' && *p != endc) { + } else if (opts.strict && *p != '\0' && *p != ch->endc) { Parse_Error(PARSE_FATAL, "Missing delimiter ':' after modifier \"%.*s\"", (int)(p - mod), mod); @@ -3717,44 +3932,46 @@ ApplySingleModifier(ApplyModifiersState *st, const char *mod, char endc, return AMR_OK; } +#if __STDC_VERSION__ >= 199901L +#define ModChain_Literal(expr, startc, endc, sep, oneBigWord) \ + (ModChain) { expr, startc, endc, sep, oneBigWord } +#else +MAKE_INLINE ModChain +ModChain_Literal(Expr *expr, char startc, char endc, char sep, bool oneBigWord) +{ + ModChain ch; + ch.expr = expr; + ch.startc = startc; + ch.endc = endc; + ch.sep = sep; + ch.oneBigWord = oneBigWord; + return ch; +} +#endif + /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ -static FStr +static void ApplyModifiers( - const char **pp, /* the parsing position, updated upon return */ - FStr value, /* the current value of the expression */ - char startc, /* '(' or '{', or '\0' for indirect modifiers */ - char endc, /* ')' or '}', or '\0' for indirect modifiers */ - Var *v, - VarExprStatus *exprStatus, - GNode *scope, /* for looking up and modifying variables */ - VarEvalFlags eflags + Expr *expr, + const char **pp, /* the parsing position, updated upon return */ + char startc, /* '(' or '{'; or '\0' for indirect modifiers */ + char endc /* ')' or '}'; or '\0' for indirect modifiers */ ) { - ApplyModifiersState st = { - startc, endc, v, scope, eflags, -#if defined(lint) - /* lint cannot parse C99 struct initializers yet. */ - { var_Error, NULL }, -#else - FStr_InitRefer(var_Error), /* .newVal */ -#endif - ' ', /* .sep */ - FALSE, /* .oneBigWord */ - *exprStatus /* .exprStatus */ - }; + ModChain ch = ModChain_Literal(expr, startc, endc, ' ', false); const char *p; const char *mod; assert(startc == '(' || startc == '{' || startc == '\0'); assert(endc == ')' || endc == '}' || endc == '\0'); - assert(value.str != NULL); + assert(expr->value.str != NULL); p = *pp; if (*p == '\0' && endc != '\0') { Error( "Unclosed variable expression (expecting '%c') for \"%s\"", - st.endc, st.var->name.str); + ch.endc, expr->name); goto cleanup; } @@ -3762,19 +3979,22 @@ ApplyModifiers( ApplyModifierResult res; if (*p == '$') { - ApplyModifiersIndirectResult amir; - amir = ApplyModifiersIndirect(&st, &p, &value); + ApplyModifiersIndirectResult amir = + ApplyModifiersIndirect(&ch, &p); if (amir == AMIR_CONTINUE) continue; if (amir == AMIR_OUT) break; + /* + * It's neither '${VAR}:' nor '${VAR}}'. Try to parse + * it as a SysV modifier, as that is the only modifier + * that can start with '$'. + */ } - /* default value, in case of errors */ - st.newVal = FStr_InitRefer(var_Error); mod = p; - res = ApplySingleModifier(&st, mod, endc, &p, &value); + res = ApplySingleModifier(&p, &ch); if (res == AMR_CLEANUP) goto cleanup; if (res == AMR_BAD) @@ -3782,48 +4002,60 @@ ApplyModifiers( } *pp = p; - assert(value.str != NULL); /* Use var_Error or varUndefined instead. */ - *exprStatus = st.exprStatus; - return value; + assert(expr->value.str != NULL); /* Use var_Error or varUndefined. */ + return; bad_modifier: /* XXX: The modifier end is only guessed. */ - Error("Bad modifier `:%.*s' for %s", - (int)strcspn(mod, ":)}"), mod, st.var->name.str); + Error("Bad modifier \":%.*s\" for variable \"%s\"", + (int)strcspn(mod, ":)}"), mod, expr->name); cleanup: + /* + * TODO: Use p + strlen(p) instead, to stop parsing immediately. + * + * In the unit tests, this generates a few unterminated strings in the + * shell commands though. Instead of producing these unfinished + * strings, commands with evaluation errors should not be run at all. + * + * To make that happen, Var_Subst must report the actual errors + * instead of returning VPR_OK unconditionally. + */ *pp = p; - FStr_Done(&value); - *exprStatus = st.exprStatus; - return FStr_InitRefer(var_Error); + Expr_SetValueRefer(expr, var_Error); } /* - * Only four of the local variables are treated specially as they are the - * only four that will be set when dynamic sources are expanded. + * Only 4 of the 7 local variables are treated specially as they are the only + * ones that will be set when dynamic sources are expanded. */ -static Boolean -VarnameIsDynamic(const char *name, size_t len) +static bool +VarnameIsDynamic(Substring varname) { + const char *name; + size_t len; + + name = varname.start; + len = Substring_Length(varname); if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) { switch (name[0]) { case '@': case '%': case '*': case '!': - return TRUE; + return true; } - return FALSE; + return false; } if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) { - return strcmp(name, ".TARGET") == 0 || - strcmp(name, ".ARCHIVE") == 0 || - strcmp(name, ".PREFIX") == 0 || - strcmp(name, ".MEMBER") == 0; + return Substring_Equals(varname, ".TARGET") || + Substring_Equals(varname, ".ARCHIVE") || + Substring_Equals(varname, ".PREFIX") || + Substring_Equals(varname, ".MEMBER"); } - return FALSE; + return false; } static const char * @@ -3857,58 +4089,45 @@ UndefinedShortVarValue(char varname, const GNode *scope) * Parse a variable name, until the end character or a colon, whichever * comes first. */ -static char * +static void ParseVarname(const char **pp, char startc, char endc, - GNode *scope, VarEvalFlags eflags, - size_t *out_varname_len) + GNode *scope, VarEvalMode emode, + LazyBuf *buf) { - Buffer buf; const char *p = *pp; - int depth = 1; + int depth = 0; /* Track depth so we can spot parse errors. */ - Buf_Init(&buf); + LazyBuf_Init(buf, p); while (*p != '\0') { - /* Track depth so we can spot parse errors. */ + if ((*p == endc || *p == ':') && depth == 0) + break; if (*p == startc) depth++; - if (*p == endc) { - if (--depth == 0) - break; - } - if (*p == ':' && depth == 1) - break; + if (*p == endc) + depth--; /* A variable inside a variable, expand. */ if (*p == '$') { FStr nested_val; - (void)Var_Parse(&p, scope, eflags, &nested_val); + (void)Var_Parse(&p, scope, emode, &nested_val); /* TODO: handle errors */ - Buf_AddStr(&buf, nested_val.str); + LazyBuf_AddStr(buf, nested_val.str); FStr_Done(&nested_val); } else { - Buf_AddByte(&buf, *p); + LazyBuf_Add(buf, *p); p++; } } *pp = p; - *out_varname_len = buf.len; - return Buf_DoneData(&buf); } static VarParseResult ValidShortVarname(char varname, const char *start) { - switch (varname) { - case '\0': - case ')': - case '}': - case ':': - case '$': - break; /* and continue below */ - default: + if (varname != '$' && varname != ':' && varname != '}' && + varname != ')' && varname != '\0') return VPR_OK; - } if (!opts.strict) return VPR_ERR; /* XXX: Missing error message */ @@ -3926,50 +4145,45 @@ ValidShortVarname(char varname, const char *start) } /* - * Parse a single-character variable name such as $V or $@. + * Parse a single-character variable name such as in $V or $@. * Return whether to continue parsing. */ -static Boolean -ParseVarnameShort(char startc, const char **pp, GNode *scope, - VarEvalFlags eflags, - VarParseResult *out_FALSE_res, const char **out_FALSE_val, - Var **out_TRUE_var) +static bool +ParseVarnameShort(char varname, const char **pp, GNode *scope, + VarEvalMode emode, + 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. - * We just need to check for the first character and return the - * value if it exists. - */ - - vpr = ValidShortVarname(startc, *pp); + vpr = ValidShortVarname(varname, *pp); if (vpr != VPR_OK) { (*pp)++; - *out_FALSE_val = var_Error; - *out_FALSE_res = vpr; - return FALSE; + *out_false_res = vpr; + *out_false_val = var_Error; + return false; } - name[0] = startc; + name[0] = varname; name[1] = '\0'; - v = VarFind(name, scope, TRUE); + v = VarFind(name, scope, true); if (v == NULL) { const char *val; *pp += 2; - val = UndefinedShortVarValue(startc, scope); + val = UndefinedShortVarValue(varname, scope); if (val == NULL) - val = eflags & VARE_UNDEFERR ? var_Error : varUndefined; + val = emode == VARE_UNDEFERR + ? var_Error : varUndefined; if (opts.strict && val == var_Error) { Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); - *out_FALSE_res = VPR_ERR; - *out_FALSE_val = val; - return FALSE; + *out_false_res = VPR_ERR; + *out_false_val = val; + return false; } /* @@ -3982,72 +4196,64 @@ ParseVarnameShort(char startc, const char **pp, GNode *scope, * If undefined expressions are allowed, this should rather * be VPR_UNDEF instead of VPR_OK. */ - *out_FALSE_res = eflags & VARE_UNDEFERR ? VPR_UNDEF : VPR_OK; - *out_FALSE_val = val; - return FALSE; + *out_false_res = emode == VARE_UNDEFERR + ? VPR_UNDEF : VPR_OK; + *out_false_val = val; + return false; } - *out_TRUE_var = v; - return TRUE; + *out_true_var = v; + return true; } /* Find variables like @F or <D. */ static Var * -FindLocalLegacyVar(const char *varname, size_t namelen, GNode *scope, +FindLocalLegacyVar(Substring varname, GNode *scope, const char **out_extraModifiers) { + Var *v; + /* Only resolve these variables if scope is a "real" target. */ if (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL) return NULL; - if (namelen != 2) + if (Substring_Length(varname) != 2) return NULL; - if (varname[1] != 'F' && varname[1] != 'D') + if (varname.start[1] != 'F' && varname.start[1] != 'D') return NULL; - if (strchr("@%?*!<>", varname[0]) == NULL) + if (strchr("@%?*!<>", varname.start[0]) == NULL) return NULL; - { - char name[] = { varname[0], '\0' }; - Var *v = VarFind(name, scope, FALSE); - - if (v != NULL) { - if (varname[1] == 'D') { - *out_extraModifiers = "H:"; - } else { /* F */ - *out_extraModifiers = "T:"; - } - } - return v; - } + v = VarFindSubstring(Substring_Sub(varname, 0, 1), scope, false); + if (v == NULL) + return NULL; + + *out_extraModifiers = varname.start[1] == 'D' ? "H:" : "T:"; + return v; } static VarParseResult -EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname, - VarEvalFlags eflags, - FStr *out_val) +EvalUndefined(bool dynamic, const char *start, const char *p, + Substring varname, VarEvalMode emode, FStr *out_val) { if (dynamic) { *out_val = FStr_InitOwn(bmake_strsedup(start, p)); - free(varname); return VPR_OK; } - if ((eflags & VARE_UNDEFERR) && opts.strict) { + if (emode == VARE_UNDEFERR && opts.strict) { Parse_Error(PARSE_FATAL, - "Variable \"%s\" is undefined", varname); - free(varname); + "Variable \"%.*s\" is undefined", + (int)Substring_Length(varname), varname.start); *out_val = FStr_InitRefer(var_Error); return VPR_ERR; } - if (eflags & VARE_UNDEFERR) { - free(varname); + if (emode == VARE_UNDEFERR) { *out_val = FStr_InitRefer(var_Error); return VPR_UNDEF; /* XXX: Should be VPR_ERR instead. */ } - free(varname); *out_val = FStr_InitRefer(varUndefined); return VPR_OK; } @@ -4058,59 +4264,60 @@ EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname, * ${VAR:Modifiers}, up to the ':' that starts the modifiers. * Return whether to continue parsing. */ -static Boolean +static bool ParseVarnameLong( - const char *p, + const char **pp, char startc, GNode *scope, - VarEvalFlags eflags, - - const char **out_FALSE_pp, - VarParseResult *out_FALSE_res, - FStr *out_FALSE_val, - - char *out_TRUE_endc, - const char **out_TRUE_p, - Var **out_TRUE_v, - Boolean *out_TRUE_haveModifier, - const char **out_TRUE_extraModifiers, - Boolean *out_TRUE_dynamic, - VarExprStatus *out_TRUE_exprStatus + VarEvalMode emode, + + const char **out_false_pp, + VarParseResult *out_false_res, + FStr *out_false_val, + + char *out_true_endc, + Var **out_true_v, + bool *out_true_haveModifier, + const char **out_true_extraModifiers, + bool *out_true_dynamic, + ExprDefined *out_true_exprDefined ) { - size_t namelen; - char *varname; + LazyBuf varname; Var *v; - Boolean haveModifier; - Boolean dynamic = FALSE; + bool haveModifier; + bool dynamic = false; + const char *p = *pp; const char *const start = p; char endc = startc == '(' ? ')' : '}'; p += 2; /* skip "${" or "$(" or "y(" */ - varname = ParseVarname(&p, startc, endc, scope, eflags, &namelen); + ParseVarname(&p, startc, endc, scope, emode, &varname); if (*p == ':') { - haveModifier = TRUE; + haveModifier = true; } else if (*p == endc) { - haveModifier = FALSE; + haveModifier = false; } else { - Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname); - free(varname); - *out_FALSE_pp = p; - *out_FALSE_val = FStr_InitRefer(var_Error); - *out_FALSE_res = VPR_ERR; - return FALSE; + Substring name = LazyBuf_Get(&varname); + Parse_Error(PARSE_FATAL, "Unclosed variable \"%.*s\"", + (int)Substring_Length(name), name.start); + LazyBuf_Done(&varname); + *out_false_pp = p; + *out_false_val = FStr_InitRefer(var_Error); + *out_false_res = VPR_ERR; + return false; } - v = VarFind(varname, scope, TRUE); + v = VarFindSubstring(LazyBuf_Get(&varname), scope, true); /* At this point, p points just after the variable name, * either at ':' or at endc. */ if (v == NULL) { - v = FindLocalLegacyVar(varname, namelen, scope, - out_TRUE_extraModifiers); + v = FindLocalLegacyVar(LazyBuf_Get(&varname), scope, + out_true_extraModifiers); } if (v == NULL) { @@ -4118,15 +4325,15 @@ ParseVarnameLong( * Defer expansion of dynamic variables if they appear in * non-local scope since they are not defined there. */ - dynamic = VarnameIsDynamic(varname, namelen) && + dynamic = VarnameIsDynamic(LazyBuf_Get(&varname)) && (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL); if (!haveModifier) { p++; /* skip endc */ - *out_FALSE_pp = p; - *out_FALSE_res = EvalUndefined(dynamic, start, p, - varname, eflags, out_FALSE_val); - return FALSE; + *out_false_pp = p; + *out_false_res = EvalUndefined(dynamic, start, p, + LazyBuf_Get(&varname), emode, out_false_val); + return false; } /* @@ -4135,34 +4342,34 @@ ParseVarnameLong( * variable name, such as :L or :?. * * Most modifiers leave this expression in the "undefined" - * state (VEF_UNDEF), only a few modifiers like :D, :U, :L, + * state (VES_UNDEF), only a few modifiers like :D, :U, :L, * :P turn this undefined expression into a defined - * expression (VEF_DEF). + * expression (VES_DEF). * - * At the end, after applying all modifiers, if the expression + * In the end, after applying all modifiers, if the expression * is still undefined, Var_Parse will return an empty string * instead of the actually computed value. */ - v = VarNew(FStr_InitOwn(varname), "", VAR_NONE); - *out_TRUE_exprStatus = VES_UNDEF; + v = VarNew(LazyBuf_DoneGet(&varname), "", false, false); + *out_true_exprDefined = DEF_UNDEF; } else - free(varname); + LazyBuf_Done(&varname); - *out_TRUE_endc = endc; - *out_TRUE_p = p; - *out_TRUE_v = v; - *out_TRUE_haveModifier = haveModifier; - *out_TRUE_dynamic = dynamic; - return TRUE; + *pp = p; + *out_true_endc = endc; + *out_true_v = v; + *out_true_haveModifier = haveModifier; + *out_true_dynamic = dynamic; + return true; } /* Free the environment variable now since we own it. */ static void -FreeEnvVar(void **out_val_freeIt, Var *v, const char *value) +FreeEnvVar(Var *v, FStr *inout_val) { char *varValue = Buf_DoneData(&v->val); - if (value == varValue) - *out_val_freeIt = varValue; + if (inout_val->str == varValue) + inout_val->freeIt = varValue; else free(varValue); @@ -4170,6 +4377,54 @@ FreeEnvVar(void **out_val_freeIt, Var *v, const char *value) free(v); } +#if __STDC_VERSION__ >= 199901L +#define Expr_Literal(name, value, emode, scope, defined) \ + { name, value, emode, scope, defined } +#else +MAKE_INLINE Expr +Expr_Literal(const char *name, FStr value, + VarEvalMode emode, GNode *scope, ExprDefined defined) +{ + Expr expr; + + expr.name = name; + expr.value = value; + expr.emode = emode; + expr.scope = scope; + expr.defined = defined; + return expr; +} +#endif + +/* + * Expressions of the form ${:U...} with a trivial value are often generated + * by .for loops and are boring, therefore parse and evaluate them in a fast + * lane without debug logging. + */ +static bool +Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value) +{ + const char *p; + + p = *pp; + if (!(p[0] == '$' && p[1] == '{' && p[2] == ':' && p[3] == 'U')) + return false; + + p += 4; + while (*p != '$' && *p != '{' && *p != ':' && *p != '\\' && + *p != '}' && *p != '\0') + p++; + if (*p != '}') + return false; + + if (emode == VARE_PARSE_ONLY) + *out_value = FStr_InitRefer(""); + else + *out_value = FStr_InitOwn(bmake_strsedup(*pp + 4, p)); + *pp = p + 1; + return true; +} + /* * Given the start of a variable expression (such as $v, $(VAR), * ${VAR:Mpattern}), extract the variable name and value, and the modifiers, @@ -4183,7 +4438,7 @@ FreeEnvVar(void **out_val_freeIt, Var *v, const char *value) * point to the "y" of "empty(VARNAME:Modifiers)", which * is syntactically the same. * scope The scope for finding variables - * eflags Control the exact details of parsing + * emode Controls the exact details of parsing and evaluation * * Output: * *pp The position where to continue parsing. @@ -4195,49 +4450,48 @@ FreeEnvVar(void **out_val_freeIt, Var *v, const char *value) * *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 + * undefined, emode is 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, + * was undefined, emode was not 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 *scope, VarEvalFlags eflags, FStr *out_val) +Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val) { const char *p = *pp; const char *const start = p; - /* TRUE if have modifiers for the variable. */ - Boolean haveModifier; + /* true if have modifiers for the variable. */ + bool haveModifier; /* Starting character if variable in parens or braces. */ char startc; /* Ending character if variable in parens or braces. */ char endc; /* - * TRUE if the variable is local and we're expanding it in a + * true if the variable is local and we're expanding it in a * non-local scope. This is done to support dynamic sources. * The result is just the expression, unaltered. */ - Boolean dynamic; + bool dynamic; const char *extramodifiers; Var *v; - FStr value; - char eflags_str[VarEvalFlags_ToStringSize]; - VarExprStatus exprStatus = VES_NONE; + Expr expr = Expr_Literal(NULL, FStr_InitRefer(NULL), emode, + scope, DEF_REGULAR); - DEBUG2(VAR, "Var_Parse: %s with %s\n", start, - VarEvalFlags_ToString(eflags_str, eflags)); + if (Var_Parse_FastLane(pp, emode, out_val)) + return VPR_OK; + + DEBUG2(VAR, "Var_Parse: %s (%s)\n", start, VarEvalMode_Name[emode]); *out_val = FStr_InitRefer(NULL); extramodifiers = NULL; /* extra modifiers to apply first */ - dynamic = FALSE; + dynamic = false; /* * Appease GCC, which thinks that the variable might not be @@ -4248,21 +4502,22 @@ Var_Parse(const char **pp, GNode *scope, VarEvalFlags eflags, FStr *out_val) startc = p[1]; if (startc != '(' && startc != '{') { VarParseResult res; - if (!ParseVarnameShort(startc, pp, scope, eflags, &res, + if (!ParseVarnameShort(startc, pp, scope, emode, &res, &out_val->str, &v)) return res; - haveModifier = FALSE; + haveModifier = false; p++; } else { VarParseResult res; - if (!ParseVarnameLong(p, startc, scope, eflags, + if (!ParseVarnameLong(&p, startc, scope, emode, pp, &res, out_val, - &endc, &p, &v, &haveModifier, &extramodifiers, - &dynamic, &exprStatus)) + &endc, &v, &haveModifier, &extramodifiers, + &dynamic, &expr.defined)) return res; } - if (v->flags & VAR_IN_USE) + expr.name = v->name.str; + if (v->inUse) Fatal("Variable %s is recursive.", v->name.str); /* @@ -4274,37 +4529,34 @@ Var_Parse(const char **pp, GNode *scope, VarEvalFlags eflags, FStr *out_val) * the then-current value of the variable. This might also invoke * undefined behavior. */ - value = FStr_InitRefer(v->val.data); + expr.value = FStr_InitRefer(v->val.data); /* * Before applying any modifiers, expand any nested expressions from * the variable value. */ - if (strchr(value.str, '$') != NULL && (eflags & VARE_WANTRES)) { + if (strchr(expr.value.str, '$') != NULL && + VarEvalMode_ShouldEval(emode)) { char *expanded; - VarEvalFlags nested_eflags = eflags; + VarEvalMode nested_emode = emode; if (opts.strict) - nested_eflags &= ~(unsigned)VARE_UNDEFERR; - v->flags |= VAR_IN_USE; - (void)Var_Subst(value.str, scope, nested_eflags, &expanded); - v->flags &= ~(unsigned)VAR_IN_USE; + nested_emode = VarEvalMode_UndefOk(nested_emode); + v->inUse = true; + (void)Var_Subst(expr.value.str, scope, nested_emode, + &expanded); + v->inUse = false; /* TODO: handle errors */ - value = FStr_InitOwn(expanded); + Expr_SetValueOwn(&expr, expanded); } - if (haveModifier || extramodifiers != NULL) { - if (extramodifiers != NULL) { - const char *em = extramodifiers; - value = ApplyModifiers(&em, value, '\0', '\0', - v, &exprStatus, scope, eflags); - } - - if (haveModifier) { - p++; /* Skip initial colon. */ + if (extramodifiers != NULL) { + const char *em = extramodifiers; + ApplyModifiers(&expr, &em, '\0', '\0'); + } - value = ApplyModifiers(&p, value, startc, endc, - v, &exprStatus, scope, eflags); - } + if (haveModifier) { + p++; /* Skip initial colon. */ + ApplyModifiers(&expr, &p, startc, endc); } if (*p != '\0') /* Skip past endc if possible. */ @@ -4312,41 +4564,40 @@ Var_Parse(const char **pp, GNode *scope, VarEvalFlags eflags, FStr *out_val) *pp = p; - if (v->flags & VAR_FROM_ENV) { - FreeEnvVar(&value.freeIt, v, value.str); + if (v->fromEnv) { + FreeEnvVar(v, &expr.value); - } else if (exprStatus != VES_NONE) { - if (exprStatus != VES_DEF) { - FStr_Done(&value); + } else if (expr.defined != DEF_REGULAR) { + if (expr.defined == DEF_UNDEF) { if (dynamic) { - value = FStr_InitOwn(bmake_strsedup(start, p)); + Expr_SetValueOwn(&expr, + bmake_strsedup(start, p)); } else { /* * The expression is still undefined, * therefore discard the actual value and * return an error marker instead. */ - value = FStr_InitRefer(eflags & VARE_UNDEFERR - ? var_Error : varUndefined); + Expr_SetValueRefer(&expr, + emode == VARE_UNDEFERR + ? var_Error : varUndefined); } } - if (value.str != v->val.data) + /* XXX: This is not standard memory management. */ + if (expr.value.str != v->val.data) Buf_Done(&v->val); FStr_Done(&v->name); free(v); } - *out_val = (FStr){ value.str, value.freeIt }; + *out_val = expr.value; return VPR_OK; /* XXX: Is not correct in all cases */ } static void -VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalFlags eflags) +VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalMode emode) { - /* - * A dollar sign may be escaped with another dollar - * sign. - */ - if (save_dollars && (eflags & VARE_KEEP_DOLLAR)) + /* A dollar sign may be escaped with another dollar sign. */ + if (save_dollars && VarEvalMode_ShouldKeepDollar(emode)) Buf_AddByte(res, '$'); Buf_AddByte(res, '$'); *pp += 2; @@ -4354,19 +4605,19 @@ VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalFlags eflags) static void VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, - VarEvalFlags eflags, Boolean *inout_errorReported) + VarEvalMode emode, bool *inout_errorReported) { const char *p = *pp; const char *nested_p = p; FStr val; - (void)Var_Parse(&nested_p, scope, eflags, &val); + (void)Var_Parse(&nested_p, scope, emode, &val); /* TODO: handle errors */ if (val.str == var_Error || val.str == varUndefined) { - if (!(eflags & VARE_KEEP_UNDEF)) { + if (!VarEvalMode_ShouldKeepUndef(emode)) { p = nested_p; - } else if ((eflags & VARE_UNDEFERR) || val.str == var_Error) { + } else if (emode == VARE_UNDEFERR || val.str == var_Error) { /* * XXX: This condition is wrong. If val == var_Error, @@ -4386,7 +4637,7 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, (int)(size_t)(nested_p - p), p); } p = nested_p; - *inout_errorReported = TRUE; + *inout_errorReported = true; } else { /* Copy the initial '$' of the undefined expression, * thereby deferring expansion of the expression, but @@ -4430,10 +4681,10 @@ VarSubstPlain(const char **pp, Buffer *res) * expanded. * scope The scope in which to start searching for * variables. The other scopes are searched as well. - * eflags Special effects during expansion. + * emode The mode for parsing or evaluating subexpressions. */ VarParseResult -Var_Subst(const char *str, GNode *scope, VarEvalFlags eflags, char **out_res) +Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res) { const char *p = str; Buffer res; @@ -4441,16 +4692,16 @@ Var_Subst(const char *str, GNode *scope, 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; + static bool errorReported; Buf_Init(&res); - errorReported = FALSE; + errorReported = false; while (*p != '\0') { if (p[0] == '$' && p[1] == '$') - VarSubstDollarDollar(&p, &res, eflags); + VarSubstDollarDollar(&p, &res, emode); else if (p[0] == '$') - VarSubstExpr(&p, &res, scope, eflags, &errorReported); + VarSubstExpr(&p, &res, scope, emode, &errorReported); else VarSubstPlain(&p, &res); } |