]> git.ipfire.org Git - thirdparty/make.git/commitdiff
[SV 10593] Export variables to $(shell ...) commands
authorPaul Smith <psmith@gnu.org>
Sun, 19 Jun 2022 00:25:30 +0000 (20:25 -0400)
committerPaul Smith <psmith@gnu.org>
Sat, 9 Jul 2022 14:44:21 +0000 (10:44 -0400)
Export all variables, including exported makefile variables, when
invoking a shell for the $(shell ...) function.  If we detect a
recursive variable expansion, silently ignore that variable and do
not export it.  We do print a debug message.

* NEWS: Announce the potential backward-incompatibility.
* doc/make.texi (Shell Function): Document the export behavior.
* src/main.c (main): Add "shell-export" to .FEATURES.
* src/job.h: New function to free struct childbase.
* src/job.c (free_childbase): Implement it; call from free_child.
* src/function.c (func_shell_base): Use target_environment() to
obtain the proper environment for the shell function.
Use free_childbase() to free memory.
(windows32_openpipe): Don't reset the environment: the caller
already provided a proper PATH variable in envp.
* src/variable.c (target_environment): If we detect a recursive
expansion and we're called from func_shell, ignore the variable.
(sync_Path_environment): Simplify and reduce memory allocation.
* tests/scripts/functions/shell: Add tests for this.

NEWS
doc/make.texi
src/function.c
src/job.c
src/job.h
src/main.c
src/variable.c
tests/scripts/functions/shell

diff --git a/NEWS b/NEWS
index f555a0d7ad426af47599796faba485f37b2c69a7..8c1e271b9f8f940d3cab85d3f5388ede26b2814a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,12 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=109&se
   variable that was visible while parsing makefiles.  Now, all options
   are available in MAKEFLAGS.
 
+* WARNING: Backward-incompatibility!
+  Previously makefile variables marked as export were not exported to commands
+  started by the $(shell ...) function.  Now, all exported variables are
+  exported to $(shell ...).
+  To detect this change search for 'shell-export' in the .FEATURES variable.
+
 * WARNING: New build requirement
   GNU make utilizes facilities from GNU Gnulib: Gnulib requires certain C99
   features in the C compiler and so these features are required by GNU make:
index 943c0941d2a1c9d8b35161c3eb83e449d9453f5e..085e9c24f3fade98be0c57c0eb49c4eb636cb8fb 100644 (file)
@@ -8616,6 +8616,23 @@ using a very strange shell, this has the same result as
 @w{@samp{$(wildcard *.c)}} (as long as at least one @samp{.c} file
 exists).@refill
 
+All variables that are marked as @code{export} will also be passed to the
+shell started by the @code{shell} function.  It is possible to create a
+variable expansion loop: consider this @file{makefile}:
+
+@example
+export HI = $(shell echo hi)
+all: ; @@echo $$HI
+@end example
+
+When @code{make} wants to run the recipe it must add the variable @var{HI} to
+the environment; to do so it must be expanded.  The value of this variable
+requires an invocation of the @code{shell} function, and to invoke it we must
+create its environment.  Since @var{HI} is exported, we need to expand it to
+create its environment.  And so on.  In this obscure case @code{make} will not
+add recursively-expanded variables to the @code{shell} environment rather than
+fail with an error.
+
 @node Guile Function,  , Shell Function, Functions
 @section The @code{guile} Function
 @findex guile
index 89449a8fa4c07c17d6b27362435f2fabe331b1b0..9247fc3f60a363c6f083d40378a4efbdf271f98a 100644 (file)
@@ -1725,12 +1725,6 @@ windows32_openpipe (int *pipedes, int errfd, pid_t *pid_p, char **command_argv,
       return -1;
     }
 
