Add support for the POSIX :::= assignment operator.

POSIX Issue 8 will require a new assignment operator, :::=.
This operator behaves similarly to the BSD make := operator: the
right-hand side is expanded immediately, but then the value is
re-escaped (all '$' are converted to '$$') and the resulting variable
is considered a recursive variable: the value is re-expanded on use.

* src/variable.h (enum variable_flavor): Add f_expand flavor.
* src/variable.c (do_variable_definition): When defining f_expand,
post-process the result to re-escape '$' characters.
Remove default: to the compiler warns about un-handled enum values.
Set recursive values for both f_recursive and f_expand.
(parse_variable_definition): Rewrite this method.
The previous version was annoying to extend to ':::='.
(print_variable): Remove default: so the compiler warns us about
un-handled enum values.
* src/function.c (func_origin): Remove default: so the compiler warns
us about un-handled enum values.
* doc/make.texi: Add documentation for :::=.
* tests/scripts/variables/define: Add a test for define :::=.
* tests/scripts/variables/flavors: Add tests for :::=.
* tests/scripts/variables/negative: Add tests for :::=.
This commit is contained in:
Paul Smith 2021-09-18 18:53:38 -04:00
parent c5d4b7b2f2
commit 6c06c547dc
8 changed files with 363 additions and 160 deletions

View file

