This is a new extension to make. I don't think it will hinder portability
all that much, since != is already an expansion.

VAR != cmd

executes cmd on the spot, and replaces the output into VAR. This is a
convenient feature, but it is rather expensive, since each such VAR
will require the execution of an external shell.

In many cases, it is superior to evaluating stuff through the shell
using `` (or $() ), because make will echo the variable value, so you
actually see the result. e.g.,

SYSDIR != cd ${.CURDIR}/../../../..; pwd
CONFDIR != cd ${.CURDIR}/../../conf; pwd

config:
        config -b ${.OBJDIR} -s ${SYSDIR} ${CONFDIR}/${.CURDIR:T}

will echo a line with everything fully expanded.

I propose that

VAR !!= cmd

had almost the same semantics:
- still expand the line on-the-spot (no way to determine where to expand
it otherwise)
- only run it through the shell the first time the variable value is
needed.

Making things "lazy" all the time would also be possible, but it would
change the semantics of things (e.g., you may do tricks like
VAR != some cmd with side effects
because you want the side effects, and then that would break.

I'm now looking for okays...

Index: make.1
===================================================================
RCS file: /cvs/src/usr.bin/make/make.1,v
retrieving revision 1.120
diff -u -p -r1.120 make.1
--- make.1      13 Mar 2015 19:58:41 -0000      1.120
+++ make.1      20 Oct 2016 13:16:05 -0000
@@ -532,11 +532,23 @@ Normally, expansion is not done until th
 .It Ic \&!=
 Expand the value and pass it to the shell for execution and assign
 the result to the variable.
-Any newlines in the result are replaced with spaces
+newlines in the result are also replaced with spaces
 .Po
 .Bx
 extension
 .Pc .
+.It Ic \&!!=
+Expand the value on the spot, pass it to the shell for execution only when
+the value is needed, and assign the result to the variable.
+.Pp
+This is almost identical to
+.Ic \&!=
+except that a shell is only run when the variable value is needed.
+Any newlines in the result are replaced with spaces
+.Po
+.Ox
+extension
+.Pc .
 .El
 .Pp
 Any whitespace before the assigned
@@ -557,6 +569,12 @@ and put its output into
 if
 .Va A
 is not yet defined.
+.Pp
+Combinations that do not make sense, such as
+.Bd -literal -offset indent
+A +!!= cmd
+.Ed
+will not work.
 .Pp
 Variables are expanded by surrounding the variable name with either
 curly braces
Index: parse.c
===================================================================
RCS file: /cvs/src/usr.bin/make/parse.c,v
retrieving revision 1.116
diff -u -p -r1.116 parse.c
--- parse.c     13 May 2016 12:18:11 -0000      1.116
+++ parse.c     20 Oct 2016 13:16:05 -0000
@@ -1322,7 +1322,7 @@ handle_poison(const char *line)
                    line);
                return false;
        } else {
-               Var_MarkPoisoned(name, ename, type);
+               Var_Mark(name, ename, type);
                return true;
        }
 }
