From 0ee5f05420ab21d679486a37454ad6b9cffa4614 Mon Sep 17 00:00:00 2001 From: psmith Date: Mon, 12 Jul 2010 05:23:19 +0000 Subject: [PATCH] Add the beginning of the .ONESHELL special feature. Original patch by David Boyce. Modified by Paul Smith. --- ChangeLog | 25 +++++++++ NEWS | 10 ++++ commands.c | 91 +++++++++++++++++++-------------- configure.in | 4 +- job.c | 131 +++++++++++++++++++++++++++++++++++++++++++++--- job.h | 1 + main.c | 6 +++ make.h | 1 + read.c | 4 ++ tests/ChangeLog | 10 ++++ tests/run_make_tests.pl | 2 + tests/test_driver.pl | 11 ++-- 12 files changed, 246 insertions(+), 50 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0bc52cf..ec3a9b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2010-07-12 Paul Smith + + Integrated new .ONESHELL feature. + Patch by David Boyce . 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 * w32/subproc/sub_proc.c: Include stdint.h. @@ -28,6 +49,10 @@ Windows builds that don't use GCC. Savannah bug #27809. Patch by Ozkan Sezer +2010-07-07 Paul Smith + + * configure.in: Bump to a new prerelease version 3.81.91. + 2010-07-06 Paul Smith * main.c (main): Set a default value of "-c" for .SHELLFLAGS. diff --git a/NEWS b/NEWS index b9577c9..41b0daa 100644 --- 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 diff --git a/commands.c b/commands.c index 6c941f2..873b1bc 100644 --- a/commands.c +++ b/commands.c @@ -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; diff --git a/configure.in b/configure.in index 2ab8292..ace98ac 100644 --- a/configure.in +++ b/configure.in @@ -17,10 +17,10 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see . -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: configure.in,v 1.152 2009/10/25 18:56:44 psmith Exp $]]) +AC_REVISION([[$Id: configure.in,v 1.153 2010/07/12 05:23:20 psmith Exp $]]) # Autoconf setup AC_CONFIG_AUX_DIR(config) diff --git a/job.c b/job.c index 49b95f7..2d8fe60 100644 --- 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; +} + /* 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 --- 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 --- 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 --- 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 --- 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', diff --git a/tests/ChangeLog b/tests/ChangeLog index a611422..66682ac 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,13 @@ +2010-07-12 Paul Smith + + * 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 * scripts/variables/SHELL: Test the new .SHELLFLAGS variable. diff --git a/tests/run_make_tests.pl b/tests/run_make_tests.pl index dabeae9..b5ee023 100755 --- a/tests/run_make_tests.pl +++ b/tests/run_make_tests.pl @@ -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), diff --git a/tests/test_driver.pl b/tests/test_driver.pl index b61d87e..9a75c24 100644 --- a/tests/test_driver.pl +++ b/tests/test_driver.pl @@ -30,7 +30,7 @@ # this routine controls the whole mess; each test suite sets up a few # variables and then calls &toplevel, which does all the real work. -# $Id: test_driver.pl,v 1.27 2009/10/25 18:56:46 psmith Exp $ +# $Id: test_driver.pl,v 1.28 2010/07/12 05:23:20 psmith Exp $ # The number of test categories we've run @@ -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; -- 2.11.4.GIT