The check in GNU Make 4.3 for suffix rules with prerequisites didn't check
single-suffix rules, only double-suffix rules. Add the missing check.
+* New feature: Any assignment operator can be made conditional
+ GNU Make has long supported the conditional operator "?=" which creates a
+ recursive variable set to a value if and only if the variable is not already
+ defined. In this release, the "?" can precede any assignment operator to
+ make it conditional. For example, "?:=" creates a simply-expanded variable
+ and expands the right-hand side if and only if the variable is not already
+ defined. The constructs "?::=", "?:::=", and "?!=" also behave as expected.
+
* New feature: Unload function for loaded objects
When a loaded object needs to be unloaded by GNU Make, it will invoke an
unload function (if one is defined) beforehand that allows the object to
chapters.
@subheading Variable Assignment
-@cindex +=, expansion
@cindex =, expansion
-@cindex ?=, expansion
-@cindex +=, expansion
+@cindex :=, expansion
+@cindex ::=, expansion
+@cindex :::=, expansion
@cindex !=, expansion
+@cindex +=, expansion
@cindex define, expansion
Variable definitions are parsed as follows:
@example
@var{immediate} = @var{deferred}
-@var{immediate} ?= @var{deferred}
@var{immediate} := @var{immediate}
@var{immediate} ::= @var{immediate}
@var{immediate} :::= @var{immediate-with-escape}
@var{deferred}
endef
-define @var{immediate} ?=
- @var{deferred}
-endef
-
define @var{immediate} :=
@var{immediate}
endef
recursively expanded variable (and will thus be re-evaluated on each
reference).
+@subsubheading Conditional Assignment Modifier
+@cindex ?=, expansion
+@cindex ?:=, expansion
+@cindex ?::=, expansion
+@cindex ?:::=, expansion
+@cindex ?!=, expansion
+
+Adding a conditional modifier (@pxref{Conditional Assignment, ,Conditional
+Variable Assignment}) to an assignment operator does not change the expansion
+style for that operator: if the variable is not defined then the assignment
+will be made using expansion as defined above.
+
+If the variable is already defined, then the right-hand side of the assignment
+is ignored and not expanded.
+
@subheading Conditional Directives
@cindex ifdef, expansion
@cindex ifeq, expansion
@cindex conditional variable assignment
@cindex variables, conditional assignment
@cindex ?=
-There is another assignment operator for variables, @samp{?=}. This
-is called a conditional variable assignment operator, because it only
-has an effect if the variable is not yet defined. This statement:
+@cindex ?:=
+@cindex ?::=
+@cindex ?:::=
+@cindex ?!=
+
+Any assignment operator can be prefixed with a conditional operator, @samp{?}.
+If this modifier is provided then the assignment will proceed normally, but
+@emph{only if} the variable is not already defined.
+
+If the variable is already defined, the assignment is ignored and the
+right-hand side (value) of the assignment is not processed.
+
+For example this statement:
@example
FOO ?= bar
endif
@end example
-Note that a variable set to an empty value is still defined, so
-@samp{?=} will not set that variable.
+More generally a statement of the form:
+
+@example
+NAME ?<op> VALUE
+@end example
+
+will do nothing if the variable @samp{NAME} is defined, and will perform the
+operation @code{NAME <op> VALUE} for any assignment operation @samp{<op>} if
+@samp{NAME} is not defined.
+
+Note that a variable set to an empty value is still defined, so assignments
+modified with @samp{?} will not set that variable.
@node Advanced
@section Advanced Features for Reference to Variables
@cindex :=
@cindex ::=
@cindex :::=
-@cindex ?=
@cindex !=
To set a variable from the makefile, write a line starting with the variable
variable into multiple physical lines for readability
(@pxref{Splitting Lines, ,Splitting Long Lines}).
-Most variable names are considered to have the empty string as a value if
-you have never set them. Several variables have built-in initial values
-that are not empty, but you can set them in the usual ways
-(@pxref{Implicit Variables, ,Variables Used by Implicit Rules}).
-Several special variables are set
-automatically to a new value for each rule; these are called the
-@dfn{automatic} variables (@pxref{Automatic Variables}).
-
-If you'd like a variable to be set to a value only if it's not already
-set, then you can use the shorthand operator @samp{?=} instead of
-@samp{=}. These two settings of the variable @samp{FOO} are identical
-(@pxref{Origin Function, ,The @code{origin} Function}):
-
-@example
-FOO ?= bar
-@end example
-
-@noindent
-and
-
-@example
-ifeq ($(origin FOO), undefined)
-FOO = bar
-endif
-@end example
+Most variable names are considered to have the empty string as a value if you
+have never set them. Several variables have built-in initial values that are
+not empty, but you can set them in the usual ways (@pxref{Implicit Variables,
+,Variables Used by Implicit Rules}). Several special variables are set
+automatically to a new value while running the recipe for a rule; these are
+called the @dfn{automatic} variables (@pxref{Automatic Variables}).
The shell assignment operator @samp{!=} can be used to execute a
shell script and set a variable to its output. This operator first
As with the @code{shell} function, the exit status of the just-invoked
shell script is stored in the @code{.SHELLSTATUS} variable.
+@node Conditionalizing
+@section Conditionally Assigning to Variables
+@cindex ?=
+@cindex ?:=
+@cindex ?::=
+@cindex ?:::=
+@cindex ?!=
+
+Sometimes you want to set a variable but only if it's not already defined.
+One way to do this is to test using the @code{origin} function (@pxref{Origin
+Function}), like this:
+
+@example
+ifeq ($(origin FOO), undefined)
+ FOO = value
+endif
+@end example
+
+However this is a lot to type, and read, and so GNU Make provides a way to
+conditionally assign variables, only if they are not already defined.
+
+To do this, prepend the conditional modifier @samp{?} to the assignment
+operator. In this form @code{make} will first check to see if the variable is
+defined; only if it is not will it proceed to execute the assignment (using
+whichever operator you specified).
+
+Instead of the above example, you get identical behavior by writing:
+
+@example
+FOO ?= value
+@end example
+
+And of course, you can also use:
+
+@example
+FOO ?:= value
+@end example
+
+@noindent
+rather than writing:
+
+@example
+ifeq ($(origin FOO), undefined)
+ FOO := value
+endif
+@end example
+
+The other assignment operators @samp{::=}, @samp{:::=}, and @samp{!=} can also
+be used.
@node Appending
@section Appending More Text to Variables
each member of the target list individually.
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).
+(@samp{=}), simple (@samp{:=} or @samp{::=}), immediate (@samp{::=}), shell
+(@samp{!=}), or appending (@samp{+=}), and may be preceded by a conditional
+modifier (@samp{?}). The variable that appears within the
+@var{variable-assignment} is 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
@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 define @var{variable} ?:=
+@itemx define @var{variable} ?::=
+@itemx define @var{variable} ?:::=
+@itemx define @var{variable} ?!=
+@itemx define @var{variable} +=
@itemx endef
Define multi-line variables.@*
@xref{Multi-Line}.
/* If the load didn't fail, add the file to the .LOADED variable. */
if (r)
- do_variable_definition(flocp, ".LOADED", ldname, o_file, f_append_value, 0);
+ do_variable_definition(flocp, ".LOADED", ldname, o_file, f_append_value, 0, 0);
return r;
}
/* Add this makefile to the list. */
do_variable_definition (&ebuf.floc, "MAKEFILE_LIST", filename, o_file,
- f_append_value, 0);
+ f_append_value, 0, 0);
/* Evaluate the makefile */
record_waiting_files ();
if (vmod.undefine_v)
- {
- do_undefine (p, origin, ebuf);
- continue;
- }
- else if (vmod.define_v)
+ {
+ do_undefine (p, origin, ebuf);
+ continue;
+ }
+ if (vmod.define_v)
v = do_define (p, origin, ebuf);
else
v = try_variable_definition (fstart, p, origin, 0);
p = parse_variable_definition (name, &var);
if (p == NULL)
- /* No assignment token, so assume recursive. */
- var.flavor = f_recursive;
+ {
+ /* No assignment token, so assume recursive. */
+ var.flavor = f_recursive;
+ var.conditional = 0;
+ }
else
{
if (var.value[0] != '\0')
else
definition[idx - 1] = '\0';
- v = do_variable_definition (&defstart, name,
- definition, origin, var.flavor, 0);
+ v = do_variable_definition (&defstart, name, definition,
+ origin, var.flavor, var.conditional, 0);
free (definition);
free (n);
return (v);
/* Now add each dir to the .INCLUDE_DIRS variable. */
- do_variable_definition (NILF, ".INCLUDE_DIRS", "", o_default, f_simple, 0);
+ do_variable_definition (NILF, ".INCLUDE_DIRS", "", o_default, f_simple, 0, 0);
for (cpp = dirs; *cpp != 0; ++cpp)
do_variable_definition (NILF, ".INCLUDE_DIRS", *cpp,
- o_default, f_append, 0);
+ o_default, f_append, 0, 0);
free ((void *) include_directories);
include_directories = dirs;
v = do_variable_definition (
&p->variable.fileinfo, p->variable.name,
p->variable.value, p->variable.origin,
- p->variable.flavor, 1);
+ p->variable.flavor, p->variable.conditional, 1);
}
/* Also mark it as a per-target and copy export status. */
See the try_variable_definition() function for details on the parameters. */
struct variable *
-do_variable_definition (const floc *flocp, const char *varname,
- const char *value, enum variable_origin origin,
- enum variable_flavor flavor, int target_var)
+do_variable_definition (const floc *flocp, const char *varname, const char *value,
+ enum variable_origin origin, enum variable_flavor flavor,
+ int conditional, int target_var)
{
const char *newval;
char *alloc_value = NULL;
struct variable *v;
int append = 0;
- int conditional = 0;
+
+ /* Conditional variable definition: only set if the var is not defined. */
+ if (conditional)
+ {
+ v = lookup_variable (varname, strlen (varname));
+ if (v)
+ return v;
+ }
/* Calculate the variable's new value in VALUE. */
newval = alloc_value;
break;
}
- case f_conditional:
- /* A conditional variable definition "var ?= value".
- The value is set IFF the variable is not defined yet. */
- v = lookup_variable (varname, strlen (varname));
- if (v)
- goto done;
-
- conditional = 1;
- flavor = f_recursive;
- /* FALLTHROUGH */
case f_recursive:
/* A recursive variable definition "var = value".
The value is used verbatim. */
If it is a variable definition, return a pointer to the char after the
assignment token and set the following fields (only) of *VAR:
- name : name of the variable (ALWAYS SET) (NOT NUL-TERMINATED!)
- length : length of the variable name
- value : value of the variable (nul-terminated)
- flavor : flavor of the variable
+ name : name of the variable (ALWAYS SET) (NOT NUL-TERMINATED!)
+ length : length of the variable name
+ value : value of the variable (nul-terminated)
+ flavor : flavor of the variable
+ conditional : whether it's a conditional assignment
Other values in *VAR are unchanged.
*/
NEXT_TOKEN (p);
var->name = (char *)p;
var->length = 0;
+ var->conditional = 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)
{
+ const char *start;
int c = *p++;
/* If we find a comment or EOS, it's not a variable definition. */
continue;
}
+ /* This is the start of a token. */
+ start = p - 1;
+
+ /* If we see a ? then it could be a conditional assignment. */
+ if (c == '?')
+ {
+ var->conditional = 1;
+ c = *p++;
+ }
+
/* If we found = we're done! */
if (c == '=')
{
if (!end)
- end = p - 1;
- var->flavor = f_recursive;
+ end = start;
+ var->flavor = f_recursive; /* = */
break;
}
if (c == ':')
{
if (!end)
- end = p - 1;
+ end = start;
/* We need to distinguish :=, ::=, and :::=, versus : outside of an
assignment (which means this is not a variable definition). */
c = *p++;
if (c == '=')
{
- var->flavor = f_simple;
+ var->flavor = f_simple; /* := */
break;
}
if (c == ':')
c = *p++;
if (c == '=')
{
- var->flavor = f_simple;
+ var->flavor = f_simple; /* ::= */
break;
}
if (c == ':' && *p++ == '=')
{
- var->flavor = f_expand;
+ var->flavor = f_expand; /* :::= */
break;
}
}
switch (c)
{
case '+':
- var->flavor = f_append;
- break;
- case '?':
- var->flavor = f_conditional;
+ var->flavor = f_append; /* += */
break;
case '!':
- var->flavor = f_shell;
+ var->flavor = f_shell; /* != */
break;
default:
goto other;
}
if (!end)
- end = p - 1;
+ end = start;
++p;
break;
}
if (c == '$')
p = skip_reference (p);
+
+ var->conditional = 0;
}
/* 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;
}
\f
return 0;
vp = do_variable_definition (flocp, v.name, v.value,
- origin, v.flavor, target_var);
+ origin, v.flavor, v.conditional, target_var);
free (v.name);
f_recursive, /* Recursive definition (=) */
f_expand, /* POSIX :::= assignment */
f_append, /* Appending definition (+=) */
- f_conditional, /* Conditional definition (?=) */
f_shell, /* Shell assignment (!=) */
f_append_value /* Append unexpanded value */
};
const char *name, const char *value,
enum variable_origin origin,
enum variable_flavor flavor,
- int target_var);
+ int conditional, int target_var);
char *parse_variable_definition (const char *line,
struct variable *v);
struct variable *assign_variable_definition (struct variable *v, const char *line);
--- /dev/null
+# -*-perl-*-
+
+$description = "Test various flavors of conditional variable setting.";
+
+$details = "";
+
+# Test ?=
+
+run_make_test(q!
+x = bar
+y = baz
+foo ?= $(x)
+biz?=$(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=10 biz=20");
+
+run_make_test(q!
+foo=1
+biz=2
+x = bar
+y = baz
+foo ?= $(x)
+biz?=$(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=1 biz=2");
+
+# Test ?:=
+
+run_make_test(q!
+x = bar
+y = baz
+foo ?:= $(x)
+biz?:=$(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=bar biz=baz");
+
+run_make_test(q!
+foo=1
+biz=2
+x = bar
+y = baz
+foo ?:= $(x)$(info expanded)
+biz?:=$(y)$(info expanded)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=1 biz=2");
+
+# Test ?::=
+
+run_make_test(q!
+x = bar
+y = baz
+foo ?::= $(x)
+biz?::=$(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=bar biz=baz");
+
+run_make_test(q!
+foo=1
+biz=2
+x = bar
+y = baz
+foo ?::= $(x)$(info expanded)
+biz?::=$(y)$(info expanded)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=1 biz=2");
+
+# Test ?:::=
+
+run_make_test(q!
+x = bar
+y = baz
+foo ?:::= $(x)
+biz?:::=$(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=bar biz=baz");
+
+run_make_test(q!
+foo=1
+biz=2
+x = bar
+y = baz
+foo ?:::= $(x)$(info expanded)
+biz?:::=$(y)$(info expanded)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+!,
+ '', "foo=1 biz=2");
+
+# Test ?!=
+
+run_make_test(q/
+x = bar
+y = baz
+foo ?!= echo $(x)
+biz?!=echo $(y)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+/,
+ '', "foo=bar biz=baz");
+
+run_make_test(q/
+foo=1
+biz=2
+x = bar
+y = baz
+foo ?!= echo $(x)$(info expanded)
+biz?!=echo $(y)$(info expanded)
+x = 10
+y = 20
+all:;@: $(info foo=$(foo) biz=$(biz))
+/,
+ '', "foo=1 biz=2");
+
+1;