From ee914ef902ae018bd4f67192832120f9bf05651f Mon Sep 17 00:00:00 2001 From: "Simon J. Gerraty" Date: Fri, 25 Jun 2021 11:16:24 -0700 Subject: [PATCH] Import 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. --- ChangeLog | 113 + FILES | 13 + VERSION | 2 +- arch.c | 104 +- buf.h | 4 +- compat.c | 42 +- cond.c | 279 +- dir.c | 62 +- dir.h | 6 +- enum.h | 62 +- for.c | 46 +- hash.c | 68 +- hash.h | 14 +- import.sh | 17 +- job.c | 423 +-- job.h | 20 +- lst.c | 10 +- lst.h | 12 +- main.c | 248 +- make.c | 91 +- make.h | 129 +- meta.c | 192 +- meta.h | 8 +- metachar.h | 4 +- mk/ChangeLog | 35 + mk/dirdeps.mk | 78 +- mk/dpadd.mk | 7 +- mk/install-mk | 4 +- mk/meta.autodep.mk | 6 +- mk/meta2deps.py | 52 +- mk/rst2htm.mk | 6 +- nonints.h | 153 +- parse.c | 512 ++-- str.c | 101 +- str.h | 366 +++ suff.c | 71 +- targ.c | 14 +- unit-tests/Makefile | 28 +- unit-tests/archive.mk | 6 +- unit-tests/cmd-errors-jobs.exp | 2 +- unit-tests/cmd-errors-lint.exp | 2 +- unit-tests/cmd-errors.exp | 2 +- unit-tests/cond-func-empty.mk | 10 +- unit-tests/cond-func-make-main.mk | 6 +- unit-tests/cond-late.exp | 2 +- unit-tests/cond-short.mk | 14 +- unit-tests/cond-token-string.exp | 2 +- unit-tests/cond-token-var.mk | 23 +- unit-tests/cond1.exp | 2 +- unit-tests/counter-append.mk | 4 +- unit-tests/counter.mk | 4 +- unit-tests/dep-var.mk | 4 +- unit-tests/deptgt-makeflags.exp | 10 +- unit-tests/deptgt-order.exp | 3 + unit-tests/deptgt-order.mk | 18 +- unit-tests/deptgt.exp | 8 +- unit-tests/deptgt.mk | 4 +- unit-tests/directive-export-impl.exp | 88 +- unit-tests/directive-export-impl.mk | 23 +- unit-tests/directive-export.mk | 15 +- unit-tests/directive-for-errors.exp | 2 +- unit-tests/directive-for-errors.mk | 6 +- unit-tests/directive-for-escape.exp | 32 +- unit-tests/directive-for-escape.mk | 21 +- unit-tests/directive-for.exp | 2 +- unit-tests/directive-undef.exp | 3 +- unit-tests/directive-undef.mk | 21 +- unit-tests/directive-unexport-env.exp | 20 +- unit-tests/directive.exp | 8 +- unit-tests/include-main.exp | 2 +- unit-tests/job-output-null.exp | 4 + unit-tests/job-output-null.mk | 32 + unit-tests/jobs-empty-commands-error.exp | 5 + unit-tests/jobs-empty-commands-error.mk | 19 + unit-tests/moderrs.exp | 97 +- unit-tests/moderrs.mk | 25 +- unit-tests/modts.exp | 4 +- unit-tests/modword.exp | 24 +- unit-tests/modword.mk | 3 +- unit-tests/opt-chdir.mk | 6 +- unit-tests/opt-debug-errors-jobs.exp | 48 + unit-tests/opt-debug-errors-jobs.mk | 36 + unit-tests/opt-debug-lint.exp | 2 +- unit-tests/opt-debug-lint.mk | 20 +- unit-tests/opt-debug.exp | 6 +- unit-tests/opt-file.mk | 6 +- unit-tests/opt-jobs-no-action.mk | 4 +- unit-tests/recursive.mk | 7 +- unit-tests/sh-jobs.mk | 8 +- unit-tests/shell-csh.mk | 4 +- unit-tests/suff-incomplete.exp | 10 +- unit-tests/suff-main-several.exp | 24 +- unit-tests/suff-rebuild.exp | 14 +- unit-tests/var-class-cmdline.exp | 3 + unit-tests/var-class-cmdline.mk | 80 +- unit-tests/var-eval-short.exp | 29 + unit-tests/var-eval-short.mk | 163 ++ unit-tests/var-op-append.exp | 12 +- unit-tests/var-op-append.mk | 4 +- unit-tests/var-op-assign.mk | 4 +- unit-tests/var-op-sunsh.mk | 12 +- unit-tests/varcmd.mk | 14 +- unit-tests/vardebug.exp | 119 +- unit-tests/varmisc.exp | 2 +- unit-tests/varmod-assign.exp | 22 +- unit-tests/varmod-assign.mk | 39 +- unit-tests/varmod-defined.exp | 34 +- unit-tests/varmod-defined.mk | 6 +- unit-tests/varmod-edge.exp | 12 +- unit-tests/varmod-edge.mk | 26 +- unit-tests/varmod-hash.exp | 6 +- unit-tests/varmod-ifelse.exp | 22 +- unit-tests/varmod-ifelse.mk | 64 +- unit-tests/varmod-indirect.exp | 84 +- unit-tests/varmod-indirect.mk | 100 +- unit-tests/varmod-loop-varname.exp | 11 + unit-tests/varmod-loop-varname.mk | 127 + unit-tests/varmod-loop.exp | 19 +- unit-tests/varmod-loop.mk | 156 +- unit-tests/varmod-match-escape.exp | 74 +- unit-tests/varmod-match-escape.mk | 8 +- unit-tests/varmod-order.exp | 4 +- unit-tests/varmod-range.exp | 10 +- unit-tests/varmod-remember.exp | 2 - unit-tests/varmod-remember.mk | 29 +- unit-tests/varmod-shell.mk | 9 +- unit-tests/varmod-subst-regex.exp | 25 +- unit-tests/varmod-subst-regex.mk | 54 +- unit-tests/varmod-subst.exp | 2 +- unit-tests/varmod-subst.mk | 10 +- unit-tests/varmod-sun-shell.exp | 2 + unit-tests/varmod-sun-shell.mk | 21 + unit-tests/varmod-sysv.exp | 147 +- unit-tests/varmod-sysv.mk | 57 +- unit-tests/varmod-to-separator.exp | 12 +- unit-tests/varmod-unique.mk | 12 +- unit-tests/varname-dot-shell.exp | 32 +- unit-tests/varname-empty.exp | 60 +- unit-tests/varname-empty.mk | 4 +- unit-tests/varname.exp | 33 +- unit-tests/varparse-dynamic.mk | 6 +- unit-tests/varparse-errors.exp | 4 +- unit-tests/varparse-errors.mk | 4 +- var.c | 3061 ++++++++++++---------- 144 files changed, 5491 insertions(+), 3540 deletions(-) create mode 100644 str.h create mode 100644 unit-tests/job-output-null.exp create mode 100644 unit-tests/job-output-null.mk create mode 100644 unit-tests/jobs-empty-commands-error.exp create mode 100644 unit-tests/jobs-empty-commands-error.mk create mode 100644 unit-tests/opt-debug-errors-jobs.exp create mode 100644 unit-tests/opt-debug-errors-jobs.mk create mode 100644 unit-tests/var-eval-short.exp create mode 100644 unit-tests/var-eval-short.mk create mode 100644 unit-tests/varmod-loop-varname.exp create mode 100644 unit-tests/varmod-loop-varname.mk create mode 100644 unit-tests/varmod-sun-shell.exp create mode 100644 unit-tests/varmod-sun-shell.mk diff --git a/ChangeLog b/ChangeLog index 5cf6f9d8fe5..35235e1f820 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,116 @@ +2021-06-21 Simon J Gerraty + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 * VERSION (_MAKE_VERSION): 20210206 diff --git a/FILES b/FILES index 5e7b8301da8..dc0f6f33c76 100644 --- a/FILES +++ b/FILES @@ -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 diff --git a/VERSION b/VERSION index 0af79496268..7c28f11013b 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20210206 +_MAKE_VERSION=20210621 diff --git a/arch.c b/arch.c index e5c0a5e4ac0..6d9dd60dfbe 100644 --- a/arch.c +++ b/arch.c @@ -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[] = "!\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); diff --git a/buf.h b/buf.h index 594e9651dbf..938820e4745 100644 --- a/buf.h +++ b/buf.h @@ -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; diff --git a/compat.c b/compat.c index 59190d8c435..f8c47397f3d 100644 --- a/compat.c +++ b/compat.c @@ -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 diff --git a/cond.c b/cond.c index 8f36fda22f1..a8d88d1d681 100644 --- a/cond.c +++ b/cond.c @@ -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 evaluates to TRUE) + * conditional (when evaluates to true) * COND_SKIP to skip the lines after the conditional - * (when evaluates to FALSE, or when a previous + * (when 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 evaluated to TRUE. */ + /* None of the previous evaluated to true. */ IFS_INITIAL = 0, - /* The previous evaluated to TRUE. + /* The previous 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 evaluated to TRUE. */ + /* One of the previous 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. */ diff --git a/dir.c b/dir.c index 026983e3ec1..627e654387f 100644 --- a/dir.c +++ b/dir.c @@ -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; diff --git a/dir.h b/dir.h index f4bf32ab5b8..d96393c62eb 100644 --- a/dir.h +++ b/dir.h @@ -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 *); diff --git a/enum.h b/enum.h index b5f4630414e..e10fcae045f 100755 --- a/enum.h +++ b/enum.h @@ -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 @@ -112,19 +112,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); } \ extern void enum_flags_rtti_dummy(void) -/* - * 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. @@ -138,19 +125,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); ENUM__JOIN_STR_2(v1, v2), \ ENUM__JOIN_STR_1(v3))) -/* - * 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. @@ -164,19 +138,6 @@ const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); ENUM__JOIN_STR_4(v1, v2, v3, v4), \ ENUM__JOIN_STR_2(v5, v6))) -/* - * 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. @@ -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 diff --git a/for.c b/for.c index 5705d9c5de0..615efb7634c 100644 --- a/for.c +++ b/for.c @@ -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...}, 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; } /* diff --git a/hash.c b/hash.c index 3afc4ac7ec4..8b503ac31fb 100644 --- a/hash.c +++ b/hash.c @@ -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; } diff --git a/hash.h b/hash.h index b101137aa0c..8e7a567b6db 100644 --- a/hash.h +++ b/hash.h @@ -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 d4554a07895..b80e120daab 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 diff --git a/job.c b/job.c index 3ba67c69540..70f9a618cfb 100644 --- a/job.c +++ b/job.c @@ -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,11 +1068,25 @@ 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) { @@ -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 diff --git a/job.h b/job.h index 0d7d7313b9d..ef66602518d 100644 --- a/job.h +++ b/job.h @@ -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); diff --git a/lst.c b/lst.c index 8f3c32ef72e..37297311278 100644 --- a/lst.c +++ b/lst.c @@ -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. */ diff --git a/lst.h b/lst.h index fe854a7647a..cddd6439f61 100644 --- a/lst.h +++ b/lst.h @@ -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 *); diff --git a/main.c b/main.c index d25bb00d371..85a8a1cce7a 100644 --- a/main.c +++ b/main.c @@ -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 true; + p = path; + while ((p = strstr(p, "/.")) != NULL) { + p += 2; + if (*p == '.') + p++; + if (*p == '/' || *p == '\0') + return true; } - return FALSE; + 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] == ':') { /* - found, and 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; } diff --git a/make.c b/make.c index 1cd7299a76c..a85a497be32 100644 --- a/make.c +++ b/make.c @@ -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 */ diff --git a/make.h b/make.h index da5f60534f3..a074923c643 100644 --- a/make.h +++ b/make.h @@ -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 -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 FALSE -#define FALSE 0 +#ifndef true +#define true 1 +#endif +#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); } diff --git a/meta.c b/meta.c index b7f1831b3cd..c1f78136fb7 100644 --- a/meta.c +++ b/meta.c @@ -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 */ +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 Boolean forceJobs; +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; diff --git a/meta.h b/meta.h index 1fc8910d3b6..d4f02da4e78 100644 --- a/meta.h +++ b/meta.h @@ -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 04f96722910..1fd1397cfe6 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 fa6ea9b9d33..f73c4fb68c6 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,38 @@ +2021-06-16 Simon J Gerraty + + * 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 + + * 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 + + * install-mk (MK_VERSION): 20210420 + + * dirdeps.mk: revert previous - not always safe. + +2021-03-20 Simon J Gerraty + + * 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 * install-mk (MK_VERSION): 20210130 diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 4eb20cedfec..38ead3de37c 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 -.else -_machine_list != 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}.* 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 -_only_machines := ${_machine_list:${NIgnoreFiles:UN*.bak}:E:O:u} +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 +DIRDEPS_ALL_MACHINES_FILTER += \ + @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m}@}@ +.endif +.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 "; } > ${_want} .endif +.endif diff --git a/mk/dpadd.mk b/mk/dpadd.mk index 45585ce76d9..aef12528f16 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 b014728ca45..96c35b1052e 100755 --- a/mk/install-mk +++ b/mk/install-mk @@ -55,7 +55,7 @@ # Simon J. Gerraty # 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 b5cb39a3085..5d09dbd88e8 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 4627e08d7c1..193e303de3d 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.debug = conf.get('debug', 0) + self.debug_out = conf.get('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.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 1db9792f412..66eb8552f87 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 38ac0c85518..7119d798432 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 *); diff --git a/parse.c b/parse.c index d14c7c46547..3c2d75acdf9 100644 --- a/parse.c +++ b/parse.c @@ -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 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); } /* diff --git a/str.c b/str.c index c486df6d3d8..b4baede4d41 100644 --- a/str.c +++ b/str.c @@ -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 00000000000..ce0bb5ad82b --- /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 + 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 *); diff --git a/suff.c b/suff.c index 91e8bc613eb..b2c926a2b8b 100644 --- a/suff.c +++ b/suff.c @@ -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; } /* diff --git a/targ.c b/targ.c index 2b02f233ac4..68573644ff3 100644 --- a/targ.c +++ b/targ.c @@ -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 d649c552a03..784223a5665 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: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -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,^.*\.\.\.:.*,,' -# For Compat_RunCommand, useShell == TRUE. +# For Compat_RunCommand, useShell == true. SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,,' SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1,' 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},,' -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: ",' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' 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 f8815cf40a4..2cd43a99e9a 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 6d9c6bb7f89..9ed0557975b 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 09924c538de..90b63bbcb08 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 6d9c6bb7f89..9ed0557975b 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 5094924f1c8..11a990cbbce 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 31b370afabd..97b91f86999 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 46c4aa2f423..e179e8c74cc 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 46c7ea26a97..113c3fd08fe 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 07b318caa81..45f9993457d 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 30eba87ad4d..168c63c46ac 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 0acd935780a..8b65d782524 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 1c4e00d6118..d234835e5ec 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 3c75d7a5032..7cf8fba7287 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 438a8a84a60..4503424e31a 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 7eb54eba7f3..11043bc5110 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 39a9383953d..5f7dde0ac69 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 003552f57a4..f241331ae1e 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 b2aeaa5a285..bdac2aee3e6 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 09f381715e6..15d7e59aece 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 1a5cf34dbfb..740daa60512 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"!} != "" -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 (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 556e5352d1c..0ad290f653d 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 40fda0968cb..942d4b371bb 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 6088a93c9a4..da5eee473ec 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 7890e2375af..602ecbf32e4 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 89a8cbc2e22..59d4c2324f1 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: beginend +make: "directive-for-escape.mk" line 67: beginend 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 d61f05cc53c..babc4b8c6e8 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 bdaf4492baf..4e882aad7b6 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 d64cb8b5afe..56c87142939 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 b9a69f73351..41ea6b5bf8f 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 677596ea4aa..6d653e65fd3 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 b93d768169a..ee866b7ee2b 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 61e716ad8ad..c8a670a1c14 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 00000000000..af9b4e64dba --- /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 00000000000..7620bdf6a7b --- /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 00000000000..1639425d901 --- /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 00000000000..b9ba4403078 --- /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 0ca1aa2aedd..9d8bd308c36 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 `:tsxy' for FIB +make: Bad modifier ":ts\65oct" for variable "" +65oct} +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 8fdcb496ee2..ffd920314c5 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 5db79fc9658..18837016add 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 9fd7f1b494f..02e9974c02d 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 383c9dca975..95bb1fec78c 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 20241f02740..a8806149f31 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 00000000000..25eb2b470b7 --- /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 00000000000..83b50987a75 --- /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 f2123f20e37..05b341b30da 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 bb1b38feb71..155e1a3de3b 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 52a36c71b4e..6a5f7b4cb3e 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 3ab8ef4e3c7..b7a1c09e6d1 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\' # Global:VAR = value\value\ # ParseReadLine (2): 'alue\' -# ParseDoDependency(alue\) +# ParseDependency(alue\) # make-2014.01.01.00.00.00: "(stdin)" line 2: Need an operator # ParseReadLine (3): 'ZZZZZZZZZZZZZZZZ' -# ParseDoDependency(ZZZZZZZZZZZZZZZZ) +# ParseDependency(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 a75fc38cf2f..19d82c5bf4b 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 73a8409fe03..5265cec59a2 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 e8d4f976109..de80de56040 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 99852e33ce1..47313563d22 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 23b959d4b4e..2331436d378 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 a494ddc6854..09fa6d63bff 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 ccb423a6086..7ef53ae2e15 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 39a9383953d..6df2155ca7e 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 c43b5351c32..679e051bb24 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. -# TODO: Implementation +# A normal global variable, without any cmdline variable nearby. +VAR= global +.info ${VAR} -all: - @:; +# 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} + +# 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 00000000000..ae0aff7d7c2 --- /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 00000000000..41782f0d782 --- /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 424ad37ccf6..32134be75a3 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 deb4af6a738..420ee376b75 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 3bcc3de0ba0..18ecf8d0d5e 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 0e16b2b42d3..0d15b8c88b9 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 9ec4f4f9a21..12739df3092 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 a9a00a11f5d..6d00acc977a 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 = -Command:.SHELL = overwritten ignored (read-only) -Global:.MAKEFLAGS = -r -k -d v -d -Global:.MAKEFLAGS = -r -k -d v -d 0 +Command: .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 e8f88d9ca51..f56f72d0ab9 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 743ef2fb408..1e43714d500 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 e4cbc249df8..f50c654f5bc 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 15f40226f1d..2f7d4dbf4ba 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 59b9d79d754..a44b9f99314 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 c90eef2756c..d9db72b2e2e 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 a0b6d9342ef..762053d281a 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 f16f3090353..1286b456c6c 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 17d4d8afcbe..e42e39525f1 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 ec6acdb2ee2..0e16032a654 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 860da778197..63ed988d0c0 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 d130c7cae76..fa58997cc84 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 00000000000..9170307bd2a --- /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 00000000000..d51e2ba76a4 --- /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 66cfd6f51e1..a4704973f6e 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 c109c775a49..4fdaa3ff4e6 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 30c14807552..42cdd7a87ac 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 e62fbe8352b..5ac69f964a6 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 99d1d6ef164..94c3cb69488 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 3a9d4d032c3..f4ada11ebde 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 448f817d896..39a9383953d 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 68eb96a122c..40381175967 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 db82e302f2a..c736042f80a 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 207a97fc25e..a09046ef764 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 91b2f0e6a2f..197691d73aa 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 3122c17b1ed..97fa2e4f149 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 3c3ee673c07..85f41e499ab 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 00000000000..5087bc66d94 --- /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 00000000000..712b36bc703 --- /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 57e69a66728..59275857f98 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 751736ceaf7..712c1731717 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 44c9f0973ed..c6e8ce98a21 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 ea469876494..04d04a575af 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 46a1b2127c9..bfbcfc96018 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 28f55368fd1..ec225c6973c 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 = -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 = +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 = +Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval) +Global: .OBJDIR = Global:delete .PATH (not found) -Global:.PATH = . -Global:.PATH = . -Global:.TARGETS = -Internal:MAKEFILE = varname-empty.mk -Global:.MAKE.MAKEFILES = varname-empty.mk -Global:.PARSEFILE = varname-empty.mk +Global: .PATH = . +Global: .PATH = . +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 492f9f2618b..f077d2ec07b 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 84f878a9f74..942532b654d 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 c65ba12e614..d4d165017a7 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 50a0766c7d7..27589e0b21a 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 113c7a292a7..f0947bb9410 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." diff --git a/var.c b/var.c index eddca9ef6da..6e5148bba96 100644 --- a/var.c +++ b/var.c @@ -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 @@ -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 { +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; - /* 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. */ +/* 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) +static Substring +CanonicalVarname(Substring 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; - } - } + + 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) { - if (!(v->flags & VAR_FROM_ENV)) - return FALSE; - - FStr_Done(&v->name); - if (freeValue) - Buf_Done(&v->val); - else - Buf_DoneData(&v->val); - free(v); - return TRUE; + return VarFindSubstring(Substring_InitStr(name), scope, elsewhere); } -/* - * 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. - */ +/* If the variable is an environment variable, free it, including its value. */ static void -VarAdd(const char *name, const char *val, GNode *scope, VarSetFlags flags) +VarFreeEnv(Var *v) +{ + if (!v->fromEnv) + return; + + FStr_Done(&v->name); + Buf_Done(&v->val); + free(v); +} + +/* 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); + + assert(word.end[0] == '\0'); /* assume null-terminated word */ + if (!Str_Match(word.start, pattern)) + SepBuf_AddSubstring(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; -} - -struct ModifyWord_SYSVSubstArgs { +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; + + if (!Substring_HasPrefix(word, args->lhsPrefix)) + goto no_match; + if (!Substring_HasSuffix(word, args->lhsSuffix)) + goto no_match; + + 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); } - /* - * Append rhs to the buffer, substituting the first '%' with the - * match, but only if the lhs had a '%' as well. - */ + percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; - (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhs_expanded); - /* TODO: handle errors */ + 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); - rhs = rhs_expanded; - percent = strchr(rhs, '%'); + FStr_Done(&rhs); + return; - 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); - - /* Append the suffix of the replacement pattern */ - SepBuf_AddStr(buf, rhs); - - 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 -ApplyModifiersState_Define(ApplyModifiersState *st) +Expr_Define(Expr *expr) { - if (st->exprStatus == VES_UNDEF) - st->exprStatus = VES_DEF; + 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 +Expr_SetValueRefer(Expr *expr, const char *value) +{ + 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; + str = LazyBuf_DoneGet(&strBuf); + args.body = str.str; - 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); + 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)++; - gn = Targ_FindNode(st->var->name.str); + if (!ModChain_ShouldEval(ch)) + return AMR_OK; + + 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); - st->newVal = FStr_InitOwn(ModifyWords(val, ModifyWord_Subst, &args, - oneBigWord, st->sep)); + oneBigWord = ch->oneBigWord; + ParsePatternFlags(pp, &args.pflags, &oneBigWord); - free(lhs); - free(rhs); + ModifyWords(ch, ModifyWord_Subst, &args, oneBigWord); + + 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 */ 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" or ":ts:" */ - 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" 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". */ } - 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, 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" 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 */ *pp = mod + 1; - return AMR_BAD; /* Found ":t". */ + 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:" or ":t". */ - *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; - /* now *pp points just after the closing ']' */ - if (**pp != ':' && **pp != st->endc) - goto bad_modifier; /* Found junk after ']' */ + if (!IsDelimiter(**pp, ch)) + 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 * * ::= Assigns 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; /* "::" */ -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); - break; - } + 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); } - free(val); - st->newVal = FStr_InitRefer(""); + + 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; + } + 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. */ - 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]); + /* + * At this point, only the first character of the modifier can + * be used since the end of the modifier is not yet known. + */ + + 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 ? "" : "\""; - 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]); + 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)\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_Assign(pp, st); - case '@': - return ApplyModifier_Loop(pp, val, st); - case '_': - return ApplyModifier_Remember(pp, val, st); - 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); - case 'g': - return ApplyModifier_Gmtime(pp, val, st); - case 'h': - return ApplyModifier_Hash(pp, val, st); - case 'l': - return ApplyModifier_Localtime(pp, val, st); - case 't': - return ApplyModifier_To(pp, val, st); - case 'N': - case 'M': - return ApplyModifier_Match(pp, val, st); - case 'S': - return ApplyModifier_Subst(pp, val, st); + return ApplyModifier_ShellCommand(pp, ch); + case ':': + return ApplyModifier_Assign(pp, ch); case '?': - return ApplyModifier_IfElse(pp, st); + return ApplyModifier_IfElse(pp, ch); + case '@': + return ApplyModifier_Loop(pp, ch); + case '[': + return ApplyModifier_Words(pp, ch); + case '_': + return ApplyModifier_Remember(pp, ch); #ifndef NO_REGEX case 'C': - return ApplyModifier_Regex(pp, val, st); + return ApplyModifier_Regex(pp, ch); #endif - case 'q': - 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 'D': + return ApplyModifier_Defined(pp, ch); case 'E': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Suffix); - case 'R': - return ApplyModifier_WordFunc(pp, val, st, ModifyWord_Root); - case 'r': - return ApplyModifier_Range(pp, val, st); + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix); + case 'g': + return ApplyModifier_Gmtime(pp, ch); + case 'H': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head); + case 'h': + return ApplyModifier_Hash(pp, ch); + case 'L': + return ApplyModifier_Literal(pp, ch); + case 'l': + return ApplyModifier_Localtime(pp, ch); + case 'M': + case 'N': + return ApplyModifier_Match(pp, ch); case 'O': - return ApplyModifier_Order(pp, val, st); - case 'u': - return ApplyModifier_Unique(pp, val, st); + return ApplyModifier_Order(pp, ch); + case 'P': + return ApplyModifier_Path(pp, ch); + case 'Q': + case 'q': + return ApplyModifier_Quote(pp, ch); + case 'R': + return ApplyModifier_WordFunc(pp, ch, ModifyWord_Root); + case 'r': + 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 ", varname[0]) == NULL) + if (strchr("@%?*!<>", varname.start[0]) == NULL) return NULL; - { - char name[] = { varname[0], '\0' }; - Var *v = VarFind(name, scope, FALSE); + v = VarFindSubstring(Substring_Sub(varname, 0, 1), scope, false); + if (v == NULL) + return NULL; - if (v != NULL) { - if (varname[1] == 'D') { - *out_extraModifiers = "H:"; - } else { /* F */ - *out_extraModifiers = "T:"; - } - } - return v; - } + *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, + VarEvalMode emode, - const char **out_FALSE_pp, - VarParseResult *out_FALSE_res, - FStr *out_FALSE_val, + 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 + 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 (extramodifiers != NULL) { + const char *em = extramodifiers; + ApplyModifiers(&expr, &em, '\0', '\0'); + } - if (haveModifier) { - p++; /* Skip initial colon. */ - - 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); }