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> 2024-05-20 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): * VERSION (_MAKE_VERSION):

View File

@ -1,2 +1,2 @@
# keep this compatible with sh and make # 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -147,7 +147,7 @@ struct ar_hdr {
#include "dir.h" #include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)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 List ArchList;
typedef struct ListNode ArchListNode; typedef struct ListNode ArchListNode;
@ -204,7 +204,7 @@ ArchFree(Arch *a)
HashIter hi; HashIter hi;
HashIter_Init(&hi, &a->members); HashIter_Init(&hi, &a->members);
while (HashIter_Next(&hi) != NULL) while (HashIter_Next(&hi))
free(hi.entry->value); free(hi.entry->value);
free(a->name); free(a->name);
@ -257,7 +257,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
bool isError; bool isError;
/* XXX: is expanded twice: once here and once below */ /* 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 */ /* TODO: handle errors */
isError = result.str == var_Error; isError = result.str == var_Error;
FStr_Done(&result); FStr_Done(&result);
@ -272,7 +273,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
spec[cp++ - spec] = '\0'; spec[cp++ - spec] = '\0';
if (expandLib) if (expandLib)
Var_Expand(&lib, scope, VARE_UNDEFERR); Var_Expand(&lib, scope, VARE_EVAL_DEFINED);
for (;;) { for (;;) {
/* /*
@ -296,7 +297,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
const char *nested_p = cp; const char *nested_p = cp;
result = Var_Parse(&nested_p, scope, result = Var_Parse(&nested_p, scope,
VARE_UNDEFERR); VARE_EVAL_DEFINED);
/* TODO: handle errors */ /* TODO: handle errors */
isError = result.str == var_Error; isError = result.str == var_Error;
FStr_Done(&result); FStr_Done(&result);
@ -341,7 +342,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
char *p; char *p;
const char *unexpandedMem = mem.str; 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 * 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 .\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved. .\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\" .\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\" .\"
.Dd March 9, 2024 .Dd June 1, 2024
.Dt BMAKE 1 .Dt BMAKE 1
.Os .Os
.Sh NAME .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 Used to create files in a separate directory, see
.Va .OBJDIR . .Va .OBJDIR .
.It Va MAKE_OBJDIR_CHECK_WRITABLE .It Va MAKE_OBJDIR_CHECK_WRITABLE
Used to force a separate directory for the created files, When true,
even if that directory is not writable, see .Nm
.Va .OBJDIR . 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 .It Va MAKEOBJDIRPREFIX
Used to create files in a separate directory, see Used to create files in a separate directory, see
.Va .OBJDIR . .Va .OBJDIR .
@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Ic .export Ar variable No ... .It Ic .export Ar variable No ...
Export the specified global variable. Export the specified global variable.
If no variable list is provided, all globals are exported .Pp
except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
For compatibility with other make programs, For compatibility with other make programs,
.Cm export Ar variable\| Ns Cm \&= Ns Ar value .Cm export Ar variable\| Ns Cm \&= Ns Ar value
(without leading dot) is also accepted. (without leading dot) is also accepted.
@ -1964,6 +1969,12 @@ For compatibility with other make programs,
Appending a variable name to Appending a variable name to
.Va .MAKE.EXPORTED .Va .MAKE.EXPORTED
is equivalent to exporting a variable. 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 ... .It Ic .export-env Ar variable No ...
The same as The same as
.Ql .export , .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. 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 _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 When true, bbmmaakkee will check that _._O_B_J_D_I_R is writable, and issue a
that directory is not writable, see _._O_B_J_D_I_R. 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 _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. 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: The directives for exporting and unexporting variables are:
..eexxppoorrtt _v_a_r_i_a_b_l_e ... ..eexxppoorrtt _v_a_r_i_a_b_l_e ...
Export the specified global variable. If no variable list is Export the specified global variable.
provided, all globals are exported except for internal variables
(those that start with `.'). This is not affected by the --XX For compatibility with other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e
flag, so should be used with caution. For compatibility with (without leading dot) is also accepted.
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 Appending a variable name to _._M_A_K_E_._E_X_P_O_R_T_E_D is equivalent to
exporting a variable. 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 ... ..eexxppoorrtt--eennvv _v_a_r_i_a_b_l_e ...
The same as `.export', except that the variable is not appended 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 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 attempt to suppress a cascade of unnecessary errors, can result in a
seemingly unexplained `*** Error code 6' 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. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -94,7 +94,7 @@
#include "pathnames.h" #include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)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 GNode *curTarg = NULL;
static pid_t compatChild; static pid_t compatChild;
@ -203,6 +203,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED)
#endif #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, * 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. * 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 */ volatile bool errCheck; /* Check errors */
WAIT_T reason; /* Reason for child's death */ WAIT_T reason; /* Reason for child's death */
WAIT_T status; /* Description of child's death */ WAIT_T status; /* Description of child's death */
pid_t cpid; /* Child actually found */
pid_t retstat; /* Result of wait */ 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 */ char **volatile mav; /* Copy of the argument vector for freeing */
bool useShell; /* True if command should be executed using a bool useShell; /* True if command should be executed using a
* shell */ * shell */
const char *volatile cmd = cmdp; const char *cmd = cmdp;
silent = (gn->type & OP_SILENT) != OP_NONE; silent = (gn->type & OP_SILENT) != OP_NONE;
errCheck = !(gn->type & OP_IGNORE); errCheck = !(gn->type & OP_IGNORE);
doIt = false; doIt = false;
EvalStack_Push(gn->name, NULL, NULL); cmdStart = Var_SubstInTarget(cmd, gn);
cmdStart = Var_Subst(cmd, gn, VARE_WANTRES);
EvalStack_Pop();
/* TODO: handle errors */ /* TODO: handle errors */
if (cmdStart[0] == '\0') { if (cmdStart[0] == '\0') {
@ -264,11 +279,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* usual '$$'. * usual '$$'.
*/ */
Lst_Append(&endNode->commands, cmdStart); Lst_Append(&endNode->commands, cmdStart);
return true; goto register_command;
} }
} }
if (strcmp(cmdStart, "...") == 0) { if (strcmp(cmdStart, "...") == 0) {
gn->type |= OP_SAVE_CMDS; gn->type |= OP_SAVE_CMDS;
register_command:
Parse_RegisterCommand(cmdStart);
return true; return true;
} }
@ -288,7 +305,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
while (ch_isspace(*cmd)) while (ch_isspace(*cmd))
cmd++; cmd++;
if (cmd[0] == '\0') if (cmd[0] == '\0')
return true; goto register_command;
useShell = UseShell(cmd); useShell = UseShell(cmd);
@ -298,7 +315,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
} }
if (!doIt && !GNode_ShouldExecute(gn)) if (!doIt && !GNode_ShouldExecute(gn))
return true; goto register_command;
DEBUG1(JOB, "Execute: '%s'\n", cmd); DEBUG1(JOB, "Execute: '%s'\n", cmd);
@ -333,19 +350,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
Var_ReexportVars(gn); Var_ReexportVars(gn);
compatChild = cpid = vfork(); compatChild = Compat_Spawn(av);
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]);
}
free(mav); free(mav);
free(bp); free(bp);
@ -355,11 +360,11 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
#ifdef USE_META #ifdef USE_META
if (useMeta) if (useMeta)
meta_compat_parent(cpid); meta_compat_parent(compatChild);
#endif #endif
/* The child is off and running. Now all we can do is wait... */ /* 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) if (retstat > 0)
JobReapChild(retstat, reason, false); /* not ours? */ JobReapChild(retstat, reason, false); /* not ours? */
if (retstat == -1 && errno != EINTR) 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. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -91,7 +91,7 @@
#include "dir.h" #include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)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: * Conditional expressions conform to this grammar:
@ -222,8 +222,8 @@ ParseWord(const char **pp, bool doEval)
break; break;
if (ch == '$') { if (ch == '$') {
VarEvalMode emode = doEval VarEvalMode emode = doEval
? VARE_UNDEFERR ? VARE_EVAL_DEFINED
: VARE_PARSE_ONLY; : VARE_PARSE;
/* /*
* TODO: make Var_Parse complain about undefined * TODO: make Var_Parse complain about undefined
* variables. * variables.
@ -396,9 +396,9 @@ CondParser_StringExpr(CondParser *par, const char *start,
const char *p; const char *p;
bool atStart; /* true means an expression outside quotes */ bool atStart; /* true means an expression outside quotes */
emode = doEval && quoted ? VARE_WANTRES emode = doEval && quoted ? VARE_EVAL
: doEval ? VARE_UNDEFERR : doEval ? VARE_EVAL_DEFINED
: VARE_PARSE_ONLY; : VARE_PARSE;
p = par->p; p = par->p;
atStart = p == start; atStart = p == start;
@ -651,8 +651,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
return false; return false;
p--; /* Make p[1] point to the '('. */ p--; /* Make p[1] point to the '('. */
val = Var_Parse(&p, SCOPE_CMDLINE, val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE);
doEval ? VARE_WANTRES : VARE_PARSE_ONLY);
/* TODO: handle errors */ /* TODO: handle errors */
if (val.str == var_Error) if (val.str == var_Error)
@ -736,8 +735,10 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
arg = ParseWord(&p, doEval); arg = ParseWord(&p, doEval);
assert(arg[0] != '\0'); assert(arg[0] != '\0');
if (*p == '=' || *p == '!' || *p == '<' || *p == '>') if (*p == '=' || *p == '!' || *p == '<' || *p == '>') {
free(arg);
return CondParser_Comparison(par, doEval); return CondParser_Comparison(par, doEval);
}
par->p = p; 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. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -132,7 +132,7 @@
#include "job.h" #include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)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 * 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 */ 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. */ /* Clean up the directories module. */
void void
Dir_End(void) Dir_End(void)
@ -511,8 +523,8 @@ Dir_End(void)
CachedDir_Assign(&dotLast, NULL); CachedDir_Assign(&dotLast, NULL);
SearchPath_Clear(&dirSearchPath); SearchPath_Clear(&dirSearchPath);
OpenDirs_Done(&openDirs); OpenDirs_Done(&openDirs);
HashTable_Done(&mtimes); FreeCachedTable(&mtimes);
HashTable_Done(&lmtimes); FreeCachedTable(&lmtimes);
#endif #endif
} }
@ -568,7 +580,7 @@ Dir_SetSYSPATH(void)
CachedDirListNode *ln; CachedDirListNode *ln;
SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs)
? defSysIncPath : sysIncPath; ? defSysIncPath : sysIncPath;
Var_ReadOnly(".SYSPATH", false); Var_ReadOnly(".SYSPATH", false);
Global_Delete(".SYSPATH"); Global_Delete(".SYSPATH");
for (ln = path->dirs.first; ln != NULL; ln = ln->next) { 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); HashIter_InitSet(&hi, &dir->files);
while (HashIter_Next(&hi) != NULL) { while (HashIter_Next(&hi)) {
const char *base = hi.entry->key; const char *base = hi.entry->key;
StrMatchResult res = Str_Match(base, pattern); StrMatchResult res = Str_Match(base, pattern);
/* TODO: handle errors from res.error */ /* TODO: handle errors from res.error */
@ -864,6 +876,7 @@ SearchPath_ExpandMiddle(SearchPath *path, const char *pattern,
(void)SearchPath_Add(partPath, dirpath); (void)SearchPath_Add(partPath, dirpath);
DirExpandPath(wildcardComponent + 1, partPath, expansions); DirExpandPath(wildcardComponent + 1, partPath, expansions);
SearchPath_Free(partPath); 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. * Copyright (c) 1992, The Regents of the University of California.
@ -58,7 +58,7 @@
#include "make.h" #include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)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 { typedef struct ForLoop {
@ -156,7 +156,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
cpp_skip_whitespace(&p); cpp_skip_whitespace(&p);
if (*p == '\0') { if (*p == '\0') {
Parse_Error(PARSE_FATAL, "missing `in' in for"); Parse_Error(PARSE_FATAL, "missing `in' in for");
f->vars.len = 0; while (f->vars.len > 0)
free(*(char **)Vector_Pop(&f->vars));
return; return;
} }
@ -166,7 +167,8 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
"invalid character '%c' " "invalid character '%c' "
"in .for loop variable name", "in .for loop variable name",
p[len]); p[len]);
f->vars.len = 0; while (f->vars.len > 0)
free(*(char **)Vector_Pop(&f->vars));
return; return;
} }
} }
@ -195,7 +197,7 @@ ForLoop_ParseItems(ForLoop *f, const char *p)
cpp_skip_whitespace(&p); cpp_skip_whitespace(&p);
items = Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES); items = Var_Subst(p, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
f->items = Substring_Words(items, false); f->items = Substring_Words(items, false);
@ -329,23 +331,6 @@ ExprLen(const char *s, const char *e)
return 0; 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...} * While expanding the body of a .for loop, write the item as a ${:U...}
* expression, escaping characters as needed. The result is later unescaped * expression, escaping characters as needed. The result is later unescaped
@ -357,11 +342,6 @@ AddEscaped(Buffer *cmds, Substring item, char endc)
const char *p; const char *p;
char ch; char ch;
if (!NeedsEscapes(item, endc)) {
Buf_AddRange(cmds, item.start, item.end);
return;
}
for (p = item.start; p != item.end;) { for (p = item.start; p != item.end;) {
ch = *p; ch = *p;
if (ch == '$') { 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. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -74,7 +74,7 @@
#include "make.h" #include "make.h"
/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)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 * The ratio of # entries to # buckets at which we rebuild the table to
@ -288,24 +288,19 @@ void
HashTable_DeleteEntry(HashTable *t, HashEntry *he) HashTable_DeleteEntry(HashTable *t, HashEntry *he)
{ {
HashEntry **ref = &t->buckets[he->hash & t->bucketsMask]; HashEntry **ref = &t->buckets[he->hash & t->bucketsMask];
HashEntry *p;
for (; (p = *ref) != NULL; ref = &p->next) { for (; *ref != he; ref = &(*ref)->next)
if (p == he) { continue;
*ref = p->next; *ref = he->next;
free(p); free(he);
t->numEntries--; t->numEntries--;
return;
}
}
abort();
} }
/* /*
* Return the next entry in the hash table, or NULL if the end of the table * Place the next entry from the hash table in hi->entry, or return false if
* is reached. * the end of the table is reached.
*/ */
HashEntry * bool
HashIter_Next(HashIter *hi) HashIter_Next(HashIter *hi)
{ {
HashTable *t = hi->table; HashTable *t = hi->table;
@ -318,11 +313,11 @@ HashIter_Next(HashIter *hi)
while (he == NULL) { /* find the next nonempty chain */ while (he == NULL) { /* find the next nonempty chain */
if (hi->nextBucket >= bucketsSize) if (hi->nextBucket >= bucketsSize)
return NULL; return false;
he = buckets[hi->nextBucket++]; he = buckets[hi->nextBucket++];
} }
hi->entry = he; hi->entry = he;
return he; return true;
} }
void 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. * 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_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *); void HashTable_DebugStats(HashTable *, const char *);
HashEntry *HashIter_Next(HashIter *); bool HashIter_Next(HashIter *) MAKE_ATTR_USE;
MAKE_INLINE void MAKE_INLINE void
HashSet_Init(HashSet *set) 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. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -154,7 +154,7 @@
#include "trace.h" #include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)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 * 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); run = GNode_ShouldExecute(job->node);
EvalStack_Push(job->node->name, NULL, NULL); xcmd = Var_SubstInTarget(ucmd, job->node);
xcmd = Var_Subst(ucmd, job->node, VARE_WANTRES);
EvalStack_Pop();
/* TODO: handle errors */ /* TODO: handle errors */
xcmdStart = xcmd; xcmdStart = xcmd;
@ -1043,11 +1041,10 @@ JobSaveCommands(Job *job)
* variables such as .TARGET, .IMPSRC. It is not intended to * variables such as .TARGET, .IMPSRC. It is not intended to
* expand the other variables as well; see deptgt-end.mk. * expand the other variables as well; see deptgt-end.mk.
*/ */
EvalStack_Push(job->node->name, NULL, NULL); expanded_cmd = Var_SubstInTarget(cmd, job->node);
expanded_cmd = Var_Subst(cmd, job->node, VARE_WANTRES);
EvalStack_Pop();
/* TODO: handle errors */ /* TODO: handle errors */
Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd); 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); debug_printf("\t%s\n", cmd);
if (strchr(cmd, '$') != NULL) { 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); debug_printf("\t=> %s\n", xcmd);
free(xcmd); free(xcmd);
} }
@ -1468,11 +1465,11 @@ JobExec(Job *job, char **argv)
* was marked close-on-exec, we must clear that bit in the * was marked close-on-exec, we must clear that bit in the
* new input. * new input.
*/ */
if (dup2(fileno(job->cmdFILE), 0) == -1) if (dup2(fileno(job->cmdFILE), STDIN_FILENO) == -1)
execDie("dup2", "job->cmdFILE"); 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"); 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"); execDie("lseek to 0", "stdin");
if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { 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 * Set up the child's output to be routed through the pipe
* we've created for it. * we've created for it.
*/ */
if (dup2(job->outPipe, 1) == -1) if (dup2(job->outPipe, STDOUT_FILENO) == -1)
execDie("dup2", "job->outPipe"); execDie("dup2", "job->outPipe");
/* /*
* The output channels are marked close on exec. This bit * 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 * to clear it before routing the shell's error output to
* the same place as its standard output. * 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"); execDie("clear close-on-exec", "stdout");
if (dup2(1, 2) == -1) if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
execDie("dup2", "1, 2"); execDie("dup2", "1, 2");
/* /*
@ -2155,7 +2152,7 @@ InitShellNameAndPath(void)
#ifdef DEFSHELL_CUSTOM #ifdef DEFSHELL_CUSTOM
if (shellName[0] == '/') { if (shellName[0] == '/') {
shellPath = shellName; shellPath = bmake_strdup(shellName);
shellName = str_basename(shellPath); shellName = str_basename(shellPath);
return; return;
} }
@ -2212,7 +2209,7 @@ Job_SetPrefix(void)
Global_Set(".MAKE.JOB.PREFIX", "---"); Global_Set(".MAKE.JOB.PREFIX", "---");
targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}", targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
} }
@ -2502,7 +2499,8 @@ Job_ParseShell(char *line)
} }
} }
} else { } else {
shellPath = path; free(UNCONST(shellPath));
shellPath = bmake_strdup(path);
shellName = newShell.name != NULL ? newShell.name shellName = newShell.name != NULL ? newShell.name
: str_basename(path); : str_basename(path);
if (!fullSpec) { 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -111,7 +111,7 @@
#include "trace.h" #include "trace.h"
/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ /* "@(#)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) #if defined(MAKE_NATIVE)
__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
"The Regents of the University of California. " "The Regents of the University of California. "
@ -372,7 +372,7 @@ MainParseArgChdir(const char *argvalue)
stat(curdir, &sb) != -1 && stat(curdir, &sb) != -1 &&
sa.st_ino == sb.st_ino && sa.st_ino == sb.st_ino &&
sa.st_dev == sb.st_dev) sa.st_dev == sb.st_dev)
strncpy(curdir, argvalue, MAXPATHLEN); snprintf(curdir, MAXPATHLEN, "%s", argvalue);
ignorePWD = true; ignorePWD = true;
} }
@ -743,6 +743,10 @@ Main_SetObjdir(bool writable, const char *fmt, ...)
if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) { if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) {
(void)fprintf(stderr, "%s: warning: %s: %s.\n", (void)fprintf(stderr, "%s: warning: %s: %s.\n",
progname, path, strerror(errno)); 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; return false;
} }
@ -766,7 +770,7 @@ SetVarObjdir(bool writable, const char *var, const char *suffix)
return false; 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); (void)Main_SetObjdir(writable, "%s%s", path.str, suffix);
@ -808,8 +812,7 @@ siginfo(int signo MAKE_ATTR_UNUSED)
static void static void
MakeMode(void) MakeMode(void)
{ {
char *mode = Var_Subst("${.MAKE.MODE:tl}", char *mode = Var_Subst("${.MAKE.MODE:tl}", SCOPE_GLOBAL, VARE_EVAL);
SCOPE_GLOBAL, VARE_WANTRES);
/* TODO: handle errors */ /* TODO: handle errors */
if (mode[0] != '\0') { if (mode[0] != '\0') {
@ -832,14 +835,14 @@ static void
PrintVar(const char *varname, bool expandVars) PrintVar(const char *varname, bool expandVars)
{ {
if (strchr(varname, '$') != NULL) { 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 */ /* TODO: handle errors */
printf("%s\n", evalue); printf("%s\n", evalue);
free(evalue); free(evalue);
} else if (expandVars) { } else if (expandVars) {
char *expr = str_concat3("${", varname, "}"); 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 */ /* TODO: handle errors */
free(expr); free(expr);
printf("%s\n", evalue); printf("%s\n", evalue);
@ -865,7 +868,7 @@ GetBooleanExpr(const char *expr, bool fallback)
char *value; char *value;
bool res; bool res;
value = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); value = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
res = ParseBoolean(value, fallback); res = ParseBoolean(value, fallback);
free(value); free(value);
@ -1050,7 +1053,7 @@ HandlePWD(const struct stat *curdir_st)
if (stat(pwd, &pwd_st) == 0 && if (stat(pwd, &pwd_st) == 0 &&
curdir_st->st_ino == pwd_st.st_ino && curdir_st->st_ino == pwd_st.st_ino &&
curdir_st->st_dev == pwd_st.st_dev) curdir_st->st_dev == pwd_st.st_dev)
(void)strncpy(curdir, pwd, MAXPATHLEN); snprintf(curdir, MAXPATHLEN, "%s", pwd);
ignore_pwd: ignore_pwd:
FStr_Done(&makeobjdir); FStr_Done(&makeobjdir);
@ -1140,9 +1143,9 @@ static void
InitVarMake(const char *argv0) InitVarMake(const char *argv0)
{ {
const char *make = argv0; const char *make = argv0;
char pathbuf[MAXPATHLEN];
if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { if (argv0[0] != '/' && strchr(argv0, '/') != NULL) {
char pathbuf[MAXPATHLEN];
const char *abspath = cached_realpath(argv0, pathbuf); const char *abspath = cached_realpath(argv0, pathbuf);
struct stat st; struct stat st;
if (abspath != NULL && abspath[0] == '/' && if (abspath != NULL && abspath[0] == '/' &&
@ -1230,7 +1233,7 @@ InitMaxJobs(void)
!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS")) !Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS"))
return; return;
value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_WANTRES); value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
n = (int)strtol(value, NULL, 0); n = (int)strtol(value, NULL, 0);
if (n < 1) { if (n < 1) {
@ -1265,7 +1268,7 @@ InitVpath(void)
if (!Var_Exists(SCOPE_CMDLINE, "VPATH")) if (!Var_Exists(SCOPE_CMDLINE, "VPATH"))
return; return;
vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_WANTRES); vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
path = vpath; path = vpath;
do { do {
@ -1302,7 +1305,7 @@ ReadFirstDefaultMakefile(void)
StringList makefiles = LST_INIT; StringList makefiles = LST_INIT;
StringListNode *ln; StringListNode *ln;
char *prefs = Var_Subst("${.MAKE.MAKEFILE_PREFERENCE}", char *prefs = Var_Subst("${.MAKE.MAKEFILE_PREFERENCE}",
SCOPE_CMDLINE, VARE_WANTRES); SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
AppendWords(&makefiles, prefs); AppendWords(&makefiles, prefs);
@ -1513,7 +1516,7 @@ main_PrepareMaking(void)
/* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
if (!opts.noBuiltins || opts.printVars == PVM_NONE) { if (!opts.noBuiltins || opts.printVars == PVM_NONE) {
makeDependfile = Var_Subst("${.MAKE.DEPENDFILE}", makeDependfile = Var_Subst("${.MAKE.DEPENDFILE}",
SCOPE_CMDLINE, VARE_WANTRES); SCOPE_CMDLINE, VARE_EVAL);
if (makeDependfile[0] != '\0') { if (makeDependfile[0] != '\0') {
/* TODO: handle errors */ /* TODO: handle errors */
doing_depend = true; doing_depend = true;
@ -1605,9 +1608,9 @@ main_CleanUp(void)
meta_finish(); meta_finish();
#endif #endif
Suff_End(); Suff_End();
Var_End();
Targ_End(); Targ_End();
Arch_End(); Arch_End();
Var_End();
Parse_End(); Parse_End();
Dir_End(); Dir_End();
Job_End(); Job_End();
@ -1988,22 +1991,19 @@ execDie(const char *af, const char *av)
static void static void
purge_relative_cached_realpaths(void) purge_relative_cached_realpaths(void)
{ {
HashEntry *he, *next;
HashIter hi; HashIter hi;
bool more;
HashIter_Init(&hi, &cached_realpaths); HashIter_Init(&hi, &cached_realpaths);
he = HashIter_Next(&hi); more = HashIter_Next(&hi);
while (he != NULL) { while (more) {
next = HashIter_Next(&hi); HashEntry *he = hi.entry;
more = HashIter_Next(&hi);
if (he->key[0] != '/') { if (he->key[0] != '/') {
DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); DEBUG1(DIR, "cached_realpath: purging %s\n", he->key);
free(he->value);
HashTable_DeleteEntry(&cached_realpaths, he); 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); rp = HashTable_FindValue(&cached_realpaths, pathname);
if (rp != NULL) { if (rp != NULL) {
/* a hit */ snprintf(resolved, MAXPATHLEN, "%s", rp);
strncpy(resolved, rp, MAXPATHLEN);
resolved[MAXPATHLEN - 1] = '\0';
return resolved; return resolved;
} }
@ -2107,7 +2105,7 @@ PrintOnError(GNode *gn, const char *msg)
{ {
char *errorVarsValues = Var_Subst( char *errorVarsValues = Var_Subst(
"${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
printf("%s", errorVarsValues); printf("%s", errorVarsValues);
free(errorVarsValues); free(errorVarsValues);
@ -2137,10 +2135,11 @@ Main_ExportMAKEFLAGS(bool first)
flags = Var_Subst( flags = Var_Subst(
"${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
SCOPE_CMDLINE, VARE_WANTRES); SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
if (flags[0] != '\0') if (flags[0] != '\0')
setenv("MAKEFLAGS", flags, 1); setenv("MAKEFLAGS", flags, 1);
free(flags);
} }
char * char *
@ -2154,7 +2153,7 @@ getTmpdir(void)
/* Honor $TMPDIR if it is valid, strip a trailing '/'. */ /* Honor $TMPDIR if it is valid, strip a trailing '/'. */
tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { 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 .\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved. .\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\" .\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\" .\"
.Dd March 9, 2024 .Dd June 1, 2024
.Dt MAKE 1 .Dt MAKE 1
.Os .Os
.Sh NAME .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 Used to create files in a separate directory, see
.Va .OBJDIR . .Va .OBJDIR .
.It Va MAKE_OBJDIR_CHECK_WRITABLE .It Va MAKE_OBJDIR_CHECK_WRITABLE
Used to force a separate directory for the created files, When true,
even if that directory is not writable, see .Nm
.Va .OBJDIR . 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 .It Va MAKEOBJDIRPREFIX
Used to create files in a separate directory, see Used to create files in a separate directory, see
.Va .OBJDIR . .Va .OBJDIR .
@ -1951,12 +1961,7 @@ The directives for exporting and unexporting variables are:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Ic .export Ar variable No ... .It Ic .export Ar variable No ...
Export the specified global variable. Export the specified global variable.
If no variable list is provided, all globals are exported .Pp
except for internal variables (those that start with
.Ql \&. ) .
This is not affected by the
.Fl X
flag, so should be used with caution.
For compatibility with other make programs, For compatibility with other make programs,
.Cm export Ar variable\| Ns Cm \&= Ns Ar value .Cm export Ar variable\| Ns Cm \&= Ns Ar value
(without leading dot) is also accepted. (without leading dot) is also accepted.
@ -1964,6 +1969,12 @@ For compatibility with other make programs,
Appending a variable name to Appending a variable name to
.Va .MAKE.EXPORTED .Va .MAKE.EXPORTED
is equivalent to exporting a variable. 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 ... .It Ic .export-env Ar variable No ...
The same as The same as
.Ql .export , .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 * Copyright (c) 1988, 1989, 1990, 1993
@ -104,7 +104,7 @@
#include "job.h" #include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)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. */ /* Sequence # to detect recursion. */
static unsigned int checked_seqno = 1; static unsigned int checked_seqno = 1;
@ -127,8 +127,8 @@ debug_printf(const char *fmt, ...)
va_end(ap); va_end(ap);
} }
static const char * static char *
GNodeType_ToString(GNodeType type, void **freeIt) GNodeType_ToString(GNodeType type)
{ {
Buffer buf; Buffer buf;
@ -166,11 +166,13 @@ GNodeType_ToString(GNodeType type, void **freeIt)
ADD(OP_DEPS_FOUND); ADD(OP_DEPS_FOUND);
ADD(OP_MARK); ADD(OP_MARK);
#undef ADD #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 * static char *
GNodeFlags_ToString(GNodeFlags flags, void **freeIt) GNodeFlags_ToString(GNodeFlags flags)
{ {
Buffer buf; Buffer buf;
@ -184,24 +186,22 @@ GNodeFlags_ToString(GNodeFlags flags, void **freeIt)
Buf_AddFlag(&buf, flags.doneAllsrc, "DONE_ALLSRC"); Buf_AddFlag(&buf, flags.doneAllsrc, "DONE_ALLSRC");
Buf_AddFlag(&buf, flags.cycle, "CYCLE"); Buf_AddFlag(&buf, flags.cycle, "CYCLE");
Buf_AddFlag(&buf, flags.doneCycle, "DONECYCLE"); 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 void
GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn,
const char *suffix) const char *suffix)
{ {
void *type_freeIt = NULL; char *type = GNodeType_ToString(gn->type);
void *flags_freeIt = NULL; char *flags = GNodeFlags_ToString(gn->flags);
fprintf(f, "%s%s, type %s, flags %s%s", fprintf(f, "%s%s, type %s, flags %s%s",
prefix, prefix, GNodeMade_Name(gn->made), type, flags, suffix);
GNodeMade_Name(gn->made), free(type);
GNodeType_ToString(gn->type, &type_freeIt), free(flags);
GNodeFlags_ToString(gn->flags, &flags_freeIt),
suffix);
free(type_freeIt);
free(flags_freeIt);
} }
bool bool
@ -443,7 +443,7 @@ Make_HandleUse(GNode *cgn, GNode *pgn)
gn->uname = gn->name; gn->uname = gn->name;
else else
free(gn->name); free(gn->name);
gn->name = Var_Subst(gn->uname, pgn, VARE_WANTRES); gn->name = Var_Subst(gn->uname, pgn, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) { if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) {
/* See if we have a target for this node. */ /* 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -938,6 +938,15 @@ void Targ_PrintType(GNodeType);
void Targ_PrintGraph(int); void Targ_PrintGraph(int);
void Targ_Propagate(void); void Targ_Propagate(void);
const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE; 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 */ /* var.c */
void Var_Init(void); void Var_Init(void);
@ -951,7 +960,7 @@ typedef enum VarEvalMode {
* TODO: Document what Var_Parse and Var_Subst return in this mode. * TODO: Document what Var_Parse and Var_Subst return in this mode.
* As of 2021-03-15, they return unspecified, inconsistent results. * As of 2021-03-15, they return unspecified, inconsistent results.
*/ */
VARE_PARSE_ONLY, VARE_PARSE,
/* /*
* Parse text in which '${...}' and '$(...)' are not parsed as * Parse text in which '${...}' and '$(...)' are not parsed as
@ -962,25 +971,13 @@ typedef enum VarEvalMode {
VARE_PARSE_BALANCED, VARE_PARSE_BALANCED,
/* Parse and evaluate the expression. */ /* Parse and evaluate the expression. */
VARE_WANTRES, VARE_EVAL,
/* /*
* Parse and evaluate the expression. It is an error if a * Parse and evaluate the expression. It is an error if a
* subexpression evaluates to undefined. * subexpression evaluates to undefined.
*/ */
VARE_UNDEFERR, VARE_EVAL_DEFINED,
/*
* 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,
/* /*
* Parse and evaluate the expression. Keep undefined variables as-is * 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 * # way) is still undefined, the updated CFLAGS becomes
* # "-I.. $(.INCLUDES)". * # "-I.. $(.INCLUDES)".
*/ */
VARE_EVAL_KEEP_UNDEF, VARE_EVAL_KEEP_UNDEFINED,
/* /*
* Parse and evaluate the expression. Keep '$$' as '$$' and preserve * Parse and evaluate the expression. Keep '$$' as '$$' and preserve
* undefined subexpressions. * undefined subexpressions.
*/ */
VARE_KEEP_DOLLAR_UNDEF VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED
} VarEvalMode; } VarEvalMode;
typedef enum VarSetFlags { typedef enum VarSetFlags {
@ -1018,6 +1015,8 @@ typedef enum VarSetFlags {
} VarSetFlags; } VarSetFlags;
typedef enum VarExportMode { typedef enum VarExportMode {
/* .export-all */
VEM_ALL,
/* .export-env */ /* .export-env */
VEM_ENV, VEM_ENV,
/* .export: Initial export or update an already exported variable. */ /* .export: Initial export or update an already exported variable. */
@ -1027,6 +1026,9 @@ typedef enum VarExportMode {
} VarExportMode; } VarExportMode;
void Var_Delete(GNode *, const char *); void Var_Delete(GNode *, const char *);
#ifdef CLEANUP
void Var_DeleteAll(GNode *scope);
#endif
void Var_Undef(const char *); void Var_Undef(const char *);
void Var_Set(GNode *, const char *, const char *); void Var_Set(GNode *, const char *, const char *);
void Var_SetExpand(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; const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE;
FStr Var_Parse(const char **, GNode *, VarEvalMode); FStr Var_Parse(const char **, GNode *, VarEvalMode);
char *Var_Subst(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_Expand(FStr *, GNode *, VarEvalMode);
void Var_Stats(void); void Var_Stats(void);
void Var_Dump(GNode *); void Var_Dump(GNode *);
@ -1053,8 +1056,6 @@ void Global_Append(const char *, const char *);
void Global_Delete(const char *); void Global_Delete(const char *);
void Global_Set_ReadOnly(const char *, 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); const char *EvalStack_Details(void);
/* util.c */ /* 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. * Implement 'meta' mode.
@ -326,7 +326,7 @@ is_submake(const char *cmd, GNode *gn)
p_len = strlen(p_make); p_len = strlen(p_make);
} }
if (strchr(cmd, '$') != NULL) { if (strchr(cmd, '$') != NULL) {
mp = Var_Subst(cmd, gn, VARE_WANTRES); mp = Var_Subst(cmd, gn, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
cmd = mp; cmd = mp;
} }
@ -372,7 +372,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn)
{ {
FStr xcmd = FStr_InitRefer(ucmd); FStr xcmd = FStr_InitRefer(ucmd);
Var_Expand(&xcmd, gn, VARE_WANTRES); Var_Expand(&xcmd, gn, VARE_EVAL);
fprintf(fp, "CMD %s\n", xcmd.str); fprintf(fp, "CMD %s\n", xcmd.str);
FStr_Done(&xcmd); FStr_Done(&xcmd);
} }
@ -481,7 +481,7 @@ meta_create(BuildMon *pbm, GNode *gn)
if (metaVerbose) { if (metaVerbose) {
/* Describe the target we are building */ /* 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 */ /* TODO: handle errors */
if (mp[0] != '\0') if (mp[0] != '\0')
fprintf(stdout, "%s\n", mp); 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} * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
*/ */
metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
AppendWords(&metaBailiwick, metaBailiwickStr); AppendWords(&metaBailiwick, metaBailiwickStr);
/* /*
@ -627,7 +627,7 @@ meta_mode_init(const char *make_mode)
Global_Append(MAKE_META_IGNORE_PATHS, Global_Append(MAKE_META_IGNORE_PATHS,
"/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}"); "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}");
metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
AppendWords(&metaIgnorePaths, metaIgnorePathsStr); AppendWords(&metaIgnorePaths, metaIgnorePathsStr);
@ -777,7 +777,7 @@ meta_job_output(Job *job, char *cp, const char *nl)
char *cp2; char *cp2;
meta_prefix = Var_Subst("${" MAKE_META_PREFIX "}", meta_prefix = Var_Subst("${" MAKE_META_PREFIX "}",
SCOPE_GLOBAL, VARE_WANTRES); SCOPE_GLOBAL, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
if ((cp2 = strchr(meta_prefix, '$')) != NULL) if ((cp2 = strchr(meta_prefix, '$')) != NULL)
meta_prefix_len = (size_t)(cp2 - meta_prefix); meta_prefix_len = (size_t)(cp2 - meta_prefix);
@ -967,7 +967,7 @@ meta_ignore(GNode *gn, const char *p)
*/ */
Var_Set(gn, ".p.", p); Var_Set(gn, ".p.", p);
expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; 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 */ /* TODO: handle errors */
if (pm[0] != '\0') { if (pm[0] != '\0') {
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
@ -986,7 +986,7 @@ meta_ignore(GNode *gn, const char *p)
snprintf(fname, sizeof fname, snprintf(fname, sizeof fname,
"${%s:L:${%s:ts:}}", "${%s:L:${%s:ts:}}",
p, MAKE_META_IGNORE_FILTER); p, MAKE_META_IGNORE_FILTER);
fm = Var_Subst(fname, gn, VARE_WANTRES); fm = Var_Subst(fname, gn, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
if (*fm == '\0') { if (*fm == '\0') {
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
@ -1046,7 +1046,7 @@ meta_filter_cmd(GNode *gn, char *s)
Var_Set(gn, META_CMD_FILTER_VAR, s); Var_Set(gn, META_CMD_FILTER_VAR, s);
s = Var_Subst( s = Var_Subst(
"${" META_CMD_FILTER_VAR ":${" MAKE_META_CMP_FILTER ":ts:}}", "${" META_CMD_FILTER_VAR ":${" MAKE_META_CMP_FILTER ":ts:}}",
gn, VARE_WANTRES); gn, VARE_EVAL);
return s; return s;
} }
@ -1514,7 +1514,7 @@ meta_oodate(GNode *gn, bool oodate)
DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n", DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n",
fname, lineno); fname, lineno);
} }
cmd = Var_Subst(cmd, gn, VARE_UNDEFERR); cmd = Var_Subst(cmd, gn, VARE_EVAL_DEFINED);
/* TODO: handle errors */ /* TODO: handle errors */
if ((cp = strchr(cmd, '\n')) != NULL) { if ((cp = strchr(cmd, '\n')) != NULL) {
@ -1650,7 +1650,8 @@ void
meta_compat_child(void) meta_compat_child(void)
{ {
meta_job_child(NULL); 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"); 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> 2024-05-04 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20240504 * 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 # SPDX-License-Identifier: BSD-2-Clause
# #
@ -139,7 +139,7 @@
# DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS) # DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS)
# It is discouraged, but sometimes necessary for a # It is discouraged, but sometimes necessary for a
# Makefile.depend file to influence the environment. # 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 # tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS
# and dirdeps.mk will do the deed: # and dirdeps.mk will do the deed:
# #
@ -695,9 +695,22 @@ DEP_DIRDEPS_FILTER = \
${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \ ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \
${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \ ${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \
${DIRDEPS_FILTER:U} ${DIRDEPS_FILTER:U}
.if empty(DEP_DIRDEPS_FILTER) .if empty(DEP_DIRDEPS_FILTER)
# something harmless # 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 .endif
# this is what we start with # this is what we start with
@ -717,6 +730,7 @@ __qual_depdirs += ${__hostdpadd}
.if ${_debug_reldir} .if ${_debug_reldir}
.info DEP_DIRDEPS_FILTER=${DEP_DIRDEPS_FILTER:ts:} .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 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 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:}} .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 # make sure we do not mess with qualifying "host" entries
_build_dirs := ${_build_dirs:M*.host*:${M_dep_qual_fixes.host:ts:}} \ _build_dirs := ${_build_dirs:M*.host*:${M_dep_qual_fixes.host:ts:}} \
${_build_dirs:N*.host*:${M_dep_qual_fixes: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} .if ${_debug_reldir}
.info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.endif .endif
@ -746,6 +761,11 @@ _build_dirs := ${_build_dirs:O:u}
_build_all_dirs += ${_build_dirs} ${_build_xtra_dirs} _build_all_dirs += ${_build_dirs} ${_build_xtra_dirs}
_build_all_dirs := ${_build_all_dirs:O:u} _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, # Normally if doing make -V something,
# we do not want to waste time chasing DIRDEPS # we do not want to waste time chasing DIRDEPS
# but if we want to count the number of Makefile.depend* read, we do. # 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 # guard against _new_dirdeps being too big for a single command line
_new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,} _new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,}
_cache_xtra_deps := ${_build_xtra_dirs: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. # Discouraged, but there are always exceptions.
# Handle it here rather than explain how. # 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_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo;
_cache_script += ${_cache_xvars} _cache_script += ${_cache_xvars}
.endif .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:}} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}}
.endif .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 # this builds the dependency graph
.for m in ${_machines} .for m in ${_machines}
.if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs) .if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs)
@ -835,6 +848,15 @@ ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m}
.endif .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. # Now find more dependencies - and recurse.
.for d in ${_build_all_dirs} .for d in ${_build_all_dirs}
.if !target(_dirdeps_checked.$d) .if !target(_dirdeps_checked.$d)

View File

@ -59,7 +59,7 @@
# Simon J. Gerraty <sjg@crufty.net> # Simon J. Gerraty <sjg@crufty.net>
# RCSid: # 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 # @(#) Copyright (c) 1994-2024 Simon J. Gerraty
# #
@ -74,7 +74,7 @@
# sjg@crufty.net # sjg@crufty.net
# #
MK_VERSION=20240504 MK_VERSION=20240616
OWNER= OWNER=
GROUP= GROUP=
MODE=444 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -121,7 +121,7 @@
#include "pathnames.h" #include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ /* "@(#)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. */ /* Detects a multiple-inclusion guard in a makefile. */
typedef enum { typedef enum {
@ -234,9 +234,9 @@ static GNodeList *targets;
#ifdef CLEANUP #ifdef CLEANUP
/* /*
* All shell commands for all targets, in no particular order and possibly * 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 * with duplicate values. Kept in a separate list since the commands from
* .USEBEFORE nodes are shared with other GNodes, thereby giving up the * .USE or .USEBEFORE nodes are shared with other GNodes, thereby giving up
* easily understandable ownership over the allocated strings. * the easily understandable ownership over the allocated strings.
*/ */
static StringList targCmds = LST_INIT; static StringList targCmds = LST_INIT;
#endif #endif
@ -537,9 +537,9 @@ ParseVErrorInternal(FILE *f, bool useVars, const GNode *gn,
(void)fprintf(f, "%s: ", progname); (void)fprintf(f, "%s: ", progname);
PrintLocation(f, useVars, gn); PrintLocation(f, useVars, gn);
fprintf(f, "%s", EvalStack_Details());
if (level == PARSE_WARNING) if (level == PARSE_WARNING)
(void)fprintf(f, "warning: "); (void)fprintf(f, "warning: ");
fprintf(f, "%s", EvalStack_Details());
(void)vfprintf(f, fmt, ap); (void)vfprintf(f, fmt, ap);
(void)fprintf(f, "\n"); (void)fprintf(f, "\n");
(void)fflush(f); (void)fflush(f);
@ -619,7 +619,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
return; return;
} }
xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES); xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_EVAL);
/* TODO: handle errors */ /* TODO: handle errors */
Parse_Error(level, "%s", xmsg); Parse_Error(level, "%s", xmsg);
@ -652,7 +652,7 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial)
Lst_Append(&cgn->parents, pgn); Lst_Append(&cgn->parents, pgn);
if (DEBUG(PARSE)) { if (DEBUG(PARSE)) {
debug_printf("# LinkSource: added child %s - %s\n", debug_printf("Target \"%s\" depends on \"%s\"\n",
pgn->name, cgn->name); pgn->name, cgn->name);
Targ_PrintNode(pgn, 0); Targ_PrintNode(pgn, 0);
Targ_PrintNode(cgn, 0); Targ_PrintNode(cgn, 0);
@ -925,8 +925,7 @@ ParseDependencyTargetWord(char **pp, const char *lstart)
break; break;
if (*p == '$') { if (*p == '$') {
FStr val = Var_Parse(&p, SCOPE_CMDLINE, FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE);
VARE_PARSE_ONLY);
/* TODO: handle errors */ /* TODO: handle errors */
FStr_Done(&val); FStr_Done(&val);
} else } else
@ -1279,13 +1278,12 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
} }
if (SkipGuarded(fullname)) if (SkipGuarded(fullname))
return; goto done;
if ((fd = open(fullname, O_RDONLY)) == -1) { if ((fd = open(fullname, O_RDONLY)) == -1) {
if (!silent) if (!silent)
Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
free(fullname); goto done;
return;
} }
buf = LoadFile(fullname, fd); 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); Parse_PushInput(fullname, 1, 0, buf, NULL);
if (depinc) if (depinc)
doing_depend = depinc; /* only turn it on */ doing_depend = depinc; /* only turn it on */
done:
free(fullname); free(fullname);
} }
@ -1811,7 +1810,7 @@ VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope)
if (opts.strict) { if (opts.strict) {
if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) { if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) {
char *parsedValue = Var_Subst(uvalue, char *parsedValue = Var_Subst(uvalue,
scope, VARE_PARSE_ONLY); scope, VARE_PARSE);
/* TODO: handle errors */ /* TODO: handle errors */
free(parsedValue); free(parsedValue);
} }
@ -1837,7 +1836,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue,
if (!Var_ExistsExpand(scope, name)) if (!Var_ExistsExpand(scope, name))
Var_SetExpand(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 */ /* TODO: handle errors */
Var_SetExpand(scope, name, evalue); Var_SetExpand(scope, name, evalue);
@ -1854,7 +1854,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope,
char *output, *error; char *output, *error;
cmd = FStr_InitRefer(uvalue); 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); output = Cmd_Exec(cmd.str, &error);
Var_SetExpand(scope, name, output); Var_SetExpand(scope, name, output);
@ -2036,7 +2036,7 @@ ParseInclude(char *directive)
*p = '\0'; *p = '\0';
Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES); Var_Expand(&file, SCOPE_CMDLINE, VARE_EVAL);
IncludeFile(file.str, endc == '>', directive[0] == 'd', silent); IncludeFile(file.str, endc == '>', directive[0] == 'd', silent);
FStr_Done(&file); FStr_Done(&file);
} }
@ -2245,7 +2245,7 @@ ParseTraditionalInclude(char *line)
pp_skip_whitespace(&file); 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 */ /* TODO: handle errors */
for (file = all_files; !done; file = p + 1) { for (file = all_files; !done; file = p + 1) {
@ -2288,7 +2288,7 @@ ParseGmakeExport(char *line)
/* /*
* Expand the value before putting it in the environment. * 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 */ /* TODO: handle errors */
setenv(variable, value, 1); setenv(variable, value, 1);
@ -2319,9 +2319,15 @@ ParseEOF(void)
Cond_EndFile(); Cond_EndFile();
if (curFile->guardState == GS_DONE) if (curFile->guardState == GS_DONE) {
HashTable_Set(&guards, curFile->name.str, curFile->guard); HashEntry *he = HashTable_CreateEntry(&guards,
else if (curFile->guard != NULL) { 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->name);
free(curFile->guard); free(curFile->guard);
} }
@ -2684,6 +2690,13 @@ FinishDependencyGroup(void)
targets = NULL; 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. */ /* Add the command to each target from the current dependency spec. */
static void static void
ParseLine_ShellCommand(const char *p) ParseLine_ShellCommand(const char *p)
@ -2706,9 +2719,7 @@ ParseLine_ShellCommand(const char *p)
GNode *gn = ln->datum; GNode *gn = ln->datum;
GNode_AddCommand(gn, cmd); GNode_AddCommand(gn, cmd);
} }
#ifdef CLEANUP Parse_RegisterCommand(cmd);
Lst_Append(&targCmds, cmd);
#endif
} }
} }
@ -2764,6 +2775,8 @@ ParseDirective(char *line)
Var_Undef(arg); Var_Undef(arg);
else if (Substring_Equals(dir, "export")) else if (Substring_Equals(dir, "export"))
Var_Export(VEM_PLAIN, arg); Var_Export(VEM_PLAIN, arg);
else if (Substring_Equals(dir, "export-all"))
Var_Export(VEM_ALL, arg);
else if (Substring_Equals(dir, "export-env")) else if (Substring_Equals(dir, "export-env"))
Var_Export(VEM_ENV, arg); Var_Export(VEM_ENV, arg);
else if (Substring_Equals(dir, "export-literal")) 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 * empty string var_Error, which cannot be detected in the result of
* Var_Subst. * 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); expanded_line = Var_Subst(line, SCOPE_CMDLINE, emode);
/* TODO: handle errors */ /* TODO: handle errors */
@ -2990,7 +3003,7 @@ Parse_End(void)
assert(includes.len == 0); assert(includes.len == 0);
Vector_Done(&includes); Vector_Done(&includes);
HashIter_Init(&hi, &guards); HashIter_Init(&hi, &guards);
while (HashIter_Next(&hi) != NULL) { while (HashIter_Next(&hi)) {
Guard *guard = hi.entry->value; Guard *guard = hi.entry->value;
free(guard->name); free(guard->name);
free(guard); 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -115,7 +115,7 @@
#include "dir.h" #include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ /* "@(#)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 List SuffixList;
typedef ListNode SuffixListNode; typedef ListNode SuffixListNode;
@ -1223,6 +1223,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn)
DEBUG1(SUFF, "%s...", name); DEBUG1(SUFF, "%s...", name);
gn = Targ_GetNode(name); gn = Targ_GetNode(name);
free(name);
/* Insert gn before the original child. */ /* Insert gn before the original child. */
Lst_InsertBefore(&pgn->children, cln, gn); Lst_InsertBefore(&pgn->children, cln, gn);
@ -1273,7 +1274,7 @@ ExpandChildrenRegular(char *p, GNode *pgn, GNodeList *members)
} else if (*p == '$') { } else if (*p == '$') {
/* Skip over the expression. */ /* Skip over the expression. */
const char *nested_p = p; 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 */ /* TODO: handle errors */
if (junk.str == var_Error) { if (junk.str == var_Error) {
Parse_Error(PARSE_FATAL, Parse_Error(PARSE_FATAL,
@ -1343,7 +1344,7 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn)
} }
DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); 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 */ /* 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 * Copyright (c) 1988, 1989, 1990, 1993
@ -107,7 +107,7 @@
#include "dir.h" #include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)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 * 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 static void
GNode_Free(GNode *gn) GNode_Free(GNode *gn)
{ {
Var_DeleteAll(gn);
free(gn->name); free(gn->name);
free(gn->uname); free(gn->uname);
free(gn->path); free(gn->path);
@ -236,20 +238,6 @@ GNode_Free(GNode *gn)
Lst_Done(&gn->order_succ); Lst_Done(&gn->order_succ);
Lst_Done(&gn->cohorts); 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); 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) # Unit tests for make(1)
# #
@ -745,11 +745,11 @@ all: ${OUTFILES}
CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp
CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= obj*.[och] lib*.a # posix1.mk
CLEANFILES+= issue* .[ab]* # suffixes.mk CLEANFILES+= issue* .[ab]* # suffixes.mk
CLEANDIRS= dir dummy # posix1.mk CLEANDIRS= dir dummy *.tmp # posix1.mk
clean: clean:
rm -f ${CLEANFILES}
rm -rf ${CLEANDIRS} rm -rf ${CLEANDIRS}
rm -f ${CLEANFILES}
TEST_MAKE?= ${.MAKE} TEST_MAKE?= ${.MAKE}
TOOL_SED?= sed TOOL_SED?= sed
@ -864,6 +864,11 @@ test: ${OUTFILES} .PHONY
echo "Failed tests: $${failed}" ; false ; \ echo "Failed tests: $${failed}" ; false ; \
else \ else \
echo "All tests passed" ; \ 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 fi
accept: 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 # Tests for the empty() function in .if conditions, which tests an
# expression for emptiness. # expression for emptiness.
@ -104,10 +104,10 @@ WORD= word
# Now the variable named " " gets a non-empty value, which demonstrates that # Now the variable named " " gets a non-empty value, which demonstrates that
# neither leading nor trailing spaces are trimmed in the argument of the # neither leading nor trailing spaces are trimmed in the argument of the
# function. If the spaces were trimmed, the variable name would be "" and # function. If the spaces were trimmed, the variable name would be "", and
# that variable is indeed undefined. Since CondParser_FuncCallEmpty calls # that variable is indeed undefined. Since CondParser_FuncCallEmpty allows
# Var_Parse without VARE_UNDEFERR, the value of the undefined variable "" # subexpressions to be based on undefined variables, the value of the
# would be returned as an empty string. # undefined variable "" would be returned as an empty string.
${:U }= space ${:U }= space
.if empty( ) .if empty( )
. error . error
@ -194,15 +194,15 @@ ${:U WORD }= variable name with spaces
# wrong variable name should have been discarded quickly after parsing it, to # wrong variable name should have been discarded quickly after parsing it, to
# prevent it from doing any harm. # prevent it from doing any harm.
# #
# The expression was expanded, and this was wrong. The # The expression was evaluated, and this was wrong. The evaluation was done
# expansion was done without VARE_WANTRES (called VARF_WANTRES back then) # without VARE_EVAL (called VARF_WANTRES back then) though. This had the
# though. This had the effect that the ${:U1} from the value of VARNAME # effect that the ${:U1} from the value of VARNAME evaluated to an empty
# expanded to an empty string. This in turn created the seemingly recursive # string. This in turn created the seemingly recursive definition
# definition VARNAME=${VARNAME}, and that definition was never meant to be # VARNAME=${VARNAME}, and that definition was evaluated even though it was
# expanded. # never meant to be evaluated.
# #
# This was fixed by expanding nested expressions in the variable name # This was fixed by evaluating nested expressions in the variable name only
# only if the flag VARE_WANTRES is given. # when the whole expression was evaluated as well.
VARNAME= ${VARNAME${:U1}} VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif .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 make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here
main-output main-output
exit status 0 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 # Test for a target whose commands are defined twice. This generates a
# warning, not an error, so ensure that the correct commands are kept. # 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 1'; \
echo '# empty line 2'; \ echo '# empty line 2'; \
echo 'all:; @echo main-output'; \ 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' echo 'all:; @echo inc-output'
# The main file must be specified using a relative path, just like the # 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 @${MAKE} -r -f dep-duplicate.main
@rm -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
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
Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 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 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: .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
Global: .MAKEFLAGS = -r -k -d v -d 0 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. # Tests for variable references in dependency declarations.
# #
@ -84,8 +84,8 @@ all: $$$$)
# in normal mode since ParseDependency does not handle any errors after # in normal mode since ParseDependency does not handle any errors after
# calling Var_Parse. # calling Var_Parse.
# expect: Var_Parse: ${:U\$)}: (eval-defined) # expect: Var_Parse: ${:U\$)}: (eval-defined)
# expect: Var_Parse: $INDIRECT_2-2-1 $): (parse-only) # expect: Var_Parse: $INDIRECT_2-2-1 $): (parse)
# expect: Var_Parse: $): (parse-only) # expect: Var_Parse: $): (parse)
undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}:
@echo ${.TARGET:Q} @echo ${.TARGET:Q}

View File

@ -1,2 +1,5 @@
value with ${UNEXPANDED} expression value with ${UNEXPANDED} expression
value literal
value indirect
value ${indirect:L}
exit status 0 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 # Tests for the .export-literal directive, which exports a variable value
# without expanding it. # without expanding it.
@ -9,5 +9,28 @@ UT_VAR= value with ${UNEXPANDED} expression
.export-literal # oops: missing argument .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: all:
@echo "$$UT_VAR" @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 36: warning: .export requires an argument.
make: "directive-export.mk" line 61: 00:00:00 make: "directive-export.mk" line 60: 00:00:00
make: "directive-export.mk" line 64: 16:00:00 make: "directive-export.mk" line 65: 00:00:00
make: "directive-export.mk" line 68: 16:00:00
exit status 0 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. # Tests for the .export directive.
# #
@ -28,7 +28,11 @@ VAR= value $$ ${INDIRECT}
. error . error
.endif .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 .export
# An empty argument means no additional variables to 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 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: end for 1
For: loop body with i = value: For: loop body with i = value:
# The identifier 'empty' can only be used in conditions such as .if, .ifdef or # 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 # condition directives, they can also occur in the modifier ':?', see
# varmod-ifelse.mk. # varmod-ifelse.mk.
CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}" CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
make: Fatal errors encountered -- cannot continue exit status 0
make: stopped in unit-tests
exit status 1

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:...)'. # 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 # In conditions, the function call to 'empty' does not look like an
# expression, therefore it is not replaced. Since there is no global variable # 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. # variables were real variables, calling 'empty' would work on them as well.
.for i in 11 12 13 .for i in 11 12 13
# Asking for an empty iteration variable does not make sense as the .for loop # 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. # splits the iteration items into words, and such a word cannot be empty.
. if empty(i) . if !empty(i)
# expect+3: Missing argument for ".error" . error # not reached, due to the leaky abstraction
# expect+2: Missing argument for ".error"
# expect+1: Missing argument for ".error"
. error # due to the leaky abstraction
. endif . endif
# The typical way of using 'empty' with variables from .for loops is pattern # The typical way of mistakenly using 'empty' with variables from .for loops
# matching using the modifiers ':M' or ':N'. # is pattern matching using the modifiers ':M' or ':N'.
. if !empty(i:M*2*) . if !empty(i:M*2*)
. if ${i} != "12" . error
. error . endif
. 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 . endif
.endfor .endfor
@ -122,3 +122,5 @@ CPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}"
# TODO: Add code that demonstrates the current interaction between variables # TODO: Add code that demonstrates the current interaction between variables
# from .for loops and the modifiers mentioned above. # 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. # Tests for error handling in .for loops.
@ -41,7 +41,7 @@
${:U\$}= dollar # see whether the "variable" '$' is local ${:U\$}= dollar # see whether the "variable" '$' is local
${:U\\}= backslash # see whether the "variable" '\' is local ${:U\\}= backslash # see whether the "variable" '\' is local
# expect+1: invalid character '$' in .for loop variable name # 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 $\ ${\} $(\). . info Dollar $$ ${$} $($) and backslash $\ ${\} $(\).
.endfor .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_OK= good
UT_F= fine UT_F= fine
@ -15,7 +15,7 @@ UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T}
# this will be ok # this will be ok
UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T} UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T}
.export .export-all
FILTER_CMD= grep ^UT_ FILTER_CMD= grep ^UT_
.include "export.mk" .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 make: Fatal errors encountered -- cannot continue
HashTable targets: size=16 numEntries=0 maxchain=0 HashTable targets: size=16 numEntries=0 maxchain=0
HashTable Global variables: size=16 numEntries=<entries> maxchain=3 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 # Tests for the -dh command line option, which adds debug logging for
# hash tables. Even more detailed logging is available by compiling # hash tables. Even more detailed logging is available by compiling
@ -6,7 +6,8 @@
.MAKEFLAGS: -dh .MAKEFLAGS: -dh
# Force a parse error, to demonstrate the newline character in the diagnostic # Force a parse error, to demonstrate the newline character in the "cannot
# that had been missing before parse.c 1.655 from 2022-01-22. # continue" diagnostic that had been missing before parse.c 1.655 from
# 2022-01-22.
# expect+1: Missing argument for ".error" # expect+1: Missing argument for ".error"
.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. # Tests for parsing expressions.
# #
@ -20,11 +20,11 @@
# #
# VarEvalMode: # VarEvalMode:
# parse # parse
# parse-balanced
# eval # eval
# eval-undeferr # eval-defined
# eval-keep-dollar # eval-keep-undefined
# eval-keep-undef # eval-keep-dollar-and-undefined
# eval-keep-dollar-undef
# #
# Global mode: # Global mode:
# without -dL # 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. # In -dL mode, a variable may get expanded before it makes sense.
# This would stop make from doing anything since the "recursive" error # 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 # The purpose of evaluating that variable early was just to detect
# whether there are unclosed variables. The variable value is therefore # 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 .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. # 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. # The shell path must be an absolute path.
# This is only obvious in parallel mode since in compat mode, # 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} != "" .if ${CSH} != ""
.SHELL: name="csh" path="${CSH}" .SHELL: name="csh" path="${CSH}"
.endif .endif

View File

@ -17,7 +17,7 @@ ParseDependency(.a.c: ${.PREFIX}.dependency)
defining transformation from `.a' to `.c' defining transformation from `.a' to `.c'
inserting ".a" (1) at end of list inserting ".a" (1) at end of list
inserting ".c" (3) 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 # .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none
# ${.PREFIX}.dependency, unmade, type none, flags none # ${.PREFIX}.dependency, unmade, type none, flags none
Parsing line 23: .DEFAULT: 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 39: : Making ${.TARGET} out of nothing.
Parsing line 40: next-main: suff-main-several.{2,3,4} Parsing line 40: next-main: suff-main-several.{2,3,4}
ParseDependency(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 # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
# suff-main-several.{2,3,4}, unmade, type none, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none
Parsing line 42: .MAKEFLAGS: -d0 -dg1 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@}) make: "var-eval-short.mk" line 46: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@})
Parsing line 159: .if 0 && ${0:?${FAIL}then:${FAIL}else} Parsing line 159: .if 0 && ${0:?${FAIL}then:${FAIL}else}
CondParser_Eval: 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:?...} Parsing modifier ${0:?...}
Var_Parse: ${FAIL}then:${FAIL}else} (parse-only) Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then" Modifier part: "${FAIL}then"
Var_Parse: ${FAIL}else} (parse-only) Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else" 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 Parsing line 167: DEFINED= defined
Global: DEFINED = defined Global: DEFINED = defined
Parsing line 168: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} Parsing line 168: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
CondParser_Eval: 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} Parsing modifier ${DEFINED:L}
Result of ${DEFINED:L} is "defined" (parse-only, regular) Result of ${DEFINED:L} is "defined" (parse, regular)
Parsing modifier ${DEFINED:?...} Parsing modifier ${DEFINED:?...}
Var_Parse: ${FAIL}then:${FAIL}else} (parse-only) Var_Parse: ${FAIL}then:${FAIL}else} (parse)
Modifier part: "${FAIL}then" Modifier part: "${FAIL}then"
Var_Parse: ${FAIL}else} (parse-only) Var_Parse: ${FAIL}else} (parse)
Modifier part: "${FAIL}else" 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 Parsing line 170: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0) ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d 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 # Tests for the :H variable modifier, which returns the dirname of
# each of the words in the variable value. # each of the words in the variable value.
@ -61,4 +61,10 @@ _!= echo "The modifier ':H' generates an empty word." 1>&2; echo
. error . error
.endif .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 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 # Tests for the ${cond:?then:else} variable modifier, which evaluates either
# the then-expression or the else-expression, depending on the condition. # the then-expression or the else-expression, depending on the condition.
@ -77,7 +77,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
# conditional expression". # conditional expression".
# #
# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse # 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 # returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the
# value of the expression is still undefined. CondParser_String is # value of the expression is still undefined. CondParser_String is
# then supposed to do proper error handling, but since varUndefined is local # 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 89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
Parsing line 92: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" Parsing line 90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$" Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$"
Parsing line 96: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} Parsing line 94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
Parsing line 118: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" Parsing line 116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$" Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$"
Parsing line 121: .MAKEFLAGS: -d0 Parsing line 119: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0) ParseDependency(.MAKEFLAGS: -d0)
:varname-overwriting-target: :x1y x2y x3y: :: :varname-overwriting-target: :x1y x2y x3y: ::
mod-loop-dollar:1: 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 # Tests for the expression modifier ':@var@body@', which replaces each word of
# the expression with the expanded body, which may contain references to the # the expression with the expanded body, which may contain references to the
@ -82,10 +82,8 @@ mod-loop-dollar:
8_DOLLARS= $$$$$$$$ 8_DOLLARS= $$$$$$$$
# This string literal is written with 8 dollars, and this is saved as the # 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 # variable value. But as soon as this value is evaluated, it goes through
# Var_Subst, which replaces each '$$' with a single '$'. This could be # Var_Subst, which replaces each '$$' with a single '$'.
# prevented by VARE_EVAL_KEEP_DOLLAR, but that flag is usually removed # See ApplyModifier_Loop and ParseModifierPart for examples.
# before expanding subexpressions. See ApplyModifier_Loop and
# ParseModifierPart for examples.
# #
.MAKEFLAGS: -dcp .MAKEFLAGS: -dcp
USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ 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} SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
# The ':=' assignment operator evaluates the variable value using the mode # The ':=' assignment operator evaluates the variable value using the mode
# VARE_KEEP_DOLLAR_UNDEF, which means that some dollar signs are preserved, # VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED, which means that some dollar signs are
# but not all. The dollar signs in the top-level expression and in the # preserved, but not all. The dollar signs in the top-level expression and in
# indirect ${8_DOLLARS} are preserved. # 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: # matter in which context it is evaluated. What happens in detail is:
# First, the modifier part "${8_DOLLARS}" is parsed without expanding it. # 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 # 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
Global: .MAKEFLAGS = -r -k -d cv -d 0 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 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: warning: while evaluating variable "WORDS": 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: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 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, # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently,
# depending on whether there was an expression somewhere before the # depending on whether there was an expression somewhere before the
@ -105,8 +105,8 @@ EXP.[^A-]]= a
EXP.[^A-]]]= a] EXP.[^A-]]]= a]
.for pattern in [A-] [A-]] [A-]]] [^A-] [^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+2: warning: while evaluating variable "WORDS": 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+1: warning: while evaluating variable "WORDS": Unfinished character list in pattern '[^A-]' of modifier ':M'
. if ${WORDS:M${pattern}} != ${EXP.${pattern}} . if ${WORDS:M${pattern}} != ${EXP.${pattern}}
. warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}} . warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}}
. endif . 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 289: warning: while evaluating variable "WORDS": 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 297: warning: while evaluating variable "WORDS": 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 305: warning: while evaluating variable "WORDS": 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 313: warning: while evaluating variable "WORDS": 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 322: warning: while evaluating variable "WORDS": 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 336: warning: while evaluating variable "WORDS": 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 344: warning: while evaluating variable "WORDS": 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 356: warning: while evaluating variable "WORDS": 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 364: warning: while evaluating variable " : :: ": Unfinished character list in pattern '[' of modifier ':M'
make: "varmod-match.mk" line 365: while evaluating variable " : :: ": Unknown modifier "]" make: "varmod-match.mk" line 364: while evaluating variable " : :: ": Unknown modifier "]"
make: "varmod-match.mk" line 365: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") make: "varmod-match.mk" line 364: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 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 # Tests for the ':M' modifier, which keeps only those words that match the
# given pattern. # given pattern.
@ -42,13 +42,12 @@
. error . error
.endif .endif
# A pattern that ends with '*' is anchored at the # A pattern that does not start with '*' is anchored at the beginning.
# beginning.
.if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa" .if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa"
. error . error
.endif .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" .if ${a aa aaa b ba baa bab:L:M*a} != "a aa aaa ba baa"
. error . error
.endif .endif
@ -286,7 +285,7 @@ ${:U*}= asterisk
# [ Incomplete empty character list, never matches. # [ Incomplete empty character list, never matches.
WORDS= a a[ 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[} != "" .if ${WORDS:Ma[} != ""
. error . error
.endif .endif
@ -294,7 +293,7 @@ WORDS= a a[
# [^ Incomplete negated empty character list, matches any single # [^ Incomplete negated empty character list, matches any single
# character. # character.
WORDS= a a[ aX 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" .if ${WORDS:Ma[^} != "a[ aX"
. error . error
.endif .endif
@ -302,7 +301,7 @@ WORDS= a a[ aX
# [-x1-3 Incomplete character list, matches those elements that can be # [-x1-3 Incomplete character list, matches those elements that can be
# parsed without lookahead. # parsed without lookahead.
WORDS= - + x xx 0 1 2 3 4 [x1-3 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" .if ${WORDS:M[-x1-3} != "- x 1 2 3"
. error . error
.endif .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 # *[-x1-3 Incomplete character list after a wildcard, matches those
# words that end with one of the characters from the list. # 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 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" .if ${WORDS:M*[-x1-3} != "- x xx 1 2 3 01 11 001 011 101 111 [x1-3"
. warning ${WORDS:M*[-x1-3} . warning ${WORDS:M*[-x1-3}
.endif .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 # Incomplete negated character list, matches any character
# except those elements that can be parsed without lookahead. # except those elements that can be parsed without lookahead.
WORDS= - + x xx 0 1 2 3 4 [x1-3 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" .if ${WORDS:M[^-x1-3} != "+ 0 4"
. error . error
.endif .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. # '\', as there is no following space that could be escaped.
WORDS= \\ \a ${:Ux\\} WORDS= \\ \a ${:Ux\\}
PATTERN= ${:U?[\\} 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\\" .if ${WORDS:M${PATTERN}} != "\\\\ x\\"
. error . error
.endif .endif
@ -341,7 +340,7 @@ PATTERN= ${:U?[\\}
# [x- Incomplete character list containing an incomplete character # [x- Incomplete character list containing an incomplete character
# range, matches only the 'x'. # range, matches only the 'x'.
WORDS= [x- x x- y 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" .if ${WORDS:M[x-} != "x"
. error . error
.endif .endif
@ -353,13 +352,13 @@ WORDS= [x- x x- y
# XXX: Even matches strings that are longer than a single # XXX: Even matches strings that are longer than a single
# character. # character.
WORDS= [x- x x- y yyyyy 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" .if ${WORDS:M[^x-} != "[x- y yyyyy"
. error . error
.endif .endif
# [:] matches never since the ':' starts the next modifier # [:] 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+2: while evaluating variable " : :: ": Unknown modifier "]"
# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") # expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":")
.if ${ ${: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. # out-of-bounds read beyond the indirect ':M' modifiers.
# #
# The argument to the inner ':U' is unescaped to 'M\'. # 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. # The pattern '\' never matches.
.if ${:U:${:UM\\}} .if ${:U:${:UM\\}}
. error . error

View File

@ -145,6 +145,8 @@ pre-middle-suffix pre%ffix=NPre% "NPre-middle-su"
suffix pre%ffix=NPre%NS "suffix" suffix pre%ffix=NPre%NS "suffix"
prefix pre%ffix=NPre%NS "prefix" prefix pre%ffix=NPre%NS "prefix"
pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS" 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: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 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 # Tests for the variable modifier ':from=to', which replaces the suffix
# "from" with "to". It can also use '%' as a wildcard. # "from" with "to". It can also use '%' as a wildcard.
@ -252,4 +252,12 @@ INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE}
. endfor . endfor
.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: 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 # Tests for the :T variable modifier, which returns the basename of each of
# the words in the variable value. # 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: all:
.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/ .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}"'" @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 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: 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 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: 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: 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: 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: 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: 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: 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: 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: 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 262: 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 271: 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 271: Malformed conditional (${:Ua b:ts\x1F60E})
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 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 # Tests for the :ts variable modifier, which joins the words of the variable
# using an arbitrary character as word separator. # 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. . warning The separator \x100 is accepted even though it is out of bounds.
.endif .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. # Negative numbers are not allowed for the separator character.
# expect+1: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) # expect+1: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
.if ${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 # Tests for the :U variable modifier, which returns the given string
# if the variable is undefined. # if the variable is undefined.
@ -7,6 +7,9 @@
# directive-for.mk # directive-for.mk
# varmod-defined.mk # varmod-defined.mk
# this test depends on
.MAKE.SAVE_DOLLARS= yes
# The pattern ${:Uword} is heavily used when expanding .for loops. # The pattern ${:Uword} is heavily used when expanding .for loops.
# #
# This is how an expanded .for loop looks like. # This is how an expanded .for loop looks like.
@ -53,6 +56,10 @@
.if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n " .if ${:U \: \} \$ \\ \a \b \n } != " : } \$ \\ \\a \\b \\n "
. error . error
.endif .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 # Even after the :U modifier has been applied, the expression still remembers
# that it originated from an undefined variable, and the :U modifier can # that it originated from an undefined variable, and the :U modifier can
@ -64,5 +71,43 @@
. error . error
.endif .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 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 117: while evaluating variable "VAR": Missing delimiter ':' after modifier "P"
make: "varmod.mk" line 119: Missing argument for ".error" 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: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 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. # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
# #
# See also: # See also:
# varparse-errors.mk # 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: # * `strict`: the parsing style used by most modifiers:
# * either uses `ParseModifierPart` or parses the modifier literal # * either uses `ParseModifierPart` or parses the modifier literal
@ -46,9 +46,9 @@
# | `U` | individual | custom parser | N/A | # | `U` | individual | custom parser | N/A |
# | `[` | strict | | no | # | `[` | strict | | no |
# | `_` | individual | strcspn | yes | # | `_` | individual | strcspn | yes |
# | `gmtime` | strict | only literal value | yes | # | `gmtime` | strict | | yes |
# | `hash` | strict | | N/A | # | `hash` | strict | | N/A |
# | `localtime` | strict | only literal value | yes | # | `localtime` | strict | | yes |
# | `q` | strict | | yes | # | `q` | strict | | yes |
# | `range` | strict | | N/A | # | `range` | strict | | N/A |
# | `sh` | strict | | N/A | # | `sh` | strict | | N/A |
@ -119,4 +119,119 @@ VAR= STOP
. error . error
.endif .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 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 first
second second
backslash newline: <\ 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 # Tests for the special .newline variable, which contains a single newline
# character (U+000A). # character (U+000A).
@ -20,12 +20,23 @@ BACKSLASH_NEWLINE:= \${.newline}
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 .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} .if ${.newline} != ${NEWLINE}
. error The .newline variable can be overwritten. It should be read-only. . error The .newline variable can be overwritten. It should be read-only.
.endif .endif
all: all:
@${MAKE} -f ${MAKEFILE} try-to-modify || true
@echo 'first${.newline}second' @echo 'first${.newline}second'
@echo 'backslash newline: <${BACKSLASH_NEWLINE}>' @echo 'backslash newline: <${BACKSLASH_NEWLINE}>'

View File

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