-  /* make sure that CreateProcess() has Path it needs */
-  sync_Path_environment ();
-  /* 'sync_Path_environment' may realloc 'environ', so take note of
-     the new value.  */
-  envp = environ;
-
   if (! process_begin (hProcess, command_argv, envp, command_argv[0], NULL))
     {
       /* register process for wait */
@@ -1845,13 +1839,13 @@ func_shell_base (char *o, char **argv, int trim_newlines)
 char *
 func_shell_base (char *o, char **argv, int trim_newlines)
 {
+  struct childbase child = {0};
   char *batch_filename = NULL;
   int errfd;
 #ifdef __MSDOS__
   FILE *fpipe;
 #endif
   char **command_argv = NULL;
-  char **envp;
   int pipedes[2];
   pid_t pid;
 
@@ -1876,26 +1870,14 @@ func_shell_base (char *o, char **argv, int trim_newlines)
     }
 #endif /* !__MSDOS__ */
 
-  /* Using a target environment for 'shell' loses in cases like:
-       export var = $(shell echo foobie)
-       bad := $(var)
-     because target_environment hits a loop trying to expand $(var) to put it
-     in the environment.  This is even more confusing when 'var' was not
-     explicitly exported, but just appeared in the calling environment.
-
-     See Savannah bug #10593.
-
-  envp = target_environment (NULL);
-  */
-
-  envp = environ;
-
   /* Set up the output in case the shell writes something.  */
   output_start ();
 
   errfd = (output_context && output_context->err >= 0
            ? output_context->err : FD_STDERR);
 
+  child.environment = target_environment (NULL);
+
 #if defined(__MSDOS__)
   fpipe = msdos_openpipe (pipedes, &pid, argv[0]);
   if (pipedes[0] < 0)
@@ -1906,7 +1888,7 @@ func_shell_base (char *o, char **argv, int trim_newlines)
     }
 
 #elif defined(WINDOWS32)
-  windows32_openpipe (pipedes, errfd, &pid, command_argv, envp);
+  windows32_openpipe (pipedes, errfd, &pid, command_argv, child.environment);
   /* Restore the value of just_print_flag.  */
   just_print_flag = j_p_f;
 
@@ -1931,18 +1913,11 @@ func_shell_base (char *o, char **argv, int trim_newlines)
   fd_noinherit (pipedes[1]);
   fd_noinherit (pipedes[0]);
 
-  {
-    struct childbase child;
-    child.cmd_name = NULL;
-    child.output.syncout = 1;
-    child.output.out = pipedes[1];
-    child.output.err = errfd;
-    child.environment = envp;
+  child.output.syncout = 1;
+  child.output.out = pipedes[1];
+  child.output.err = errfd;
 
-    pid = child_execute_job (&child, 1, command_argv);
-
-    free (child.cmd_name);
-  }
+  pid = child_execute_job (&child, 1, command_argv);
 
   if (pid < 0)
     {
@@ -2029,6 +2004,8 @@ func_shell_base (char *o, char **argv, int trim_newlines)
       free (command_argv);
     }
 
+  free_childbase (&child);
+
   return o;
 }
 
index 80d261bbae81c43bd54a759ffab2aaad7b23471d..b68fb63804deb7dc01a778c6ca6de4b0997d9cd8 100644 (file)
--- a/src/job.c
+++ b/src/job.c
@@ -1117,6 +1117,20 @@ reap_children (int block, int err)
 \f
 /* Free the storage allocated for CHILD.  */
 
+void
+free_childbase (struct childbase *child)
+{
+  if (child->environment != 0)
+    {
+      char **ep = child->environment;
+      while (*ep != 0)
+        free (*ep++);
+      free (child->environment);
+    }
+
+  free (child->cmd_name);
+}
+
 static void
 free_child (struct child *child)
 {
@@ -1149,15 +1163,8 @@ free_child (struct child *child)
       free (child->command_lines);
     }
 
-  if (child->environment != 0)
-    {
-      char **ep = child->environment;
-      while (*ep != 0)
-        free (*ep++);
-      free (child->environment);
-    }
+  free_childbase ((struct childbase*)child);
 
-  free (child->cmd_name);
   free (child);
 }
 \f
index c32e84b0f36f87e8ddd2e0f94922486179c96d5f..7a06f81f54696dbb3d39268827c2ad7603b865c9 100644 (file)
--- a/src/job.h
+++ b/src/job.h
@@ -73,6 +73,7 @@ int is_bourne_compatible_shell(const char *path);
 void new_job (struct file *file);
 void reap_children (int block, int err);
 void start_waiting_jobs (void);
+void free_childbase (struct childbase* child);
 
 char **construct_command_argv (char *line, char **restp, struct file *file,
                                int cmd_flags, char** batch_file);
index d31223dd41115ed09616d2fa7785c2c35a394408..859846f408f38391611699e43ce6ba0e5e202446 100644 (file)
@@ -1344,6 +1344,7 @@ main (int argc, char **argv, char **envp)
     const char *features = "target-specific order-only second-expansion"
                            " else-if shortest-stem undefine oneshell nocomment"
                            " grouped-target extra-prereqs notintermediate"
+                           " shell-export"
 #ifndef NO_ARCHIVES
                            " archives"
 #endif
index 3cddfd6d5102b274e34bd2458d8244d4c0a75c8d..c53ce5a74a5a459db2f530201aeb3b903e06e70d 100644 (file)
@@ -19,6 +19,7 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <assert.h>
 
 #include "filedef.h"