@ -258,6 +258,13 @@ How to Use Variables
* Suppressing Inheritance:: Suppress inheritance of variables.
* Special Variables:: Variables with special meaning or behavior.
The Two Flavors of Variables
* Recursive Assignment:: Setting recursively expanded variables.
* Simple Assignment:: Setting simply expanded variables.
* Immediate Assignment:: Setting immediately expanded variables.
* Conditional Assignment:: Assigning variable values conditionally.
Advanced Features for Reference to Variables
* Substitution Refs:: Referencing a variable with
@ -1528,6 +1535,7 @@ Variable definitions are parsed as follows:
@var{immediate} ?= @var{deferred}
@var{immediate} := @var{immediate}
@var{immediate} ::= @var{immediate}
@var{immediate} :::= @var{immediate-with-escape}
@var{immediate} += @var{deferred} or @var{immediate}
@var{immediate} != @var{immediate}
@ -1551,6 +1559,10 @@ define @var{immediate} ::=
@var{immediate}
endef
define @var{immediate} :::=
@var{immediate-with-escape}
endef
define @var{immediate} +=
@var{deferred} or @var{immediate}
endef
@ -1564,6 +1576,11 @@ For the append operator @samp{+=}, the right-hand side is considered
immediate if the variable was previously set as a simple variable
(@samp{:=} or @samp{::=}), and deferred otherwise.
For the @var{immediate-with-escape} operator @samp{:::=}, the value on
the right-hand side is immediately expanded but then escaped (that is,
all instances of @code{$} in the result of the expansion are replaced
with @code{$$}).
For the shell assignment operator @samp{!=}, the right-hand side is
evaluated immediately and handed to the shell. The result is stored
in the variable named on the left, and that variable is considered a
@ -5357,10 +5374,20 @@ often improved is automatic variables (@pxref{Automatic Variables}).
@cindex recursively expanded variables
@cindex variables, recursively expanded
There are two ways that a variable in GNU @code{make} can have a value;
we call them the two @dfn{flavors} of variables. The two flavors are
distinguished in how they are defined and in what they do when expanded.
There are different ways that a variable in GNU @code{make} can get a value;
we call them the @dfn{flavors} of variables. The flavors are distinguished in
how they handle the values they are assigned in the makefile, and in how those
values are managed when the variable is later used and expanded.
@menu
* Recursive Assignment:: Setting recursively expanded variables.
* Simple Assignment:: Setting simply expanded variables.
* Immediate Assignment:: Setting immediately expanded variables.
* Conditional Assignment:: Assigning variable values conditionally.
@end menu
@node Recursive Assignment, Simple Assignment, Flavors, Flavors
@subsection Recursively Expanded Variable Assignment
@cindex =
The first flavor of variable is a @dfn{recursively expanded} variable.
Variables of this sort are defined by lines using @samp{=}
@ -5417,7 +5444,9 @@ expanded. This makes @code{make} run slower; worse, it causes the
because you cannot easily control when they are called, or even how many
times.
To avoid all the problems and inconveniences of recursively expanded
@node Simple Assignment, Immediate Assignment, Recursive Assignment, Flavors
@subsection Simply Expanded Variable Assignment
To avoid the problems and inconveniences of recursively expanded
variables, there is another flavor: simply expanded variables.
@cindex simply expanded variables
@ -5427,34 +5456,35 @@ variables, there is another flavor: simply expanded variables.
@dfn{Simply expanded variables} are defined by lines using @samp{:=}
or @samp{::=} (@pxref{Setting, ,Setting Variables}). Both forms are
equivalent in GNU @code{make}; however only the @samp{::=} form is
described by the POSIX standard (support for @samp{::=} was added to
the POSIX standard in 2012, so older versions of @code{make} won't
accept this form either).
described by the POSIX standard (support for @samp{::=} is added to
the POSIX standard for POSIX Issue 8).
The value of a simply expanded variable is scanned
once and for all, expanding any references to other variables and
functions, when the variable is defined. The actual value of the simply
expanded variable is the result of expanding the text that you write.
It does not contain any references to other variables; it contains their
values @emph{as of the time this variable was defined}. Therefore,
The value of a simply expanded variable is scanned once, expanding any
references to other variables and functions, when the variable is
defined. Once that expansion is complete the value of the variable is
never expanded again: when the variable is used the value is copied
verbatim as the expansion. If the value contained variable references
the result of the expansion will contain their values @emph{as of the
time this variable was defined}. Therefore,
@example
@group
x := foo
y := $(x) bar
x := later
@end group
@end example
@noindent
is equivalent to
@example
@group
y := foo bar
x := later
@end group
@end example
When a simply expanded variable is referenced, its value is substituted
verbatim.
Here is a somewhat more complicated example, illustrating the use of
@samp{:=} in conjunction with the @code{shell} function.
(@xref{Shell Function, , The @code{shell} Function}.) This example
@ -5503,8 +5533,10 @@ this means you can include leading spaces in a variable value by
protecting them with variable references, like this:
@example
@group
nullstring :=
space := $(nullstring) # end of the line
@end group
@end example
@noindent
@ -5528,6 +5560,84 @@ Here the value of the variable @code{dir} is @w{@samp{/foo/bar }}
(with four trailing spaces), which was probably not the intention.
(Imagine something like @w{@samp{$(dir)/file}} with this definition!)
@node Immediate Assignment, Conditional Assignment, Simple Assignment, Flavors
@subsection Immediately Expanded Variable Assignment
@cindex immediate variable assignment
@cindex variables, immediate assignment
@cindex :::=
Another form of assignment allows for immediate expansion, but unlike simple
assignment the resulting variable is recursive: it will be re-expanded again
on every use. In order to avoid unexpected results, after the value is
immediately expanded it will automatically be quoted: all instances of
@code{$} in the value after expansion will be converted into @code{$$}. This
type of assignment uses the @samp{:::=} operator. For example,
@example
@group
var = first
OUT :::= $(var)
var = second
@end group
@end example
@noindent
results in the @code{OUT} variable containing the text @samp{first}, while here:
@example
@group
var = one$$two
OUT :::= $(var)
var = three$$four
@end group
@end example
@noindent
results in the @code{OUT} variable containing the text @samp{one$$two}. The
value is expanded when the variable is assigned, so the result is the
expansion of the first value of @code{var}, @samp{one$two}; then the value is
re-escaped before the assignment is complete giving the final result of
@samp{one$$two}.
The variable @code{OUT} is thereafter considered a recursive variable, so it
will be re-expanded when it is used.
This seems functionally equivalent to the @samp{:=} / @samp{::=} operators,
but there are a few differences:
First, after assignment the variable is a normal recursive variable; when you
append to it with @samp{+=} the value on the right-hand side is not expanded
immediately. If you prefer the @samp{+=} operator to expand the right-hand
side immediately you should use the @samp{:=} / @samp{::=} assignment instead.
Second, these variables are slightly less efficient than simply expanded
variables since they do need to be re-expanded when they are used, rather than
merely copied. However since all variable references are escaped this
expansion simply un-escapes the value, it won't expand any variables or run
any functions.
Here is another example:
@example
@group
var = one$$two
OUT :::= $(var)
OUT += $(var)
var = three$$four
@end group
@end example
After this, the value of @code{OUT} is the text @samp{one$$two $(var)}. When
this variable is used it will be expanded and the result will be
@samp{one$two three$four}.
This style of assignment is equivalent to the traditional BSD @code{make}
@samp{:=} operator; as you can see it works slightly differently than the GNU
@code{make} @samp{:=} operator. The @code{:::=} operator is added to the
POSIX specification in Issue 8 to provide portability.
@node Conditional Assignment, , Immediate Assignment, Flavors
@subsection Conditional Variable Assignment
@cindex conditional variable assignment
@cindex variables, conditional assignment
@cindex ?=
@ -5632,12 +5742,9 @@ sets @samp{bar} to @samp{a.c b.c l.a c.c}.
@cindex @code{$}, in variable name
@cindex dollar sign (@code{$}), in variable name
Computed variable names are a complicated concept needed only for
sophisticated makefile programming. For most purposes you need not
consider them, except to know that making a variable with a dollar sign
in its name might have strange results. However, if you are the type
that wants to understand everything, or you are actually interested in
what they do, read on.
Computed variable names are an advanced concept, very useful in more
sophisticated makefile programming. In simple situations you need not
consider them, but they can be extremely useful.
Variables may be referenced inside the name of a variable. This is
called a @dfn{computed variable name} or a @dfn{nested variable
@ -5685,10 +5792,9 @@ a := $($(x))
defines @code{a} as @samp{Hello}: @samp{$($(x))} becomes @samp{$($(y))}
which becomes @samp{$(z)} which becomes @samp{Hello}.
Nested variable references can also contain modified references and
function invocations (@pxref{Functions, ,Functions for Transforming Text}),
just like any other reference.
For example, using the @code{subst} function
Nested variable references can also contain modified references and function
invocations (@pxref{Functions, ,Functions for Transforming Text}), just like
any other reference. For example, using the @code{subst} function
(@pxref{Text Functions, ,Functions for String Substitution and Analysis}):
@example
@ -5857,27 +5963,30 @@ Several variables have constant initial values.
@cindex =
@cindex :=
@cindex ::=
@cindex :::=
@cindex ?=
@cindex !=
To set a variable from the makefile, write a line starting with the
variable name followed by @samp{=}, @samp{:=}, or @samp{::=}. Whatever
follows the @samp{=}, @samp{:=}, or @samp{::=} on the line becomes the
value. For example,
To set a variable from the makefile, write a line starting with the variable
name followed by one of the assignment operators @samp{=}, @samp{:=},
@samp{::=}, or @samp{:::=}. Whatever follows the operator and any initial
whitespace on the line becomes the value. For example,
@example
objects = main.o foo.o bar.o utils.o
@end example
@noindent
defines a variable named @code{objects}. Whitespace around the variable
name and immediately after the @samp{=} is ignored.
defines a variable named @code{objects} to contain the value @samp{main.o
foo.o bar.o utils.o}. Whitespace around the variable name and immediately
after the @samp{=} is ignored.
Variables defined with @samp{=} are @dfn{recursively expanded}
variables. Variables defined with @samp{:=} or @samp{::=} are
@dfn{simply expanded} variables; these definitions can contain
variable references which will be expanded before the definition is
made. @xref{Flavors, ,The Two Flavors of Variables}.
Variables defined with @samp{=} are @dfn{recursively expanded} variables.
Variables defined with @samp{:=} or @samp{::=} are @dfn{simply expanded}
variables; these definitions can contain variable references which will be
expanded before the definition is made. Variables defined with @samp{:::=}
are @dfn{immediately expanded} variables. The different assignment operators
are described in @xref{Flavors, ,The Two Flavors of Variables}.
The variable name may contain function and variable references, which
are expanded when the line is read to find the actual variable name to use.
@ -5987,13 +6096,12 @@ originally. @xref{Flavors, ,The Two Flavors of Variables}, for an
explanation of the two flavors of variables.
When you add to a variable's value with @samp{+=}, @code{make} acts
essentially as if you had included the extra text in the initial
definition of the variable. If you defined it first with @samp{:=} or
@samp{::=}, making it a simply-expanded variable, @samp{+=} adds to
that simply-expanded definition, and expands the new text before
appending it to the old value just as @samp{:=} does (see
@ref{Setting, ,Setting Variables}, for a full explanation of
@samp{:=} or @samp{::=}). In fact,
essentially as if you had included the extra text in the initial definition of
the variable. If you defined it first with @samp{:=} or @samp{::=}, making it
a simply-expanded variable, @samp{+=} adds to that simply-expanded definition,
and expands the new text before appending it to the old value just as
@samp{:=} does (see @ref{Setting, ,Setting Variables}, for a full explanation
of @samp{:=} or @samp{::=}). In fact,
@example
variable := value
@ -6010,15 +6118,9 @@ variable := $(variable) more
@end example
On the other hand, when you use @samp{+=} with a variable that you defined
first to be recursively-expanded using plain @samp{=}, @code{make} does
something a bit different. Recall that when you define a
recursively-expanded variable, @code{make} does not expand the value you set
for variable and function references immediately. Instead it stores the text
verbatim, and saves these variable and function references to be expanded
later, when you refer to the new variable (@pxref{Flavors, ,The Two Flavors
of Variables}). When you use @samp{+=} on a recursively-expanded variable,
it is this unexpanded text to which @code{make} appends the new text you
specify.
first to be recursively-expanded using plain @samp{=} or @samp{:::=},
@code{make} appends the un-expanded text to the existing value, whatever it
is. This means that
@example
@group
@ -6247,6 +6349,7 @@ In such situations you may want to use the @code{undefine} directive to
make a variable appear as if it was never set. For example:
@example
@group
foo := foo
bar = bar
@ -6255,6 +6358,7 @@ undefine bar
$(info $(origin foo))
$(info $(flavor bar))
@end group
@end example
This example will print ``undefined'' for both variables.
@ -6342,14 +6446,14 @@ variable only.
Multiple @var{target} values create a target-specific variable value for
each member of the target list individually.
The @var{variable-assignment} can be any valid form of assignment;
recursive (@samp{=}), simple (@samp{:=} or @samp{::=}), appending
(@samp{+=}), or conditional (@samp{?=}). All variables that appear
within the @var{variable-assignment} are evaluated within the context
of the target: thus, any previously-defined target-specific variable
values will be in effect. Note that this variable is actually
distinct from any ``global'' value: the two variables do not have to
have the same flavor (recursive vs.@: simple).
The @var{variable-assignment} can be any valid form of assignment; recursive
(@samp{=}), simple (@samp{:=} or @samp{::=}), immediate (@samp{::=}),
appending (@samp{+=}), or conditional (@samp{?=}). All variables that appear
within the @var{variable-assignment} are evaluated within the context of the
target: thus, any previously-defined target-specific variable values will be
in effect. Note that this variable is actually distinct from any ``global''
value: the two variables do not have to have the same flavor (recursive vs.@:
simple).
Target-specific variables have the same priority as any other makefile
variable. Variables provided on the command line (and in the
@ -12362,6 +12466,7 @@ Here is a summary of the directives GNU @code{make} recognizes:
@itemx define @var{variable} =
@itemx define @var{variable} :=
@itemx define @var{variable} ::=
@itemx define @var{variable} :::=
@itemx define @var{variable} +=
@itemx define @var{variable} ?=
@itemx endef

View file

@ -465,7 +465,6 @@ func_origin (char *o, char **argv, const char *funcname UNUSED)
else
switch (v->origin)
{
default:
case o_invalid:
abort ();
break;

View file

@ -407,7 +407,7 @@ extern int unixy_shell;
#define NONE_SET(_v,_m) (! ANY_SET ((_v),(_m)))
#define MAP_NUL 0x0001
#define MAP_BLANK 0x0002
#define MAP_BLANK 0x0002 /* space, TAB */
#define MAP_NEWLINE 0x0004
#define MAP_COMMENT 0x0008
#define MAP_SEMI 0x0010

View file

@ -1194,7 +1194,6 @@ do_variable_definition (const floc *flocp, const char *varname,
switch (flavor)
{
default:
case f_bogus:
/* Should not be possible. */
abort ();
@ -1205,6 +1204,25 @@ do_variable_definition (const floc *flocp, const char *varname,
target-specific variable. */
p = alloc_value = allocated_variable_expand (value);
break;
case f_expand:
{
/* A POSIX "var :::= value" assignment. Expand the value, then it
becomes a recursive variable. After expansion convert all '$'
tokens to '$$' to resolve to '$' when recursively expanded. */
char *t = allocated_variable_expand (value);
char *np = alloc_value = xmalloc (strlen (t) * 2 + 1);
p = t;
while (p[0] != '\0')
{
if (p[0] == '$')
*(np++) = '$';
*(np++) = *(p++);
}
*np = '\0';
p = alloc_value;
free (t);
break;
}
case f_shell:
{
/* A shell definition "var != value". Expand value, pass it to
@ -1440,8 +1458,8 @@ do_variable_definition (const floc *flocp, const char *varname,
invoked in places where we want to define globally visible variables,
make sure we define this variable in the global set. */
v = define_variable_in_set (varname, strlen (varname), p,
origin, flavor == f_recursive,
v = define_variable_in_set (varname, strlen (varname), p, origin,
flavor == f_recursive || flavor == f_expand,
(target_var
? current_variable_set_list->set : NULL),
flocp);
@ -1456,7 +1474,7 @@ do_variable_definition (const floc *flocp, const char *varname,
/* Parse P (a null-terminated string) as a variable definition.
If it is not a variable definition, return NULL and the contents of *VAR
are undefined, except NAME is set to the first non-space character or NIL.
are undefined, except NAME points to the first non-space character or EOS.
If it is a variable definition, return a pointer to the char after the
assignment token and set the following fields (only) of *VAR:
@ -1468,15 +1486,17 @@ do_variable_definition (const floc *flocp, const char *varname,
*/
char *
parse_variable_definition (const char *p, struct variable *var)
parse_variable_definition (const char *str, struct variable *var)
{
int wspace = 0;
const char *e = NULL;
const char *p = str;
const char *end = NULL;
NEXT_TOKEN (p);
var->name = (char *)p;
var->length = 0;
/* Walk through STR until we find a valid assignment operator. Each time
through this loop P points to the next character to consider. */
while (1)
{
int c = *p++;
@ -1485,26 +1505,112 @@ parse_variable_definition (const char *p, struct variable *var)
if (STOP_SET (c, MAP_COMMENT|MAP_NUL))
return NULL;
if (ISBLANK (c))
{
/* Variable names can't contain spaces so if this is the second set
of spaces we know it's not a variable assignment. */
if (end)
return NULL;
end = p - 1;
NEXT_TOKEN (p);
continue;
}
/* If we found = we're done! */
if (c == '=')
{
if (!end)
end = p - 1;
var->flavor = f_recursive;
break;
}
if (c == ':')
{
if (!end)
end = p - 1;
/* We need to distinguish :=, ::=, and :::=, and : outside of an
assignment (which means this is not a variable definition). */
c = *p++;
if (c == '=')
{
var->flavor = f_simple;
break;
}
if (c == ':')
{
c = *p++;
if (c == '=')
{
var->flavor = f_simple;
break;
}
if (c == ':' && *p++ == '=')
{
var->flavor = f_expand;
break;
}
}
return NULL;
}
/* See if it's one of the other two-byte operators. */
if (*p == '=')
{
switch (c)
{
case '+':
var->flavor = f_append;
break;
case '?':
var->flavor = f_conditional;
break;
case '!':
var->flavor = f_shell;
break;
default:
goto other;
}
if (!end)
end = p - 1;
++p;
break;
}
other:
/* We found a char which is not part of an assignment operator.
If we've seen whitespace, then we know this is not a variable
assignment since variable names cannot contain whitespace. */
if (end)
return NULL;
if (c == '$')
{
/* This begins a variable expansion reference. Make sure we don't
treat chars inside the reference as assignment tokens. */
/* Skip any variable reference, to ensure we don't treat chars
inside the reference as assignment operators. */
char closeparen;
unsigned int count;
c = *p++;
if (c == '(')
closeparen = ')';
else if (c == '{')
closeparen = '}';
else if (c == '\0')
return NULL;
else
/* '$$' or '$X'. Either way, nothing special to do here. */
continue;
switch (c)
{
case '(':
closeparen = ')';
break;
case '{':
closeparen = '}';
break;
case '\0':
return NULL;
default:
/* '$$' or '$X': skip it. */
continue;
}
/* P now points past the opening paren or brace.
Count parens or braces until it is matched. */
/* P now points past the opening paren or brace. Count parens or
braces until we find the closing paren/brace. */
for (count = 1; *p != '\0'; ++p)
{
if (*p == closeparen && --count == 0)
@ -1515,82 +1621,12 @@ parse_variable_definition (const char *p, struct variable *var)
if (*p == c)
++count;
}
continue;
}
/* If we find whitespace skip it, and remember we found it. */
if (ISBLANK (c))
{
wspace = 1;
e = p - 1;
NEXT_TOKEN (p);
c = *p;
if (c == '\0')
return NULL;
++p;
}
if (c == '=')
{
var->flavor = f_recursive;
if (! e)
e = p - 1;
break;
}
/* Match assignment variants (:=, +=, ?=, !=) */
if (*p == '=')
{
switch (c)
{
case ':':
var->flavor = f_simple;
break;
case '+':
var->flavor = f_append;
break;
case '?':
var->flavor = f_conditional;
break;
case '!':
var->flavor = f_shell;
break;
default:
/* If we skipped whitespace, non-assignments means no var. */
if (wspace)
return NULL;
/* Might be assignment, or might be $= or #=. Check. */
continue;
}
if (! e)
e = p - 1;
++p;
break;
}
/* Check for POSIX ::= syntax */
if (c == ':')
{
/* A colon other than :=/::= is not a variable defn. */
if (*p != ':' || p[1] != '=')
return NULL;
/* POSIX allows ::= to be the same as GNU make's := */
var->flavor = f_simple;
if (! e)
e = p - 1;
p += 2;
break;
}
/* If we skipped whitespace, non-assignments means no var. */
if (wspace)
return NULL;
}
var->length = (unsigned int) (e - var->name);
/* We found a valid variable assignment: END points to the char after the
end of the variable name and P points to the char after the =. */
var->length = (unsigned int) (end - var->name);
var->value = next_token (p);
return (char *)p;
}
@ -1690,7 +1726,6 @@ print_variable (const void *item, void *arg)
origin = _("'override' directive");
break;
case o_invalid:
default:
abort ();
}
fputs ("# ", stdout);

View file

@ -35,6 +35,7 @@ enum variable_flavor
f_bogus, /* Bogus (error) */
f_simple, /* Simple definition (:= or ::=) */
f_recursive, /* Recursive definition (=) */
f_expand, /* POSIX :::= assignment */
f_append, /* Appending definition (+=) */
f_conditional, /* Conditional definition (?=) */
f_shell, /* Shell assignment (!=) */

View file

@ -61,7 +61,7 @@ all: ; $(multi)
# TEST 1a: Various new-style define/endef, with no spaces
run_make_test('
run_make_test(q!
FOO = foo
define multi=
@ -77,6 +77,10 @@ define posix::=
@echo $(FOO)
endef
define posixbsd:::=
@echo '$(FOO)$$bar'
endef
append = @echo a
define append+=
@ -97,10 +101,11 @@ FOO = there
all: ; $(multi)
$(simple)
$(posix)
$(posixbsd)
$(append)
$(cond)
',
'', "echo hi\nhi\nthere\nfoo\nfoo\na\nb\nfirst\n");
!,
'', "echo hi\nhi\nthere\nfoo\nfoo\nfoo\$bar\na\nb\nfirst\n");
# TEST 2: define in true section of conditional (containing conditional)

View file

@ -153,4 +153,48 @@ dep: ; @: $(info recur=/$(recur)/ simple=/$(simple)/)
!,
'', "recur=/onetwothree/ simple=/fourfivesix/\n");
# Test POSIX :::=
# This creates a recursive variable, but it expands the RHS first. Any
# variable escapes ('$$') are preserved so that this recursive variable can be
# expanded again without changing its contents.
run_make_test('
bar = Goodbye
foo :::= $(bar)
bar = ${ugh}
ugh = Hello
all: ; @echo $(foo)
',
'', "Goodbye");
# POSIX :::= no spaces
run_make_test(q!
bar = Goodbye
foo:::=$(bar)
bar = ${ugh}
ugh = Hello
all: ; @echo $(foo)
!,
'', "Goodbye");
# Variable escapes ('$$') are preserved.
run_make_test(q!
bar = Good$$bye
foo :::= $(bar) $$what
bar = ${ugh}
ugh = Hello
all: ; @echo '$(foo)'
!,
'', 'Good$bye $what');
# Append works as expected
run_make_test(q!
bar = Good$$bye
foo :::= $(bar)
foo += $$what $(bar)
bar = ${ugh}
ugh = Hello
all: ; @echo '$(foo)'
!,
'', 'Good$bye $what Hello');
1;

View file

@ -43,4 +43,18 @@ run_make_test(undef,
'#MAKEFILE#:4: *** unterminated variable reference. Stop.',
512);
# Whitespace not allowed in variable names
run_make_test('x y =', '',
'#MAKEFILE#:1: *** missing separator. Stop.', 512);
run_make_test('x y=', '',
'#MAKEFILE#:1: *** missing separator. Stop.', 512);
# In theory an empty variable should be ignored, but during parsing it's a
# real token and so this fails. I'm not 100% sure if this is right or not.
run_make_test('x $X=', '',
'#MAKEFILE#:1: *** missing separator. Stop.', 512);
1;