Import bmake-20240625

Intersting/relevant changes since bmake-20240520

ChangeLog since bmake-20240520

2024-06-25  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240625
	Merge with NetBSD make, pick up
	o job.c: ensure shellPath is always duped, avoid upsetting free()

2024-06-16  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240616
	Merge with NetBSD make, pick up
	o clean up collection of context information for error messages
	o in warnings, move the word "warning" to the front
	o var.c: throw an error on attempt to override an internal
	read-only variable

2024-06-10  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240610
	Merge with NetBSD make, pick up
	o for.c: remove redundant shortcut for building the .for loop body

2024-06-02  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240602
	Merge with NetBSD make, pick up
	o rename some VarEvalMode constants to better match debug names.
	o var.c: avoid out-of-bounds read when parsing indirect modifiers.

2024-06-01  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240601
	Merge with NetBSD make, pick up
	o add .export-all rather than allow .export with no argument
	which can happen accidentally.
	o if lua is available, run check-expect.lua after unit-tests
	o main.c: use snprintf rather than strncpy
	fix memory leak when purging realpath cache.

2024-05-28  Simon J Gerraty  <sjg@beast.crufty.net>

	* VERSION (_MAKE_VERSION): 20240528
	Merge with NetBSD make, pick up
	o fix a number of memory leaks
	o replace magic numbers with POSIX FILENO constants
	o hash.c: remove dead code from HashTable_DeleteEntry
	o main.c: when complaining about unusable .OBJDIR
	call PrintOnError if MAKE_DEBUG_OBJDIR_CHECK_WRITABLE is true.
	o parse.c: use fewer technical terms in debug message for dependency

mk/ChangeLog since bmake-20240520

2024-06-22  Simon J Gerraty  <sjg@beast.crufty.net>

	* install-mk (MK_VERSION): 20240616

	* dirdeps.mk: apply DEP_DIRDEPS_BUILD_DIR_FILTER after we have
	computed build dirs, since some filters cannot be easily expressed via
	DEP_DIRDEPS_FILTER.

2024-05-31  Simon J Gerraty  <sjg@beast.crufty.net>

	* dirdeps.mk: move reset of DIRDEPS_EXPORT_VARS
	until after we a finished with it if building a cache.
This commit is contained in:
Simon J. Gerraty 2024-06-28 17:16:32 -07:00
parent 29efb3dcae
commit dbb5be7f07
69 changed files with 1018 additions and 578 deletions

View File

@ -1,3 +1,52 @@
2024-06-25 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240625
Merge with NetBSD make, pick up
o job.c: ensure shellPath is always duped, avoid upsetting free()
2024-06-16 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240616
Merge with NetBSD make, pick up
o clean up collection of context information for error messages
o in warnings, move the word "warning" to the front
o var.c: throw an error on attempt to override an internal
read-only variable
2024-06-10 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240610
Merge with NetBSD make, pick up
o for.c: remove redundant shortcut for building the .for loop body
2024-06-02 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240602
Merge with NetBSD make, pick up
o rename some VarEvalMode constants to better match debug names.
o var.c: avoid out-of-bounds read when parsing indirect modifiers.
2024-06-01 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240601
Merge with NetBSD make, pick up
o add .export-all rather than allow .export with no argument
which can happen accidentally.
o if lua is available, run check-expect.lua after unit-tests
o main.c: use snprintf rather than strncpy
fix memory leak when purging realpath cache.
2024-05-28 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20240528
Merge with NetBSD make, pick up
o fix a number of memory leaks
o replace magic numbers with POSIX FILENO constants
o hash.c: remove dead code from HashTable_DeleteEntry
o main.c: when complaining about unusable .OBJDIR
call PrintOnError if MAKE_DEBUG_OBJDIR_CHECK_WRITABLE is true.
o parse.c: use fewer technical terms in debug message for dependency
2024-05-20 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION):

View File

@ -1,2 +1,2 @@
# keep this compatible with sh and make
_MAKE_VERSION=20240520
_MAKE_VERSION=20240625

