Add the beginning of the .ONESHELL special feature.

Original patch by David Boyce.  Modified by Paul Smith.
This commit is contained in:
Paul Smith 2010-07-12 05:23:19 +00:00
parent b85b7e0a55
commit 7ba7dbca47
12 changed files with 244 additions and 48 deletions

View File

@ -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> 2010-07-09 Eli Zaretskii <eliz@gnu.org>
* w32/subproc/sub_proc.c: Include stdint.h. * w32/subproc/sub_proc.c: Include stdint.h.
@ -28,6 +49,10 @@
Windows builds that don't use GCC. Windows builds that don't use GCC.
Savannah bug #27809. Patch by Ozkan Sezer <sezeroz@gmail.com> 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> 2010-07-06 Paul Smith <psmith@gnu.org>
* main.c (main): Set a default value of "-c" for .SHELLFLAGS. * main.c (main): Set a default value of "-c" for .SHELLFLAGS.

10
NEWS
View File

@ -71,6 +71,16 @@ Version 3.81.90
the shell when it invokes recipes. By default the value will be "-c" (or the shell when it invokes recipes. By default the value will be "-c" (or
"-ec" if .POSIX is set). "-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 * New variable modifier 'private': prefixing a variable assignment with the
modifier 'private' suppresses inheritance of that variable by modifier 'private' suppresses inheritance of that variable by
prerequisites. This is most useful for target- and pattern-specific prerequisites. This is most useful for target- and pattern-specific

View File

@ -330,7 +330,6 @@ set_file_variables (struct file *file)
void void
chop_commands (struct commands *cmds) chop_commands (struct commands *cmds)
{ {
const char *p;
unsigned int nlines, idx; unsigned int nlines, idx;
char **lines; char **lines;
@ -340,64 +339,82 @@ chop_commands (struct commands *cmds)
if (!cmds || cmds->command_lines != 0) if (!cmds || cmds->command_lines != 0)
return; return;
/* Chop CMDS->commands up into lines in CMDS->command_lines. /* Chop CMDS->commands up into lines in CMDS->command_lines. */
Also set the corresponding CMDS->lines_flags elements,
and the CMDS->any_recurse flag. */
nlines = 5; if (one_shell)
lines = xmalloc (5 * sizeof (char *));
idx = 0;
p = cmds->commands;
while (*p != '\0')
{ {
const char *end = p; int l = strlen (cmds->commands);
find_end:;
end = strchr (end, '\n'); nlines = 1;
if (end == 0) lines = xmalloc (nlines * sizeof (char *));
end = p + strlen (p); lines[0] = xstrdup (cmds->commands);
else if (end > p && end[-1] == '\\')
/* 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 *end = p;
const char *b; find_end:;
for (b = end - 2; b >= p && *b == '\\'; --b) end = strchr (end, '\n');
backslash = !backslash; if (end == 0)
if (backslash) end = p + strlen (p);
else if (end > p && end[-1] == '\\')
{ {
++end; int backslash = 1;
goto find_end; const char *b;
for (b = end - 2; b >= p && *b == '\\'; --b)
backslash = !backslash;
if (backslash)
{
++end;
goto find_end;
}
} }
if (idx == nlines)
{
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 = xrealloc (lines, nlines * sizeof (char *));
} }
lines[idx++] = xstrndup (p, end - p);
p = end;
if (*p != '\0')
++p;
} }
if (idx != nlines) /* Finally, set the corresponding CMDS->lines_flags elements and the
{ CMDS->any_recurse flag. */
nlines = idx;
lines = xrealloc (lines, nlines * sizeof (char *));
}
cmds->ncommand_lines = nlines; cmds->ncommand_lines = nlines;
cmds->command_lines = lines; cmds->command_lines = lines;
cmds->any_recurse = 0; cmds->any_recurse = 0;
cmds->lines_flags = xmalloc (nlines); cmds->lines_flags = xmalloc (nlines);
for (idx = 0; idx < nlines; ++idx) for (idx = 0; idx < nlines; ++idx)
{ {
int flags = 0; int flags = 0;
const char *p = lines[idx];
for (p = lines[idx]; while (isblank (*p) || *p == '-' || *p == '@' || *p == '+')
isblank ((unsigned char)*p) || *p == '-' || *p == '@' || *p == '+'; switch (*(p++))
++p)
switch (*p)
{ {
case '+': case '+':
flags |= COMMANDS_RECURSE; flags |= COMMANDS_RECURSE;

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>. # 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_PREREQ(2.59)
AC_REVISION([[$Id$]]) AC_REVISION([[$Id$]])

131
job.c
View File

@ -382,6 +382,53 @@ _is_unixy_shell (const char *path)
} }
#endif /* __EMX__ */ #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 /* Write an error message describing the exit status given in
EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME. 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__) #if defined __MSDOS__ || defined (__EMX__)
unixy_shell /* the test is complicated and we already did it */ unixy_shell /* the test is complicated and we already did it */
#else #else
(argv[0] && !strcmp (argv[0], "/bin/sh")) (argv[0] && is_bourne_compatible_shell(argv[0]))
#endif #endif
&& (argv[1] && (argv[1] && argv[1][0] == '-'
&& argv[1][0] == '-' && argv[1][1] == 'c' && argv[1][2] == '\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[2] && argv[2][0] == ':' && argv[2][1] == '\0')
&& argv[3] == NULL) && argv[3] == NULL)
{ {
@ -2511,6 +2561,9 @@ construct_command_argv_internal (char *line, char **restp, char *shell,
else if (strchr (sh_chars, *p) != 0) else if (strchr (sh_chars, *p) != 0)
/* Not inside a string, but it's a special char. */ /* Not inside a string, but it's a special char. */
goto slow; goto slow;
else if (one_shell && *p == '\n')
/* In .ONESHELL mode \n is a separator like ; or && */
goto slow;
#ifdef __MSDOS__ #ifdef __MSDOS__
else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.') else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.')
/* `...' is a wildcard in DJGPP. */ /* `...' 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 shell_len = strlen (shell);
unsigned int line_len = strlen (line); unsigned int line_len = strlen (line);
unsigned int sflags_len = strlen (shellflags); 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 *command_ptr = NULL; /* used for batch_mode_shell mode */
char *new_line;
# ifdef __EMX__ /* is this necessary? */ # ifdef __EMX__ /* is this necessary? */
if (!unixy_shell) if (!unixy_shell)
shellflags[0] = '/'; /* "/c" */ shellflags[0] = '/'; /* "/c" */
# endif # 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; ap = new_line;
memcpy (ap, shell, shell_len); memcpy (ap, shell, shell_len);
ap += shell_len; ap += shell_len;
@ -3108,7 +3225,7 @@ dup2 (int old, int new)
return fd; return fd;
} }
#endif /* !HAPE_DUP2 && !_AMIGA */ #endif /* !HAVE_DUP2 && !_AMIGA */
/* On VMS systems, include special VMS functions. */ /* On VMS systems, include special VMS functions. */

1
job.h
View File

@ -67,6 +67,7 @@ struct child
extern struct child *children; extern struct child *children;
int is_bourne_compatible_shell(const char *path);
void new_job (struct file *file); void new_job (struct file *file);
void reap_children (int block, int err); void reap_children (int block, int err);
void start_waiting_jobs (void); void start_waiting_jobs (void);

6
main.c
View File

@ -496,6 +496,12 @@ int posix_pedantic;
int second_expansion; 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. /* Nonzero if we have seen the `.NOTPARALLEL' target.
This turns off parallel builds for this invocation of make. */ This turns off parallel builds for this invocation of make. */

1
make.h
View File

@ -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 print_version_flag, print_directory_flag, check_symlink_flag;
extern int warn_undefined_variables_flag, posix_pedantic, not_parallel; extern int warn_undefined_variables_flag, posix_pedantic, not_parallel;
extern int second_expansion, clock_skew_detected, rebuilding_makefiles; 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? */ /* can we run commands via 'sh -c xxx' or must we use batch files? */
extern int batch_mode_shell; extern int batch_mode_shell;

4
read.c
View File

@ -1964,6 +1964,10 @@ record_files (struct nameseq *filenames, const char *pattern,
} }
else if (streq (name, ".SECONDEXPANSION")) else if (streq (name, ".SECONDEXPANSION"))
second_expansion = 1; 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: /* If this is a static pattern rule:
`targets: target%pattern: prereq%pattern; recipe', `targets: target%pattern: prereq%pattern; recipe',

View 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> 2010-07-06 Paul Smith <psmith@gnu.org>
* scripts/variables/SHELL: Test the new .SHELLFLAGS variable. * scripts/variables/SHELL: Test the new .SHELLFLAGS variable.

View File

@ -122,6 +122,7 @@ sub run_make_test
$makestring =~ s/#MAKEFILE#/$makefile/g; $makestring =~ s/#MAKEFILE#/$makefile/g;
$makestring =~ s/#MAKEPATH#/$mkpath/g; $makestring =~ s/#MAKEPATH#/$mkpath/g;
$makestring =~ s/#MAKE#/$make_name/g; $makestring =~ s/#MAKE#/$make_name/g;
$makestring =~ s/#PERL#/$perl_name/g;
$makestring =~ s/#PWD#/$pwd/g; $makestring =~ s/#PWD#/$pwd/g;
# Populate the makefile! # Populate the makefile!
@ -136,6 +137,7 @@ sub run_make_test
$answer =~ s/#MAKEFILE#/$makefile/g; $answer =~ s/#MAKEFILE#/$makefile/g;
$answer =~ s/#MAKEPATH#/$mkpath/g; $answer =~ s/#MAKEPATH#/$mkpath/g;
$answer =~ s/#MAKE#/$make_name/g; $answer =~ s/#MAKE#/$make_name/g;
$answer =~ s/#PERL#/$perl_name/g;
$answer =~ s/#PWD#/$pwd/g; $answer =~ s/#PWD#/$pwd/g;
run_make_with_options($makefile, $options, &get_logfile(0), run_make_with_options($makefile, $options, &get_logfile(0),

View File

@ -54,6 +54,8 @@ $test_passed = 1;
# Timeout in seconds. If the test takes longer than this we'll fail it. # Timeout in seconds. If the test takes longer than this we'll fail it.
$test_timeout = 5; $test_timeout = 5;
# Path to Perl
$perl_name = $^X;
# %makeENV is the cleaned-out environment. # %makeENV is the cleaned-out environment.
%makeENV = (); %makeENV = ();
@ -236,9 +238,10 @@ sub toplevel
sub get_osname sub get_osname
{ {
# Set up an initial value. In perl5 we can do it the easy way. # Set up an initial value. In perl5 we can do it the easy way.
#
$osname = defined($^O) ? $^O : ''; $osname = defined($^O) ? $^O : '';
# Find a path to Perl
# See if the filesystem supports long file names with multiple # See if the filesystem supports long file names with multiple
# dots. DOS doesn't. # dots. DOS doesn't.
$short_filenames = 0; $short_filenames = 0;
@ -273,14 +276,14 @@ sub get_osname
eval "chop (\$osname = `sh -c 'uname -nmsr 2>&1'`)"; eval "chop (\$osname = `sh -c 'uname -nmsr 2>&1'`)";
if ($osname =~ /not found/i) if ($osname =~ /not found/i)
{ {
$osname = "(something unixy with no uname)"; $osname = "(something posixy with no uname)";
} }
elsif ($@ ne "" || $?) elsif ($@ ne "" || $?)
{ {
eval "chop (\$osname = `sh -c 'uname -a 2>&1'`)"; eval "chop (\$osname = `sh -c 'uname -a 2>&1'`)";
if ($@ ne "" || $?) if ($@ ne "" || $?)
{ {
$osname = "(something unixy)"; $osname = "(something posixy)";
} }
} }
$vos = 0; $vos = 0;