diff --git a/NEWS b/NEWS index 7d296ac7..676794d6 100644 --- a/NEWS +++ b/NEWS @@ -41,6 +41,14 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=111&se 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 diff --git a/doc/make.texi b/doc/make.texi index 21bee987..b2e0141c 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -1572,18 +1572,18 @@ reference this section as you become familiar with them, in later 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} @@ -1598,10 +1598,6 @@ define @var{immediate} = @var{deferred} endef -define @var{immediate} ?= - @var{deferred} -endef - define @var{immediate} := @var{immediate} endef @@ -1638,6 +1634,21 @@ in the variable named on the left, and that variable is considered a 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 @@ -5822,9 +5833,19 @@ POSIX specification in Issue 8 to provide portability. @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 @@ -5840,8 +5861,18 @@ ifeq ($(origin FOO), undefined) 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 ? VALUE +@end example + +will do nothing if the variable @samp{NAME} is defined, and will perform the +operation @code{NAME VALUE} for any assignment operation @samp{} 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 @@ -6142,7 +6173,6 @@ Several variables have constant initial values. @cindex := @cindex ::= @cindex :::= -@cindex ?= @cindex != To set a variable from the makefile, write a line starting with the variable @@ -6174,31 +6204,12 @@ amount of memory on the computer. You can split the value of a 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 @@ -6229,6 +6240,55 @@ var := $(shell find . -name "*.c") 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 @@ -6625,13 +6685,13 @@ 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{::=}), 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 @@ -13069,8 +13129,13 @@ 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 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}. diff --git a/src/load.c b/src/load.c index b76ddb01..626c34f0 100644 --- a/src/load.c +++ b/src/load.c @@ -230,7 +230,7 @@ load_file (const floc *flocp, struct file *file, int noerror) /* 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; } diff --git a/src/read.c b/src/read.c index a6ee0450..069f9c30 100644 --- a/src/read.c +++ b/src/read.c @@ -413,7 +413,7 @@ eval_makefile (const char *filename, unsigned short flags) /* 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 */ @@ -733,11 +733,11 @@ eval (struct ebuffer *ebuf, int set_default) 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); @@ -1395,8 +1395,11 @@ do_define (char *name, enum variable_origin origin, struct ebuffer *ebuf) 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') @@ -1480,8 +1483,8 @@ do_define (char *name, enum variable_origin origin, struct ebuffer *ebuf) 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); @@ -2954,10 +2957,10 @@ construct_include_path (const char **arg_dirs) /* 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; diff --git a/src/variable.c b/src/variable.c index 8b63df2d..5cd28bf7 100644 --- a/src/variable.c +++ b/src/variable.c @@ -708,7 +708,7 @@ initialize_file_variables (struct file *file, int reading) 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. */ @@ -1370,15 +1370,22 @@ shell_result (const char *p) 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. */ @@ -1421,16 +1428,6 @@ do_variable_definition (const floc *flocp, const char *varname, 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. */ @@ -1680,10 +1677,11 @@ do_variable_definition (const floc *flocp, const char *varname, 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. */ @@ -1696,11 +1694,13 @@ parse_variable_definition (const char *str, struct variable *var) 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. */ @@ -1718,26 +1718,36 @@ parse_variable_definition (const char *str, struct variable *var) 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 == ':') @@ -1745,12 +1755,12 @@ parse_variable_definition (const char *str, struct variable *var) 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; } } @@ -1763,20 +1773,17 @@ parse_variable_definition (const char *str, struct variable *var) 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; } @@ -1790,12 +1797,15 @@ parse_variable_definition (const char *str, struct variable *var) 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; } @@ -1854,7 +1864,7 @@ try_variable_definition (const floc *flocp, const char *line, 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); diff --git a/src/variable.h b/src/variable.h index a82b512c..0b84c2e5 100644 --- a/src/variable.h +++ b/src/variable.h @@ -39,7 +39,6 @@ enum variable_flavor 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 */ }; @@ -172,7 +171,7 @@ struct variable *do_variable_definition (const floc *flocp, 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); diff --git a/tests/scripts/variables/conditional b/tests/scripts/variables/conditional new file mode 100644 index 00000000..eb3424cd --- /dev/null +++ b/tests/scripts/variables/conditional @@ -0,0 +1,137 @@ +# -*-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;