Add the beginning of the .ONESHELL special feature.
authorPaul Smith <psmith@gnu.org>
Mon, 12 Jul 2010 05:23:19 +0000 (05:23 +0000)
committerPaul Smith <psmith@gnu.org>
Mon, 12 Jul 2010 05:23:19 +0000 (05:23 +0000)
Original patch by David Boyce.  Modified by Paul Smith.

12 files changed:
ChangeLog
NEWS
commands.c
configure.in
job.c
job.h
main.c
make.h
read.c
tests/ChangeLog
tests/run_make_tests.pl
tests/test_driver.pl

index 0bc52cf..ec3a9b7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2010-07-12  Paul Smith  <psmith@gnu.org>
+
+       Integrated new .ONESHELL feature.
+       Patch by David Boyce <dsb@boyski.com>.  Modified by me.
+
+       * NEWS: Add a note about the new feature.
+       * job.c (is_bourne_compatible_shell): Determine whether we're
+       using a standard POSIX shell or not.
+       (start_job_command): Accept '-ec' as POSIX shell flags.
+       (construct_command_argv_internal): If one_shell is set and we are
+       using a POSIX shell, remove "interior" prefix characters such as
+       "@", "+", "-".  Also treat "\n" as a special character when
+       choosing the slow path, if ONESHELL is set.
+       * job.h (is_bourne_compatible_argv): Define the new function.
+
+       * make.h (one_shell): New global variable to remember setting.
+       * main.c: Declare it.
+       * read.c (record_files): Set it.
+       * commands.c (chop_commands): If one_shell is set, don't chop
+       commands into multiple lines; just keep one line.
+
 2010-07-09  Eli Zaretskii  <eliz@gnu.org>
 
        * w32/subproc/sub_proc.c: Include stdint.h.
        Windows builds that don't use GCC.
        Savannah bug #27809.  Patch by Ozkan Sezer <sezeroz@gmail.com>
 
+2010-07-07  Paul Smith  <psmith@gnu.org>
+
+       * configure.in: Bump to a new prerelease version 3.81.91.
+
 2010-07-06  Paul Smith  <psmith@gnu.org>
 
        * main.c (main): Set a default value of "-c" for .SHELLFLAGS.
diff --git a/NEWS b/NEWS
index b9577c9..41b0daa 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -71,6 +71,16 @@ Version 3.81.90
   the shell when it invokes recipes.  By default the value will be "-c" (or
   "-ec" if .POSIX is set).
 
+* New special target: .ONESHELL instructs make to invoke a single instance of
+  the shell and provide it with the entire recipe, regardless of how many
+  lines it contains.  As a special feature to allow more straightforward
+  conversion of makefiles to use .ONESHELL, any recipe line control characters
+  ('@', '+', or '-') will be removed from the second and subsequent recipe
+  lines.  This happens _only_ if the SHELL value is deemed to be a standard
+  POSIX-style shell.  If not, then no interior line control characters are
+  removed (as they may be part of the scripting language used with the
+  alternate SHELL).
+
 * New variable modifier 'private': prefixing a variable assignment with the
   modifier 'private' suppresses inheritance of that variable by
   prerequisites.  This is most useful for target- and pattern-specific