+#include "debug.h"
 #include "dep.h"
 #include "job.h"
 #include "commands.h"
@@ -1099,17 +1100,24 @@ target_environment (struct file *file)
         /* If V is recursively expanded and didn't come from the environment,
            expand its value.  If it came from the environment, it should
            go back into the environment unchanged.  */
-        if (v->recursive
-            && v->origin != o_env && v->origin != o_env_override)
+        if (v->recursive && v->origin != o_env && v->origin != o_env_override)
           {
-            char *value = recursively_expand_for_file (v, file);
+            /* If V is being recursively expanded and this is for a shell
+               function, just skip it.  */
+            if (v->expanding && file == NULL)
+              DB (DB_VERBOSE, (_("%s:%lu: Skipping export of %s to shell function due to recursive expansion"),
+                               v->fileinfo.filenm, v->fileinfo.lineno, v->name));
+            else
+              {
+                char *value = recursively_expand_for_file (v, file);
 #ifdef WINDOWS32
-            if (strcmp (v->name, "Path") == 0 ||
-                strcmp (v->name, "PATH") == 0)
-              convert_Path_to_windows32 (value, ';');
+                if (strcmp (v->name, "Path") == 0 ||
+                    strcmp (v->name, "PATH") == 0)
+                  convert_Path_to_windows32 (value, ';');
 #endif
-            *result++ = xstrdup (concat (3, v->name, "=", value));
-            free (value);
+                *result++ = xstrdup (concat (3, v->name, "=", value));
+                free (value);
+              }
           }
         else
           {
@@ -1858,21 +1866,20 @@ print_target_variables (const struct file *file)
 
 #ifdef WINDOWS32
 void
-sync_Path_environment (void)
+sync_Path_environment ()
 {
-  char *path = allocated_variable_expand ("$(PATH)");
   static char *environ_path = NULL;
+  char *oldpath = environ_path;
+  char *path = allocated_variable_expand ("PATH=$(PATH)");
 
   if (!path)
     return;
 
-  /* If done this before, free the previous entry before allocating new one.  */
-  free (environ_path);
-
-  /* Create something WINDOWS32 world can grok.  */
+  /* Convert PATH into something WINDOWS32 world can grok.  */
   convert_Path_to_windows32 (path, ';');
-  environ_path = xstrdup (concat (3, "PATH", "=", path));
+
+  environ_path = path;
   putenv (environ_path);
-  free (path);
+  free (oldpath);
 }
 #endif
index 63320a2b5e178921800bfec963f8a2fdcd3d67bb..804251729eac1f31bf959a7c3b2c7000cfec7d7d 100644 (file)
@@ -32,14 +32,61 @@ foo: ; echo '$(FOO)'
 !,
               '', "echo '#'\n#\n");
 
-# Test shells inside exported environment variables.
-# This is the test that fails if we try to put make exported variables into
-# the environment for a $(shell ...) call.
+# Test that exported variables are passed to $(shell ...)
+$ENV{FOO} = 'baz';
+run_make_test(q!
+OUT = $(shell echo $$FOO)
+foo: ; @echo '$(OUT)'
+!,
+              '', 'baz');
+
+$ENV{FOO} = 'baz';
+run_make_test(q!
+FOO = bar
+OUT = $(shell echo $$FOO)
+foo: ; @echo '$(OUT)'
+!,
+              '', 'bar');
+
+run_make_test(q!
+export FOO = bar
+OUT = $(shell echo $$FOO)
+foo: ; @echo '$(OUT)'
+!,
+              '', 'bar');
+
+# Test shells inside exported environment variables, simply expanded.
+run_make_test('
+export HI := $(shell echo hi)
+.PHONY: all
+all: ; @echo $$HI
+',
+              '','hi');
+
+# Test shells inside exported environment variables.  See SV 10593
 run_make_test('
 export HI = $(shell echo hi)
 .PHONY: all
 all: ; @echo $$HI
-    ','','hi');
+',
+              '',"hi");
+
+$ENV{HI} = 'foo';
+run_make_test('
+HI = $(shell echo hi)
+.PHONY: all
+all: ; @echo $$HI
+',
+              '',"hi");
+
+$ENV{HI} = 'foo';
+run_make_test('
+HI = $(shell echo hi)
+bad := $(HI)
+.PHONY: all
+all: ; @echo $$HI $(bad)
+',
+              '',"hi hi");
 
 if ($port_type ne 'W32') {
     # Test shell errors in recipes including offset