Index: parsevar.c
===================================================================
RCS file: /cvs/src/usr.bin/make/parsevar.c,v
retrieving revision 1.15
diff -u -p -r1.15 parsevar.c
--- parsevar.c  22 Nov 2013 15:47:35 -0000      1.15
+++ parsevar.c  20 Oct 2016 13:16:05 -0000
@@ -79,6 +79,8 @@ parse_variable_assignment(const char *li
 #define VAR_APPEND     2
 #define VAR_SHELL      4
 #define VAR_OPT                8
+#define VAR_LAZYSHELL  16
+#define VAR_SUNSHELL   32
        int type;
        struct Name name;
 
@@ -90,11 +92,14 @@ parse_variable_assignment(const char *li
 
        type = VAR_NORMAL;
 
+       /* double operators (except for :) are forbidden */
+       /* OPT and APPEND don't match */
+       /* APPEND and LAZYSHELL can't really work */
        while (*arg != '=') {
                /* Check operator type.  */
                switch (*arg++) {
                case '+':
-                       if (type & (VAR_OPT|VAR_APPEND))
+                       if (type & (VAR_OPT|VAR_LAZYSHELL|VAR_APPEND))
                                type = VAR_INVALID;
                        else
                                type |= VAR_APPEND;
@@ -110,7 +115,7 @@ parse_variable_assignment(const char *li
                case ':':
                        if (FEATURES(FEATURE_SUNSHCMD) &&
                            strncmp(arg, "sh", 2) == 0) {
-                               type = VAR_SHELL;
+                               type = VAR_SUNSHELL;
                                arg += 2;
                                while (*arg != '=' && *arg != '\0')
                                        arg++;
@@ -124,6 +129,11 @@ parse_variable_assignment(const char *li
 
                case '!':
                        if (type & VAR_SHELL)
+                               if (type & (VAR_APPEND))
+                                       type = VAR_INVALID;
+                               else
+                                       type = VAR_LAZYSHELL;
+                       else if (type & (VAR_LAZYSHELL|VAR_SUNSHELL))
                                type = VAR_INVALID;
                        else
                                type |= VAR_SHELL;
@@ -147,7 +157,7 @@ parse_variable_assignment(const char *li
                VarName_Free(&name);
                return true;
        }
-       if (type & VAR_SHELL) {
+       if (type & (VAR_SHELL|VAR_SUNSHELL)) {
                char *err;
 
                if (strchr(arg, '$') != NULL) {
@@ -164,6 +174,13 @@ parse_variable_assignment(const char *li
                        Parse_Error(PARSE_WARNING, err, arg);
                arg = res1;
        }
+       if (type & VAR_LAZYSHELL) {
+               if (strchr(arg, '$') != NULL) {
+                       /* There's a dollar sign in the command, so perform
+                        * variable expansion on the whole thing. */
+                       arg = Var_Subst(arg, NULL, true);
+               }
+       }
        if (type & VAR_SUBST) {
                /*
                 * Allow variables in the old value to be undefined, but leave
@@ -195,6 +212,8 @@ parse_variable_assignment(const char *li
                Var_Appendi_with_ctxt(name.s, name.e, arg, ctxt);
        else
                Var_Seti_with_ctxt(name.s, name.e, arg, ctxt);
+       if (type & VAR_LAZYSHELL)
+               Var_Mark(name.s, name.e, VAR_EXEC_LATER);
 
        VarName_Free(&name);
        free(res2);
Index: var.c
===================================================================
RCS file: /cvs/src/usr.bin/make/var.c,v
retrieving revision 1.99
diff -u -p -r1.99 var.c
--- var.c       27 Sep 2015 16:58:16 -0000      1.99
+++ var.c       20 Oct 2016 13:16:05 -0000
@@ -72,6 +72,7 @@
 #include "config.h"
 #include "defines.h"
 #include "buf.h"
+#include "cmd_exec.h"
 #include "stats.h"
 #include "pathnames.h"
 #include "varmodifiers.h"
@@ -235,7 +236,10 @@ static void fill_from_env(Var *);
 static Var *create_var(const char *, const char *);
 static void var_set_initial_value(Var *, const char *);
 static void var_set_value(Var *, const char *);
-#define var_get_value(v)       Buf_Retrieve(&((v)->val))
+#define var_get_value(v)       ((v)->flags & VAR_EXEC_LATER ? \
+       var_exec_cmd(v) : \
+       Buf_Retrieve(&((v)->val)))
+static char *var_exec_cmd(Var *);
 static void var_append_value(Var *, const char *);
 static void poison_check(Var *);
 static void var_set_append(const char *, const char *, const char *, int, 
bool);
@@ -524,10 +528,10 @@ find_global_var(const char *name, const 
        return v;
 }
 
-/* mark variable as poisoned, in a given setup.
+/* mark variable with special flags, in a given setup.
  */
 void
-Var_MarkPoisoned(const char *name, const char *ename, unsigned int type)
+Var_Mark(const char *name, const char *ename, unsigned int type)
 {
        Var   *v;
        uint32_t        k;
@@ -664,6 +668,21 @@ Var_Appendi_with_ctxt(const char *name, 
     int ctxt)
 {
        var_set_append(name, ename, val, ctxt, true);
+}
+
+static char *
+var_exec_cmd(Var *v)
+{
+       char *arg = Buf_Retrieve(&(v->val));
+       char *err;
+       char *res1;
+       res1 = Cmd_Exec(arg, &err);
+       if (err)
+               Parse_Error(PARSE_WARNING, err, arg);
+       var_set_value(v, res1);
+       free(res1);
+       v->flags &= ~VAR_EXEC_LATER;
+       return Buf_Retrieve(&(v->val));
 }
 
 /* XXX different semantics for Var_Valuei() and Var_Definedi():
Index: var.h
===================================================================
RCS file: /cvs/src/usr.bin/make/var.h,v
retrieving revision 1.17
diff -u -p -r1.17 var.h
--- var.h       6 Jan 2014 12:08:18 -0000       1.17
+++ var.h       20 Oct 2016 13:16:05 -0000
@@ -161,6 +161,7 @@ extern bool errorIsOkay;
 #define POISON_NORMAL          64
 #define POISON_EMPTY           128
 #define POISON_NOT_DEFINED     256
+#define VAR_EXEC_LATER         512
 
-extern void Var_MarkPoisoned(const char *, const char *, unsigned int);
+extern void Var_Mark(const char *, const char *, unsigned int);
 #endif

Reply via email to