index 6c941f2..873b1bc 100644 (file)
@@ -330,7 +330,6 @@ set_file_variables (struct file *file)
 void
 chop_commands (struct commands *cmds)
 {
-  const char *p;
   unsigned int nlines, idx;
   char **lines;
 
@@ -340,64 +339,82 @@ chop_commands (struct commands *cmds)
   if (!cmds || cmds->command_lines != 0)
     return;
 
-  /* Chop CMDS->commands up into lines in CMDS->command_lines.
-        Also set the corresponding CMDS->lines_flags elements,
-        and the CMDS->any_recurse flag.  */
+  /* Chop CMDS->commands up into lines in CMDS->command_lines.  */
 
-  nlines = 5;
-  lines = xmalloc (5 * sizeof (char *));
-  idx = 0;
-  p = cmds->commands;
-  while (*p != '\0')
+  if (one_shell)
     {
-      const char *end = p;
-    find_end:;
-      end = strchr (end, '\n');
-      if (end == 0)
-        end = p + strlen (p);
-      else if (end > p && end[-1] == '\\')
+      int l = strlen (cmds->commands);
+
+      nlines = 1;
+      lines = xmalloc (nlines * sizeof (char *));
+      lines[0] = xstrdup (cmds->commands);
+
+      /* Strip the trailing newline.  */
+      if (l > 0 && lines[0][l-1] == '\n')
+        lines[0][l-1] = '\0';
+    }
+  else
+    {
+      const char *p;
+
+      nlines = 5;
+      lines = xmalloc (nlines * sizeof (char *));
+      idx = 0;
+      p = cmds->commands;
+      while (*p != '\0')
         {
-          int backslash = 1;
-          const char *b;
-          for (b = end - 2; b >= p && *b == '\\'; --b)
-            backslash = !backslash;
-          if (backslash)
+          const char *end = p;
+        find_end:;
+          end = strchr (end, '\n');
+          if (end == 0)
+            end = p + strlen (p);
+          else if (end > p && end[-1] == '\\')
+            {
+              int backslash = 1;
+              const char *b;
+              for (b = end - 2; b >= p && *b == '\\'; --b)
+                backslash = !backslash;
+              if (backslash)
+                {
+                  ++end;
+                  goto find_end;
+                }
+            }
+
+          if (idx == nlines)
             {
-              ++end;
-              goto find_end;
+              nlines += 2;
+              lines = xrealloc (lines, nlines * sizeof (char *));
             }
+          lines[idx++] = xstrndup (p, end - p);
+          p = end;
+          if (*p != '\0')
+            ++p;
         }
 
-      if (idx == nlines)
+      if (idx != nlines)
         {
-          nlines += 2;
+          nlines = idx;
           lines = xrealloc (lines, nlines * sizeof (char *));
         }
-      lines[idx++] = xstrndup (p, end - p);
-      p = end;
-      if (*p != '\0')
-        ++p;
     }
 
-  if (idx != nlines)
-    {
-      nlines = idx;
-      lines = xrealloc (lines, nlines * sizeof (char *));
-    }
+  /* Finally, set the corresponding CMDS->lines_flags elements and the
+     CMDS->any_recurse flag.  */
 
   cmds->ncommand_lines = nlines;
   cmds->command_lines = lines;
 
   cmds->any_recurse = 0;
   cmds->lines_flags = xmalloc (nlines);
+
   for (idx = 0; idx < nlines; ++idx)
     {
       int flags = 0;
+      const char *p = lines[idx];
 
-      for (p = lines[idx];
-           isblank ((unsigned char)*p) || *p == '-' || *p == '@' || *p == '+';
-           ++p)
-        switch (*p)
+      while (isblank (*p) || *p == '-' || *p == '@' || *p == '+')
+        switch (*(p++))
           {
           case '+':
             flags |= COMMANDS_RECURSE;
index 42af12d..ce2650c 100644 (file)
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License along with
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 
-AC_INIT([GNU make],[3.81.90],[bug-make@gnu.org])
+AC_INIT([GNU make],[3.81.91],[bug-make@gnu.org])
 
 AC_PREREQ(2.59)
 AC_REVISION([[$Id$]])
diff --git a/job.c b/job.c
index 49b95f7..2d8fe60 100644 (file)
--- a/job.c
+++ b/job.c
@@ -382,6 +382,53 @@ _is_unixy_shell (const char *path)
 }
 #endif /* __EMX__ */
 
+/* determines whether path looks to be a Bourne-like shell. */
+int
+is_bourne_compatible_shell (const char *path)
+{
+  /* list of known unix (Bourne-like) shells */
+  const char *unix_shells[] = {
+    "sh",
+    "bash",
+    "ksh",
+    "rksh",
+    "zsh",
+    "ash",
+    "dash",
+    NULL
+  };
+  unsigned i, len;
+
+  /* find the rightmost '/' or '\\' */
+  const char *name = strrchr (path, '/');
+  char *p = strrchr (path, '\\');
+
+  if (name && p)    /* take the max */
+    name = (name > p) ? name : p;
+  else if (p)       /* name must be 0 */
+    name = p;
+  else if (!name)   /* name and p must be 0 */
+    name = path;
+
+  if (*name == '/' || *name == '\\') name++;
+
+  /* this should be able to deal with extensions on Windows-like systems */
+  for (i = 0; unix_shells[i] != NULL; i++) {
+    len = strlen(unix_shells[i]);
+#if defined(WINDOWS32) || defined(__MSDOS__)
+    if ((strncasecmp (name, unix_shells[i], len) == 0) &&
+      (strlen(name) >= len && (name[len] == '\0' || name[len] == '.')))
+#else
+    if ((strncmp (name, unix_shells[i], len) == 0) &&
+      (strlen(name) >= len && name[len] == '\0'))
+#endif
+       return 1; /* a known unix-style shell */
+  }
+
+  /* if not on the list, assume it's not a Bourne-like shell */
+  return 0;
+}
+
 \f
 /* Write an error message describing the exit status given in
    EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
@@ -1117,10 +1164,13 @@ start_job_command (struct child *child)
 #if defined __MSDOS__ || defined (__EMX__)
       unixy_shell      /* the test is complicated and we already did it */
 #else
-      (argv[0] && !strcmp (argv[0], "/bin/sh"))
+      (argv[0] && is_bourne_compatible_shell(argv[0]))
 #endif
-      && (argv[1]
-          && argv[1][0] == '-' && argv[1][1] == 'c' && argv[1][2] == '\0')
+      && (argv[1] && argv[1][0] == '-'
+       &&
+           ((argv[1][1] == 'c' && argv[1][2] == '\0')
+         ||
+            (argv[1][1] == 'e' && argv[1][2] == 'c' && argv[1][3] == '\0')))
       && (argv[2] && argv[2][0] == ':' && argv[2][1] == '\0')
       && argv[3] == NULL)
     {
@@ -2511,6 +2561,9 @@ construct_command_argv_internal (char *line, char **restp, char *shell,
       else if (strchr (sh_chars, *p) != 0)
        /* Not inside a string, but it's a special char.  */
        goto slow;
+      else if (one_shell && *p == '\n')
+       /* In .ONESHELL mode \n is a separator like ; or && */
+       goto slow;
 #ifdef  __MSDOS__
       else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.')
        /* `...' is a wildcard in DJGPP.  */
@@ -2738,16 +2791,80 @@ construct_command_argv_internal (char *line, char **restp, char *shell,
     unsigned int shell_len = strlen (shell);
     unsigned int line_len = strlen (line);
     unsigned int sflags_len = strlen (shellflags);
-
-    char *new_line = alloca (shell_len + 1 + sflags_len + 1
-                             + (line_len*2) + 1);
     char *command_ptr = NULL; /* used for batch_mode_shell mode */
+    char *new_line;
 
 # ifdef __EMX__ /* is this necessary? */
     if (!unixy_shell)
       shellflags[0] = '/'; /* "/c" */
 # endif
 
+    /* In .ONESHELL mode we are allowed to throw the entire current
+       recipe string at a single shell and trust that the user
+       has configured the shell and shell flags, and formatted
+       the string, appropriately. */
+    if (one_shell)
+      {
+       /* If the shell is Bourne compatible, we must remove and ignore
+          interior special chars [@+-] because they're meaningless to
+          the shell itself. If, however, we're in .ONESHELL mode and
+          have changed SHELL to something non-standard, we should
+          leave those alone because they could be part of the
+          script. In this case we must also leave in place
+          any leading [@+-] for the same reason.  */
+
+       /* Remove and ignore interior prefix chars [@+-] because they're
+            meaningless given a single shell. */
+#if defined __MSDOS__ || defined (__EMX__)
+       if (unixy_shell)     /* the test is complicated and we already did it */
+#else
+       if (is_bourne_compatible_shell(shell))
+#endif
+          {
+            const char *f = line;
+            char *t = line;
+
+            /* Copy the recipe, removing and ignoring interior prefix chars
+               [@+-]: they're meaningless in .ONESHELL mode.  */
+            while (f[0] != '\0')
+              {
+                int esc = 0;
+
+                /* This is the start of a new recipe line.
+                   Skip whitespace and prefix characters.  */
+                while (isblank (*f) || *f == '-' || *f == '@' || *f == '+')
+                  ++f;
+
+                /* Copy until we get to the next logical recipe line.  */
+                while (*f != '\0')
+                  {
+                    *(t++) = *(f++);
+                    if (f[-1] == '\\')
+                      esc = !esc;
+                    else
+                      {
+                        /* On unescaped newline, we're done with this line.  */
+                        if (f[-1] == '\n' && ! esc)
+                          break;
+
+                        /* Something else: reset the escape sequence.  */
+                        esc = 0;
+                      }
+                  }
+              }
+            *t = '\0';
+          }
+
+       new_argv = xmalloc (4 * sizeof (char *));
+       new_argv[0] = xstrdup(shell);
+       new_argv[1] = xstrdup(shellflags);
+       new_argv[2] = line;
+       new_argv[3] = NULL;
+       return new_argv;
+      }
+
+    new_line = alloca (shell_len + 1 + sflags_len + 1
+                             + (line_len*2) + 1);
     ap = new_line;
     memcpy (ap, shell, shell_len);
     ap += shell_len;
@@ -3108,7 +3225,7 @@ dup2 (int old, int new)
 
   return fd;
 }
-#endif /* !HAPE_DUP2 && !_AMIGA */
+#endif /* !HAVE_DUP2 && !_AMIGA */
 
 /* On VMS systems, include special VMS functions.  */
 
diff --git a/job.h b/job.h
index 4664e75..d4626f2 100644 (file)
--- a/job.h
+++ b/job.h
@@ -67,6 +67,7 @@ struct child
 
 extern struct child *children;
 
+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);
diff --git a/main.c b/main.c
index d7f3253..61e30d0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -496,6 +496,12 @@ int posix_pedantic;
 
 int second_expansion;
 
+/* Nonzero if we have seen the '.ONESHELL' target.
+   This causes the entire recipe to be handed to SHELL
+   as a single string, potentially containing newlines.  */
+
+int one_shell;
+
 /* Nonzero if we have seen the `.NOTPARALLEL' target.
    This turns off parallel builds for this invocation of make.  */
 
diff --git a/make.h b/make.h
index 65faadc..2abe7ed 100644 (file)
--- a/make.h
+++ b/make.h
@@ -500,6 +500,7 @@ extern int env_overrides, no_builtin_rules_flag, no_builtin_variables_flag;
 extern int print_version_flag, print_directory_flag, check_symlink_flag;
 extern int warn_undefined_variables_flag, posix_pedantic, not_parallel;
 extern int second_expansion, clock_skew_detected, rebuilding_makefiles;
+extern int one_shell;
 
 /* can we run commands via 'sh -c xxx' or must we use batch files? */
 extern int batch_mode_shell;
diff --git a/read.c b/read.c
index 1e8d2f3..f4484c4 100644 (file)
--- a/read.c
+++ b/read.c
@@ -1964,6 +1964,10 @@ record_files (struct nameseq *filenames, const char *pattern,
         }
       else if (streq (name, ".SECONDEXPANSION"))
         second_expansion = 1;
+#if !defined(WINDOWS32) && !defined (__MSDOS__) && !defined (__EMX__)
+      else if (streq (name, ".ONESHELL"))
+        one_shell = 1;
+#endif
 
       /* If this is a static pattern rule:
          `targets: target%pattern: prereq%pattern; recipe',
index a611422..66682ac 100644 (file)
@@ -1,3 +1,13 @@
+2010-07-12  Paul Smith  <psmith@gnu.org>
+
+       * test_driver.pl: Add a new $perl_name containing the path to Perl.
+       * run_make_tests.pl (run_make_test): Replace the special string
+       #PERL# in a makefile etc. with the path the Perl executable so
+       makefiles can use it.
+
+       * scripts/targets/ONESHELL: Add a new set of regression tests for
+       the .ONESHELL feature.
+
 2010-07-06  Paul Smith  <psmith@gnu.org>
 
        * scripts/variables/SHELL: Test the new .SHELLFLAGS variable.
index dabeae9..b5ee023 100755 (executable)
@@ -122,6 +122,7 @@ sub run_make_test
     $makestring =~ s/#MAKEFILE#/$makefile/g;
     $makestring =~ s/#MAKEPATH#/$mkpath/g;
     $makestring =~ s/#MAKE#/$make_name/g;
+    $makestring =~ s/#PERL#/$perl_name/g;
     $makestring =~ s/#PWD#/$pwd/g;
 
     # Populate the makefile!
@@ -136,6 +137,7 @@ sub run_make_test
   $answer =~ s/#MAKEFILE#/$makefile/g;
   $answer =~ s/#MAKEPATH#/$mkpath/g;
   $answer =~ s/#MAKE#/$make_name/g;
+  $answer =~ s/#PERL#/$perl_name/g;
   $answer =~ s/#PWD#/$pwd/g;
 
   run_make_with_options($makefile, $options, &get_logfile(0),
index e96c84c..8319d40 100644 (file)
@@ -54,6 +54,8 @@ $test_passed = 1;
 # Timeout in seconds.  If the test takes longer than this we'll fail it.
 $test_timeout = 5;
 
+# Path to Perl
+$perl_name = $^X;
 
 # %makeENV is the cleaned-out environment.
 %makeENV = ();
@@ -236,9 +238,10 @@ sub toplevel
 sub get_osname
 {
   # Set up an initial value.  In perl5 we can do it the easy way.
-  #
   $osname = defined($^O) ? $^O : '';
 
+  # Find a path to Perl
+
   # See if the filesystem supports long file names with multiple
   # dots.  DOS doesn't.
   $short_filenames = 0;
@@ -273,14 +276,14 @@ sub get_osname
     eval "chop (\$osname = `sh -c 'uname -nmsr 2>&1'`)";
     if ($osname =~ /not found/i)
     {
-       $osname = "(something unixy with no uname)";
+       $osname = "(something posixy with no uname)";
     }
     elsif ($@ ne "" || $?)
     {
         eval "chop (\$osname = `sh -c 'uname -a 2>&1'`)";
         if ($@ ne "" || $?)
         {
-           $osname = "(something unixy)";
+           $osname = "(something posixy)";
        }
     }
     $vos = 0;