summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--ChangeLog17
-rw-r--r--NEWS9
-rw-r--r--doc/make.texi50
-rw-r--r--function.c36
-rw-r--r--read.c5
-rw-r--r--tests/ChangeLog4
-rw-r--r--tests/scripts/features/shell_assignment65
-rw-r--r--variable.c38
-rw-r--r--variable.h5
10 files changed, 213 insertions, 17 deletions
diff --git a/AUTHORS b/AUTHORS
index 8de80e6..87b077c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -61,6 +61,7 @@ Other contributors:
Carl Staelin (Princeton University)
Ian Stewartson (Data Logic Limited)
Ramon Garcia Fernandez <ramon.garcia.f@gmail.com>
+ David A. Wheeler <dwheeler@dwheeler.com>
With suggestions/comments/bug reports from a cast of ... well ...
hundreds, anyway :)
diff --git a/ChangeLog b/ChangeLog
index 4c16994..7d2155a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2011-04-17 David A. Wheeler <dwheeler@dwheeler.com>
+
+ * doc/make.texi (Reading Makefiles): Document "!=".
+ (Setting): Ditto.
+ (Features): Ditto.
+ * variable.h (enum variable_flavor): New type "f_shell".
+ * variable.c (shell_result): Send a string to the shell and store
+ the output.
+ (do_variable_definition): Handle f_shell variables: expand the
+ value, then send it to the shell and store the result.
+ (parse_variable_definition): Parse "!=" shell assignments.
+ * read.c (get_next_mword): Treat "!=" as a varassign word.
+ * function.c (fold_newlines): If trim_newlines is set remove all
+ trailing newlines; otherwise remove only the last newline.
+ (func_shell_base): Move the guts of the shell function here.
+ (func_shell): Call func_shell_base().
+
2011-02-21 Paul Smith <psmith@gnu.org>
* strcache.c (various): Increase performance based on comments
diff --git a/NEWS b/NEWS
index a421375..6cf9a57 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,6 @@
GNU make NEWS -*-indented-text-*-
History of user-visible changes.
- 29 August 2010
+ 17 April 2011
See the end of this file for copyrights and conditions.
@@ -22,6 +22,13 @@ http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=101&set
multiple consecutive backslash/newlines do not condense into one space.
* In recipes, a recipe prefix following a backslash-newlines is removed.
+* New feature: "!=" shell assignment operator as an alternative to the
+ $(shell ...) function. Implemented for portability of BSD makefiles.
+ WARNING: Backward-incompatibility!
+ Variables ending in "!" previously defined as "variable!= value" will now be
+ interpreted as shell assignment. Change your assignment to add whitespace
+ between the "!" and "=": "variable! = value"
+
* New command line option: --trace enables tracing of targets. When enabled
the recipe to be invoked is printed even if it would otherwise be suppressed
by .SILENT or a "@" prefix character. Also before each recipe is run the
diff --git a/doc/make.texi b/doc/make.texi
index 2965540..b62fee0 100644
--- a/doc/make.texi
+++ b/doc/make.texi
@@ -1305,7 +1305,7 @@ specified by the existing contents of @file{mfile}.
Sometimes it is useful to have a makefile that is mostly just like
another makefile. You can often use the @samp{include} directive to
include one in the other, and add more targets or variable definitions.
-However, it is illegal for two makefiles to give different recipes for
+However, it is invalid for two makefiles to give different recipes for
the same target. But there is another way.
@cindex match-anything rule, used to override
@@ -1379,6 +1379,7 @@ chapters.
@cindex =, expansion
@cindex ?=, expansion
@cindex +=, expansion
+@cindex !=, expansion
@cindex define, expansion
Variable definitions are parsed as follows:
@@ -1388,6 +1389,7 @@ Variable definitions are parsed as follows:
@var{immediate} ?= @var{deferred}
@var{immediate} := @var{immediate}
@var{immediate} += @var{deferred} or @var{immediate}
+@var{immediate} != @var{immediate}
define @var{immediate}
@var{deferred}
@@ -1408,12 +1410,21 @@ endef
define @var{immediate} +=
@var{deferred} or @var{immediate}
endef
+
+define @var{immediate} !=
+ @var{immediate}
+endef
@end example
For the append operator, @samp{+=}, the right-hand side is considered
immediate if the variable was previously set as a simple variable
(@samp{:=}), and deferred otherwise.
+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 becomes a simple variable
+(and will thus be re-evaluated on each reference).
+
@subheading Conditional Directives
@cindex ifdef, expansion
@cindex ifeq, expansion
@@ -5402,6 +5413,7 @@ Several variables have constant initial values.
@cindex =
@cindex :=
@cindex ?=
+@cindex !=
To set a variable from the makefile, write a line starting with the
variable name followed by @samp{=} or @samp{:=}. Whatever follows the
@@ -5457,6 +5469,33 @@ FOO = bar
endif
@end example
+The shell assignment operator @samp{!=} can be used to execute a
+program and set a variable to its output. This operator first
+evaluates the right-hand side, then passes that result to the shell
+for execution. If the result of the execution ends in a newline, that
+one newline is removed; all other newlines are replaced by spaces.
+The resulting string is then placed into the named
+recursively-expanded variable. For example:
+
+@example
+hash != printf '\043'
+file_list != find . -name '*.c'
+@end example
+
+If the result of the execution could produce a @code{$}, and you don't
+intend what follows that to be interpreted as a make variable or
+function reference, then you must replace every @code{$} with
+@code{$$} as part of the execution. Alternatively, you can set a
+simply expanded variable to the result of running a program using the
+@code{shell} function call. @xref{Shell Function, , The @code{shell}
+Function}. For example:
+
+@example
+hash := $(shell printf '\043')
+var := $(shell find . -name "*.c")
+@end example
+
+
@node Appending, Override Directive, Setting, Using Variables
@section Appending More Text to Variables
@cindex +=
@@ -5977,7 +6016,7 @@ prog: a.o b.o
Due to the @code{private} modifier, @code{a.o} and @code{b.o} will not
inherit the @code{EXTRA_CFLAGS} variable assignment from the
-@code{progs} target.
+@code{prog} target.
@node Special Variables, , Suppressing Inheritance, Using Variables
@comment node-name, next, previous, up
@@ -6073,7 +6112,7 @@ foo
@end example
Note that assigning more than one target name to @code{.DEFAULT_GOAL} is
-illegal and will result in an error.
+invalid and will result in an error.
@vindex MAKE_RESTARTS @r{(number of times @code{make} has restarted)}
@item MAKE_RESTARTS
@@ -10444,6 +10483,11 @@ nonexistent file comes from SunOS 4 @code{make}. (But note that SunOS 4
@code{make} does not allow multiple makefiles to be specified in one
@code{-include} directive.) The same feature appears with the name
@code{sinclude} in SGI @code{make} and perhaps others.
+
+@item
+The @code{!=} shell assignment operator exists in many BSD of
+@code{make} and is purposefully implemented here to behave identically
+to those implementations.
@end itemize
The remaining features are inventions new in GNU @code{make}:
diff --git a/function.c b/function.c
index 2bc61fe..613b8ba 100644
--- a/function.c
+++ b/function.c
@@ -1393,14 +1393,14 @@ func_value (char *o, char **argv, const char *funcname UNUSED)
}
/*
- \r is replaced on UNIX as well. Is this desirable?
+ \r is replaced on UNIX as well. Is this desirable?
*/
static void
-fold_newlines (char *buffer, unsigned int *length)
+fold_newlines (char *buffer, unsigned int *length, int trim_newlines)
{
char *dst = buffer;
char *src = buffer;
- char *last_nonnl = buffer -1;
+ char *last_nonnl = buffer - 1;
src[*length] = 0;
for (; *src != '\0'; ++src)
{
@@ -1416,6 +1416,10 @@ fold_newlines (char *buffer, unsigned int *length)
*dst++ = *src;
}
}
+
+ if (!trim_newlines && (last_nonnl < (dst - 2)))
+ last_nonnl = dst - 2;
+
*(++last_nonnl) = '\0';
*length = last_nonnl - buffer;
}
@@ -1578,12 +1582,20 @@ msdos_openpipe (int* pipedes, int *pidp, char *text)
#ifdef VMS
/* VMS can't do $(shell ...) */
+
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
+{
+ fprintf (stderr, "This platform does not support shell\n");
+ die (EXIT_FAILURE);
+}
+
#define func_shell 0
#else
#ifndef _AMIGA
-static char *
-func_shell (char *o, char **argv, const char *funcname UNUSED)
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
{
char *batch_filename = NULL;
@@ -1762,7 +1774,7 @@ func_shell (char *o, char **argv, const char *funcname UNUSED)
{
/* The child finished normally. Replace all newlines in its output
with spaces, and put that in the variable output buffer. */
- fold_newlines (buffer, &i);
+ fold_newlines (buffer, &i, trim_newlines);
o = variable_buffer_output (o, buffer, i);
}
@@ -1776,8 +1788,8 @@ func_shell (char *o, char **argv, const char *funcname UNUSED)
/* Do the Amiga version of func_shell. */
-static char *
-func_shell (char *o, char **argv, const char *funcname)
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
{
/* Amiga can't fork nor spawn, but I can start a program with
redirection of my choice. However, this means that we
@@ -1854,12 +1866,18 @@ func_shell (char *o, char **argv, const char *funcname)
Close (child_stdout);
- fold_newlines (buffer, &i);
+ fold_newlines (buffer, &i, trim_newlines);
o = variable_buffer_output (o, buffer, i);
free (buffer);
return o;
}
#endif /* _AMIGA */
+
+char *
+func_shell (char *o, char **argv, const char *funcname UNUSED)
+{
+ return func_shell_base (o, argv, 1);
+}
#endif /* !VMS */
#ifdef EXPERIMENTAL
diff --git a/read.c b/read.c
index 761e976..c13ead8 100644
--- a/read.c
+++ b/read.c
@@ -2463,7 +2463,7 @@ readline (struct ebuffer *ebuf)
w_colon A colon
w_dcolon A double-colon
w_semicolon A semicolon
- w_varassign A variable assignment operator (=, :=, +=, or ?=)
+ w_varassign A variable assignment operator (=, :=, +=, ?=, or !=)
Note that this function is only used when reading certain parts of the
makefile. Don't use it where special rules hold sway (RHS of a variable,
@@ -2514,6 +2514,7 @@ get_next_mword (char *buffer, char *delim, char **startp, unsigned int *length)
case '+':
case '?':
+ case '!':
if (*p == '=')
{
++p;
@@ -2533,7 +2534,7 @@ get_next_mword (char *buffer, char *delim, char **startp, unsigned int *length)
/* This is some non-operator word. A word consists of the longest
string of characters that doesn't contain whitespace, one of [:=#],
- or [?+]=, or one of the chars in the DELIM string. */
+ or [?+!]=, or one of the chars in the DELIM string. */
/* We start out assuming a static word; if we see a variable we'll
adjust our assumptions then. */
diff --git a/tests/ChangeLog b/tests/ChangeLog
index b13e2ae..3fdf5ca 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,7 @@
+2011-04-17 David A. Wheeler <dwheeler@dwheeler.com>
+
+ * scripts/features/shell_assignment: Regression for "!=" feature
+
2010-11-06 Paul Smith <psmith@gnu.org>
* scripts/features/targetvars: Fix known-good output for BS/NL changes.
diff --git a/tests/scripts/features/shell_assignment b/tests/scripts/features/shell_assignment
new file mode 100644
index 0000000..686e4bd
--- /dev/null
+++ b/tests/scripts/features/shell_assignment
@@ -0,0 +1,65 @@
+# -*-perl-*-
+
+$description = "Test BSD-style shell assignments (VAR != VAL) for variables.";
+
+$details = "";
+
+# TEST 0: Basic shell assignment (!=).
+
+run_make_test('
+.POSIX:
+
+demo1!=printf \' 1 2 3\n4\n\n5 \n \n 6\n\n\n\n\'
+demo2 != printf \'7 8\n \'
+demo3 != printf \'$$(demo2)\'
+demo4 != printf \' 2 3 \n\'
+demo5 != printf \' 2 3 \n\n\'
+all: ; @echo "<$(demo1)> <$(demo2)> <$(demo3)> <$(demo4)> <${demo5}>"
+',
+ '', "< 1 2 3 4 5 6 > <7 8 > <7 8 > < 2 3 > < 2 3 >\n");
+
+# TEST 1: Handle '#' the same way as BSD make
+
+run_make_test('
+foo1!=echo bar#baz
+hash != printf \'\043\'
+foo2!= echo "bar$(hash)baz"
+
+all: ; @echo "<$(foo1)> <$(hash)> <$(foo2)>"
+',
+ '', "<bar> <#> <bar#baz>\n");
+
+# TEST 2: shell assignment variables (from !=) should be recursive.
+# Note that variables are re-evaluated later, so the shell can output
+# a value like $(XYZZY) as part of !=. The $(XYZZY) will be EVALUATED
+# when the value containing it is evaluated. On the negative side, this
+# means if you don't want this, you need to escape dollar signs as $$.
+# On the positive side, it means that shell programs can output macros
+# that are then evaluated as they are traditionally evaluated.. and that
+# you can use traditional macro evaluation semantics to implement !=.
+
+run_make_test('
+XYZZY = fiddle-dee-dee
+dollar = $$
+VAR3 != printf \'%s\' \'$(dollar)(XYZZY)\'
+
+all: ; @echo "<$(VAR3)>"
+',
+ '', "<fiddle-dee-dee>\n");
+
+
+# TEST 3: Overrides invoke shell anyway; they just don't store the result
+# in a way that is visible.
+
+run_make_test('
+
+override != echo abc > ,abc ; cat ,abc
+
+all: ; @echo "<$(override)>" ; cat ,abc
+',
+ 'override=xyz', "<xyz>\nabc\n");
+
+unlink(',abc');
+
+
+1;
diff --git a/variable.c b/variable.c
index b699088..d0b0b0a 100644
--- a/variable.c
+++ b/variable.c
@@ -1111,6 +1111,29 @@ set_special_var (struct variable *var)
return var;
}
+/* Given a string, shell-execute it and return a malloc'ed string of the
+ * result. This removes only ONE newline (if any) at the end, for maximum
+ * compatibility with the *BSD makes. If it fails, returns NULL. */
+
+char *
+shell_result (const char *p)
+{
+ char *buf;
+ unsigned int len;
+ char *args[2];
+ char *result;
+
+ install_variable_buffer (&buf, &len);
+
+ args[0] = (char *) p;
+ args[1] = NULL;
+ variable_buffer_output (func_shell_base (variable_buffer, args, 0), "\0", 1);
+ result = strdup (variable_buffer);
+
+ restore_variable_buffer (buf, len);
+ return result;
+}
+
/* Given a variable, a value, and a flavor, define the variable.
See the try_variable_definition() function for details on the parameters. */
@@ -1140,6 +1163,16 @@ do_variable_definition (const struct floc *flocp, const char *varname,
target-specific variable. */
p = alloc_value = allocated_variable_expand (value);
break;
+ case f_shell:
+ {
+ /* A shell definition "var != value". Expand value, pass it to
+ the shell, and store the result in recursively-expanded var. */
+ char *q = allocated_variable_expand (value);
+ p = alloc_value = shell_result (q);
+ free (q);
+ flavor = f_recursive;
+ break;
+ }
case f_conditional:
/* A conditional variable definition "var ?= value".
The value is set IFF the variable is not defined yet. */
@@ -1432,7 +1465,7 @@ parse_variable_definition (const char *p, enum variable_flavor *flavor)
return (char *)p;
}
- /* Match assignment variants (:=, +=, ?=) */
+ /* Match assignment variants (:=, +=, ?=, !=) */
if (*p == '=')
{
switch (c)
@@ -1446,6 +1479,9 @@ parse_variable_definition (const char *p, enum variable_flavor *flavor)
case '?':
*flavor = f_conditional;
break;
+ case '!':
+ *flavor = f_shell;
+ break;
default:
/* If we skipped whitespace, non-assignments means no var. */
if (wspace)
diff --git a/variable.h b/variable.h
index 04ca074..e930279 100644
--- a/variable.h
+++ b/variable.h
@@ -38,7 +38,8 @@ enum variable_flavor
f_simple, /* Simple definition (:=) */
f_recursive, /* Recursive definition (=) */
f_append, /* Appending definition (+=) */
- f_conditional /* Conditional definition (?=) */
+ f_conditional, /* Conditional definition (?=) */
+ f_shell /* Shell assignment (!=) */
};
/* Structure that represents one variable definition.
@@ -134,6 +135,8 @@ char *patsubst_expand_pat (char *o, const char *text, const char *pattern,
const char *replace, const char *pattern_percent,
const char *replace_percent);
char *patsubst_expand (char *o, const char *text, char *pattern, char *replace);
+char *func_shell_base (char *o, char **argv, int trim_newlines);
+
/* expand.c */
char *recursively_expand_for_file (struct variable *v, struct file *file);