15
arch.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: arch.c,v 1.217 2024/04/27 20:41:32 rillig Exp $ */
/* $NetBSD: arch.c,v 1.219 2024/06/02 15:31:25 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.217 2024/04/27 20:41:32 rillig Exp $");
MAKE_RCSID("$NetBSD: arch.c,v 1.219 2024/06/02 15:31:25 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
@ -204,7 +204,7 @@ ArchFree(Arch *a)
HashIter hi;
HashIter_Init(&hi, &a->members);
while (HashIter_Next(&hi) != NULL)
while (HashIter_Next(&hi))
free(hi.entry->value);
free(a->name);
@ -257,7 +257,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
bool isError;
/* XXX: is expanded twice: once here and once below */
result = Var_Parse(&nested_p, scope, VARE_UNDEFERR);
result = Var_Parse(&nested_p, scope,
VARE_EVAL_DEFINED);
/* TODO: handle errors */
isError = result.str == var_Error;
FStr_Done(&result);
@ -272,7 +273,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
spec[cp++ - spec] = '\0';
if (expandLib)
Var_Expand(&lib, scope, VARE_UNDEFERR);
Var_Expand(&lib, scope, VARE_EVAL_DEFINED);
for (;;) {
/*
@ -296,7 +297,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
const char *nested_p = cp;
result = Var_Parse(&nested_p, scope,
VARE_UNDEFERR);
VARE_EVAL_DEFINED);
/* TODO: handle errors */
isError = result.str == var_Error;
FStr_Done(&result);
@ -341,7 +342,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
char *p;
const char *unexpandedMem = mem.str;
Var_Expand(&mem, scope, VARE_UNDEFERR);
Var_Expand(&mem, scope, VARE_EVAL_DEFINED);
/*
* Now form an archive spec and recurse to deal with

33
bmake.1
View File

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.375 2024/03/10 02:53:37 sjg Exp $
.\" $NetBSD: make.1,v 1.377 2024/06/01 06:26:36 sjg Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
.Dd March 9, 2024
.Dd June 1, 2024
.Dt BMAKE 1
.Os
.Sh NAME
@ -1143,9 +1143,19 @@ This mode can be used to detect undeclared dependencies between files.
Used to create files in a separate directory, see
.Va .OBJDIR .
.It Va MAKE_OBJDIR_CHECK_WRITABLE
Used to force a separate directory for the created files,
even if that directory is not writable, see
.Va .OBJDIR .
When true,
.Nm
will check that
.Va .OBJDIR
is writable, and issue a warning if not.
.It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE
When true and
.Nm
is warning about an unwritable
.Va .OBJDIR ,
report the variables listed in
.Va MAKE_PRINT_VAR_ON_ERROR
to help debug.
.It Va MAKEOBJDIRPREFIX
Used to create files in a separate directory, see
.Va .OBJDIR .
@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are:
.Bl -tag -width Ds
.It Ic .export Ar variable No ...
Export the specified global variable.
If no variable list is provided, all globals are exported
except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
.Pp
For compatibility with other make programs,
.Cm export Ar variable\| Ns Cm \&= Ns Ar value
(without leading dot) is also accepted.
@ -1964,6 +1969,12 @@ For compatibility with other make programs,
Appending a variable name to
.Va .MAKE.EXPORTED
is equivalent to exporting a variable.
.It Ic .export-all
Export all globals except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
.It Ic .export-env Ar variable No ...
The same as
.Ql .export ,

View File

@ -756,8 +756,13 @@ VVAARRIIAABBLLEE AASSSSIIGGNNMMEENNTTSS
Used to create files in a separate directory, see _._O_B_J_D_I_R.
_M_A_K_E___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E
Used to force a separate directory for the created files, even if
that directory is not writable, see _._O_B_J_D_I_R.
When true, bbmmaakkee will check that _._O_B_J_D_I_R is writable, and issue a
warning if not.
_M_A_K_E___D_E_B_U_G___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E
When true and bbmmaakkee is warning about an unwritable _._O_B_J_D_I_R,
report the variables listed in _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R to help
debug.
_M_A_K_E_O_B_J_D_I_R_P_R_E_F_I_X
Used to create files in a separate directory, see _._O_B_J_D_I_R.
@ -1230,16 +1235,19 @@ DDIIRREECCTTIIVVEESS
The directives for exporting and unexporting variables are:
..eexxppoorrtt _v_a_r_i_a_b_l_e ...
Export the specified global variable. If no variable list is
provided, all globals are exported except for internal variables
(those that start with `.'). This is not affected by the --XX
flag, so should be used with caution. For compatibility with
other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e (without leading dot)
is also accepted.
Export the specified global variable.
For compatibility with other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e
(without leading dot) is also accepted.
Appending a variable name to _._M_A_K_E_._E_X_P_O_R_T_E_D is equivalent to
exporting a variable.
..eexxppoorrtt--aallll
Export all globals except for internal variables (those that
start with `.'). This is not affected by the --XX flag, so should
be used with caution.
..eexxppoorrtt--eennvv _v_a_r_i_a_b_l_e ...
The same as `.export', except that the variable is not appended
to _._M_A_K_E_._E_X_P_O_R_T_E_D. This allows exporting a value to the
@ -1780,4 +1788,4 @@ BBUUGGSS
attempt to suppress a cascade of unnecessary errors, can result in a
seemingly unexplained `*** Error code 6'
FreeBSD 13.2-RELEASE-p10 March 9, 2024 FreeBSD 13.2-RELEASE-p10
FreeBSD 13.2-RELEASE-p11 June 1, 2024 FreeBSD 13.2-RELEASE-p11

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -1,4 +1,4 @@
/* $NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $ */
/* $NetBSD: compat.c,v 1.259 2024/06/15 20:02:45 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -94,7 +94,7 @@
#include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $");
MAKE_RCSID("$NetBSD: compat.c,v 1.259 2024/06/15 20:02:45 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
@ -203,6 +203,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED)
#endif
}
static int
Compat_Spawn(const char **av)
{
int pid = vfork();
if (pid < 0)
Fatal("Could not fork");
if (pid == 0) {
#ifdef USE_META
if (useMeta)
meta_compat_child();
#endif
(void)execvp(av[0], (char *const *)UNCONST(av));
execDie("exec", av[0]);
}
return pid;
}
/*
* Execute the next command for a target. If the command returns an error,
* the node's made field is set to ERROR and creation stops.
@ -225,21 +243,18 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
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 */
const char **av; /* Arguments for the child process */
char **volatile mav; /* Copy of the argument vector for freeing */
bool useShell; /* True if command should be executed using a
* shell */
const char *volatile cmd = cmdp;
const char *cmd = cmdp;
silent = (gn->type & OP_SILENT) != OP_NONE;
errCheck = !(gn->type & OP_IGNORE);
doIt = false;
EvalStack_Push(gn->name, NULL, NULL);
cmdStart = Var_Subst(cmd, gn, VARE_WANTRES);
EvalStack_Pop();
cmdStart = Var_SubstInTarget(cmd, gn);
/* TODO: handle errors */
if (cmdStart[0] == '\0') {
@ -264,11 +279,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* usual '$$'.
*/
Lst_Append(&endNode->commands, cmdStart);
return true;
goto register_command;
}
}
if (strcmp(cmdStart, "...") == 0) {
gn->type |= OP_SAVE_CMDS;
register_command:
Parse_RegisterCommand(cmdStart);
return true;
}
@ -288,7 +305,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
while (ch_isspace(*cmd))
cmd++;
if (cmd[0] == '\0')
return true;
goto register_command;
useShell = UseShell(cmd);
@ -298,7 +315,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
}
if (!doIt && !GNode_ShouldExecute(gn))
return true;
goto register_command;
DEBUG1(JOB, "Execute: '%s'\n", cmd);
@ -333,19 +350,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
Var_ReexportVars(gn);
compatChild = cpid = vfork();
if (cpid < 0)
Fatal("Could not fork");
if (cpid == 0) {
#ifdef USE_META
if (useMeta)
meta_compat_child();
#endif
(void)execvp(av[0], (char *const *)UNCONST(av));
execDie("exec", av[0]);
}
compatChild = Compat_Spawn(av);
free(mav);
free(bp);
@ -355,11 +360,11 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
#ifdef USE_META
if (useMeta)
meta_compat_parent(cpid);
meta_compat_parent(compatChild);
#endif
/* The child is off and running. Now all we can do is wait... */
while ((retstat = wait(&reason)) != cpid) {
while ((retstat = wait(&reason)) != compatChild) {
if (retstat > 0)
JobReapChild(retstat, reason, false); /* not ours? */
if (retstat == -1 && errno != EINTR)

21
cond.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: cond.c,v 1.363 2024/04/23 22:51:28 rillig Exp $ */
/* $NetBSD: cond.c,v 1.365 2024/06/02 15:31:25 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -91,7 +91,7 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: cond.c,v 1.363 2024/04/23 22:51:28 rillig Exp $");
MAKE_RCSID("$NetBSD: cond.c,v 1.365 2024/06/02 15:31:25 rillig Exp $");
/*
* Conditional expressions conform to this grammar:
@ -222,8 +222,8 @@ ParseWord(const char **pp, bool doEval)
break;
if (ch == '$') {
VarEvalMode emode = doEval
? VARE_UNDEFERR
: VARE_PARSE_ONLY;
? VARE_EVAL_DEFINED
: VARE_PARSE;
/*
* TODO: make Var_Parse complain about undefined
* variables.
@ -396,9 +396,9 @@ CondParser_StringExpr(CondParser *par, const char *start,
const char *p;
bool atStart; /* true means an expression outside quotes */
emode = doEval && quoted ? VARE_WANTRES
: doEval ? VARE_UNDEFERR
: VARE_PARSE_ONLY;
emode = doEval && quoted ? VARE_EVAL
: doEval ? VARE_EVAL_DEFINED
: VARE_PARSE;
p = par->p;
atStart = p == start;
@ -651,8 +651,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
return false;
p--; /* Make p[1] point to the '('. */
val = Var_Parse(&p, SCOPE_CMDLINE,
doEval ? VARE_WANTRES : VARE_PARSE_ONLY);
val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE);
/* TODO: handle errors */
if (val.str == var_Error)
@ -736,8 +735,10 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
arg = ParseWord(&p, doEval);
assert(arg[0] != '\0');
if (*p == '=' || *p == '!' || *p == '<' || *p == '>')
if (*p == '=' || *p == '!' || *p == '<' || *p == '>') {
free(arg);
return CondParser_Comparison(par, doEval);
}
par->p = p;
/*

25
dir.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: dir.c,v 1.290 2024/05/20 19:14:12 sjg Exp $ */
/* $NetBSD: dir.c,v 1.294 2024/05/31 05:50:11 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -132,7 +132,7 @@
#include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: dir.c,v 1.290 2024/05/20 19:14:12 sjg Exp $");
MAKE_RCSID("$NetBSD: dir.c,v 1.294 2024/05/31 05:50:11 rillig Exp $");
/*
* A search path is a list of CachedDir structures. A CachedDir has in it the
@ -501,6 +501,18 @@ Dir_InitDot(void)
Dir_SetPATH(); /* initialize */
}
#ifdef CLEANUP
static void
FreeCachedTable(HashTable *tbl)
{
HashIter hi;
HashIter_Init(&hi, tbl);
while (HashIter_Next(&hi))
free(hi.entry->value);
HashTable_Done(tbl);
}
#endif
/* Clean up the directories module. */
void
Dir_End(void)
@ -511,8 +523,8 @@ Dir_End(void)
CachedDir_Assign(&dotLast, NULL);
SearchPath_Clear(&dirSearchPath);
OpenDirs_Done(&openDirs);
HashTable_Done(&mtimes);
HashTable_Done(&lmtimes);
FreeCachedTable(&mtimes);
FreeCachedTable(&lmtimes);
#endif
}
@ -568,7 +580,7 @@ Dir_SetSYSPATH(void)
CachedDirListNode *ln;
SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs)
? defSysIncPath : sysIncPath;
Var_ReadOnly(".SYSPATH", false);
Global_Delete(".SYSPATH");
for (ln = path->dirs.first; ln != NULL; ln = ln->next) {
@ -644,7 +656,7 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
*/
HashIter_InitSet(&hi, &dir->files);
while (HashIter_Next(&hi) != NULL) {
while (HashIter_Next(&hi)) {
const char *base = hi.entry->key;
StrMatchResult res = Str_Match(base, pattern);
/* TODO: handle errors from res.error */
@ -864,6 +876,7 @@ SearchPath_ExpandMiddle(SearchPath *path, const char *pattern,
(void)SearchPath_Add(partPath, dirpath);
DirExpandPath(wildcardComponent + 1, partPath, expansions);
SearchPath_Free(partPath);
free(dirpath);
}
/*

34
for.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: for.c,v 1.179 2024/04/01 12:33:27 rillig Exp $ */
/* $NetBSD: for.c,v 1.182 2024/06/07 18:57:30 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.179 2024/04/01 12:33:27 rillig Exp $");
MAKE_RCSID("$NetBSD: for.c,v 1.182 2024/06/07 18:57:30 rillig Exp $");
typedef struct ForLoop {
@ -156,7 +156,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
cpp_skip_whitespace(&p);
if (*p == '\0') {
Parse_Error(PARSE_FATAL, "missing `in' in for");
f->vars.len = 0;
while (f->vars.len > 0)
free(*(char **)Vector_Pop(&f->vars));
return;
}
@ -166,7 +167,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
"invalid character '%c' "
"in .for loop variable name",
p[len]);
f->vars.len = 0;
while (f->vars.len > 0)
free(*(char **)Vector_Pop(&f->vars));
return;
}
}
@ -195,7 +197,7 @@ ForLoop_ParseItems(ForLoop *f, const char *p)
cpp_skip_whitespace(&p);
items = Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES);
items = Var_Subst(p, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
f->items = Substring_Words(items, false);
@ -329,23 +331,6 @@ ExprLen(const char *s, const char *e)
return 0;
}
/*
* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped.
*/
static bool
NeedsEscapes(Substring value, char endc)
{
const char *p;
for (p = value.start; p != value.end; p++) {
if (*p == ':' || *p == '$' || *p == '\\' || *p == endc ||
*p == '\n')
return true;
}
return false;
}
/*
* While expanding the body of a .for loop, write the item as a ${:U...}
* expression, escaping characters as needed. The result is later unescaped
@ -357,11 +342,6 @@ AddEscaped(Buffer *cmds, Substring item, char endc)
const char *p;
char ch;
if (!NeedsEscapes(item, endc)) {
Buf_AddRange(cmds, item.start, item.end);
return;
}
for (p = item.start; p != item.end;) {
ch = *p;
if (ch == '$') {

29
hash.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: hash.c,v 1.74 2023/12/19 19:33:39 rillig Exp $ */
/* $NetBSD: hash.c,v 1.78 2024/06/05 22:06:53 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.74 2023/12/19 19:33:39 rillig Exp $");
MAKE_RCSID("$NetBSD: hash.c,v 1.78 2024/06/05 22:06:53 rillig Exp $");
/*
* The ratio of # entries to # buckets at which we rebuild the table to
@ -288,24 +288,19 @@ void
HashTable_DeleteEntry(HashTable *t, HashEntry *he)
{
HashEntry **ref = &t->buckets[he->hash & t->bucketsMask];
HashEntry *p;
for (; (p = *ref) != NULL; ref = &p->next) {
if (p == he) {
*ref = p->next;
free(p);
t->numEntries--;
return;
}
}
abort();
for (; *ref != he; ref = &(*ref)->next)
continue;
*ref = he->next;
free(he);
t->numEntries--;
}
/*
* Return the next entry in the hash table, or NULL if the end of the table
* is reached.
* Place the next entry from the hash table in hi->entry, or return false if
* the end of the table is reached.
*/
HashEntry *
bool
HashIter_Next(HashIter *hi)
{
HashTable *t = hi->table;
@ -318,11 +313,11 @@ HashIter_Next(HashIter *hi)
while (he == NULL) { /* find the next nonempty chain */
if (hi->nextBucket >= bucketsSize)
return NULL;
return false;
he = buckets[hi->nextBucket++];
}
hi->entry = he;
return he;
return true;
}
void

4
hash.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: hash.h,v 1.48 2023/12/19 19:33:39 rillig Exp $ */
/* $NetBSD: hash.h,v 1.50 2024/06/01 10:10:50 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -140,7 +140,7 @@ void HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *);
HashEntry *HashIter_Next(HashIter *);
bool HashIter_Next(HashIter *) MAKE_ATTR_USE;
MAKE_INLINE void
HashSet_Init(HashSet *set)

36
job.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: job.c,v 1.471 2024/05/07 18:26:22 sjg Exp $ */
/* $NetBSD: job.c,v 1.477 2024/06/25 05:18:38 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -154,7 +154,7 @@
#include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: job.c,v 1.471 2024/05/07 18:26:22 sjg Exp $");
MAKE_RCSID("$NetBSD: job.c,v 1.477 2024/06/25 05:18:38 rillig Exp $");
/*
* A shell defines how the commands are run. All commands for a target are
@ -914,9 +914,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
run = GNode_ShouldExecute(job->node);
EvalStack_Push(job->node->name, NULL, NULL);
xcmd = Var_Subst(ucmd, job->node, VARE_WANTRES);
EvalStack_Pop();
xcmd = Var_SubstInTarget(ucmd, job->node);
/* TODO: handle errors */
xcmdStart = xcmd;
@ -1043,11 +1041,10 @@ JobSaveCommands(Job *job)
* variables such as .TARGET, .IMPSRC. It is not intended to
* expand the other variables as well; see deptgt-end.mk.
*/
EvalStack_Push(job->node->name, NULL, NULL);
expanded_cmd = Var_Subst(cmd, job->node, VARE_WANTRES);
EvalStack_Pop();
expanded_cmd = Var_SubstInTarget(cmd, job->node);
/* TODO: handle errors */
Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd);
Parse_RegisterCommand(expanded_cmd);
}
}
@ -1082,7 +1079,7 @@ DebugFailedJob(const Job *job)
debug_printf("\t%s\n", cmd);
if (strchr(cmd, '$') != NULL) {
char *xcmd = Var_Subst(cmd, job->node, VARE_WANTRES);
char *xcmd = Var_Subst(cmd, job->node, VARE_EVAL);
debug_printf("\t=> %s\n", xcmd);
free(xcmd);
}
@ -1468,11 +1465,11 @@ JobExec(Job *job, char **argv)
* was marked close-on-exec, we must clear that bit in the
* new input.
*/
if (dup2(fileno(job->cmdFILE), 0) == -1)
if (dup2(fileno(job->cmdFILE), STDIN_FILENO) == -1)
execDie("dup2", "job->cmdFILE");
if (fcntl(0, F_SETFD, 0) == -1)
if (fcntl(STDIN_FILENO, F_SETFD, 0) == -1)
execDie("fcntl clear close-on-exec", "stdin");
if (lseek(0, 0, SEEK_SET) == -1)
if (lseek(STDIN_FILENO, 0, SEEK_SET) == -1)
execDie("lseek to 0", "stdin");
if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
@ -1489,18 +1486,18 @@ JobExec(Job *job, char **argv)
* Set up the child's output to be routed through the pipe
* we've created for it.
*/
if (dup2(job->outPipe, 1) == -1)
if (dup2(job->outPipe, STDOUT_FILENO) == -1)
execDie("dup2", "job->outPipe");
/*
* The output channels are marked close on exec. This bit
* was duplicated by the dup2(on some systems), so we have
* was duplicated by dup2 (on some systems), so we have
* to clear it before routing the shell's error output to
* the same place as its standard output.
*/
if (fcntl(1, F_SETFD, 0) == -1)
if (fcntl(STDOUT_FILENO, F_SETFD, 0) == -1)
execDie("clear close-on-exec", "stdout");
if (dup2(1, 2) == -1)
if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
execDie("dup2", "1, 2");
/*
@ -2155,7 +2152,7 @@ InitShellNameAndPath(void)
#ifdef DEFSHELL_CUSTOM
if (shellName[0] == '/') {
shellPath = shellName;
shellPath = bmake_strdup(shellName);
shellName = str_basename(shellPath);
return;
}
@ -2212,7 +2209,7 @@ Job_SetPrefix(void)
Global_Set(".MAKE.JOB.PREFIX", "---");
targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
}
@ -2502,7 +2499,8 @@ Job_ParseShell(char *line)
}
}
} else {
shellPath = path;
free(UNCONST(shellPath));
shellPath = bmake_strdup(path);
shellName = newShell.name != NULL ? newShell.name
: str_basename(path);
if (!fullSpec) {

61
main.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: main.c,v 1.616 2024/05/19 17:55:54 sjg Exp $ */
/* $NetBSD: main.c,v 1.624 2024/06/02 15:31:26 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.616 2024/05/19 17:55:54 sjg Exp $");
MAKE_RCSID("$NetBSD: main.c,v 1.624 2024/06/02 15:31:26 rillig Exp $");
#if defined(MAKE_NATIVE)
__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
"The Regents of the University of California. "
@ -372,7 +372,7 @@ MainParseArgChdir(const char *argvalue)
stat(curdir, &sb) != -1 &&
sa.st_ino == sb.st_ino &&
sa.st_dev == sb.st_dev)
strncpy(curdir, argvalue, MAXPATHLEN);
snprintf(curdir, MAXPATHLEN, "%s", argvalue);
ignorePWD = true;
}
@ -743,6 +743,10 @@ Main_SetObjdir(bool writable, const char *fmt, ...)
if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) {
(void)fprintf(stderr, "%s: warning: %s: %s.\n",
progname, path, strerror(errno));
/* Allow debugging how we got here - not always obvious */
if (GetBooleanExpr("${MAKE_DEBUG_OBJDIR_CHECK_WRITABLE}",
false))
PrintOnError(NULL, "");
return false;
}
@ -766,7 +770,7 @@ SetVarObjdir(bool writable, const char *var, const char *suffix)
return false;
}
Var_Expand(&path, SCOPE_GLOBAL, VARE_WANTRES);
Var_Expand(&path, SCOPE_GLOBAL, VARE_EVAL);
(void)Main_SetObjdir(writable, "%s%s", path.str, suffix);
@ -808,8 +812,7 @@ siginfo(int signo MAKE_ATTR_UNUSED)
static void
MakeMode(void)
{
char *mode = Var_Subst("${.MAKE.MODE:tl}",
SCOPE_GLOBAL, VARE_WANTRES);
char *mode = Var_Subst("${.MAKE.MODE:tl}", SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
if (mode[0] != '\0') {
@ -832,14 +835,14 @@ static void
PrintVar(const char *varname, bool expandVars)
{
if (strchr(varname, '$') != NULL) {
char *evalue = Var_Subst(varname, SCOPE_GLOBAL, VARE_WANTRES);
char *evalue = Var_Subst(varname, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
printf("%s\n", evalue);
free(evalue);
} else if (expandVars) {
char *expr = str_concat3("${", varname, "}");
char *evalue = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES);
char *evalue = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
free(expr);
printf("%s\n", evalue);
@ -865,7 +868,7 @@ GetBooleanExpr(const char *expr, bool fallback)
char *value;
bool res;
value = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES);
value = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
res = ParseBoolean(value, fallback);
free(value);
@ -1050,7 +1053,7 @@ HandlePWD(const struct stat *curdir_st)
if (stat(pwd, &pwd_st) == 0 &&
curdir_st->st_ino == pwd_st.st_ino &&
curdir_st->st_dev == pwd_st.st_dev)
(void)strncpy(curdir, pwd, MAXPATHLEN);
snprintf(curdir, MAXPATHLEN, "%s", pwd);
ignore_pwd:
FStr_Done(&makeobjdir);
@ -1140,9 +1143,9 @@ static void
InitVarMake(const char *argv0)
{
const char *make = argv0;
char pathbuf[MAXPATHLEN];
if (argv0[0] != '/' && strchr(argv0, '/') != NULL) {
char pathbuf[MAXPATHLEN];
const char *abspath = cached_realpath(argv0, pathbuf);
struct stat st;
if (abspath != NULL && abspath[0] == '/' &&
@ -1230,7 +1233,7 @@ InitMaxJobs(void)
!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS"))
return;
value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_WANTRES);
value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
n = (int)strtol(value, NULL, 0);
if (n < 1) {
@ -1265,7 +1268,7 @@ InitVpath(void)
if (!Var_Exists(SCOPE_CMDLINE, "VPATH"))
return;
vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_WANTRES);
vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
path = vpath;
do {
@ -1302,7 +1305,7 @@ ReadFirstDefaultMakefile(void)
StringList makefiles = LST_INIT;
StringListNode *ln;
char *prefs = Var_Subst("${.MAKE.MAKEFILE_PREFERENCE}",
SCOPE_CMDLINE, VARE_WANTRES);
SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
AppendWords(&makefiles, prefs);
@ -1513,7 +1516,7 @@ main_PrepareMaking(void)
/* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
if (!opts.noBuiltins || opts.printVars == PVM_NONE) {
makeDependfile = Var_Subst("${.MAKE.DEPENDFILE}",
SCOPE_CMDLINE, VARE_WANTRES);
SCOPE_CMDLINE, VARE_EVAL);
if (makeDependfile[0] != '\0') {
/* TODO: handle errors */
doing_depend = true;
@ -1605,9 +1608,9 @@ main_CleanUp(void)
meta_finish();
#endif
Suff_End();
Var_End();
Targ_End();
Arch_End();
Var_End();
Parse_End();
Dir_End();
Job_End();
@ -1988,22 +1991,19 @@ execDie(const char *af, const char *av)
static void
purge_relative_cached_realpaths(void)
{
HashEntry *he, *next;
HashIter hi;
bool more;
HashIter_Init(&hi, &cached_realpaths);
he = HashIter_Next(&hi);
while (he != NULL) {
next = HashIter_Next(&hi);
more = HashIter_Next(&hi);
while (more) {
HashEntry *he = hi.entry;
more = HashIter_Next(&hi);
if (he->key[0] != '/') {
DEBUG1(DIR, "cached_realpath: purging %s\n", he->key);
free(he->value);
HashTable_DeleteEntry(&cached_realpaths, he);
/*
* XXX: What about the allocated he->value? Either
* free them or document why they cannot be freed.
*/
}
he = next;
}
}
@ -2017,9 +2017,7 @@ cached_realpath(const char *pathname, char *resolved)
rp = HashTable_FindValue(&cached_realpaths, pathname);
if (rp != NULL) {
/* a hit */
strncpy(resolved, rp, MAXPATHLEN);
resolved[MAXPATHLEN - 1] = '\0';
snprintf(resolved, MAXPATHLEN, "%s", rp);
return resolved;
}
@ -2107,7 +2105,7 @@ PrintOnError(GNode *gn, const char *msg)
{
char *errorVarsValues = Var_Subst(
"${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
printf("%s", errorVarsValues);
free(errorVarsValues);
@ -2137,10 +2135,11 @@ Main_ExportMAKEFLAGS(bool first)
flags = Var_Subst(
"${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
SCOPE_CMDLINE, VARE_WANTRES);
SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
if (flags[0] != '\0')
setenv("MAKEFLAGS", flags, 1);
free(flags);
}
char *
@ -2154,7 +2153,7 @@ getTmpdir(void)
/* Honor $TMPDIR if it is valid, strip a trailing '/'. */
tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) {

33
make.1
View File

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.375 2024/03/10 02:53:37 sjg Exp $
.\" $NetBSD: make.1,v 1.377 2024/06/01 06:26:36 sjg Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
.Dd March 9, 2024
.Dd June 1, 2024
.Dt MAKE 1
.Os
.Sh NAME
@ -1143,9 +1143,19 @@ This mode can be used to detect undeclared dependencies between files.
Used to create files in a separate directory, see
.Va .OBJDIR .
.It Va MAKE_OBJDIR_CHECK_WRITABLE
Used to force a separate directory for the created files,
even if that directory is not writable, see
.Va .OBJDIR .
When true,
.Nm
will check that
.Va .OBJDIR
is writable, and issue a warning if not.
.It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE
When true and
.Nm
is warning about an unwritable
.Va .OBJDIR ,
report the variables listed in
.Va MAKE_PRINT_VAR_ON_ERROR
to help debug.
.It Va MAKEOBJDIRPREFIX
Used to create files in a separate directory, see
.Va .OBJDIR .
@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are:
.Bl -tag -width Ds
.It Ic .export Ar variable No ...
Export the specified global variable.
If no variable list is provided, all globals are exported
except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
.Pp
For compatibility with other make programs,
.Cm export Ar variable\| Ns Cm \&= Ns Ar value
(without leading dot) is also accepted.
@ -1964,6 +1969,12 @@ For compatibility with other make programs,
Appending a variable name to
.Va .MAKE.EXPORTED
is equivalent to exporting a variable.
.It Ic .export-all
Export all globals except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
.It Ic .export-env Ar variable No ...
The same as
.Ql .export ,

36
make.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $ */
/* $NetBSD: make.c,v 1.264 2024/06/02 15:31:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -104,7 +104,7 @@
#include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $");
MAKE_RCSID("$NetBSD: make.c,v 1.264 2024/06/02 15:31:26 rillig Exp $");
/* Sequence # to detect recursion. */
static unsigned int checked_seqno = 1;
@ -127,8 +127,8 @@ debug_printf(const char *fmt, ...)
va_end(ap);
}
static const char *
GNodeType_ToString(GNodeType type, void **freeIt)
static char *
GNodeType_ToString(GNodeType type)
{
Buffer buf;
@ -166,11 +166,13 @@ GNodeType_ToString(GNodeType type, void **freeIt)
ADD(OP_DEPS_FOUND);
ADD(OP_MARK);
#undef ADD
return buf.len == 0 ? "none" : (*freeIt = Buf_DoneData(&buf));
if (buf.len == 0)
Buf_AddStr(&buf, "none");
return Buf_DoneData(&buf);
}
static const char *
GNodeFlags_ToString(GNodeFlags flags, void **freeIt)
static char *
GNodeFlags_ToString(GNodeFlags flags)
{
Buffer buf;
@ -184,24 +186,22 @@ GNodeFlags_ToString(GNodeFlags flags, void **freeIt)
Buf_AddFlag(&buf, flags.doneAllsrc, "DONE_ALLSRC");
Buf_AddFlag(&buf, flags.cycle, "CYCLE");
Buf_AddFlag(&buf, flags.doneCycle, "DONECYCLE");
return buf.len == 0 ? "none" : (*freeIt = Buf_DoneData(&buf));
if (buf.len == 0)
Buf_AddStr(&buf, "none");
return Buf_DoneData(&buf);
}
void
GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn,
const char *suffix)
{
void *type_freeIt = NULL;
void *flags_freeIt = NULL;
char *type = GNodeType_ToString(gn->type);
char *flags = GNodeFlags_ToString(gn->flags);
fprintf(f, "%s%s, type %s, flags %s%s",
prefix,
GNodeMade_Name(gn->made),
GNodeType_ToString(gn->type, &type_freeIt),
GNodeFlags_ToString(gn->flags, &flags_freeIt),
suffix);
free(type_freeIt);
free(flags_freeIt);
prefix, GNodeMade_Name(gn->made), type, flags, suffix);
free(type);
free(flags);
}
bool
@ -443,7 +443,7 @@ Make_HandleUse(GNode *cgn, GNode *pgn)
gn->uname = gn->name;
else
free(gn->name);
gn->name = Var_Subst(gn->uname, pgn, VARE_WANTRES);
gn->name = Var_Subst(gn->uname, pgn, VARE_EVAL);
/* TODO: handle errors */
if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) {
/* See if we have a target for this node. */

41
make.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: make.h,v 1.333 2024/05/07 18:26:22 sjg Exp $ */
/* $NetBSD: make.h,v 1.339 2024/06/15 20:02:45 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -938,6 +938,15 @@ void Targ_PrintType(GNodeType);
void Targ_PrintGraph(int);
void Targ_Propagate(void);
const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE;
#ifdef CLEANUP
void Parse_RegisterCommand(char *);
#else
/* ARGSUSED */
MAKE_INLINE
void Parse_RegisterCommand(char *cmd MAKE_ATTR_UNUSED)
{
}
#endif
/* var.c */
void Var_Init(void);
@ -951,7 +960,7 @@ typedef enum VarEvalMode {
* TODO: Document what Var_Parse and Var_Subst return in this mode.
* As of 2021-03-15, they return unspecified, inconsistent results.
*/
VARE_PARSE_ONLY,
VARE_PARSE,
/*
* Parse text in which '${...}' and '$(...)' are not parsed as
@ -962,25 +971,13 @@ typedef enum VarEvalMode {
VARE_PARSE_BALANCED,
/* Parse and evaluate the expression. */
VARE_WANTRES,
VARE_EVAL,
/*
* Parse and evaluate the expression. It is an error if a
* subexpression evaluates to undefined.
*/
VARE_UNDEFERR,
/*
* 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_EVAL_KEEP_DOLLAR,
VARE_EVAL_DEFINED,
/*
* Parse and evaluate the expression. Keep undefined variables as-is
@ -993,13 +990,13 @@ typedef enum VarEvalMode {
* # way) is still undefined, the updated CFLAGS becomes
* # "-I.. $(.INCLUDES)".
*/
VARE_EVAL_KEEP_UNDEF,
VARE_EVAL_KEEP_UNDEFINED,
/*
* Parse and evaluate the expression. Keep '$$' as '$$' and preserve
* undefined subexpressions.
*/
VARE_KEEP_DOLLAR_UNDEF
VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED
} VarEvalMode;
typedef enum VarSetFlags {
@ -1018,6 +1015,8 @@ typedef enum VarSetFlags {
} VarSetFlags;
typedef enum VarExportMode {
/* .export-all */
VEM_ALL,
/* .export-env */
VEM_ENV,
/* .export: Initial export or update an already exported variable. */
@ -1027,6 +1026,9 @@ typedef enum VarExportMode {
} VarExportMode;
void Var_Delete(GNode *, const char *);
#ifdef CLEANUP
void Var_DeleteAll(GNode *scope);
#endif
void Var_Undef(const char *);
void Var_Set(GNode *, const char *, const char *);
void Var_SetExpand(GNode *, const char *, const char *);
@ -1039,6 +1041,7 @@ FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE;
const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE;
FStr Var_Parse(const char **, GNode *, VarEvalMode);
char *Var_Subst(const char *, GNode *, VarEvalMode);
char *Var_SubstInTarget(const char *, GNode *);
void Var_Expand(FStr *, GNode *, VarEvalMode);
void Var_Stats(void);
void Var_Dump(GNode *);
@ -1053,8 +1056,6 @@ void Global_Append(const char *, const char *);
void Global_Delete(const char *);
void Global_Set_ReadOnly(const char *, const char *);
void EvalStack_Push(const char *, const char *, const char *);
void EvalStack_Pop(void);
const char *EvalStack_Details(void);
/* util.c */

25
meta.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: meta.c,v 1.208 2024/04/27 17:33:46 rillig Exp $ */
/* $NetBSD: meta.c,v 1.210 2024/06/02 15:31:26 rillig Exp $ */
/*
* Implement 'meta' mode.
@ -326,7 +326,7 @@ is_submake(const char *cmd, GNode *gn)
p_len = strlen(p_make);
}
if (strchr(cmd, '$') != NULL) {
mp = Var_Subst(cmd, gn, VARE_WANTRES);
mp = Var_Subst(cmd, gn, VARE_EVAL);
/* TODO: handle errors */
cmd = mp;
}
@ -372,7 +372,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn)
{
FStr xcmd = FStr_InitRefer(ucmd);
Var_Expand(&xcmd, gn, VARE_WANTRES);
Var_Expand(&xcmd, gn, VARE_EVAL);
fprintf(fp, "CMD %s\n", xcmd.str);
FStr_Done(&xcmd);
}
@ -481,7 +481,7 @@ meta_create(BuildMon *pbm, GNode *gn)
if (metaVerbose) {
/* Describe the target we are building */
char *mp = Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES);
char *mp = Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_EVAL);
/* TODO: handle errors */
if (mp[0] != '\0')
fprintf(stdout, "%s\n", mp);
@ -618,7 +618,7 @@ meta_mode_init(const char *make_mode)
* We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
*/
metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
AppendWords(&metaBailiwick, metaBailiwickStr);
/*
@ -627,7 +627,7 @@ meta_mode_init(const char *make_mode)
Global_Append(MAKE_META_IGNORE_PATHS,
"/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}");
metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
AppendWords(&metaIgnorePaths, metaIgnorePathsStr);
@ -777,7 +777,7 @@ meta_job_output(Job *job, char *cp, const char *nl)
char *cp2;
meta_prefix = Var_Subst("${" MAKE_META_PREFIX "}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
if ((cp2 = strchr(meta_prefix, '$')) != NULL)
meta_prefix_len = (size_t)(cp2 - meta_prefix);
@ -967,7 +967,7 @@ meta_ignore(GNode *gn, const char *p)
*/
Var_Set(gn, ".p.", p);
expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}";
pm = Var_Subst(expr, gn, VARE_WANTRES);
pm = Var_Subst(expr, gn, VARE_EVAL);
/* TODO: handle errors */
if (pm[0] != '\0') {
#ifdef DEBUG_META_MODE
@ -986,7 +986,7 @@ meta_ignore(GNode *gn, const char *p)
snprintf(fname, sizeof fname,
"${%s:L:${%s:ts:}}",
p, MAKE_META_IGNORE_FILTER);
fm = Var_Subst(fname, gn, VARE_WANTRES);
fm = Var_Subst(fname, gn, VARE_EVAL);
/* TODO: handle errors */
if (*fm == '\0') {
#ifdef DEBUG_META_MODE
@ -1046,7 +1046,7 @@ meta_filter_cmd(GNode *gn, char *s)
Var_Set(gn, META_CMD_FILTER_VAR, s);
s = Var_Subst(
"${" META_CMD_FILTER_VAR ":${" MAKE_META_CMP_FILTER ":ts:}}",
gn, VARE_WANTRES);
gn, VARE_EVAL);
return s;
}
@ -1514,7 +1514,7 @@ meta_oodate(GNode *gn, bool oodate)
DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n",
fname, lineno);
}
cmd = Var_Subst(cmd, gn, VARE_UNDEFERR);
cmd = Var_Subst(cmd, gn, VARE_EVAL_DEFINED);
/* TODO: handle errors */
if ((cp = strchr(cmd, '\n')) != NULL) {
@ -1650,7 +1650,8 @@ void
meta_compat_child(void)
{
meta_job_child(NULL);
if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0)
if (dup2(childPipe[1], STDOUT_FILENO) < 0
|| dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
execDie("dup2", "pipe");
}

View File

@ -1,3 +1,16 @@
2024-06-22 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20240616
* dirdeps.mk: apply DEP_DIRDEPS_BUILD_DIR_FILTER after we have
computed build dirs, since some filters cannot be easily expressed via
DEP_DIRDEPS_FILTER.
2024-05-31 Simon J Gerraty <sjg@beast.crufty.net>
* dirdeps.mk: move reset of DIRDEPS_EXPORT_VARS
until after we a finished with it if building a cache.
2024-05-04 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20240504

View File

@ -1,4 +1,4 @@
# $Id: dirdeps.mk,v 1.167 2024/05/06 20:41:08 sjg Exp $
# $Id: dirdeps.mk,v 1.170 2024/06/24 02:21:00 sjg Exp $
# SPDX-License-Identifier: BSD-2-Clause
#
@ -139,7 +139,7 @@
# DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS)
# It is discouraged, but sometimes necessary for a
# Makefile.depend file to influence the environment.
# Doing this is correctly (especially if using DIRDEPS_CACHE) is
# Doing this correctly (especially if using DIRDEPS_CACHE) is
# tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS
# and dirdeps.mk will do the deed:
#
@ -695,9 +695,22 @@ DEP_DIRDEPS_FILTER = \
${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \
${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \
${DIRDEPS_FILTER:U}
.if empty(DEP_DIRDEPS_FILTER)
# something harmless
DEP_DIRDEPS_FILTER = U
DEP_DIRDEPS_FILTER = u
.endif
# this is applied after we have computed build dirs
# so everything is fully qualified and starts with ${SRCTOP}/
DEP_DIRDEPS_BUILD_DIR_FILTER = \
${DIRDEPS_BUILD_DIR_FILTER.${DEP_TARGET_SPEC}:U} \
${TARGET_SPEC_VARS:@v@${DIRDEPS_BUILD_DIR_FILTER.${DEP_$v}:U}@} \
${DIRDEPS_BUILD_DIR_FILTER:U}
.if empty(DEP_DIRDEPS_BUILD_DIR_FILTER)
# something harmless
DEP_DIRDEPS_BUILD_DIR_FILTER = u
.endif
# this is what we start with
@ -717,6 +730,7 @@ __qual_depdirs += ${__hostdpadd}
.if ${_debug_reldir}
.info DEP_DIRDEPS_FILTER=${DEP_DIRDEPS_FILTER:ts:}
.info DEP_DIRDEPS_BUILD_DIR_FILTER=${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:}
.info depdirs=${__depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.info qualified=${__qual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.info unqualified=${__unqual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
@ -736,7 +750,8 @@ _build_dirs += \
# make sure we do not mess with qualifying "host" entries
_build_dirs := ${_build_dirs:M*.host*:${M_dep_qual_fixes.host:ts:}} \
${_build_dirs:N*.host*:${M_dep_qual_fixes:ts:}}
_build_dirs := ${_build_dirs:O:u}
# some filters can only be applied now
_build_dirs := ${_build_dirs:${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:}:O:u}
.if ${_debug_reldir}
.info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.endif
@ -746,6 +761,11 @@ _build_dirs := ${_build_dirs:O:u}
_build_all_dirs += ${_build_dirs} ${_build_xtra_dirs}
_build_all_dirs := ${_build_all_dirs:O:u}
# we prefer DIRDEPS_EXPORT_VARS
.if empty(DIRDEPS_EXPORT_VARS) && !empty(DEP_EXPORT_VARS)
DIRDEPS_EXPORT_VARS = ${DEP_EXPORT_VARS}
.endif
# Normally if doing make -V something,
# we do not want to waste time chasing DIRDEPS
# but if we want to count the number of Makefile.depend* read, we do.
@ -757,10 +777,9 @@ _cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}';
# guard against _new_dirdeps being too big for a single command line
_new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,}
_cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,}
.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
.if !empty(DIRDEPS_EXPORT_VARS)
# Discouraged, but there are always exceptions.
# Handle it here rather than explain how.
DIRDEPS_EXPORT_VARS ?= ${DEP_EXPORT_VARS}
_cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo;
_cache_script += ${_cache_xvars}
.endif
@ -774,12 +793,6 @@ ${_build_all_dirs}: _DIRDEP_USE
.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.endif
.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
.export ${DIRDEPS_EXPORT_VARS} ${DEP_EXPORT_VARS}
DIRDEPS_EXPORT_VARS =
DEP_EXPORT_VARS =
.endif
# this builds the dependency graph
.for m in ${_machines}
.if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs)
@ -835,6 +848,15 @@ ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m}
.endif
.if !empty(DIRDEPS_EXPORT_VARS)
.if ${BUILD_DIRDEPS_CACHE} == "no"
.export ${DIRDEPS_EXPORT_VARS}
.endif
# Reset these, we are done with them for this iteration.
DIRDEPS_EXPORT_VARS =
DEP_EXPORT_VARS =
.endif
# Now find more dependencies - and recurse.
.for d in ${_build_all_dirs}
.if !target(_dirdeps_checked.$d)

View File

@ -59,7 +59,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
# $Id: install-mk,v 1.254 2024/05/06 20:41:08 sjg Exp $
# $Id: install-mk,v 1.255 2024/06/24 02:21:00 sjg Exp $
#
# @(#) Copyright (c) 1994-2024 Simon J. Gerraty
#
@ -74,7 +74,7 @@
# sjg@crufty.net
#
MK_VERSION=20240504
MK_VERSION=20240616
OWNER=
GROUP=
MODE=444

67
parse.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: parse.c,v 1.723 2024/05/19 20:09:40 sjg Exp $ */
/* $NetBSD: parse.c,v 1.731 2024/06/15 19:43:56 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -121,7 +121,7 @@
#include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: parse.c,v 1.723 2024/05/19 20:09:40 sjg Exp $");
MAKE_RCSID("$NetBSD: parse.c,v 1.731 2024/06/15 19:43:56 rillig Exp $");
/* Detects a multiple-inclusion guard in a makefile. */
typedef enum {
@ -234,9 +234,9 @@ static GNodeList *targets;
#ifdef CLEANUP
/*
* All shell commands for all targets, in no particular order and possibly
* with duplicates. Kept in a separate list since the commands from .USE or
* .USEBEFORE nodes are shared with other GNodes, thereby giving up the
* easily understandable ownership over the allocated strings.
* with duplicate values. Kept in a separate list since the commands from
* .USE or .USEBEFORE nodes are shared with other GNodes, thereby giving up
* the easily understandable ownership over the allocated strings.
*/
static StringList targCmds = LST_INIT;
#endif
@ -537,9 +537,9 @@ ParseVErrorInternal(FILE *f, bool useVars, const GNode *gn,
(void)fprintf(f, "%s: ", progname);
PrintLocation(f, useVars, gn);
fprintf(f, "%s", EvalStack_Details());
if (level == PARSE_WARNING)
(void)fprintf(f, "warning: ");
fprintf(f, "%s", EvalStack_Details());
(void)vfprintf(f, fmt, ap);
(void)fprintf(f, "\n");
(void)fflush(f);
@ -619,7 +619,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
return;
}
xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES);
xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
Parse_Error(level, "%s", xmsg);
@ -652,7 +652,7 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial)
Lst_Append(&cgn->parents, pgn);
if (DEBUG(PARSE)) {
debug_printf("# LinkSource: added child %s - %s\n",
debug_printf("Target \"%s\" depends on \"%s\"\n",
pgn->name, cgn->name);
Targ_PrintNode(pgn, 0);
Targ_PrintNode(cgn, 0);
@ -925,8 +925,7 @@ ParseDependencyTargetWord(char **pp, const char *lstart)
break;
if (*p == '$') {
FStr val = Var_Parse(&p, SCOPE_CMDLINE,
VARE_PARSE_ONLY);
FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE);
/* TODO: handle errors */
FStr_Done(&val);
} else
@ -1279,13 +1278,12 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
}
if (SkipGuarded(fullname))
return;
goto done;
if ((fd = open(fullname, O_RDONLY)) == -1) {
if (!silent)
Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
free(fullname);
return;
goto done;
}
buf = LoadFile(fullname, fd);
@ -1294,6 +1292,7 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
Parse_PushInput(fullname, 1, 0, buf, NULL);
if (depinc)
doing_depend = depinc; /* only turn it on */
done:
free(fullname);
}
@ -1811,7 +1810,7 @@ VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope)
if (opts.strict) {
if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) {
char *parsedValue = Var_Subst(uvalue,
scope, VARE_PARSE_ONLY);
scope, VARE_PARSE);
/* TODO: handle errors */
free(parsedValue);
}
@ -1837,7 +1836,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue,
if (!Var_ExistsExpand(scope, name))
Var_SetExpand(scope, name, "");
evalue = Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF);
evalue = Var_Subst(uvalue, scope,
VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED);
/* TODO: handle errors */
Var_SetExpand(scope, name, evalue);
@ -1854,7 +1854,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope,
char *output, *error;
cmd = FStr_InitRefer(uvalue);
Var_Expand(&cmd, SCOPE_CMDLINE, VARE_UNDEFERR);
Var_Expand(&cmd, SCOPE_CMDLINE, VARE_EVAL_DEFINED);
output = Cmd_Exec(cmd.str, &error);
Var_SetExpand(scope, name, output);
@ -2036,7 +2036,7 @@ ParseInclude(char *directive)
*p = '\0';
Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES);
Var_Expand(&file, SCOPE_CMDLINE, VARE_EVAL);
IncludeFile(file.str, endc == '>', directive[0] == 'd', silent);
FStr_Done(&file);
}
@ -2245,7 +2245,7 @@ ParseTraditionalInclude(char *line)
pp_skip_whitespace(&file);
all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES);
all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
for (file = all_files; !done; file = p + 1) {
@ -2288,7 +2288,7 @@ ParseGmakeExport(char *line)
/*
* Expand the value before putting it in the environment.
*/
value = Var_Subst(value, SCOPE_CMDLINE, VARE_WANTRES);
value = Var_Subst(value, SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */
setenv(variable, value, 1);
@ -2319,9 +2319,15 @@ ParseEOF(void)
Cond_EndFile();
if (curFile->guardState == GS_DONE)
HashTable_Set(&guards, curFile->name.str, curFile->guard);
else if (curFile->guard != NULL) {
if (curFile->guardState == GS_DONE) {
HashEntry *he = HashTable_CreateEntry(&guards,
curFile->name.str, NULL);
if (he->value != NULL) {
free(((Guard *)he->value)->name);
free(he->value);
}
HashEntry_Set(he, curFile->guard);
} else if (curFile->guard != NULL) {
free(curFile->guard->name);
free(curFile->guard);
}
@ -2684,6 +2690,13 @@ FinishDependencyGroup(void)
targets = NULL;
}
#ifdef CLEANUP
void Parse_RegisterCommand(char *cmd)
{
Lst_Append(&targCmds, cmd);
}
#endif
/* Add the command to each target from the current dependency spec. */
static void
ParseLine_ShellCommand(const char *p)
@ -2706,9 +2719,7 @@ ParseLine_ShellCommand(const char *p)
GNode *gn = ln->datum;
GNode_AddCommand(gn, cmd);
}
#ifdef CLEANUP
Lst_Append(&targCmds, cmd);
#endif
Parse_RegisterCommand(cmd);
}
}
@ -2764,6 +2775,8 @@ ParseDirective(char *line)
Var_Undef(arg);
else if (Substring_Equals(dir, "export"))
Var_Export(VEM_PLAIN, arg);
else if (Substring_Equals(dir, "export-all"))
Var_Export(VEM_ALL, arg);
else if (Substring_Equals(dir, "export-env"))
Var_Export(VEM_ENV, arg);
else if (Substring_Equals(dir, "export-literal"))
@ -2884,7 +2897,7 @@ ParseDependencyLine(char *line)
* empty string var_Error, which cannot be detected in the result of
* Var_Subst.
*/
emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR;
emode = opts.strict ? VARE_EVAL : VARE_EVAL_DEFINED;
expanded_line = Var_Subst(line, SCOPE_CMDLINE, emode);
/* TODO: handle errors */
@ -2990,7 +3003,7 @@ Parse_End(void)
assert(includes.len == 0);
Vector_Done(&includes);
HashIter_Init(&hi, &guards);
while (HashIter_Next(&hi) != NULL) {
while (HashIter_Next(&hi)) {
Guard *guard = hi.entry->value;
free(guard->name);
free(guard);

9
suff.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: suff.c,v 1.378 2024/02/07 06:43:02 rillig Exp $ */
/* $NetBSD: suff.c,v 1.380 2024/06/02 15:31:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -115,7 +115,7 @@
#include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */
MAKE_RCSID("$NetBSD: suff.c,v 1.378 2024/02/07 06:43:02 rillig Exp $");
MAKE_RCSID("$NetBSD: suff.c,v 1.380 2024/06/02 15:31:26 rillig Exp $");
typedef List SuffixList;
typedef ListNode SuffixListNode;
@ -1223,6 +1223,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn)
DEBUG1(SUFF, "%s...", name);
gn = Targ_GetNode(name);
free(name);
/* Insert gn before the original child. */
Lst_InsertBefore(&pgn->children, cln, gn);
@ -1273,7 +1274,7 @@ ExpandChildrenRegular(char *p, GNode *pgn, GNodeList *members)
} else if (*p == '$') {
/* Skip over the expression. */
const char *nested_p = p;
FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY);
FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE);
/* TODO: handle errors */
if (junk.str == var_Error) {
Parse_Error(PARSE_FATAL,
@ -1343,7 +1344,7 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn)
}
DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name);
expanded = Var_Subst(cgn->name, pgn, VARE_UNDEFERR);
expanded = Var_Subst(cgn->name, pgn, VARE_EVAL_DEFINED);
/* TODO: handle errors */
{

20
targ.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: targ.c,v 1.181 2024/04/27 17:33:47 rillig Exp $ */
/* $NetBSD: targ.c,v 1.183 2024/05/25 21:07:48 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -107,7 +107,7 @@
#include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: targ.c,v 1.181 2024/04/27 17:33:47 rillig Exp $");
MAKE_RCSID("$NetBSD: targ.c,v 1.183 2024/05/25 21:07:48 rillig Exp $");
/*
* All target nodes that appeared on the left-hand side of one of the
@ -219,6 +219,8 @@ GNode_New(const char *name)
static void
GNode_Free(GNode *gn)
{
Var_DeleteAll(gn);
free(gn->name);
free(gn->uname);
free(gn->path);
@ -236,20 +238,6 @@ GNode_Free(GNode *gn)
Lst_Done(&gn->order_succ);
Lst_Done(&gn->cohorts);
/*
* Do not free the variables themselves, even though they are owned
* by this node.
*
* XXX: For the nodes that represent targets or sources (and not
* 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 (SCOPE_CMD,
* SCOPE_GLOBAL, SCOPE_INTERNAL) are not freed at all (see Var_End,
* where they are not mentioned). These may be freed if their
* variable values are indeed not used anywhere else (see Trace_Init
* for the only suspicious use).
*/
HashTable_Done(&gn->vars);
/*

View File

@ -1,6 +1,6 @@
# $Id: Makefile,v 1.216 2024/04/30 16:42:50 sjg Exp $
# $Id: Makefile,v 1.219 2024/06/01 16:14:47 sjg Exp $
#
# $NetBSD: Makefile,v 1.344 2024/04/30 16:41:32 sjg Exp $
# $NetBSD: Makefile,v 1.347 2024/06/01 15:54:40 sjg Exp $
#
# Unit tests for make(1)
#
@ -745,11 +745,11 @@ all: ${OUTFILES}
CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp
CLEANFILES+= obj*.[och] lib*.a # posix1.mk
CLEANFILES+= issue* .[ab]* # suffixes.mk
CLEANDIRS= dir dummy # posix1.mk
CLEANDIRS= dir dummy *.tmp # posix1.mk
clean:
rm -f ${CLEANFILES}
rm -rf ${CLEANDIRS}
rm -f ${CLEANFILES}
TEST_MAKE?= ${.MAKE}
TOOL_SED?= sed
@ -864,6 +864,11 @@ test: ${OUTFILES} .PHONY
echo "Failed tests: $${failed}" ; false ; \
else \
echo "All tests passed" ; \
lua=${LUA:Ulua} ; \
have_lua=$$("$$lua" -e 'print "yes"' 2>&1) ; \
if [ "$$have_lua" = "yes" -a -s ${.CURDIR}/check-expect.lua ]; then \
(cd ${.CURDIR} && "$$lua" ./check-expect.lua *.mk); \
fi; \
fi
accept:

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-func-empty.mk,v 1.24 2023/12/19 19:33:40 rillig Exp $
# $NetBSD: cond-func-empty.mk,v 1.25 2024/06/02 15:31:26 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests an
# expression for emptiness.
@ -104,10 +104,10 @@ WORD= word
# Now the variable named " " gets a non-empty value, which demonstrates that
# 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 CondParser_FuncCallEmpty calls
# Var_Parse without VARE_UNDEFERR, the value of the undefined variable ""
# would be returned as an empty string.
# function. If the spaces were trimmed, the variable name would be "", and
# that variable is indeed undefined. Since CondParser_FuncCallEmpty allows
# subexpressions to be based on undefined variables, the value of the
# undefined variable "" would be returned as an empty string.
${:U }= space
.if empty( )
. error
@ -194,15 +194,15 @@ ${:U WORD }= variable name with spaces
# wrong variable name should have been discarded quickly after parsing it, to
# prevent it from doing any harm.
#
# The expression was expanded, and this was wrong. The
# 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
# expanded.
# The expression was evaluated, and this was wrong. The evaluation was done
# without VARE_EVAL (called VARF_WANTRES back then) though. This had the
# effect that the ${:U1} from the value of VARNAME evaluated to an empty
# string. This in turn created the seemingly recursive definition
# VARNAME=${VARNAME}, and that definition was evaluated even though it was
# never meant to be evaluated.
#
# This was fixed by expanding nested expressions in the variable name
# only if the flag VARE_WANTRES is given.
# This was fixed by evaluating nested expressions in the variable name only
# when the whole expression was evaluated as well.
VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif

View File

@ -1,4 +1,4 @@
make: "dep-duplicate.inc" line 1: warning: duplicate script for target "all" ignored
make: "dep-duplicate.tmp" line 1: warning: duplicate script for target "all" ignored
make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here
main-output
exit status 0

View File

@ -1,4 +1,4 @@
# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $
# $NetBSD: dep-duplicate.mk,v 1.4 2024/05/25 21:11:30 rillig Exp $
#
# Test for a target whose commands are defined twice. This generates a
# warning, not an error, so ensure that the correct commands are kept.
@ -13,9 +13,9 @@ all: .PHONY
echo '# empty line 1'; \
echo '# empty line 2'; \
echo 'all:; @echo main-output'; \
echo '.include "dep-duplicate.inc"'
echo '.include "dep-duplicate.tmp"'
@exec > dep-duplicate.inc; \
@exec > dep-duplicate.tmp; \
echo 'all:; @echo inc-output'
# The main file must be specified using a relative path, just like the
@ -24,4 +24,4 @@ all: .PHONY
@${MAKE} -r -f dep-duplicate.main
@rm -f dep-duplicate.main
@rm -f dep-duplicate.inc
@rm -f dep-duplicate.tmp

View File

@ -16,9 +16,9 @@ Result of ${:U\$)} is "$)" (eval-defined, defined)
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b
Var_Parse: $INDIRECT_2-2-1 $): (parse-only)
Var_Parse: $INDIRECT_2-2-1 $): (parse)
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1
Var_Parse: $): (parse-only)
Var_Parse: $): (parse)
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 $)
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0

View File

@ -1,4 +1,4 @@
# $NetBSD: dep-var.mk,v 1.11 2023/12/19 19:33:40 rillig Exp $
# $NetBSD: dep-var.mk,v 1.12 2024/06/02 15:31:26 rillig Exp $
#
# Tests for variable references in dependency declarations.
#
@ -84,8 +84,8 @@ all: $$$$)
# in normal mode since ParseDependency does not handle any errors after
# calling Var_Parse.
# expect: Var_Parse: ${:U\$)}: (eval-defined)
# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse-only)
# expect: Var_Parse: $): (parse-only)
# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse)
# expect: Var_Parse: $): (parse)
undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}:
@echo ${.TARGET:Q}

View File

@ -1,2 +1,5 @@
value with ${UNEXPANDED} expression
value literal
value indirect
value ${indirect:L}
exit status 0

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-export-literal.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $
# $NetBSD: directive-export-literal.mk,v 1.8 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the .export-literal directive, which exports a variable value
# without expanding it.
@ -9,5 +9,28 @@ UT_VAR= value with ${UNEXPANDED} expression
.export-literal # oops: missing argument
# After a variable whose value does not contain a '$' is exported, a following
# .export-literal can be skipped, to avoid a setenv call, which may leak
# memory on some platforms.
UT_TWICE_LITERAL= value literal
.export UT_TWICE_LITERAL
.export-literal UT_TWICE_LITERAL
# XXX: After an .export, an .export-literal has no effect, even when the
# variable value contains a '$'.
UT_TWICE_EXPR= value ${indirect:L}
.export UT_TWICE_EXPR
.export-literal UT_TWICE_EXPR
# After an .export, an .unexport resets the variable's exported state,
# re-enabling a later .export-literal.
UT_TWICE_EXPR_UNEXPORT= value ${indirect:L}
.export UT_TWICE_EXPR_UNEXPORT
.unexport UT_TWICE_EXPR_UNEXPORT
.export-literal UT_TWICE_EXPR_UNEXPORT
all:
@echo "$$UT_VAR"
@echo "$$UT_TWICE_LITERAL"
@echo "$$UT_TWICE_EXPR"
@echo "$$UT_TWICE_EXPR_UNEXPORT"

View File

@ -1,4 +1,5 @@
make: "directive-export.mk" line 56: 00:00:00
make: "directive-export.mk" line 61: 00:00:00
make: "directive-export.mk" line 64: 16:00:00
make: "directive-export.mk" line 36: warning: .export requires an argument.
make: "directive-export.mk" line 60: 00:00:00
make: "directive-export.mk" line 65: 00:00:00
make: "directive-export.mk" line 68: 16:00:00
exit status 0

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-export.mk,v 1.10 2023/11/19 09:45:19 rillig Exp $
# $NetBSD: directive-export.mk,v 1.12 2024/06/01 10:06:23 rillig Exp $
#
# Tests for the .export directive.
#
@ -28,7 +28,11 @@ VAR= value $$ ${INDIRECT}
. error
.endif
# No syntactical argument means to export all variables.
# Before var.c 1.1117 from 2024-06-01, a plain ".export" without a syntactical
# argument exported all global variables. This case could be triggered
# unintentionally by writing a line of the form ".export ${VARNAMES}" to a
# makefile, when VARNAMES was an empty list.
# expect+1: warning: .export requires an argument.
.export
# An empty argument means no additional variables to export.

View File

@ -1,7 +1,4 @@
make: "directive-for-empty.mk" line 22: 2
make: "directive-for-empty.mk" line 38: Missing argument for ".error"
make: "directive-for-empty.mk" line 38: Missing argument for ".error"
make: "directive-for-empty.mk" line 38: Missing argument for ".error"
For: end for 1
For: loop body with i = value:
# The identifier 'empty' can only be used in conditions such as .if, .ifdef or
@ -22,6 +19,4 @@ CPPFLAGS+= -Dmessage="empty(i)"
# condition directives, they can also occur in the modifier ':?', see
# varmod-ifelse.mk.
CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
exit status 0

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-for-empty.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $
# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $
#
# Tests for .for loops containing conditions of the form 'empty(var:...)'.
#
@ -26,23 +26,23 @@
# In conditions, the function call to 'empty' does not look like an
# expression, therefore it is not replaced. Since there is no global variable
# named 'i', this expression makes for a leaky abstraction. If the .for
# named 'i', this condition makes for a leaky abstraction. If the .for
# variables were real variables, calling 'empty' would work on them as well.
.for i in 11 12 13
# Asking for an empty iteration variable does not make sense as the .for loop
# splits the iteration items into words, and such a word cannot be empty.
. if empty(i)
# expect+3: Missing argument for ".error"
# expect+2: Missing argument for ".error"
# expect+1: Missing argument for ".error"
. error # due to the leaky abstraction
. if !empty(i)
. error # not reached, due to the leaky abstraction
. endif
# The typical way of using 'empty' with variables from .for loops is pattern
# matching using the modifiers ':M' or ':N'.
# The typical way of mistakenly using 'empty' with variables from .for loops
# is pattern matching using the modifiers ':M' or ':N'.
. if !empty(i:M*2*)
. if ${i} != "12"
. error
. endif
. error
. endif
# Instead of the 'empty' function, the variables from .for loops can be
# queried using conditions of the form '${var:...} != ""'.
. if $i == "12" && ${i:M*2*} != "12"
. error
. endif
.endfor
@ -122,3 +122,5 @@ CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
# TODO: Add code that demonstrates the current interaction between variables
# from .for loops and the modifiers mentioned above.
all:

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-for-errors.mk,v 1.10 2024/04/20 10:18:55 rillig Exp $
# $NetBSD: directive-for-errors.mk,v 1.11 2024/06/01 11:24:11 rillig Exp $
#
# Tests for error handling in .for loops.
@ -41,7 +41,7 @@
${:U\$}= dollar # see whether the "variable" '$' is local
${:U\\}= backslash # see whether the "variable" '\' is local
# expect+1: invalid character '$' in .for loop variable name
.for $ \ in 1 2 3 4
.for a b $ \ in 1 2 3 4
. info Dollar $$ ${$} $($) and backslash $\ ${\} $(\).
.endfor

View File

@ -1,4 +1,4 @@
# $NetBSD: export-all.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $
# $NetBSD: export-all.mk,v 1.6 2024/06/01 06:26:36 sjg Exp $
UT_OK= good
UT_F= fine
@ -15,7 +15,7 @@ UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T}
# this will be ok
UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T}
.export
.export-all
FILTER_CMD= grep ^UT_
.include "export.mk"

View File

@ -1,4 +1,4 @@
make: "opt-debug-hash.mk" line 12: Missing argument for ".error"
make: "opt-debug-hash.mk" line 13: Missing argument for ".error"
make: Fatal errors encountered -- cannot continue
HashTable targets: size=16 numEntries=0 maxchain=0
HashTable Global variables: size=16 numEntries=<entries> maxchain=3

View File

@ -1,4 +1,4 @@
# $NetBSD: opt-debug-hash.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $
# $NetBSD: opt-debug-hash.mk,v 1.5 2024/05/31 07:13:12 rillig Exp $
#
# Tests for the -dh command line option, which adds debug logging for
# hash tables. Even more detailed logging is available by compiling
@ -6,7 +6,8 @@
.MAKEFLAGS: -dh
# Force a parse error, to demonstrate the newline character in the diagnostic
# that had been missing before parse.c 1.655 from 2022-01-22.
# Force a parse error, to demonstrate the newline character in the "cannot
# continue" diagnostic that had been missing before parse.c 1.655 from
# 2022-01-22.
# expect+1: Missing argument for ".error"
.error

View File

@ -1,4 +1,4 @@
# $NetBSD: parse-var.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $
# $NetBSD: parse-var.mk,v 1.10 2024/06/02 15:31:26 rillig Exp $
#
# Tests for parsing expressions.
#
@ -20,11 +20,11 @@
#
# VarEvalMode:
# parse
# parse-balanced
# eval
# eval-undeferr
# eval-keep-dollar
# eval-keep-undef
# eval-keep-dollar-undef
# eval-defined
# eval-keep-undefined
# eval-keep-dollar-and-undefined
#
# Global mode:
# without -dL

View File

@ -1,4 +1,4 @@
# $NetBSD: recursive.mk,v 1.7 2023/10/19 18:24:33 rillig Exp $
# $NetBSD: recursive.mk,v 1.8 2024/06/02 15:31:26 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
@ -6,7 +6,7 @@
#
# The purpose of evaluating that variable early was just to detect
# whether there are unclosed variables. The variable value is therefore
# parsed with VARE_PARSE_ONLY for that purpose.
# parsed with VARE_PARSE for that purpose.
#
.MAKEFLAGS: -dL

View File

@ -1,4 +1,4 @@
# $NetBSD: shell-csh.mk,v 1.8 2021/04/04 09:58:51 rillig Exp $
# $NetBSD: shell-csh.mk,v 1.9 2024/05/25 15:37:17 rillig Exp $
#
# Tests for using a C shell for running the commands.
@ -6,7 +6,7 @@ CSH!= which csh 2> /dev/null || true
# The shell path must be an absolute path.
# This is only obvious in parallel mode since in compat mode,
# simple commands are executed via execve directly.
# simple commands are executed via execvp directly.
.if ${CSH} != ""
.SHELL: name="csh" path="${CSH}"
.endif

View File

@ -17,7 +17,7 @@ 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
# LinkSource: added child .a.c - ${.PREFIX}.dependency
Target ".a.c" depends on "${.PREFIX}.dependency"
# .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none
# ${.PREFIX}.dependency, unmade, type none, flags none
Parsing line 23: .DEFAULT:

View File

@ -58,7 +58,7 @@ ParseDependency(suff-main-several.1:)
Parsing line 39: : Making ${.TARGET} out of nothing.
Parsing line 40: 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}
Target "next-main" depends on "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
Parsing line 42: .MAKEFLAGS: -d0 -dg1

View File

@ -2,26 +2,26 @@ make: "var-eval-short.mk" line 46: while evaluating "${:Uword:@${FAIL}@expr@}":
make: "var-eval-short.mk" line 46: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@})
Parsing line 159: .if 0 && ${0:?${FAIL}then:${FAIL}else}
CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else}
Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only)
Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse)
Parsing modifier ${0:?...}
Var_Parse: ${FAIL}then:${FAIL}else} (parse-only)
Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then"
Var_Parse: ${FAIL}else} (parse-only)
Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else"
Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined)
Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse, defined)
Parsing line 167: DEFINED= defined
Global: DEFINED = defined
Parsing line 168: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only)
Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse)
Parsing modifier ${DEFINED:L}
Result of ${DEFINED:L} is "defined" (parse-only, regular)
Result of ${DEFINED:L} is "defined" (parse, regular)
Parsing modifier ${DEFINED:?...}
Var_Parse: ${FAIL}then:${FAIL}else} (parse-only)
Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then"
Var_Parse: ${FAIL}else} (parse-only)
Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else"
Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular)
Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse, regular)
Parsing line 170: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-head.mk,v 1.5 2022/07/10 21:11:49 rillig Exp $
# $NetBSD: varmod-head.mk,v 1.6 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the :H variable modifier, which returns the dirname of
# each of the words in the variable value.
@ -61,4 +61,10 @@ _!= echo "The modifier ':H' generates an empty word." 1>&2; echo
. error
.endif
# If the ':H' is not directly followed by a delimiting ':' or '}', the
# ':from=to' modifier is tried as a fallback.
.if ${:U Head :Head=replaced} != "replaced"
. error
.endif
all: .PHONY

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-ifelse.mk,v 1.28 2024/04/23 22:51:28 rillig Exp $
# $NetBSD: varmod-ifelse.mk,v 1.29 2024/06/02 15:31:26 rillig Exp $
#
# Tests for the ${cond:?then:else} variable modifier, which evaluates either
# the then-expression or the else-expression, depending on the condition.
@ -77,7 +77,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. When ApplyModifier_IfElse
# being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse
# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the
# value of the expression is still undefined. CondParser_String is
# then supposed to do proper error handling, but since varUndefined is local

View File

@ -1,12 +1,12 @@
Parsing line 91: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
Parsing line 92: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
Parsing line 89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
Parsing line 90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$"
Parsing line 96: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
Parsing line 118: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
Parsing line 94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
Parsing line 116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$"
Parsing line 121: .MAKEFLAGS: -d0
Parsing line 119: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
:varname-overwriting-target: :x1y x2y x3y: ::
mod-loop-dollar:1:

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-loop.mk,v 1.24 2023/11/19 21:47:52 rillig Exp $
# $NetBSD: varmod-loop.mk,v 1.26 2024/06/02 15:31:26 rillig Exp $
#
# Tests for the expression modifier ':@var@body@', which replaces each word of
# the expression with the expanded body, which may contain references to the
@ -82,10 +82,8 @@ mod-loop-dollar:
8_DOLLARS= $$$$$$$$
# This string literal is written with 8 dollars, and this is saved as the
# variable value. But as soon as this value is evaluated, it goes through
# Var_Subst, which replaces each '$$' with a single '$'. This could be
# prevented by VARE_EVAL_KEEP_DOLLAR, but that flag is usually removed
# before expanding subexpressions. See ApplyModifier_Loop and
# ParseModifierPart for examples.
# Var_Subst, which replaces each '$$' with a single '$'.
# See ApplyModifier_Loop and ParseModifierPart for examples.
#
.MAKEFLAGS: -dcp
USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
@ -95,11 +93,11 @@ USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
#
SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
# 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.
# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED, 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
# The 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

View File

@ -34,8 +34,8 @@ make: "varmod-match-escape.mk" line 43: warning: XXX: Oops
Global: .MAKEFLAGS = -r -k -d cv -d
Global: .MAKEFLAGS = -r -k -d cv -d 0
make: "varmod-match-escape.mk" line 69: while evaluating "${:U\$:M\$} != """: Dollar followed by nothing
make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[A-]' of modifier ':M'
make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^A-]' of modifier ':M'
make: "varmod-match-escape.mk" line 110: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[A-]' of modifier ':M'
make: "varmod-match-escape.mk" line 110: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^A-]' of modifier ':M'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-match-escape.mk,v 1.13 2024/04/20 10:18:55 rillig Exp $
# $NetBSD: varmod-match-escape.mk,v 1.14 2024/06/15 19:43:56 rillig Exp $
#
# As of 2020-08-01, the :M and :N modifiers interpret backslashes differently,
# depending on whether there was an expression somewhere before the
@ -105,8 +105,8 @@ EXP.[^A-]]= a
EXP.[^A-]]]= a]
.for pattern in [A-] [A-]] [A-]]] [^A-] [^A-]] [^A-]]]
# expect+2: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[A-]' of modifier ':M'
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^A-]' of modifier ':M'
# expect+2: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[A-]' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^A-]' of modifier ':M'
. if ${WORDS:M${pattern}} != ${EXP.${pattern}}
. warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}}
. endif

View File

@ -1,14 +1,14 @@
make: "varmod-match.mk" line 290: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[' of modifier ':M'
make: "varmod-match.mk" line 298: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[^' of modifier ':M'
make: "varmod-match.mk" line 306: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[-x1-3' of modifier ':M'
make: "varmod-match.mk" line 314: while evaluating variable "WORDS": warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M'
make: "varmod-match.mk" line 323: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M'
make: "varmod-match.mk" line 337: while evaluating variable "WORDS": warning: Unfinished character list in pattern '?[\' of modifier ':M'
make: "varmod-match.mk" line 345: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[x-' of modifier ':M'
make: "varmod-match.mk" line 357: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[^x-' of modifier ':M'
make: "varmod-match.mk" line 365: while evaluating variable " : :: ": warning: Unfinished character list in pattern '[' of modifier ':M'
make: "varmod-match.mk" line 365: while evaluating variable " : :: ": Unknown modifier "]"
make: "varmod-match.mk" line 365: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
make: "varmod-match.mk" line 289: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[' of modifier ':M'
make: "varmod-match.mk" line 297: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[^' of modifier ':M'
make: "varmod-match.mk" line 305: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[-x1-3' of modifier ':M'
make: "varmod-match.mk" line 313: warning: while evaluating variable "WORDS": Unfinished character list in pattern '*[-x1-3' of modifier ':M'
make: "varmod-match.mk" line 322: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^-x1-3' of modifier ':M'
make: "varmod-match.mk" line 336: warning: while evaluating variable "WORDS": Unfinished character list in pattern '?[\' of modifier ':M'
make: "varmod-match.mk" line 344: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[x-' of modifier ':M'
make: "varmod-match.mk" line 356: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[^x-' of modifier ':M'
make: "varmod-match.mk" line 364: warning: while evaluating variable " : :: ": Unfinished character list in pattern '[' of modifier ':M'
make: "varmod-match.mk" line 364: while evaluating variable " : :: ": Unknown modifier "]"
make: "varmod-match.mk" line 364: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-match.mk,v 1.22 2024/04/23 22:51:28 rillig Exp $
# $NetBSD: varmod-match.mk,v 1.24 2024/06/15 19:43:56 rillig Exp $
#
# Tests for the ':M' modifier, which keeps only those words that match the
# given pattern.
@ -42,13 +42,12 @@
. error
.endif
# A pattern that ends with '*' is anchored at the
# beginning.
# A pattern that does not start with '*' is anchored at the beginning.
.if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa"
. error
.endif
# A pattern that starts with '*' is anchored at the end.
# A pattern that does not end with '*' is anchored at the end.
.if ${a aa aaa b ba baa bab:L:M*a} != "a aa aaa ba baa"
. error
.endif
@ -286,7 +285,7 @@ ${:U*}= asterisk
# [ Incomplete empty character list, never matches.
WORDS= a a[
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[' of modifier ':M'
.if ${WORDS:Ma[} != ""
. error
.endif
@ -294,7 +293,7 @@ WORDS= a a[
# [^ Incomplete negated empty character list, matches any single
# character.
WORDS= a a[ aX
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern 'a[^' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern 'a[^' of modifier ':M'
.if ${WORDS:Ma[^} != "a[ aX"
. error
.endif
@ -302,7 +301,7 @@ WORDS= a a[ aX
# [-x1-3 Incomplete character list, matches those elements that can be
# parsed without lookahead.
WORDS= - + x xx 0 1 2 3 4 [x1-3
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[-x1-3' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[-x1-3' of modifier ':M'
.if ${WORDS:M[-x1-3} != "- x 1 2 3"
. error
.endif
@ -310,7 +309,7 @@ WORDS= - + x xx 0 1 2 3 4 [x1-3
# *[-x1-3 Incomplete character list after a wildcard, matches those
# words that end with one of the characters from the list.
WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '*[-x1-3' of modifier ':M'
.if ${WORDS:M*[-x1-3} != "- x xx 1 2 3 01 11 001 011 101 111 [x1-3"
. warning ${WORDS:M*[-x1-3}
.endif
@ -319,7 +318,7 @@ WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3
# Incomplete negated character list, matches any character
# except those elements that can be parsed without lookahead.
WORDS= - + x xx 0 1 2 3 4 [x1-3
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^-x1-3' of modifier ':M'
.if ${WORDS:M[^-x1-3} != "+ 0 4"
. error
.endif
@ -333,7 +332,7 @@ WORDS= - + x xx 0 1 2 3 4 [x1-3
# '\', as there is no following space that could be escaped.
WORDS= \\ \a ${:Ux\\}
PATTERN= ${:U?[\\}
# expect+1: while evaluating variable "WORDS": warning: Unfinished character list in pattern '?[\' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '?[\' of modifier ':M'
.if ${WORDS:M${PATTERN}} != "\\\\ x\\"
. error
.endif
@ -341,7 +340,7 @@ PATTERN= ${:U?[\\}
# [x- Incomplete character list containing an incomplete character
# range, matches only the 'x'.
WORDS= [x- x x- y
# expect+1: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[x-' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[x-' of modifier ':M'
.if ${WORDS:M[x-} != "x"
. error
.endif
@ -353,13 +352,13 @@ WORDS= [x- x x- y
# XXX: Even matches strings that are longer than a single
# character.
WORDS= [x- x x- y yyyyy
# expect+1: while evaluating variable "WORDS": warning: Unfinished character range in pattern '[^x-' of modifier ':M'
# expect+1: warning: while evaluating variable "WORDS": Unfinished character range in pattern '[^x-' of modifier ':M'
.if ${WORDS:M[^x-} != "[x- y yyyyy"
. error
.endif
# [:] matches never since the ':' starts the next modifier
# expect+3: while evaluating variable " : :: ": warning: Unfinished character list in pattern '[' of modifier ':M'
# expect+3: warning: while evaluating variable " : :: ": Unfinished character list in pattern '[' of modifier ':M'
# expect+2: while evaluating variable " : :: ": Unknown modifier "]"
# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
.if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":"
@ -375,7 +374,7 @@ WORDS= [x- x x- y yyyyy
# out-of-bounds read beyond the indirect ':M' modifiers.
#
# The argument to the inner ':U' is unescaped to 'M\'.
# This 'M\' becomes an # indirect modifier ':M' with the pattern '\'.
# This 'M\' becomes an indirect modifier ':M' with the pattern '\'.
# The pattern '\' never matches.
.if ${:U:${:UM\\}}
. error

View File

@ -145,6 +145,8 @@ pre-middle-suffix pre%ffix=NPre% "NPre-middle-su"
suffix pre%ffix=NPre%NS "suffix"
prefix pre%ffix=NPre%NS "prefix"
pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS"
make: Unfinished modifier for "error" ('}' missing)
make: "varmod-sysv.mk" line 259: Malformed conditional (${error:L:from=$(}))
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-sysv.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $
# $NetBSD: varmod-sysv.mk,v 1.17 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the variable modifier ':from=to', which replaces the suffix
# "from" with "to". It can also use '%' as a wildcard.
@ -252,4 +252,12 @@ INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE}
. endfor
.endfor
# The error case of an unfinished ':from=to' modifier after the '=' requires
# an expression that is missing the closing '}'.
# expect+1: Malformed conditional (${error:L:from=$(}))
.if ${error:L:from=$(})
.endif
all:

View File

@ -1,8 +1,16 @@
# $NetBSD: varmod-tail.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
# $NetBSD: varmod-tail.mk,v 1.5 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the :T variable modifier, which returns the basename of each of
# the words in the variable value.
# If the ':T' is not directly followed by a delimiting ':' or '}', the
# ':from=to' modifier is tried as a fallback.
.if ${:U Tail :Tail=replaced} != "replaced"
. error
.endif
all:
.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
@echo "tail (basename) of '"${path:Q}"' is '"${path:T:Q}"'"

View File

@ -2,24 +2,28 @@ make: "varmod-to-separator.mk" line 155: while evaluating variable "WORDS": Inva
make: "varmod-to-separator.mk" line 155: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
make: "varmod-to-separator.mk" line 171: while evaluating variable "WORDS": Invalid character number at "100:tu}"
make: "varmod-to-separator.mk" line 171: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
make: "varmod-to-separator.mk" line 180: while evaluating variable "word": Invalid character number at ",}"
make: "varmod-to-separator.mk" line 180: Malformed conditional (${word:L:ts\x,})
make: "varmod-to-separator.mk" line 187: while evaluating variable "word": Invalid character number at "112233445566778899}"
make: "varmod-to-separator.mk" line 187: Malformed conditional (${word:L:ts\x112233445566778899})
make: Bad modifier ":ts\-300" for variable "WORDS"
make: "varmod-to-separator.mk" line 179: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
make: "varmod-to-separator.mk" line 192: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
make: Bad modifier ":ts\8" for variable "1 2 3"
make: "varmod-to-separator.mk" line 188: Malformed conditional (${1 2 3:L:ts\8:tu})
make: "varmod-to-separator.mk" line 201: Malformed conditional (${1 2 3:L:ts\8:tu})
make: Bad modifier ":ts\100L" for variable "1 2 3"
make: "varmod-to-separator.mk" line 196: Malformed conditional (${1 2 3:L:ts\100L})
make: "varmod-to-separator.mk" line 209: Malformed conditional (${1 2 3:L:ts\100L})
make: Bad modifier ":ts\x40g" for variable "1 2 3"
make: "varmod-to-separator.mk" line 204: Malformed conditional (${1 2 3:L:ts\x40g})
make: "varmod-to-separator.mk" line 217: Malformed conditional (${1 2 3:L:ts\x40g})
make: Bad modifier ":tx" for variable "WORDS"
make: "varmod-to-separator.mk" line 214: Malformed conditional (${WORDS:tx})
make: "varmod-to-separator.mk" line 227: Malformed conditional (${WORDS:tx})
make: Bad modifier ":ts\X" for variable "WORDS"
make: "varmod-to-separator.mk" line 223: Malformed conditional (${WORDS:ts\X})
make: "varmod-to-separator.mk" line 236: Malformed conditional (${WORDS:ts\X})
make: Bad modifier ":t\X" for variable "WORDS"
make: "varmod-to-separator.mk" line 232: Malformed conditional (${WORDS:t\X} != "anything")
make: "varmod-to-separator.mk" line 245: Malformed conditional (${WORDS:t\X} != "anything")
make: Bad modifier ":ts\69" for variable ""
make: "varmod-to-separator.mk" line 249: Malformed conditional (${:Ua b:ts\69})
make: "varmod-to-separator.mk" line 258: while evaluating "${:Ua b:ts\x1F60E}": Invalid character number at "1F60E}"
make: "varmod-to-separator.mk" line 258: Malformed conditional (${:Ua b:ts\x1F60E})
make: "varmod-to-separator.mk" line 262: Malformed conditional (${:Ua b:ts\69})
make: "varmod-to-separator.mk" line 271: while evaluating "${:Ua b:ts\x1F60E}": Invalid character number at "1F60E}"
make: "varmod-to-separator.mk" line 271: Malformed conditional (${:Ua b:ts\x1F60E})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-to-separator.mk,v 1.14 2024/04/20 10:18:55 rillig Exp $
# $NetBSD: varmod-to-separator.mk,v 1.15 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the :ts variable modifier, which joins the words of the variable
# using an arbitrary character as word separator.
@ -174,6 +174,19 @@ WORDS= one two three four five six
. warning The separator \x100 is accepted even though it is out of bounds.
.endif
# The number after ':ts\x' must be hexadecimal.
# expect+2: while evaluating variable "word": Invalid character number at ",}"
# expect+1: Malformed conditional (${word:L:ts\x,})
.if ${word:L:ts\x,}
.endif
# The hexadecimal number must be in the range of 'unsigned long' on all
# supported platforms.
# expect+2: while evaluating variable "word": Invalid character number at "112233445566778899}"
# expect+1: Malformed conditional (${word:L:ts\x112233445566778899})
.if ${word:L:ts\x112233445566778899}
.endif
# Negative numbers are not allowed for the separator character.
# expect+1: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
.if ${WORDS:[1..3]:ts\-300:tu}

View File

@ -1,4 +1,4 @@
# $NetBSD: varmod-undefined.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $
# $NetBSD: varmod-undefined.mk,v 1.11 2024/06/03 02:46:29 sjg Exp $
#
# Tests for the :U variable modifier, which returns the given string
# if the variable is undefined.
@ -7,6 +7,9 @@
# directive-for.mk
# varmod-defined.mk
# this test depends on
.MAKE.SAVE_DOLLARS= yes
# The pattern ${:Uword} is heavily used when expanding .for loops.
#
# This is how an expanded .for loop looks like.
@ -53,6 +56,10 @@
.if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n "
. error
.endif
# An expression enclosed in quotes may be based on an undefined variable.
.if "${:U \: \} \$ \\ \a \b \n }" != " : } \$ \\ \\a \\b \\n "
. error
.endif
# Even after the :U modifier has been applied, the expression still remembers
# that it originated from an undefined variable, and the :U modifier can
@ -64,5 +71,43 @@
. error
.endif
all:
@:;
# VARE_PARSE
.if 0 && ${:U . \: \} \$ \\ ${EXPR}}
. error
.endif
# VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED
SUBST:= ${:U . \: \} \$ \\ ${EXPR}}
${:U }= <space>
EXPR= <expr>
.if ${SUBST} != " . : } <space>\\ "
. error
.endif
8_DOLLAR= $$$$$$$$
.if ${8_DOLLAR} != "\$\$\$\$"
. error
.endif
.if ${:U${8_DOLLAR}} != "\$\$\$\$"
. error
.endif
.if ${x:L:@_@${8_DOLLAR}@} != "\$\$\$\$"
. error
.endif
EXPR:= ${8_DOLLAR}
.if ${EXPR} != "\$\$\$\$"
. error
.endif
EXPR:= ${:U${8_DOLLAR}}
.if ${EXPR} != "\$\$\$\$"
. error
.endif
# VARE_EVAL_KEEP_UNDEFINED
EXPR:= ${x:L:@_@${8_DOLLAR}@}
.if ${EXPR} != "\$\$"
. error
.endif
all: .PHONY

View File

@ -3,6 +3,31 @@ make: "varmod.mk" line 101: Invalid variable name ':', at "$:L} != """
make: "varmod.mk" line 107: while evaluating "${:Uword:@word@${word}$@} != "word"": Dollar followed by nothing
make: "varmod.mk" line 117: while evaluating variable "VAR": Missing delimiter ':' after modifier "P"
make: "varmod.mk" line 119: Missing argument for ".error"
make: Bad modifier ":[99333000222000111000]" for variable "word"
make: "varmod.mk" line 125: Malformed conditional (${word:L:[99333000222000111000]})
make: Bad modifier ":[2147483648]" for variable "word"
make: "varmod.mk" line 128: Malformed conditional (${word:L:[2147483648]})
make: "varmod.mk" line 135: while evaluating variable "word": Invalid number "99333000222000111000}" for ':range' modifier
make: "varmod.mk" line 135: Malformed conditional (${word:L:range=99333000222000111000})
make: "varmod.mk" line 143: while evaluating "${:${:Ugmtime=\\}}": Invalid time value "\"
make: "varmod.mk" line 143: Malformed conditional (${:${:Ugmtime=\\}})
make: "varmod.mk" line 158: while evaluating variable "VAR": Dollar followed by nothing
make: "varmod.mk" line 164: while evaluating variable "VAR": Dollar followed by nothing
make: "varmod.mk" line 164: while evaluating variable "VAR": Dollar followed by nothing
make: "varmod.mk" line 174: while evaluating variable "word": Dollar followed by nothing
make: Bad modifier ":[$]" for variable "word"
make: "varmod.mk" line 179: Malformed conditional (${word:[$]})
make: "varmod.mk" line 196: while evaluating variable "VAR": Dollar followed by nothing
make: "varmod.mk" line 196: while evaluating variable "VAR": Invalid variable name '}', at "$} != "set""
make: "varmod.mk" line 200: while evaluating "${:Ufallback$} != "fallback"": Invalid variable name '}', at "$} != "fallback""
make: "varmod.mk" line 205: while evaluating variable "%y": Invalid time value "1000$"
make: "varmod.mk" line 205: Malformed conditional (${%y:L:gmtime=1000$})
make: "varmod.mk" line 212: while evaluating variable "%y": Invalid time value "1000$"
make: "varmod.mk" line 212: Malformed conditional (${%y:L:localtime=1000$})
make: "varmod.mk" line 218: while evaluating variable "word": Dollar followed by nothing
make: "varmod.mk" line 222: while evaluating variable "word": Dollar followed by nothing
make: "varmod.mk" line 227: while evaluating variable ".": Invalid argument 'fallback$' for modifier ':mtime'
make: "varmod.mk" line 227: Malformed conditional (${.:L:mtime=fallback$})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,11 +1,11 @@
# $NetBSD: varmod.mk,v 1.11 2024/04/20 10:18:56 rillig Exp $
# $NetBSD: varmod.mk,v 1.15 2024/06/06 20:41:50 rillig Exp $
#
# Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
#
# See also:
# varparse-errors.mk
# As of 2022-08-06, the possible behaviors during parsing are:
# As of 2024-06-05, the possible behaviors during parsing are:
#
# * `strict`: the parsing style used by most modifiers:
# * either uses `ParseModifierPart` or parses the modifier literal
@ -46,9 +46,9 @@
# | `U` | individual | custom parser | N/A |
# | `[` | strict | | no |
# | `_` | individual | strcspn | yes |
# | `gmtime` | strict | only literal value | yes |
# | `gmtime` | strict | | yes |
# | `hash` | strict | | N/A |
# | `localtime` | strict | only literal value | yes |
# | `localtime` | strict | | yes |
# | `q` | strict | | yes |
# | `range` | strict | | N/A |
# | `sh` | strict | | N/A |
@ -119,4 +119,119 @@ VAR= STOP
. error
.endif
all: # nothing
# Test the word selection modifier ':[n]' with a very large number that is
# larger than ULONG_MAX for any supported platform.
# expect+1: Malformed conditional (${word:L:[99333000222000111000]})
.if ${word:L:[99333000222000111000]}
.endif
# expect+1: Malformed conditional (${word:L:[2147483648]})
.if ${word:L:[2147483648]}
.endif
# Test the range generation modifier ':range=n' with a very large number that
# is larger than SIZE_MAX for any supported platform.
# expect+2: Malformed conditional (${word:L:range=99333000222000111000})
# expect+1: while evaluating variable "word": Invalid number "99333000222000111000}" for ':range' modifier
.if ${word:L:range=99333000222000111000}
.endif
# In an indirect modifier, the delimiter is '\0', which at the same time marks
# the end of the string. The sequence '\\' '\0' is not an escaped delimiter,
# as it would be wrong to skip past the end of the string.
# expect+2: while evaluating "${:${:Ugmtime=\\}}": Invalid time value "\"
# expect+1: Malformed conditional (${:${:Ugmtime=\\}})
.if ${:${:Ugmtime=\\}}
. error
.endif
# Test a '$' at the end of a modifier part, for all modifiers in the order
# listed in ApplyModifier.
#
# The only modifier parts where an unescaped '$' makes sense at the end are
# the 'from' parts of the ':S' and ':C' modifiers. In all other modifier
# parts, an unescaped '$' is an undocumented and discouraged edge case, as it
# means the same as an escaped '$'.
.if ${:U:!printf '%s\n' $!} != "\$"
. error
.endif
# expect+1: while evaluating variable "VAR": Dollar followed by nothing
.if ${VAR::=value$} != "" || ${VAR} != "value"
. error
.endif
${:U }= <space>
# expect+2: while evaluating variable "VAR": Dollar followed by nothing
# expect+1: while evaluating variable "VAR": Dollar followed by nothing
.if ${VAR::+=appended$} != "" || ${VAR} != "value<space>appended"
. error
.endif
.if ${1:?then$:else$} != "then\$"
. error
.endif
.if ${0:?then$:else$} != "else\$"
. error
.endif
# expect+1: while evaluating variable "word": Dollar followed by nothing
.if ${word:L:@w@$w$@} != "word"
. error
.endif
# expect: make: Bad modifier ":[$]" for variable "word"
# expect+1: Malformed conditional (${word:[$]})
.if ${word:[$]}
. error
.else
. error
.endif
VAR_DOLLAR= VAR$$
.if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word"
. error
.endif
.if ${word:L:C,d$,m,} != "worm"
. error
.endif
.if ${word:L:C,d,$,} != "wor\$"
. error
.endif
# expect+2: while evaluating variable "VAR": Invalid variable name '}', at "$} != "set""
# expect+1: while evaluating variable "VAR": Dollar followed by nothing
.if ${VAR:Dset$} != "set"
. error
.endif
# expect+1: while evaluating "${:Ufallback$} != "fallback"": Invalid variable name '}', at "$} != "fallback""
.if ${:Ufallback$} != "fallback"
. error
.endif
# expect+2: Malformed conditional (${%y:L:gmtime=1000$})
# expect+1: while evaluating variable "%y": Invalid time value "1000$"
.if ${%y:L:gmtime=1000$}
. error
.else
. error
.endif
# expect+2: Malformed conditional (${%y:L:localtime=1000$})
# expect+1: while evaluating variable "%y": Invalid time value "1000$"
.if ${%y:L:localtime=1000$}
. error
.else
. error
.endif
# expect+1: while evaluating variable "word": Dollar followed by nothing
.if ${word:L:Mw*$} != "word"
. error
.endif
# expect+1: while evaluating variable "word": Dollar followed by nothing
.if ${word:L:NX*$} != "word"
. error
.endif
# expect+2: while evaluating variable ".": Invalid argument 'fallback$' for modifier ':mtime'
# expect+1: Malformed conditional (${.:L:mtime=fallback$})
.if ${.:L:mtime=fallback$}
. error
.else
. error
.endif
.if ${word:L:S,d$,m,} != "worm"
. error
.endif
.if ${word:L:S,d,m$,} != "worm\$"
. error
.endif

View File

@ -1 +1,4 @@
level 1: variable 0, env 1
level 2: variable 1, env 2
level 3: variable 2, env 3
exit status 0

View File

@ -1,8 +1,22 @@
# $NetBSD: varname-dot-make-level.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: varname-dot-make-level.mk,v 1.3 2024/06/01 18:44:05 rillig Exp $
#
# Tests for the special .MAKE.LEVEL variable.
# Tests for the special .MAKE.LEVEL variable, which informs about the
# recursion level. It is related to the environment variable MAKELEVEL,
# even though they don't have the same value.
# TODO: Implementation
level_1: .PHONY
@printf 'level 1: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
@${MAKE} -f ${MAKEFILE} level_2
all:
@:;
level_2: .PHONY
@printf 'level 2: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
@${MAKE} -f ${MAKEFILE} level_3
level_3: .PHONY
@printf 'level 3: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}"
# The .unexport-env directive clears the environment, except for the
# MAKE_LEVEL variable.
.if make(level_2)
.unexport-env
.endif

View File

@ -1,3 +1,8 @@
make: "varname-dot-newline.mk" line 28: Cannot overwrite ".newline" as it is read-only
make: "varname-dot-newline.mk" line 30: Cannot append to ".newline" as it is read-only
make: "varname-dot-newline.mk" line 32: Cannot delete ".newline" as it is read-only
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
first
second
backslash newline: <\

View File

@ -1,4 +1,4 @@
# $NetBSD: varname-dot-newline.mk,v 1.6 2023/01/26 20:48:18 sjg Exp $
# $NetBSD: varname-dot-newline.mk,v 1.7 2024/06/15 22:06:31 rillig Exp $
#
# Tests for the special .newline variable, which contains a single newline
# character (U+000A).
@ -20,12 +20,23 @@ BACKSLASH_NEWLINE:= \${.newline}
NEWLINE:= ${.newline}
.if make(try-to-modify)
# A '?=' assignment is fine. This pattern can be used to provide the variable
# to older or other variants of make that don't know that variable.
.newline?= fallback
# expect+1: Cannot overwrite ".newline" as it is read-only
.newline= overwritten
# expect+1: Cannot append to ".newline" as it is read-only
.newline+= appended
# expect+1: Cannot delete ".newline" as it is read-only
.undef .newline
.endif
.if ${.newline} != ${NEWLINE}
. error The .newline variable can be overwritten. It should be read-only.
.endif
all:
@${MAKE} -f ${MAKEFILE} try-to-modify || true
@echo 'first${.newline}second'
@echo 'backslash newline: <${BACKSLASH_NEWLINE}>'

View File

@ -1 +1,2 @@
: purge-cache was reached.
exit status 0

View File

@ -1,8 +1,15 @@
# $NetBSD: varname-dot-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: varname-dot-objdir.mk,v 1.3 2024/06/01 11:06:17 rillig Exp $
#
# Tests for the special .OBJDIR variable.
# TODO: Implementation
all:
@:;
# Add an entry to the cached_realpath table, to test cleaning up
# that table in purge_relative_cached_realpaths.
# Having a ':=' assignment in the command line is construed but works
# well enough to reach the code.
@${MAKE} -f ${MAKEFILE} 'VAR:=$${:U.:tA}' purge-cache
purge-cache:
: ${.TARGET} was reached.

View File

@ -1,4 +1,4 @@
# $NetBSD: varparse-errors.mk,v 1.12 2024/04/20 10:18:56 rillig Exp $
# $NetBSD: varparse-errors.mk,v 1.13 2024/06/02 15:31:26 rillig Exp $
# Tests for parsing and evaluating all kinds of expressions.
#
@ -24,7 +24,7 @@ ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier.
ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}.
# In a conditional, an expression that is not enclosed in quotes is
# expanded using the mode VARE_UNDEFERR.
# expanded using the mode VARE_EVAL_DEFINED.
# The variable itself must be defined.
# It may refer to undefined variables though.
.if ${REF_UNDEF} != "A reference to an undefined variable."

298
var.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: var.c,v 1.1109 2024/05/07 18:26:22 sjg Exp $ */
/* $NetBSD: var.c,v 1.1121 2024/06/15 22:06:30 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -143,7 +143,7 @@
#include "metachar.h"
/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: var.c,v 1.1109 2024/05/07 18:26:22 sjg Exp $");
MAKE_RCSID("$NetBSD: var.c,v 1.1121 2024/06/15 22:06:30 rillig Exp $");
/*
* Variables are defined using one of the VAR=value assignments. Their
@ -202,6 +202,12 @@ typedef struct Var {
*/
bool readOnly:1;
/*
* The variable is read-only and immune to the .NOREADONLY special
* target. Any attempt to modify it results in an error.
*/
bool readOnlyLoud:1;
/*
* The variable is currently being accessed by Var_Parse or Var_Subst.
* This temporary marker is used to avoid endless recursion.
@ -264,10 +270,15 @@ typedef struct SepBuf {
char sep;
} SepBuf;
typedef enum {
VSK_TARGET,
VSK_VARNAME,
VSK_EXPR
} EvalStackElementKind;
typedef struct {
const char *target;
const char *varname;
const char *expr;
EvalStackElementKind kind;
const char *str;
} EvalStackElement;
typedef struct {
@ -289,11 +300,11 @@ char var_Error[] = "";
/*
* Special return value for Var_Parse, indicating an undefined variable in
* a case where VARE_UNDEFERR is not set. This undefined variable is
* a case where VARE_EVAL_DEFINED 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.
* See VARE_EVAL_KEEP_UNDEFINED.
*/
static char varUndefined[] = "";
@ -335,11 +346,10 @@ GNode *SCOPE_INTERNAL;
static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE;
static const char VarEvalMode_Name[][32] = {
"parse-only",
"parse",
"parse-balanced",
"eval",
"eval-defined",
"eval-keep-dollar",
"eval-keep-undefined",
"eval-keep-dollar-and-undefined",
};
@ -347,21 +357,20 @@ static const char VarEvalMode_Name[][32] = {
static EvalStack evalStack;
void
EvalStack_Push(const char *target, const char *expr, const char *varname)
static void
EvalStack_Push(EvalStackElementKind kind, const char *str)
{
if (evalStack.len >= evalStack.cap) {
evalStack.cap = 16 + 2 * evalStack.cap;
evalStack.elems = bmake_realloc(evalStack.elems,
evalStack.cap * sizeof(*evalStack.elems));
}
evalStack.elems[evalStack.len].target = target;
evalStack.elems[evalStack.len].expr = expr;
evalStack.elems[evalStack.len].varname = varname;
evalStack.elems[evalStack.len].kind = kind;
evalStack.elems[evalStack.len].str = str;
evalStack.len++;
}
void
static void
EvalStack_Pop(void)
{
assert(evalStack.len > 0);
@ -378,21 +387,12 @@ EvalStack_Details(void)
buf->len = 0;
for (i = 0; i < evalStack.len; i++) {
EvalStackElement *elem = evalStack.elems + i;
if (elem->target != NULL) {
Buf_AddStr(buf, "in target \"");
Buf_AddStr(buf, elem->target);
Buf_AddStr(buf, "\": ");
}
if (elem->expr != NULL) {
Buf_AddStr(buf, "while evaluating \"");
Buf_AddStr(buf, elem->expr);
Buf_AddStr(buf, "\": ");
}
if (elem->varname != NULL) {
Buf_AddStr(buf, "while evaluating variable \"");
Buf_AddStr(buf, elem->varname);
Buf_AddStr(buf, "\": ");
}
Buf_AddStr(buf,
elem->kind == VSK_TARGET ? "in target \"" :
elem->kind == VSK_EXPR ? "while evaluating \"" :
"while evaluating variable \"");
Buf_AddStr(buf, elem->str);
Buf_AddStr(buf, "\": ");
}
return buf->len > 0 ? buf->data : "";
}
@ -410,6 +410,7 @@ VarNew(FStr name, const char *value,
var->shortLived = shortLived;
var->fromEnvironment = fromEnvironment;
var->readOnly = readOnly;
var->readOnlyLoud = false;
var->inUse = false;
var->exported = false;
var->reexport = false;
@ -569,6 +570,12 @@ Var_Delete(GNode *scope, const char *varname)
}
v = he->value;
if (v->readOnlyLoud) {
Parse_Error(PARSE_FATAL,
"Cannot delete \"%s\" as it is read-only",
v->name.str);
return;
}
if (v->readOnly) {
DEBUG2(VAR, "%s: ignoring delete '%s' as it is read-only\n",
scope->name, varname);
@ -593,6 +600,20 @@ Var_Delete(GNode *scope, const char *varname)
free(v);
}
#ifdef CLEANUP
void
Var_DeleteAll(GNode *scope)
{
HashIter hi;
HashIter_Init(&hi, &scope->vars);
while (HashIter_Next(&hi)) {
Var *v = hi.entry->value;
Buf_Done(&v->val);
free(v);
}
}
#endif
/*
* Undefine one or more variables from the global scope.
* The argument is expanded exactly once and then split into words.
@ -610,7 +631,7 @@ Var_Undef(const char *arg)
return;
}
expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_WANTRES);
expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_EVAL);
if (expanded == var_Error) {
/* TODO: Make this part of the code reachable. */
Parse_Error(PARSE_FATAL,
@ -677,7 +698,7 @@ ExportVarEnv(Var *v, GNode *scope)
/* XXX: name is injected without escaping it */
expr = str_concat3("${", name, "}");
val = Var_Subst(expr, scope, VARE_WANTRES);
val = Var_Subst(expr, scope, VARE_EVAL);
if (scope != SCOPE_GLOBAL) {
/* we will need to re-export the global version */
v = VarFind(name, SCOPE_GLOBAL, false);
@ -778,7 +799,7 @@ Var_ReexportVars(GNode *scope)
/* Ouch! Exporting all variables at once is crazy. */
HashIter_Init(&hi, &SCOPE_GLOBAL->vars);
while (HashIter_Next(&hi) != NULL) {
while (HashIter_Next(&hi)) {
Var *var = hi.entry->value;
ExportVar(var->name.str, scope, VEM_ENV);
}
@ -786,7 +807,7 @@ Var_ReexportVars(GNode *scope)
}
xvarnames = Var_Subst("${.MAKE.EXPORTED:O:u}", SCOPE_GLOBAL,
VARE_WANTRES);
VARE_EVAL);
/* TODO: handle errors */
if (xvarnames[0] != '\0') {
Words varnames = Str_Words(xvarnames, false);
@ -826,7 +847,7 @@ ExportVars(const char *varnames, bool isExport, VarExportMode mode)
static void
ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode)
{
char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_WANTRES);
char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
ExportVars(xvarnames, isExport, mode);
free(xvarnames);
@ -836,9 +857,12 @@ ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode)
void
Var_Export(VarExportMode mode, const char *varnames)
{
if (mode == VEM_PLAIN && varnames[0] == '\0') {
if (mode == VEM_ALL) {
var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
return;
} else if (mode == VEM_PLAIN && varnames[0] == '\0') {
Parse_Error(PARSE_WARNING, ".export requires an argument.");
return;
}
ExportVarsExpand(varnames, true, mode);
@ -901,7 +925,7 @@ GetVarnamesToUnexport(bool isEnv, const char *arg,
if (what != UNEXPORT_NAMED) {
char *expanded = Var_Subst("${.MAKE.EXPORTED:O:u}",
SCOPE_GLOBAL, VARE_WANTRES);
SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
varnames = FStr_InitOwn(expanded);
}
@ -932,7 +956,7 @@ UnexportVar(Substring varname, UnexportWhat what)
/* XXX: v->name is injected without escaping it */
char *expr = str_concat3(
"${.MAKE.EXPORTED:N", v->name.str, "}");
char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES);
char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */
Global_Set(".MAKE.EXPORTED", filtered);
free(filtered);
@ -1025,6 +1049,12 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
}
v = VarAdd(name, val, scope, flags);
} else {
if (v->readOnlyLoud) {
Parse_Error(PARSE_FATAL,
"Cannot overwrite \"%s\" as it is read-only",
name);
return;
}
if (v->readOnly && !(flags & VAR_SET_READONLY)) {
DEBUG3(VAR,
"%s: ignoring '%s = %s' as it is read-only\n",
@ -1089,7 +1119,7 @@ Var_SetExpand(GNode *scope, const char *name, const char *val)
assert(val != NULL);
Var_Expand(&varname, scope, VARE_WANTRES);
Var_Expand(&varname, scope, VARE_EVAL);
if (varname.str[0] == '\0') {
DEBUG4(VAR,
@ -1117,7 +1147,8 @@ Global_Delete(const char *name)
void
Global_Set_ReadOnly(const char *name, const char *value)
{
Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_READONLY);
Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_NONE);
VarFind(name, SCOPE_GLOBAL, false)->readOnlyLoud = true;
}
/*
@ -1135,6 +1166,10 @@ Var_Append(GNode *scope, const char *name, const char *val)
if (v == NULL) {
Var_SetWithFlags(scope, name, val, VAR_SET_NONE);
} else if (v->readOnlyLoud) {
Parse_Error(PARSE_FATAL,
"Cannot append to \"%s\" as it is read-only", name);
return;
} else if (v->readOnly) {
DEBUG3(VAR, "%s: ignoring '%s += %s' as it is read-only\n",
scope->name, name, val);
@ -1172,7 +1207,7 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val)
assert(val != NULL);
Var_Expand(&xname, scope, VARE_WANTRES);
Var_Expand(&xname, scope, VARE_EVAL);
if (xname.str != name && xname.str[0] == '\0')
DEBUG4(VAR,
"%s: ignoring '%s += %s' "
@ -1215,7 +1250,7 @@ Var_ExistsExpand(GNode *scope, const char *name)
FStr varname = FStr_InitRefer(name);
bool exists;
Var_Expand(&varname, scope, VARE_WANTRES);
Var_Expand(&varname, scope, VARE_EVAL);
exists = Var_Exists(scope, varname.str);
FStr_Done(&varname);
return exists;
@ -1281,37 +1316,33 @@ GNode_ValueDirect(GNode *gn, const char *name)
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;
return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED
? VARE_EVAL_KEEP_UNDEFINED : emode;
}
static VarEvalMode
VarEvalMode_UndefOk(VarEvalMode emode)
{
return emode == VARE_UNDEFERR ? VARE_WANTRES : emode;
return emode == VARE_EVAL_DEFINED ? VARE_EVAL : emode;
}
static bool
VarEvalMode_ShouldEval(VarEvalMode emode)
{
return emode != VARE_PARSE_ONLY;
return emode != VARE_PARSE;
}
static bool
VarEvalMode_ShouldKeepUndef(VarEvalMode emode)
{
return emode == VARE_EVAL_KEEP_UNDEF ||
emode == VARE_KEEP_DOLLAR_UNDEF;
return emode == VARE_EVAL_KEEP_UNDEFINED ||
emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED;
}
static bool
VarEvalMode_ShouldKeepDollar(VarEvalMode emode)
{
return emode == VARE_EVAL_KEEP_DOLLAR ||
emode == VARE_KEEP_DOLLAR_UNDEF;
return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED;
}
@ -1440,7 +1471,7 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data)
}
rhs = FStr_InitRefer(args->rhs);
Var_Expand(&rhs, args->scope, VARE_WANTRES);
Var_Expand(&rhs, args->scope, VARE_EVAL);
percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL;
@ -2084,7 +2115,7 @@ static bool
IsEscapedModifierPart(const char *p, char delim,
struct ModifyWord_SubstArgs *subst)
{
if (p[0] != '\\')
if (p[0] != '\\' || p[1] == '\0')
return false;
if (p[1] == delim || p[1] == '\\' || p[1] == '$')
return true;
@ -2143,13 +2174,23 @@ ParseModifierPartBalanced(const char **pp, LazyBuf *part)
}
}
/* See ParseModifierPart for the documentation. */
/*
* Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or
* the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and
* including the next unescaped delimiter. The delimiter, as well as the
* backslash or the dollar, can be escaped with a backslash.
*
* Return true 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 bool
ParseModifierPartSubst(
ParseModifierPart(
/* The parsing position, updated upon return */
const char **pp,
/* If true, parse up to but excluding the next ':' or ch->endc. */
bool whole,
char delim,
char end1,
char end2,
/* Mode for evaluating nested expressions. */
VarEvalMode emode,
ModChain *ch,
LazyBuf *part,
@ -2165,16 +2206,11 @@ ParseModifierPartSubst(
struct ModifyWord_SubstArgs *subst
)
{
const char *p;
char end1, end2;
const char *p = *pp;
p = *pp;
LazyBuf_Init(part, p);
end1 = whole ? ':' : delim;
end2 = whole ? ch->endc : delim;
while (*p != '\0' && *p != end1 && *p != end2) {
if (IsEscapedModifierPart(p, delim, subst)) {
if (IsEscapedModifierPart(p, end2, subst)) {
LazyBuf_Add(part, p[1]);
p += 2;
} else if (*p != '$') { /* Unescaped, simple text */
@ -2183,7 +2219,7 @@ ParseModifierPartSubst(
else
LazyBuf_Add(part, *p);
p++;
} else if (p[1] == delim) { /* Unescaped '$' at end */
} else if (p[1] == end2) { /* Unescaped '$' at end */
if (out_pflags != NULL)
out_pflags->anchorEnd = true;
else
@ -2202,7 +2238,7 @@ ParseModifierPartSubst(
LazyBuf_Done(part);
return false;
}
if (!whole)
if (end1 == end2)
(*pp)++;
{
@ -2214,32 +2250,6 @@ ParseModifierPartSubst(
return true;
}
/*
* Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or
* the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and
* including the next unescaped delimiter. The delimiter, as well as the
* backslash or the dollar, can be escaped with a backslash.
*
* Return true 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 bool
ParseModifierPart(
/* The parsing position, updated upon return */
const char **pp,
/* Parsing stops at this delimiter */
char delim,
/* Mode for evaluating nested expressions. */
VarEvalMode emode,
ModChain *ch,
LazyBuf *part
)
{
return ParseModifierPartSubst(pp, false, delim, emode, ch, part,
NULL, NULL);
}
MAKE_INLINE bool
IsDelimiter(char c, const ModChain *ch)
{
@ -2384,7 +2394,8 @@ ApplyModifier_Loop(const char **pp, ModChain *ch)
args.scope = expr->scope;
(*pp)++; /* Skip the first '@' */
if (!ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf))
if (!ParseModifierPart(pp, '@', '@', VARE_PARSE,
ch, &tvarBuf, NULL, NULL))
return AMR_CLEANUP;
tvar = LazyBuf_DoneGet(&tvarBuf);
args.var = tvar.str;
@ -2393,11 +2404,12 @@ ApplyModifier_Loop(const char **pp, ModChain *ch)
"In the :@ modifier, the variable name \"%s\" "
"must not contain a dollar",
args.var);
return AMR_CLEANUP;
goto cleanup_tvar;
}
if (!ParseModifierPart(pp, '@', VARE_PARSE_BALANCED, ch, &strBuf))
return AMR_CLEANUP;
if (!ParseModifierPart(pp, '@', '@', VARE_PARSE_BALANCED,
ch, &strBuf, NULL, NULL))
goto cleanup_tvar;
str = LazyBuf_DoneGet(&strBuf);
args.body = str.str;
@ -2416,6 +2428,10 @@ done:
FStr_Done(&tvar);
FStr_Done(&str);
return AMR_OK;
cleanup_tvar:
FStr_Done(&tvar);
return AMR_CLEANUP;
}
static void
@ -2448,7 +2464,7 @@ ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval,
if (*p == '$') {
FStr val = Var_Parse(&p, ch->expr->scope,
shouldEval ? ch->expr->emode : VARE_PARSE_ONLY);
shouldEval ? ch->expr->emode : VARE_PARSE);
/* TODO: handle errors */
if (shouldEval)
LazyBuf_AddStr(buf, val.str);
@ -2478,6 +2494,7 @@ ApplyModifier_Defined(const char **pp, ModChain *ch)
Expr_Define(expr);
if (shouldEval)
Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf)));
LazyBuf_Done(&buf);
return AMR_OK;
}
@ -2535,7 +2552,7 @@ ApplyModifier_Time(const char **pp, ModChain *ch)
const char *p = args + 1;
LazyBuf buf;
FStr arg;
if (!ParseModifierPartSubst(&p, true, '\0', ch->expr->emode,
if (!ParseModifierPart(&p, ':', ch->endc, ch->expr->emode,
ch, &buf, NULL, NULL))
return AMR_CLEANUP;
arg = LazyBuf_DoneGet(&buf);
@ -2617,7 +2634,8 @@ ApplyModifier_ShellCommand(const char **pp, ModChain *ch)
FStr cmd;
(*pp)++;
if (!ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf))
if (!ParseModifierPart(pp, '!', '!', expr->emode,
ch, &cmdBuf, NULL, NULL))
return AMR_CLEANUP;
cmd = LazyBuf_DoneGet(&cmdBuf);
@ -2934,13 +2952,13 @@ ApplyModifier_Subst(const char **pp, ModChain *ch)
(*pp)++;
}
if (!ParseModifierPartSubst(pp,
false, delim, ch->expr->emode, ch, &lhsBuf, &args.pflags, NULL))
if (!ParseModifierPart(pp, delim, delim, ch->expr->emode,
ch, &lhsBuf, &args.pflags, NULL))
return AMR_CLEANUP;
args.lhs = LazyBuf_Get(&lhsBuf);
if (!ParseModifierPartSubst(pp,
false, delim, ch->expr->emode, ch, &rhsBuf, NULL, &args)) {
if (!ParseModifierPart(pp, delim, delim, ch->expr->emode,
ch, &rhsBuf, NULL, &args)) {
LazyBuf_Done(&lhsBuf);
return AMR_CLEANUP;
}
@ -2977,11 +2995,13 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
*pp += 2;
if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf))
if (!ParseModifierPart(pp, delim, delim, ch->expr->emode,
ch, &reBuf, NULL, NULL))
return AMR_CLEANUP;
re = LazyBuf_DoneGet(&reBuf);
if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf)) {
if (!ParseModifierPart(pp, delim, delim, ch->expr->emode,
ch, &replaceBuf, NULL, NULL)) {
FStr_Done(&re);
return AMR_CLEANUP;
}
@ -3058,7 +3078,7 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch)
/*
* Even in parse-only mode, apply the side effects, since the side
* effects are neither observable nor is there a performance penalty.
* Checking for wantRes for every single piece of code in here
* Checking for VARE_EVAL for every single piece of code in here
* would make the code in this function too hard to read.
*/
@ -3210,7 +3230,8 @@ ApplyModifier_Words(const char **pp, ModChain *ch)
FStr arg;
(*pp)++; /* skip the '[' */
if (!ParseModifierPart(pp, ']', expr->emode, ch, &argBuf))
if (!ParseModifierPart(pp, ']', ']', expr->emode,
ch, &argBuf, NULL, NULL))
return AMR_CLEANUP;
arg = LazyBuf_DoneGet(&argBuf);
p = arg.str;
@ -3434,8 +3455,8 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch)
LazyBuf thenBuf;
LazyBuf elseBuf;
VarEvalMode then_emode = VARE_PARSE_ONLY;
VarEvalMode else_emode = VARE_PARSE_ONLY;
VarEvalMode then_emode = VARE_PARSE;
VarEvalMode else_emode = VARE_PARSE;
CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */
if (Expr_ShouldEval(expr)) {
@ -3447,10 +3468,12 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch)
}
(*pp)++; /* skip past the '?' */
if (!ParseModifierPart(pp, ':', then_emode, ch, &thenBuf))
if (!ParseModifierPart(pp, ':', ':', then_emode,
ch, &thenBuf, NULL, NULL))
return AMR_CLEANUP;
if (!ParseModifierPart(pp, ch->endc, else_emode, ch, &elseBuf)) {
if (!ParseModifierPart(pp, ch->endc, ch->endc, else_emode,
ch, &elseBuf, NULL, NULL)) {
LazyBuf_Done(&thenBuf);
return AMR_CLEANUP;
}
@ -3530,7 +3553,8 @@ found_op:
*pp = mod + (op[0] != '=' ? 3 : 2);
if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf))
if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode,
ch, &buf, NULL, NULL))
return AMR_CLEANUP;
val = LazyBuf_DoneGet(&buf);
@ -3689,13 +3713,12 @@ ApplyModifier_SysV(const char **pp, ModChain *ch)
if (!IsSysVModifier(mod, ch->startc, ch->endc))
return AMR_UNKNOWN;
if (!ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf))
if (!ParseModifierPart(pp, '=', '=', expr->emode,
ch, &lhsBuf, NULL, NULL))
return AMR_CLEANUP;
/*
* The SysV modifier lasts until the end of the expression.
*/
if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf)) {
if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode,
ch, &rhsBuf, NULL, NULL)) {
LazyBuf_Done(&lhsBuf);
return AMR_CLEANUP;
}
@ -3755,9 +3778,8 @@ ApplyModifier_SunShell(const char **pp, ModChain *ch)
static bool
ShouldLogInSimpleFormat(const Expr *expr)
{
return (expr->emode == VARE_WANTRES ||
expr->emode == VARE_UNDEFERR) &&
expr->defined == DEF_REGULAR;
return (expr->emode == VARE_EVAL || expr->emode == VARE_EVAL_DEFINED)
&& expr->defined == DEF_REGULAR;
}
static void
@ -4245,7 +4267,7 @@ ParseVarnameShort(char varname, const char **pp, GNode *scope,
val = UndefinedShortVarValue(varname, scope);
if (val == NULL)
val = emode == VARE_UNDEFERR ? var_Error : varUndefined;
val = emode == VARE_EVAL_DEFINED ? var_Error : varUndefined;
if (opts.strict && val == var_Error) {
Parse_Error(PARSE_FATAL,
@ -4290,7 +4312,7 @@ EvalUndefined(bool dynamic, const char *start, const char *p,
if (dynamic)
return FStr_InitOwn(bmake_strsedup(start, p));
if (emode == VARE_UNDEFERR && opts.strict) {
if (emode == VARE_EVAL_DEFINED && opts.strict) {
Parse_Error(PARSE_FATAL,
"Variable \"%.*s\" is undefined",
(int)Substring_Length(varname), varname.start);
@ -4298,7 +4320,7 @@ EvalUndefined(bool dynamic, const char *start, const char *p,
}
return FStr_InitRefer(
emode == VARE_UNDEFERR ? var_Error : varUndefined);
emode == VARE_EVAL_DEFINED ? var_Error : varUndefined);
}
/*
@ -4451,7 +4473,7 @@ Var_Parse_U(const char **pp, VarEvalMode emode, FStr *out_value)
if (*p != '}')
return false;
*out_value = emode == VARE_PARSE_ONLY
*out_value = emode == VARE_PARSE
? FStr_InitRefer("")
: FStr_InitOwn(bmake_strsedup(*pp + 4, p));
*pp = p + 1;
@ -4480,13 +4502,13 @@ Var_Parse_U(const char **pp, VarEvalMode emode, FStr *out_value)
* return The value of the expression, never NULL.
* return var_Error if there was a parse error.
* return var_Error if the base variable of the expression was
* undefined, emode is VARE_UNDEFERR, and none of
* undefined, emode is VARE_EVAL_DEFINED, 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.
* return varUndefined if the base variable of the expression
* was undefined, emode was not VARE_UNDEFERR,
* was undefined, emode was not VARE_EVAL_DEFINED,
* and none of the modifiers turned the undefined
* expression into a defined expression.
* XXX: It is not guaranteed that an error message has
@ -4569,9 +4591,9 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode)
expr.value = FStr_InitRefer(v->val.data);
if (expr.name[0] != '\0')
EvalStack_Push(NULL, NULL, expr.name);
EvalStack_Push(VSK_VARNAME, expr.name);
else
EvalStack_Push(NULL, start, NULL);
EvalStack_Push(VSK_EXPR, start);
/*
* Before applying any modifiers, expand any nested expressions from
@ -4615,7 +4637,7 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode)
* instead.
*/
Expr_SetValueRefer(&expr,
emode == VARE_UNDEFERR
emode == VARE_EVAL_DEFINED
? var_Error : varUndefined);
}
}
@ -4749,6 +4771,16 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode)
return Buf_DoneData(&res);
}
char *
Var_SubstInTarget(const char *str, GNode *scope)
{
char *res;
EvalStack_Push(VSK_TARGET, scope->name);
res = Var_Subst(str, scope, VARE_EVAL);
EvalStack_Pop();
return res;
}
void
Var_Expand(FStr *str, GNode *scope, VarEvalMode emode)
{
@ -4804,7 +4836,7 @@ Var_Dump(GNode *scope)
Vector_Init(&vec, sizeof(const char *));
HashIter_Init(&hi, &scope->vars);
while (HashIter_Next(&hi) != NULL)
while (HashIter_Next(&hi))
*(const char **)Vector_Push(&vec) = hi.entry->key;
varnames = vec.items;