diff --git a/.gitignore b/.gitignore
index c79bb9888..5c3bc90fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/Basic.mk
/ChangeLog
/ChangeLog-spell-corrected
+/ChangeLog.orig
/Makefile
/Makefile.DOS
/Makefile.in
diff --git a/configure.ac b/configure.ac
index 2305b3c23..878bb3612 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,7 +49,7 @@ AH_BOTTOM(
# We have to enable "foreign" because ChangeLog is auto-generated
# Automake 1.15 and gnulib don't get along: gnulib has some strange error
# in the way it handles getloadavg.c which causes make distcheck to fail.
-# http://lists.gnu.org/archive/html/bug-gnulib/2018-06/msg00024.html
+# https://lists.gnu.org/archive/html/bug-gnulib/2018-06/msg00024.html
#
# We have to enable "info-in-builddir" because future versions of automake
# would place generated .info files in srcdir instead of builddir. Using that
@@ -658,56 +658,6 @@ Makefile lib/Makefile po/Makefile.in doc/Makefile \
tests/config-flags.pm \
libdebugger/Makefile
])
-# We don't need this: the standard automake output suffices for POSIX systems.
-#mk/Posix.mk
-
-# We like mondo-warnings!
-# Also force comments to be preserved. This helps when using ccache, in
-# combination with GCC 7's implicit-fallthrough warning.
-# check for some supported compiler flags, instead of have 'em hard coded in
-# maintMakefile
-# -Werror is too much, on different platform we get different warnings FOR
-# e.g. guile headers.
-# rocky: However to *test* for whether to use options below, we want to turn
-# warnings into errors, hence, the separate check for -Werror.
-WARN_TO_ERROR_CFLAG=""
-AX_CHECK_COMPILE_FLAG([-Werror], [WARN_TO_ERROR_CFLAG="-Werror "])
-CHECK_CFLAGS="
- -C
- -Wall
- -Wbad-function-cast
- -Wchar-subscripts
- -Wdeclaration-after-statement
- -Wdisabled-optimization
- -Wduplicate-conk
- -Wduplicate-enum
- -Wendif-labels
- -Wextra
- -Wformat-security
- -Wformat-signedness
- -Wignore-qualifiers
- -Winline
- -Wlogical-op
- -Wlong-long
- -Wnested-externs
- -Wno-sign-compare
- -Wpointer-arith
- -Wshadow
- -Wtype-limits
- -Wunused
- -Wunused-but-set-parameter
- -Wunused-parameter
- -Wwrite-strings
-"
-
-# Warnings not used (because they trigger messages):
-# -Wmissing-declarations
-# -Wstrict-prototypes
-# -Wundef
-
-for OPT in $CHECK_CFLAGS; do
- AX_CHECK_COMPILE_FLAG([$OPT $WARN_TO_ERROR_CFLAG], [AM_CFLAGS="$AM_CFLAGS $OPT"])
-done
AC_SUBST([AM_CFLAGS])
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 18d4e7b53..b9475b7e2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,5 @@
# List of source files containing translatable strings.
-# Copyright (C) 2000-2020 Free Software Foundation, Inc.
+# Copyright (C) 2000-2022 Free Software Foundation, Inc.
# This file is part of GNU Make.
#
# GNU Make is free software; you can redistribute it and/or modify it under
@@ -13,7 +13,7 @@
# details.
#
# You should have received a copy of the GNU General Public License along with
-# this program. If not, see .
+# this program. If not, see .
libdebugger/break.c
libdebugger/cmd.c
@@ -52,6 +52,7 @@ src/read.c
src/remake.c
src/remote-cstms.c
src/rule.c
+src/shuffle.c
src/signame.c
src/strcache.c
src/variable.c
diff --git a/src/dep.h b/src/dep.h
index 45e8e5c06..b7a694c48 100644
--- a/src/dep.h
+++ b/src/dep.h
@@ -55,7 +55,9 @@ struct nameseq
unsigned int ignore_mtime : 1; \
unsigned int staticpattern : 1; \
unsigned int need_2nd_expansion : 1; \
- unsigned int ignore_automatic_vars : 1
+ unsigned int ignore_automatic_vars : 1; \
+ unsigned int is_explicit : 1; \
+ unsigned int wait_here : 1
struct dep
{
diff --git a/src/expand.c b/src/expand.c
index 598f45302..48c5b5c33 100644
--- a/src/expand.c
+++ b/src/expand.c
@@ -1,5 +1,5 @@
-/* Builtin function expansion header for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+/* Variable expansion functions for GNU Make.
+Copyright (C) 1988-2022 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,15 +12,16 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see . */
+this program. If not, see . */
#include "makeint.h"
#include
+#include "commands.h"
+#include "debug.h"
#include "filedef.h"
#include "job.h"
-#include "commands.h"
#include "variable.h"
#include "rule.h"
#include "function.h"
@@ -43,6 +44,9 @@ const gmk_floc **expanding_var = &reading_file;
#define VARIABLE_BUFFER_ZONE 5
+char * variable_expand (const char *line);
+
+
static size_t variable_buffer_length;
char *variable_buffer;
@@ -68,13 +72,13 @@ variable_buffer_output (char *ptr, const char *string, size_t length)
ptr = variable_buffer + offset;
}
- memcpy (ptr, string, length);
- return ptr + length;
+ return mempcpy (ptr, string, length);
}
-/* Return a pointer to the beginning of the variable buffer. */
+/* Return a pointer to the beginning of the variable buffer.
+ This is called from main() and it should never be null afterward. */
-static char *
+char *
initialize_variable_output (void)
{
/* If we don't have a variable output buffer yet, get one. */
@@ -91,6 +95,22 @@ initialize_variable_output (void)
/* Recursively expand V. The returned string is malloc'd. */
+/** Expand PSZ_LINE. Expansion uses P_FILE_SET if it is not NULL. */
+char *
+variable_expand_set (char *psz_line, variable_set_list_t *p_file_vars)
+{
+ char *psz_result;
+ variable_set_list_t *p_vars_save;
+
+ p_vars_save = current_variable_set_list;
+ if (p_file_vars)
+ current_variable_set_list = p_file_vars;
+ psz_result = variable_expand (psz_line);
+ current_variable_set_list = p_vars_save;
+
+ return psz_result;
+}
+
static char *allocated_variable_append (const struct variable *v);
char *
@@ -102,6 +122,29 @@ recursively_expand_for_file (struct variable *v, struct file *file)
struct variable_set_list *save = 0;
int set_reading = 0;
+ /* If we're expanding to put into the environment of a shell function then
+ ignore any recursion issues: for backward-compatibility we will use
+ the value of the environment variable we were started with. */
+ if (v->expanding && env_recursion)
+ {
+ size_t nl = strlen (v->name);
+ char **ep;
+ DB (DB_VERBOSE,
+ (_("%s:%lu: not recursively expanding %s to export to shell function\n"),
+ v->fileinfo.filenm, v->fileinfo.lineno, v->name));
+
+ /* We could create a hash for the original environment for speed, but a
+ reasonably written makefile shouldn't hit this situation... */
+ for (ep = environ; *ep != 0; ++ep)
+ if ((*ep)[nl] == '=' && strncmp (*ep, v->name, nl) == 0)
+ return xstrdup ((*ep) + nl + 1);
+
+ /* If there's nothing in the parent environment, use the empty string.
+ This isn't quite correct since the variable should not exist at all,
+ but getting that to work would be involved. */
+ return xstrdup ("");
+ }
+
/* Don't install a new location if this location is empty.
This can happen for command-line variables, builtin variables, etc. */
saved_varp = expanding_var;
@@ -189,7 +232,6 @@ reference_variable (char *o, const char *name, size_t length)
if (v == 0)
warn_undefined (name, length);
-
/* If there's no variable by that name or it has no value, stop now. */
if (v == 0 || (*v->value == '\0' && !v->append))
return o;
@@ -230,7 +272,7 @@ variable_expand_string (char *line, const char *string, size_t length)
if (length == 0)
{
variable_buffer_output (o, "", 1);
- return (variable_buffer);
+ return variable_buffer;
}
/* We need a copy of STRING: due to eval, it's possible that it will get
@@ -503,27 +545,11 @@ variable_expand_for_file (const char *line, struct file *file)
return result;
}
-/** Expand PSZ_LINE. Expansion uses P_FILE_SET if it is not NULL. */
-char *
-variable_expand_set (char *psz_line, variable_set_list_t *p_file_vars)
-{
- char *psz_result;
- variable_set_list_t *p_vars_save;
-
- p_vars_save = current_variable_set_list;
- if (p_file_vars)
- current_variable_set_list = p_file_vars;
- psz_result = variable_expand (psz_line);
- current_variable_set_list = p_vars_save;
-
- return psz_result;
-}
-
/* Like allocated_variable_expand, but for += target-specific variables.
First recursively construct the variable value from its appended parts in
any upper variable sets. Then expand the resulting value. */
-static char *
+char *
variable_append (const char *name, size_t length,
const struct variable_set_list *set, int local)
{
diff --git a/src/file.c b/src/file.c
index 27ee4f1bb..016355ef9 100644
--- a/src/file.c
+++ b/src/file.c
@@ -1,5 +1,5 @@
/* Target file management for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2025 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,7 +12,7 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see . */
+this program. If not, see . */
#include "makeint.h"
#include "file_basic.h"
@@ -23,6 +23,7 @@ this program. If not, see . */
#include "file.h"
#include "dep.h"
#include "job.h"
+#include "shuffle.h"
#include "commands.h"
#include "variable.h"
#include "debug.h"
@@ -42,6 +43,14 @@ int snapped_deps = 0;
/* Hash table of files the makefile knows how to make. */
+/* We can't free files we take out of the hash table, because they are still
+ likely pointed to in various places. The check_renamed() will be used if
+ we come across these, to find the new correct file. This is mainly to
+ prevent leak checkers from complaining. */
+static struct file **rehashed_files = NULL;
+static size_t rehashed_files_len = 0;
+#define REHASHED_FILES_INCR 5
+
/* Whether or not .SECONDARY with no prerequisites was given. */
static int all_secondary = 0;
@@ -67,8 +76,7 @@ rehash_file (struct file *from_file, const char *to_hname)
/* Find the end of the renamed list for the "from" file. */
file_key.hname = from_file->hname;
- while (from_file->renamed != 0)
- from_file = from_file->renamed;
+ check_renamed (from_file);
if (file_hash_cmp (from_file, &file_key))
/* hname changed unexpectedly!! */
abort ();
@@ -107,25 +115,25 @@ rehash_file (struct file *from_file, const char *to_hname)
{
size_t l = strlen (from_file->name);
/* We have two sets of commands. We will go with the
- one given in the rule explicitly mentioning this name,
+ one given in the rule found through directory search,
but give a message to let the user know what's going on. */
if (to_file->cmds->fileinfo.filenm != 0)
error (&from_file->cmds->fileinfo,
l + strlen (to_file->cmds->fileinfo.filenm) + INTSTR_LENGTH,
- _("Recipe was specified for file '%s' at %s:%lu,"),
- from_file->name, to_file->cmds->fileinfo.filenm,
- to_file->cmds->fileinfo.lineno);
+ _("recipe was specified for file '%s' at %s:%lu,"),
+ from_file->name, from_file->cmds->fileinfo.filenm,
+ from_file->cmds->fileinfo.lineno);
else
error (&from_file->cmds->fileinfo, l,
- _("Recipe for file '%s' was found by implicit rule search,"),
+ _("recipe for file '%s' was found by implicit rule search,"),
from_file->name);
l += strlen (to_hname);
error (&from_file->cmds->fileinfo, l,
- _("but '%s' is now considered the same file as '%s'."),
+ _("but '%s' is now considered the same file as '%s'"),
from_file->name, to_hname);
error (&from_file->cmds->fileinfo, l,
- _("Recipe for '%s' will be ignored in favor of the one for '%s'."),
- to_hname, from_file->name);
+ _("recipe for '%s' will be ignored in favor of the one for '%s'"),
+ from_file->name, to_hname);
}
}
@@ -164,18 +172,29 @@ rehash_file (struct file *from_file, const char *to_hname)
#define MERGE(field) to_file->field |= from_file->field
MERGE (precious);
+ MERGE (loaded);
MERGE (tried_implicit);
MERGE (updating);
MERGE (updated);
MERGE (is_target);
MERGE (cmd_target);
MERGE (phony);
- MERGE (loaded);
+ /* Don't merge intermediate because this file might be pre-existing */
+ MERGE (is_explicit);
+ MERGE (secondary);
+ MERGE (notintermediate);
MERGE (ignore_vpath);
#undef MERGE
to_file->builtin = 0;
from_file->renamed = to_file;
+
+ if (rehashed_files_len % REHASHED_FILES_INCR == 0)
+ rehashed_files = xrealloc (rehashed_files,
+ sizeof (struct file *) * (rehashed_files_len + REHASHED_FILES_INCR));
+
+ rehashed_files[rehashed_files_len++] = from_file;
+
}
/* Rename FILE to NAME. This is not as simple as resetting
diff --git a/src/filedef.h b/src/filedef.h
index b126b51af..c6583524d 100644
--- a/src/filedef.h
+++ b/src/filedef.h
@@ -97,6 +97,7 @@ struct file
unsigned int builtin:1; /* True if the file is a builtin rule. */
unsigned int precious:1; /* Non-0 means don't delete file on quit */
unsigned int loaded:1; /* True if the file is a loaded object. */
+ unsigned int unloaded:1; /* True if this loaded object was unloaded. */
unsigned int low_resolution_time:1; /* Nonzero if this file's time stamp
has only one-second resolution. */
unsigned int tried_implicit:1; /* Nonzero if have searched
@@ -109,8 +110,11 @@ struct file
unsigned int phony:1; /* Nonzero if this is a phony file
i.e., a prerequisite of .PHONY. */
unsigned int intermediate:1;/* Nonzero if this is an intermediate file. */
+ unsigned int is_explicit:1; /* Nonzero if explicitly mentioned. */
unsigned int secondary:1; /* Nonzero means remove_intermediates should
not delete it. */
+ unsigned int notintermediate:1; /* Nonzero means a file is a prereq to
+ .NOTINTERMEDIATE. */
unsigned int dontcare:1; /* Nonzero if no complaint is to be made if
this target cannot be remade. */
unsigned int ignore_vpath:1;/* Nonzero if we threw out VPATH name. */
@@ -118,6 +122,11 @@ struct file
pattern-specific variables. */
unsigned int no_diag:1; /* True if the file failed to update and no
diagnostics has been issued (dontcare). */
+ unsigned int was_shuffled:1; /* Did we already shuffle 'deps'? used when
+ --shuffle passes through the graph. */
+ unsigned int snapped:1; /* True if the deps of this file have been
+ secondary expanded. */
+ unsigned int suffix:1; /* True if this is a suffix rule. */
unsigned int file_profiled:1; /* True if --profile has been set and
we have emitted a callgrind file line. */
};
diff --git a/src/function.c b/src/function.c
index 4251c73f2..abd614ddb 100644
--- a/src/function.c
+++ b/src/function.c
@@ -1,5 +1,5 @@
/* Builtin function expansion for GNU Make.
-Copyright (C) 1988-2020, Free Software Foundation, Inc.
+Copyright (C) 1988-2022 Free Software Foundation, Inc.
Copyright (C) 2026 R. Bernstein
This file is part of GNU Make.
@@ -13,7 +13,7 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see . */
+this program. If not, see . */
#include "makeint.h"
#include "filedef.h"
@@ -657,14 +657,14 @@ func_lastword (char *o, char **argv, const char *funcname UNUSED)
static char *
func_words (char *o, char **argv, const char *funcname UNUSED)
{
- int i = 0;
+ unsigned int i = 0;
const char *word_iterator = argv[0];
- char buf[20];
+ char buf[INTSTR_LENGTH];
while (find_next_token (&word_iterator, NULL) != 0)
++i;
- sprintf (buf, "%d", i);
+ sprintf (buf, "%u", i);
o = variable_buffer_output (o, buf, strlen (buf));
return o;
@@ -702,18 +702,39 @@ check_numeric (const char *s, const char *msg)
+static long
+parse_numeric (const char *s, const char *msg)
+{
+ const char *beg = s;
+ const char *end = s + strlen (s) - 1;
+ char *endp;
+ long num;
+ strip_whitespace (&beg, &end);
+
+ if (beg > end)
+ OS (fatal, *expanding_var, _("%s: empty value"), msg);
+
+ errno = 0;
+ num = strtoll (beg, &endp, 10);
+ if (errno == ERANGE)
+ OSS (fatal, *expanding_var, _("%s: '%s' out of range"), msg, s);
+ else if (endp == beg || endp <= end)
+ /* Empty or non-numeric input */
+ OSS (fatal, *expanding_var, "%s: '%s'", msg, s);
+
+ return num;
+}
+
static char *
func_word (char *o, char **argv, const char *funcname UNUSED)
{
const char *end_p;
const char *p;
- int i;
-
- /* Check the first argument. */
- check_numeric (argv[0], _("non-numeric first argument to 'word' function"));
- i = atoi (argv[0]);
+ long i;
- if (i == 0)
+ i = parse_numeric (argv[0],
+ _("invalid first argument to 'word' function"));
+ if (i < 1)
O (fatal, *expanding_var,
_("first argument to 'word' function must be greater than 0"));
@@ -768,6 +789,46 @@ func_wordlist (char *o, char **argv, const char *funcname UNUSED)
return o;
}
+/* static char * */
+/* func_wordlist (char *o, char **argv, const char *funcname UNUSED) */
+/* { */
+/* char buf[INTSTR_LENGTH + 1]; */
+/* long start, stop, count; */
+/* const char* badfirst = _("invalid first argument to 'wordlist' function"); */
+/* const char* badsecond = _("invalid second argument to 'wordlist' function"); */
+
+/* start = parse_numeric (argv[0], badfirst); */
+/* if (start < 1) */
+/* OSS (fatal, *expanding_var, "%s: '%s'", badfirst, make_lltoa (start, buf)); */
+
+/* stop = parse_numeric (argv[1], badsecond); */
+/* if (stop < 0) */
+/* OSS (fatal, *expanding_var, "%s: '%s'", badsecond, make_lltoa (stop, buf)); */
+
+/* count = stop - start + 1; */
+
+/* if (count > 0) */
+/* { */
+/* const char *p; */
+/* const char *end_p = argv[2]; */
+
+/* /\* Find the beginning of the "start"th word. *\/ */
+/* while (((p = find_next_token (&end_p, 0)) != 0) && --start) */
+/* ; */
+
+/* if (p) */
+/* { */
+/* /\* Find the end of the "count"th word from start. *\/ */
+/* while (--count && (find_next_token (&end_p, 0) != 0)) */
+/* ; */
+
+/* /\* Return the stuff in the middle. *\/ */
+/* o = variable_buffer_output (o, p, end_p - p); */
+/* } */
+/* } */
+
+/* return o; */
+/* } */
static char *
func_findstring (char *o, char **argv, const char *funcname UNUSED)
@@ -806,7 +867,7 @@ func_foreach (char *o, char **argv, const char *funcname UNUSED)
char *result = 0;
free (var->value);
- var->value = strndup (p, len);
+ var->value = xstrndup (p, len);
result = allocated_variable_expand (body);
@@ -827,9 +888,58 @@ func_foreach (char *o, char **argv, const char *funcname UNUSED)
return o;
}
+static char *
+func_let (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* expand only the first two. */
+ char *varnames = expand_argument (argv[0], NULL);
+ char *list = expand_argument (argv[1], NULL);
+ const char *body = argv[2];
+
+ const char *vp;
+ const char *vp_next = varnames;
+ const char *list_iterator = list;
+ char *p;
+ size_t len;
+ size_t vlen;
+
+ push_new_variable_scope ();
+
+ /* loop through LIST for all but the last VARNAME */
+ vp = find_next_token (&vp_next, &vlen);
+ NEXT_TOKEN (vp_next);
+ while (*vp_next != '\0')
+ {
+ p = find_next_token (&list_iterator, &len);
+ if (*list_iterator != '\0')
+ {
+ ++list_iterator;
+ p[len] = '\0';
+ }
+ define_variable (vp, vlen, p ? p : "", o_automatic, 0);
+
+ vp = find_next_token (&vp_next, &vlen);
+ NEXT_TOKEN (vp_next);
+ }
+
+ /* set the last VARNAME to the remainder of LIST */
+ if (vp)
+ define_variable (vp, vlen, next_token (list_iterator), o_automatic, 0);
+
+ /* Expand the body in the context of the arguments, adding the result to
+ the variable buffer. */
+
+ o = variable_expand_string (o, body, SIZE_MAX);
+
+ pop_variable_scope ();
+ free (varnames);
+ free (list);
+
+ return o + strlen (o);
+}
+
struct a_word
{
- struct a_word *next;
struct a_word *chain;
char *str;
size_t length;
@@ -851,16 +961,17 @@ a_word_hash_2 (const void *key)
static int
a_word_hash_cmp (const void *x, const void *y)
{
- int result = (int) ((struct a_word const *) x)->length - ((struct a_word const *) y)->length;
- if (result)
- return result;
- return_STRING_COMPARE (((struct a_word const *) x)->str,
- ((struct a_word const *) y)->str);
+ const struct a_word *ax = x;
+ const struct a_word *ay = y;
+
+ if (ax->length != ay->length)
+ return ax->length > ay->length ? 1 : -1;
+
+ return_STRING_N_COMPARE (ax->str, ay->str, ax->length);
}
struct a_pattern
{
- struct a_pattern *next;
char *str;
char *percent;
size_t length;
@@ -869,78 +980,84 @@ struct a_pattern
static char *
func_filter_filterout (char *o, char **argv, const char *funcname)
{
- struct a_word *wordhead;
- struct a_word **wordtail;
+ struct a_word *words;
+ struct a_word *word_end;
struct a_word *wp;
- struct a_pattern *pathead;
- struct a_pattern **pattail;
+ struct a_pattern *patterns;
+ struct a_pattern *pat_end;
struct a_pattern *pp;
+ unsigned long pat_count = 0, word_count = 0;
struct hash_table a_word_table;
int is_filter = funcname[CSTRLEN ("filter")] == '\0';
- const char *pat_iterator = argv[0];
- const char *word_iterator = argv[1];
+ const char *cp;
int literals = 0;
- int words = 0;
int hashing = 0;
char *p;
size_t len;
+ int doneany = 0;
- /* Chop ARGV[0] up into patterns to match against the words.
- We don't need to preserve it because our caller frees all the
- argument memory anyway. */
+ /* Find the number of words and get memory for them. */
+ cp = argv[1];
+ while ((p = find_next_token (&cp, NULL)) != 0)
+ ++word_count;
- pattail = &pathead;
- while ((p = find_next_token (&pat_iterator, &len)) != 0)
- {
- struct a_pattern *pat = alloca (sizeof (struct a_pattern));
+ if (!word_count)
+ return o;
+
+ words = xcalloc (word_count * sizeof (struct a_word));
+ word_end = words + word_count;
- *pattail = pat;
- pattail = &pat->next;
+ /* Find the number of patterns and get memory for them. */
+ cp = argv[0];
+ while ((p = find_next_token (&cp, NULL)) != 0)
+ ++pat_count;
- if (*pat_iterator != '\0')
- ++pat_iterator;
+ patterns = xcalloc (pat_count * sizeof (struct a_pattern));
+ pat_end = patterns + pat_count;
+
+ /* Chop argv[0] up into patterns to match against the words. */
+
+ cp = argv[0];
+ pp = patterns;
+ while ((p = find_next_token (&cp, &len)) != 0)
+ {
+ if (*cp != '\0')
+ ++cp;
- pat->str = p;
p[len] = '\0';
- pat->percent = find_percent (p);
- if (pat->percent == 0)
+ pp->str = p;
+ pp->percent = find_percent (p);
+ if (pp->percent == 0)
literals++;
-
/* find_percent() might shorten the string so LEN is wrong. */
- pat->length = strlen (pat->str);
+ pp->length = strlen (pp->str);
+
+ ++pp;
}
- *pattail = 0;
/* Chop ARGV[1] up into words to match against the patterns. */
- wordtail = &wordhead;
- while ((p = find_next_token (&word_iterator, &len)) != 0)
+ cp = argv[1];
+ wp = words;
+ while ((p = find_next_token (&cp, &len)) != 0)
{
- struct a_word *word = alloca (sizeof (struct a_word));
-
- *wordtail = word;
- wordtail = &word->next;
-
- if (*word_iterator != '\0')
- ++word_iterator;
+ if (*cp != '\0')
+ ++cp;
p[len] = '\0';
- word->str = p;
- word->length = len;
- word->matched = 0;
- word->chain = 0;
- words++;
+ wp->str = p;
+ wp->length = len;
+ ++wp;
}
- *wordtail = 0;
/* Only use a hash table if arg list lengths justifies the cost. */
- hashing = (literals >= 2 && (literals * words) >= 10);
+ hashing = (literals > 1 && (literals * word_count) >= 10);
if (hashing)
{
- hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2,
+ hash_init (&a_word_table, word_count, a_word_hash_1, a_word_hash_2,
a_word_hash_cmp);
- for (wp = wordhead; wp != 0; wp = wp->next)
+ for (wp = words; wp < word_end; ++wp)
{
struct a_word *owp = hash_insert (&a_word_table, wp);
if (owp)
@@ -948,51 +1065,49 @@ func_filter_filterout (char *o, char **argv, const char *funcname)
}
}
- if (words)
+ /* Run each pattern through the words, killing words. */
+ for (pp = patterns; pp < pat_end; ++pp)
{
- int doneany = 0;
-
- /* Run each pattern through the words, killing words. */
- for (pp = pathead; pp != 0; pp = pp->next)
+ if (pp->percent)
+ for (wp = words; wp < word_end; ++wp)
+ wp->matched |= pattern_matches (pp->str, pp->percent, wp->str);
+ else if (hashing)
{
- if (pp->percent)
- for (wp = wordhead; wp != 0; wp = wp->next)
- wp->matched |= pattern_matches (pp->str, pp->percent, wp->str);
- else if (hashing)
+ struct a_word a_word_key;
+ a_word_key.str = pp->str;
+ a_word_key.length = pp->length;
+ wp = hash_find_item (&a_word_table, &a_word_key);
+ while (wp)
{
- struct a_word a_word_key;
- a_word_key.str = pp->str;
- a_word_key.length = pp->length;
- wp = hash_find_item (&a_word_table, &a_word_key);
- while (wp)
- {
- wp->matched |= 1;
- wp = wp->chain;
- }
+ wp->matched |= 1;
+ wp = wp->chain;
}
- else
- for (wp = wordhead; wp != 0; wp = wp->next)
- wp->matched |= (wp->length == pp->length
- && strneq (pp->str, wp->str, wp->length));
}
+ else
+ for (wp = words; wp < word_end; ++wp)
+ wp->matched |= (wp->length == pp->length
+ && memcmp (pp->str, wp->str, wp->length) == 0);
+ }
- /* Output the words that matched (or didn't, for filter-out). */
- for (wp = wordhead; wp != 0; wp = wp->next)
- if (is_filter ? wp->matched : !wp->matched)
- {
- o = variable_buffer_output (o, wp->str, strlen (wp->str));
- o = variable_buffer_output (o, " ", 1);
- doneany = 1;
- }
+ /* Output the words that matched (or didn't, for filter-out). */
+ for (wp = words; wp < word_end; ++wp)
+ if (is_filter ? wp->matched : !wp->matched)
+ {
+ o = variable_buffer_output (o, wp->str, strlen (wp->str));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
- if (doneany)
- /* Kill the last space. */
- --o;
- }
+ if (doneany)
+ /* Kill the last space. */
+ --o;
if (hashing)
hash_free (&a_word_table, 0);
+ free (patterns);
+ free (words);
+
return o;
}
@@ -1032,41 +1147,25 @@ func_strip (char *o, char **argv, const char *funcname UNUSED)
static char *
func_error (char *o, char **argv, const char *funcname)
{
- char **argvp;
- char *msg, *p;
- size_t len;
-
- /* The arguments will be broken on commas. Rather than create yet
- another special case where function arguments aren't broken up,
- just create a format string that puts them back together. */
- for (len=0, argvp=argv; *argvp != 0; ++argvp)
- len += strlen (*argvp) + 2;
-
- p = msg = alloca (len + 1);
- msg[0] = '\0';
-
- for (argvp=argv; argvp[1] != 0; ++argvp)
- {
- strcpy (p, *argvp);
- p += strlen (*argvp);
- *(p++) = ',';
- *(p++) = ' ';
- }
- strcpy (p, *argvp);
-
switch (*funcname)
{
case 'e':
- OS (fatal, reading_file, "%s", msg);
+ OS (fatal, reading_file, "%s", argv[0]);
case 'w':
- OS (error, reading_file, "%s", msg);
+ OS (error, reading_file, "%s", argv[0]);
break;
case 'i':
- outputs (0, msg);
- outputs (0, "\n");
- break;
+ {
+ size_t len = strlen (argv[0]);
+ char *msg = alloca (len + 2);
+ memcpy (msg, argv[0], len);
+ msg[len] = '\n';
+ msg[len + 1] = '\0';
+ outputs (0, msg);
+ break;
+ }
default:
OS (fatal, *expanding_var, "Internal error: func_error: '%s'", funcname);
@@ -1122,7 +1221,7 @@ func_sort (char *o, char **argv, const char *funcname UNUSED)
{
len = strlen (words[i]);
if (i == wordi - 1 || strlen (words[i + 1]) != len
- || strcmp (words[i], words[i + 1]))
+ || memcmp (words[i], words[i + 1], len))
{
o = variable_buffer_output (o, words[i], len);
o = variable_buffer_output (o, " ", 1);
@@ -1138,6 +1237,115 @@ func_sort (char *o, char **argv, const char *funcname UNUSED)
return o;
}
+/*
+ Traverse NUMBER consisting of optional leading white space, optional
+ sign, digits, and optional trailing white space.
+ If number is not of the proper form, diagnose with MSG. Otherwise,
+ return the address of of the first character after NUMBER, store
+ into *SIGN an integer consistent with the number's sign (-1, 0, or 1)
+ and store into *NUMSTART the address of NUMBER's first nonzero digit
+ (if NUMBER contains only zero digits, store the address of the first
+ character after NUMBER).
+*/
+static const char *
+parse_textint (const char *number, const char *msg,
+ int *sign, const char **numstart)
+{
+ const char *after_sign, *after_number;
+ const char *p = next_token (number);
+ int negative = *p == '-';
+ int nonzero;
+
+ if (*p == '\0')
+ OS (fatal, *expanding_var, _("%s: empty value"), msg);
+
+ p += negative || *p == '+';
+ after_sign = p;
+
+ while (*p == '0')
+ p++;
+ *numstart = p;
+
+ while (ISDIGIT (*p))
+ ++p;
+ after_number = p;
+ nonzero = *numstart != after_number;
+ *sign = negative ? -nonzero : nonzero;
+
+ /* Check for extra non-whitespace stuff after the value. */
+ if (after_number == after_sign || *next_token (p) != '\0')
+ OSS (fatal, *expanding_var, "%s: '%s'", msg, number);
+
+ return after_number;
+}
+
+
+/*
+ $(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]])
+
+ LHS and RHS must be integer values (leading/trailing whitespace is ignored).
+ If none of LT-PART, EQ-PART, or GT-PART are given then the function expands
+ to empty if LHS and RHS are not equal, or the numeric value if they are equal.
+ LT-PART is evaluated when LHS is strictly less than RHS, EQ-PART is evaluated
+ when LHS is equal to RHS, and GT-part is evaluated when LHS is strictly
+ greater than RHS.
+ If GT-PART is not provided, it defaults to EQ-PART. When neither EQ-PART
+ nor GT-PART are provided, the function expands to empty if LHS is not
+ strictly less than RHS.
+*/
+
+static char *
+func_intcmp (char *o, char **argv, const char *funcname UNUSED)
+{
+ int lsign, rsign;
+ const char *lnum, *rnum;
+ char *lhs_str = expand_argument (argv[0], NULL);
+ char *rhs_str = expand_argument (argv[1], NULL);
+ const char *llim = parse_textint (lhs_str, _("non-numeric first argument to 'intcmp' function"), &lsign, &lnum);
+ const char *rlim = parse_textint (rhs_str, _("non-numeric second argument to 'intcmp' function"), &rsign, &rnum);
+ ptrdiff_t llen = llim - lnum;
+ ptrdiff_t rlen = rlim - rnum;
+ int cmp = lsign - rsign;
+
+ if (cmp == 0)
+ {
+ cmp = (llen > rlen) - (llen < rlen);
+ if (cmp == 0)
+ cmp = memcmp (lnum, rnum, llen);
+ }
+
+ argv += 2;
+
+ /* Handle the special case where there are only two arguments. */
+ if (!*argv && cmp == 0)
+ {
+ if (lsign < 0)
+ o = variable_buffer_output (o, "-", 1);
+ o = variable_buffer_output(o, lnum - !lsign, llen + !lsign);
+ }
+
+ free (lhs_str);
+ free (rhs_str);
+
+ if (*argv && cmp >= 0)
+ {
+ ++argv;
+ if (cmp > 0 && *argv && *(argv + 1))
+ ++argv;
+ }
+
+ if (*argv)
+ {
+ char *expansion = expand_argument (*argv, NULL);
+
+ o = variable_buffer_output (o, expansion, strlen (expansion));
+
+ free (expansion);
+ }
+
+ return o;
+}
+
/*
$(if condition,true-part[,false-part])
@@ -1478,7 +1686,7 @@ static int shell_function_completed;
void
shell_completed (int exit_code, int exit_sig)
{
- char buf[256];
+ char buf[INTSTR_LENGTH];
shell_function_pid = 0;
if (exit_sig == 0 && exit_code == 127)
@@ -1544,7 +1752,7 @@ windows32_openpipe (int *pipedes, int errfd, pid_t *pid_p, char **command_argv,
if (hIn == INVALID_HANDLE_VALUE)
{
ON (error, NILF,
- _("windows32_openpipe: DuplicateHandle(In) failed (e=%ld)\n"), e);
+ _("windows32_openpipe: DuplicateHandle(In) failed (e=%lu)\n"), e);
return -1;
}
}
@@ -1568,14 +1776,14 @@ windows32_openpipe (int *pipedes, int errfd, pid_t *pid_p, char **command_argv,
if (hErr == INVALID_HANDLE_VALUE)
{
ON (error, NILF,
- _("windows32_openpipe: DuplicateHandle(Err) failed (e=%ld)\n"), e);
+ _("windows32_openpipe: DuplicateHandle(Err) failed (e=%lu)\n"), e);
return -1;
}
}
if (! CreatePipe (&hChildOutRd, &hChildOutWr, &saAttr, 0))
{
- ON (error, NILF, _("CreatePipe() failed (e=%ld)\n"), GetLastError());
+ ON (error, NILF, _("CreatePipe() failed (e=%lu)\n"), GetLastError());
return -1;
}
@@ -1587,12 +1795,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 */
@@ -1691,10 +1893,10 @@ msdos_openpipe (int* pipedes, int *pidp, char *text)
char *
func_shell_base (char *o, char **argv, int trim_newlines)
{
+ struct childbase child = {0};
char *batch_filename = NULL;
int errfd;
char **command_argv = NULL;
- char **envp;
int pipedes[2];
pid_t pid;
@@ -1719,26 +1921,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, 0);
+
#if defined(__MSDOS__)
fpipe = msdos_openpipe (pipedes, &pid, argv[0]);
if (pipedes[0] < 0)
@@ -1749,7 +1939,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;
@@ -1774,18 +1964,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)
{
@@ -1876,6 +2059,8 @@ func_shell_base (char *o, char **argv, int trim_newlines)
free (command_argv);
}
+ free_childbase (&child);
+
return o;
}
@@ -1917,7 +2102,7 @@ func_not (char *o, char **argv, char *funcname UNUSED)
#ifdef HAVE_DOS_PATHS
# ifdef __CYGWIN__
-# define IS_ABSOLUTE(n) ((n[0] && n[1] == ':') || STOP_SET (n[0], MAP_DIRSEP))
+# define IS_ABSOLUTE(n) ((n[0] && n[1] == ':') || ISDIRSEP (n[0]))
# else
# define IS_ABSOLUTE(n) (n[0] && n[1] == ':')
# endif
@@ -1951,9 +2136,9 @@ abspath (const char *name, char *apath)
strcpy (apath, starting_directory);
#ifdef HAVE_DOS_PATHS
- if (STOP_SET (name[0], MAP_DIRSEP))
+ if (ISDIRSEP (name[0]))
{
- if (STOP_SET (name[1], MAP_DIRSEP))
+ if (ISDIRSEP (name[1]))
{
/* A UNC. Don't prepend a drive letter. */
apath[0] = name[0];
@@ -1973,7 +2158,7 @@ abspath (const char *name, char *apath)
else
{
#if defined(__CYGWIN__) && defined(HAVE_DOS_PATHS)
- if (STOP_SET (name[0], MAP_DIRSEP))
+ if (ISDIRSEP (name[0]))
root_len = 1;
#endif
memcpy (apath, name, root_len);
@@ -1982,7 +2167,7 @@ abspath (const char *name, char *apath)
/* Get past the root, since we already copied it. */
name += root_len;
#ifdef HAVE_DOS_PATHS
- if (! STOP_SET (apath[root_len - 1], MAP_DIRSEP))
+ if (! ISDIRSEP (apath[root_len - 1]))
{
/* Convert d:foo into d:./foo and increase root_len. */
apath[2] = '.';
@@ -2002,7 +2187,7 @@ abspath (const char *name, char *apath)
size_t len;
/* Skip sequence of multiple path-separators. */
- while (STOP_SET (*start, MAP_DIRSEP))
+ while (ISDIRSEP (*start))
++start;
/* Find end of path component. */
@@ -2019,25 +2204,24 @@ abspath (const char *name, char *apath)
{
/* Back up to previous component, ignore if at root already. */
if (dest > apath + root_len)
- for (--dest; ! STOP_SET (dest[-1], MAP_DIRSEP); --dest)
+ for (--dest; ! ISDIRSEP (dest[-1]); --dest)
;
}
else
{
- if (! STOP_SET (dest[-1], MAP_DIRSEP))
+ if (! ISDIRSEP (dest[-1]))
*dest++ = '/';
if (dest + len >= apath_limit)
return NULL;
- dest = memcpy (dest, start, len);
- dest += len;
+ dest = mempcpy (dest, start, len);
*dest = '\0';
}
}
/* Unless it is root strip trailing separator. */
- if (dest > apath + root_len && STOP_SET (dest[-1], MAP_DIRSEP))
+ if (dest > apath + root_len && ISDIRSEP (dest[-1]))
--dest;
*dest = '\0';
@@ -2129,6 +2313,11 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
if (fp == NULL)
OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno));
+ /* We've changed the contents of a directory, possibly.
+ Another option would be to look up the directory we changed and reset
+ its counter to 0. */
+ ++command_count;
+
if (argv[1])
{
size_t l = strlen (argv[1]);
@@ -2142,7 +2331,7 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
}
else if (fn[0] == '<')
{
- char *preo = o;
+ size_t n = 0;
FILE *fp;
++fn;
@@ -2166,8 +2355,10 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
char buf[1024];
size_t l = fread (buf, 1, sizeof (buf), fp);
if (l > 0)
- o = variable_buffer_output (o, buf, l);
-
+ {
+ o = variable_buffer_output (o, buf, l);
+ n += l;
+ }
if (ferror (fp))
if (errno != EINTR)
OSS (fatal, reading_file, _("read: %s: %s"), fn, strerror (errno));
@@ -2178,9 +2369,8 @@ func_file (char *o, char **argv, const char *funcname UNUSED)
OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno));
/* Remove trailing newline. */
- if (o > preo && o[-1] == '\n')
- if (--o > preo && o[-1] == '\r')
- --o;
+ if (n && o[-1] == '\n')
+ o -= 1 + (n > 1 && o[-2] == '\r');
}
else
OS (fatal, *expanding_var, _("file: invalid file operation: %s"), fn);
@@ -2233,12 +2423,13 @@ func_abspath (char *o, char **argv, const char *funcname UNUSED)
comma-separated values are treated as arguments.
EXPAND_ARGS means that all arguments should be expanded before invocation.
- Functions that do namespace tricks (foreach) don't automatically expand. */
+ Functions that do namespace tricks (foreach, let) don't automatically
+ expand. */
static char *func_call (char *o, char **argv, const char *funcname);
#define FT_ENTRY(_name, _min, _max, _exp, _func) \
- { { (_func) }, STRING_SIZE_TUPLE(_name), (_min), (_max), (_exp), 0 }
+ { { (_func) }, STRING_SIZE_TUPLE(_name), (_min), (_max), (_exp), 0, 0 }
static struct function_table_entry function_table_init[] =
{
@@ -2269,10 +2460,12 @@ static struct function_table_entry function_table_init[] =
FT_ENTRY ("words", 0, 1, 1, func_words),
FT_ENTRY ("origin", 0, 1, 1, func_origin),
FT_ENTRY ("foreach", 3, 3, 0, func_foreach),
+ FT_ENTRY ("let", 3, 3, 0, func_let),
FT_ENTRY ("call", 1, 0, 1, func_call),
FT_ENTRY ("info", 0, 1, 1, func_error),
FT_ENTRY ("error", 0, 1, 1, func_error),
FT_ENTRY ("warning", 0, 1, 1, func_error),
+ FT_ENTRY ("intcmp", 2, 5, 0, func_intcmp),
FT_ENTRY ("if", 2, 3, 0, func_if),
FT_ENTRY ("or", 1, 0, 0, func_or),
FT_ENTRY ("and", 1, 0, 0, func_and),
@@ -2298,23 +2491,27 @@ static struct function_table_entry function_table_init[] =
/* These must come after the definition of function_table. */
static char *
-expand_builtin_function (char *o, int argc, char **argv,
+expand_builtin_function (char *o, unsigned int argc, char **argv,
const struct function_table_entry *entry_p)
{
char *p;
- if (argc < (int)entry_p->minimum_args)
+ if (argc < entry_p->minimum_args)
fatal (*expanding_var, strlen (entry_p->name),
- _("insufficient number of arguments (%d) to function '%s'"),
+ _("insufficient number of arguments (%u) to function '%s'"),
argc, entry_p->name);
/* Nullary functions (argc == 0) are treated in the code that expands variables. */
- if (argc == 0) return o;
+ if (!argc && !entry_p->alloc_fn)
+ return o;
if (!entry_p->fptr.func_ptr)
OS (fatal, *expanding_var,
_("unimplemented on this platform: function '%s'"), entry_p->name);
+ if (entry_p->adds_command)
+ ++command_count;
+
if (!entry_p->alloc_fn)
return entry_p->fptr.func_ptr (o, argv, entry_p->name);
@@ -2347,7 +2544,7 @@ handle_function (char **op, const char **stringp)
int count = 0;
char *abeg = NULL;
char **argv, **argvp;
- int nargs;
+ unsigned int nargs;
beg = *stringp + 1;
@@ -2418,9 +2615,8 @@ handle_function (char **op, const char **stringp)
char *p, *aend;
abeg = xmalloc (len+1);
- memcpy (abeg, beg, len);
- abeg[len] = '\0';
- aend = abeg + len;
+ aend = mempcpy (abeg, beg, len);
+ *aend = '\0';
for (p=abeg, nargs=0; p <= aend; ++argvp)
{
@@ -2460,11 +2656,11 @@ handle_function (char **op, const char **stringp)
static char *
func_call (char *o, char **argv, const char *funcname UNUSED)
{
- static int max_args = 0;
+ static unsigned int max_args = 0;
char *fname;
char *body;
size_t flen;
- int i;
+ unsigned int i;
int saved_args;
const struct function_table_entry *entry_p;
struct variable *v;
@@ -2513,9 +2709,9 @@ func_call (char *o, char **argv, const char *funcname UNUSED)
for (i=0; *argv; ++i, ++argv)
{
- char num[11];
+ char num[INTSTR_LENGTH];
- sprintf (num, "%d", i);
+ sprintf (num, "%u", i);
define_variable (num, strlen (num), *argv, o_automatic, 0);
}
@@ -2526,9 +2722,9 @@ func_call (char *o, char **argv, const char *funcname UNUSED)
for (; i < max_args; ++i)
{
- char num[11];
+ char num[INTSTR_LENGTH];
- sprintf (num, "%d", i);
+ sprintf (num, "%u", i);
define_variable (num, strlen (num), "", o_automatic, 0);
}
@@ -2582,9 +2778,12 @@ define_new_function (const gmk_floc *flocp, const char *name,
ent->maximum_args = (unsigned char) max;
ent->expand_args = ANY_SET(flags, GMK_FUNC_NOEXPAND) ? 0 : 1;
ent->alloc_fn = 1;
+ /* We don't know what this function will do. */
+ ent->adds_command = 1;
ent->fptr.alloc_func_ptr = func;
- hash_insert (&function_table, ent);
+ ent = hash_insert (&function_table, ent);
+ free (ent);
}
void
diff --git a/src/function.h b/src/function.h
index b3f0835b6..045745361 100644
--- a/src/function.h
+++ b/src/function.h
@@ -69,6 +69,7 @@ struct function_table_entry
unsigned char maximum_args;
unsigned int expand_args:1;
unsigned int alloc_fn:1;
+ unsigned int adds_command:1;
};
extern const struct function_table_entry *lookup_function (const char *s);
diff --git a/src/hash.h b/src/hash.h
index 667d65082..dc80e9488 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -51,6 +51,7 @@ struct hash_table
unsigned long ht_collisions; /* # of failed calls to comparison function */
unsigned long ht_lookups; /* # of queries */
unsigned int ht_rehashes; /* # of times we've expanded table */
+ unsigned int ht_in_map:1; /* 1 if we're inside a hash_map*() function */
};
typedef int (*qsort_cmp_t) __P((void const *, void const *));
diff --git a/src/job.c b/src/job.c
index 25918be66..4b13006d7 100644
--- a/src/job.c
+++ b/src/job.c
@@ -962,6 +962,20 @@ reap_children (int block, int err, target_stack_node_t *p_call_stack)
/* 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)
{
@@ -1235,7 +1249,7 @@ start_job_command (child_t *child,
/* Set up the environment for the child. */
if (child->environment == 0)
- child->environment = target_environment (child->file);
+ child->environment = target_environment (child->file, child->recursive);
#if !defined(__MSDOS__) && !defined(WINDOWS32)
diff --git a/src/job.h b/src/job.h
index 2208e39b3..40dc057de 100644
--- a/src/job.h
+++ b/src/job.h
@@ -1,5 +1,5 @@
/* Definitions for managing subprocesses in GNU Make.
-Copyright (C) 1992-2020 Free Software Foundation, Inc.
+Copyright (C) 1992-2022 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify it under the
@@ -22,20 +22,10 @@ this program. If not, see . */
/* Structure describing a running or dead child process. */
-#ifdef VMS
-#define VMSCHILD \
- char *comname; /* Temporary command file name */ \
- int efn; /* Completion event flag number */ \
- int cstatus; /* Completion status */ \
- int vms_launch_status; /* non-zero if lib$spawn, etc failed */
-#else
-#define VMSCHILD
-#endif
#define CHILDBASE \
char *cmd_name; /* Alloced copy of command run. */ \
char **environment; /* Environment for commands. */ \
- VMSCHILD \
struct output output /* Output for this child. */
@@ -78,6 +68,7 @@ extern void new_job (file_t *file, target_stack_node_t *p_call_stack);
extern void reap_children (int block, int err,
target_stack_node_t *p_call_stack);
extern void start_waiting_jobs (target_stack_node_t *p_call_stack);
+void free_childbase (struct childbase* child);
char **construct_command_argv (char *line, char **restp, struct file *file,
int cmd_flags, char** batch_file);
diff --git a/src/main.c b/src/main.c
index e9df8d613..4479bf2de 100644
--- a/src/main.c
+++ b/src/main.c
@@ -68,7 +68,6 @@ static void print_data_base (void);
void print_rule_data_base (bool b_verbose);
static void print_version (void);
static void decode_switches (int argc, const char **argv, int env);
-static void decode_env_switches (const char *envar, size_t len);
static struct variable *define_makeflags (int all, int makefile);
static char *quote_for_env (char *out, const char *in);
static void initialize_global_hash_tables (void);
@@ -2965,7 +2964,7 @@ decode_switches (int argc, const char **argv, int env)
dash to the first word if it lacks one, and passing the vector to
decode_switches. */
-static void
+void
decode_env_switches (const char *envar, size_t len)
{
char *varref = alloca (2 + len + 2);
diff --git a/src/makeint.h b/src/makeint.h
index e7e05c708..cded71621 100644
--- a/src/makeint.h
+++ b/src/makeint.h
@@ -544,11 +544,14 @@ void out_of_memory (void) NORETURN;
/* void decode_env_switches (const char*, size_t line); */
void temp_stdin_unlink (void);
+void decode_env_switches (const char*, size_t line);
void die (int) NORETURN;
void pfatal_with_name (const char *) NORETURN;
void perror_with_name (const char *, const char *);
#define xstrlen(_s) ((_s)==NULL ? 0 : strlen (_s))
unsigned int make_toui (const char*, const char**);
+char *make_lltoa (long, char *);
+char *make_ulltoa (unsigned long, char *);
void make_seed (unsigned int);
unsigned int make_rand (void);
pid_t make_pid (void);
@@ -560,6 +563,7 @@ char *xstrndup (const char *, size_t);
char *find_next_token (const char **, size_t *);
char *next_token (const char *);
char *end_of_token (const char *);
+char *skip_reference (const char *);
void collapse_continuations (char *);
char *lindex (const char *, const char *, int);
int alpha_compare (const void *, const void *);
@@ -701,6 +705,7 @@ extern int print_version_flag, print_directory, check_symlink_flag;
extern int warn_undefined_variables_flag, trace_flag, posix_pedantic;
extern int not_parallel, second_expansion, clock_skew_detected;
extern int rebuilding_makefiles, one_shell, output_sync, verify_flag;
+extern unsigned long command_count;
extern int watch_flag;
struct goaldep;
void watch_loop (struct goaldep *goals, struct goaldep *read_files,
@@ -709,6 +714,9 @@ void watch_loop (struct goaldep *goals, struct goaldep *read_files,
/* can we run commands via 'sh -c xxx' or must we use batch files? */
extern int batch_mode_shell;
+#define GNUMAKEFLAGS_NAME "GNUMAKEFLAGS"
+#define MAKEFLAGS_NAME "MAKEFLAGS"
+
/* Resetting the command script introduction prefix character. */
#define RECIPEPREFIX_NAME ".RECIPEPREFIX"
#define RECIPEPREFIX_DEFAULT '\t'
diff --git a/src/misc.c b/src/misc.c
index 4c02cea9f..725e4a359 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -53,6 +53,24 @@ make_toui (const char *str, const char **error)
return val;
}
+/* Convert val into a string, written to buf. buf must be large enough
+ to hold the largest possible value, plus a nul byte. Returns buf.
+ We can't use standard PRI* here: those are based on intNN_t types. */
+
+char *
+make_lltoa (long val, char *buf)
+{
+ sprintf (buf, "%ld", val);
+ return buf;
+}
+
+char *
+make_ulltoa (unsigned long val, char *buf)
+{
+ sprintf (buf, "%lu", val);
+ return buf;
+}
+
/* Simple random number generator, for use with shuffle.
This doesn't need to be truly random, just pretty random. Use our own
implementation rather than relying on the C runtime's rand() so we always
@@ -379,6 +397,50 @@ next_token (const char *s)
return (char *)s;
}
+/* This function returns P if P points to EOS, or P+1 if P is NOT an open
+ paren or brace, or a pointer to the character after the matching close
+ paren or brace, skipping matched internal parens or braces.
+
+ It is typically called when we have seen a '$' in a string and we want to
+ treat it as a variable reference and find the end of it: in that case P
+ should point to the character after the '$'. */
+
+char *
+skip_reference (const char *p)
+{
+ char openparen = *p;
+ char closeparen;
+ int count = 1;
+
+ if (openparen == '\0')
+ return (char*)p;
+
+ if (openparen == '(')
+ closeparen = ')';
+ else if (openparen == '{')
+ closeparen = '}';
+ else
+ return (char*)(p+1);
+
+ while (1)
+ {
+ ++p;
+ if (!STOP_SET (*p, MAP_NUL|MAP_VARSEP))
+ continue;
+ if (*p == '\0')
+ break;
+ if (*p == openparen)
+ ++count;
+ else if (*p == closeparen && --count == 0)
+ {
+ ++p;
+ break;
+ }
+ }
+
+ return (char*)p;
+}
+
/* Find the next token in PTR; return the address of it, and store the length
of the token into *LENGTHPTR if LENGTHPTR is not nil. Set *PTR to the end
of the token, so this function can be called repeatedly in a loop. */
diff --git a/src/posixos.c b/src/posixos.c
index 44aeb3464..c2dc50e68 100644
--- a/src/posixos.c
+++ b/src/posixos.c
@@ -151,8 +151,8 @@ jobserver_setup (int slots, const char *style)
fifo_name = xmalloc (strlen (tmpdir) + CSTRLEN (FNAME_PREFIX)
+ INTSTR_LENGTH + 2);
- sprintf (fifo_name, "%s/" FNAME_PREFIX "%" MK_PRI64_PREFIX "d",
- tmpdir, (long long)make_pid ());
+ sprintf (fifo_name, "%s/" FNAME_PREFIX "%ld",
+ tmpdir, (long)make_pid ());
EINTRLOOP (r, mkfifo (fifo_name, 0600));
if (r < 0)
diff --git a/src/read.c b/src/read.c
index f99ecb59c..ca1ab682c 100644
--- a/src/read.c
+++ b/src/read.c
@@ -443,7 +443,10 @@ eval_makefile (const char *filename, unsigned short flags)
fclose (ebuf.fp);
free (ebuf.bufstart);
- alloca (0);
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ (void) alloca (0);
+#pragma GCC diagnostic pop
errno = 0;
return deps;
@@ -485,7 +488,11 @@ eval_buffer (char *buffer, const gmk_floc *flocp)
reading_file = curfile;
- alloca (0);
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ (void) alloca (0);
+#pragma GCC diagnostic pop
+
}
/* Check LINE to see if it's a variable assignment or undefine.
diff --git a/src/remake.c b/src/remake.c
index c07f6e06f..8fbab794f 100644
--- a/src/remake.c
+++ b/src/remake.c
@@ -342,7 +342,10 @@ update_file (struct file *file, unsigned int depth,
check_renamed (f);
/* Clean up any alloca() used during the update. */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-result"
alloca (0);
+# pragma GCC diagnostic pop
/* If we got an error, don't bother with double_colon etc. */
if (new && !keep_going_flag)
diff --git a/src/shuffle.c b/src/shuffle.c
new file mode 100644
index 000000000..2a320d7ac
--- /dev/null
+++ b/src/shuffle.c
@@ -0,0 +1,241 @@
+/* Provide prerequisite shuffle support.
+Copyright (C) 2022-2025 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see . */
+
+#include "makeint.h"
+
+#include "shuffle.h"
+
+#include "filedef.h"
+#include "dep.h"
+
+/* Supported shuffle modes. */
+static void random_shuffle_array (void ** a, size_t len);
+static void reverse_shuffle_array (void ** a, size_t len);
+static void identity_shuffle_array (void ** a, size_t len);
+
+/* The way goals and rules are shuffled during update. */
+enum shuffle_mode
+ {
+ /* No shuffle data is populated or used. */
+ sm_none,
+ /* Random within dependency list. */
+ sm_random,
+ /* Inverse order. */
+ sm_reverse,
+ /* identity order. Differs from SM_NONE by explicitly populating
+ the traversal order. */
+ sm_identity,
+ };
+
+/* Shuffle configuration. */
+static struct
+ {
+ enum shuffle_mode mode;
+ unsigned int seed;
+ void (*shuffler) (void **a, size_t len);
+ char strval[INTSTR_LENGTH + 1];
+ } config = { sm_none, 0, NULL, "" };
+
+/* Return string value of --shuffle= option passed.
+ If none was passed or --shuffle=none was used function
+ returns NULL. */
+const char *
+shuffle_get_mode ()
+{
+ return config.strval[0] == '\0' ? NULL : config.strval;
+}
+
+void
+shuffle_set_mode (const char *cmdarg)
+{
+ /* Parse supported '--shuffle' mode. */
+ if (strcasecmp (cmdarg, "reverse") == 0)
+ {
+ config.mode = sm_reverse;
+ config.shuffler = reverse_shuffle_array;
+ strcpy (config.strval, "reverse");
+ }
+ else if (strcasecmp (cmdarg, "identity") == 0)
+ {
+ config.mode = sm_identity;
+ config.shuffler = identity_shuffle_array;
+ strcpy (config.strval, "identity");
+ }
+ else if (strcasecmp (cmdarg, "none") == 0)
+ {
+ config.mode = sm_none;
+ config.shuffler = NULL;
+ config.strval[0] = '\0';
+ }
+ else
+ {
+ if (strcasecmp (cmdarg, "random") == 0)
+ config.seed = make_rand ();
+ else
+ {
+ /* Assume explicit seed. */
+ const char *err;
+ config.seed = make_toui (cmdarg, &err);
+ if (err)
+ OSS (fatal, NILF, _("invalid shuffle mode: %s: '%s'"), err, cmdarg);
+ }
+
+ config.mode = sm_random;
+ config.shuffler = random_shuffle_array;
+ sprintf (config.strval, "%u", config.seed);
+ }
+}
+
+/* Shuffle array elements using RAND(). */
+static void
+random_shuffle_array (void **a, size_t len)
+{
+ size_t i;
+
+ if (len <= 1)
+ return;
+
+ for (i = len - 1; i >= 1; i--)
+ {
+ void *t;
+
+ /* Pick random element and swap. */
+ unsigned int j = make_rand () % (i + 1);
+ if (i == j)
+ continue;
+
+ /* Swap. */
+ t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+}
+
+/* Shuffle array elements using reverse order. */
+static void
+reverse_shuffle_array (void **a, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len / 2; i++)
+ {
+ void *t;
+
+ /* Pick mirror and swap. */
+ size_t j = len - 1 - i;
+
+ /* Swap. */
+ t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+}
+
+/* Shuffle array elements using identity order. */
+static void
+identity_shuffle_array (void **a UNUSED, size_t len UNUSED)
+{
+ /* No-op! */
+}
+
+/* Shuffle list of dependencies by populating '->shuf'
+ field in each 'struct dep'. */
+static void
+shuffle_deps (struct dep *deps)
+{
+ size_t ndeps = 0;
+ struct dep *dep;
+ void **da;
+ void **dp;
+
+ for (dep = deps; dep; dep = dep->next)
+ {
+ /* Do not reshuffle prerequisites if any .WAIT is present. */
+ if (dep->wait_here)
+ return;
+
+ ndeps++;
+ }
+
+ if (ndeps == 0)
+ return;
+
+ /* Allocate array of all deps, store, shuffle, write back. */
+ da = xmalloc (sizeof (struct dep *) * ndeps);
+
+ /* Store locally. */
+ for (dep = deps, dp = da; dep; dep = dep->next, dp++)
+ *dp = dep;
+
+ /* Shuffle. */
+ config.shuffler (da, ndeps);
+
+ /* Write back. */
+ for (dep = deps, dp = da; dep; dep = dep->next, dp++)
+ dep->shuf = *dp;
+
+ free (da);
+}
+
+/* Shuffle 'deps' of each 'file' recursively. */
+static void
+shuffle_file_deps_recursive (struct file *f)
+{
+ struct dep *dep;
+
+ /* Implicit rules do not always provide any depends. */
+ if (!f)
+ return;
+
+ /* Avoid repeated shuffles and loops. */
+ if (f->was_shuffled)
+ return;
+ f->was_shuffled = 1;
+
+ shuffle_deps (f->deps);
+
+ /* Shuffle dependencies. */
+ for (dep = f->deps; dep; dep = dep->next)
+ shuffle_file_deps_recursive (dep->file);
+}
+
+/* Shuffle goal dependencies first, then shuffle dependency list
+ of each file reachable from goaldep recursively. Used by
+ --shuffle flag to introduce artificial non-determinism in build
+ order. .*/
+
+void
+shuffle_deps_recursive (struct dep *deps)
+{
+ struct dep *dep;
+
+ /* Exit early if shuffling was not requested. */
+ if (config.mode == sm_none)
+ return;
+
+ /* Do not reshuffle prerequisites if .NOTPARALLEL was specified. */
+ if (not_parallel)
+ return;
+
+ /* Set specific seed at the top level of recursion. */
+ if (config.mode == sm_random)
+ make_seed (config.seed);
+
+ shuffle_deps (deps);
+
+ /* Shuffle dependencies. */
+ for (dep = deps; dep; dep = dep->next)
+ shuffle_file_deps_recursive (dep->file);
+}
diff --git a/src/shuffle.h b/src/shuffle.h
new file mode 100644
index 000000000..029733922
--- /dev/null
+++ b/src/shuffle.h
@@ -0,0 +1,26 @@
+/* Declarations for target shuffling support.
+Copyright (C) 2022-2025 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see . */
+
+struct dep;
+struct goaldep;
+
+void shuffle_set_mode (const char *cmdarg);
+const char *shuffle_get_mode (void);
+void shuffle_deps_recursive (struct dep* g);
+
+#define shuffle_goaldeps_recursive(_g) do{ \
+ shuffle_deps_recursive ((struct dep *)_g); \
+ } while(0)
diff --git a/src/variable.c b/src/variable.c
index a452e37ff..fb03ed0f8 100644
--- a/src/variable.c
+++ b/src/variable.c
@@ -1,5 +1,5 @@
/* Internals of variables for GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2022 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,17 +12,19 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see . */
+this program. If not, see . */
#include "makeint.h"
#include
#include "filedef.h"
+#include "debug.h"
#include "dep.h"
#include "job.h"
#include "commands.h"
#include "variable.h"
+#include "os.h"
#include "rule.h"
#ifdef WINDOWS32
#include "pathstuff.h"
@@ -30,12 +32,15 @@ this program. If not, see . */
#include "hash.h"
#include "main.h"
+/* Incremented every time we enter target_environment(). */
+unsigned long env_recursion = 0;
+
/* Incremented every time we add or remove a global variable. */
-static unsigned long variable_changenum;
+static unsigned long variable_changenum = 0;
/* Chain of all pattern-specific variables. */
-static struct pattern_var *pattern_vars;
+static struct pattern_var *pattern_vars = NULL;
/* Pointer to the last struct in the pack of a specific size, from 1 to 255.*/
@@ -137,10 +142,10 @@ create_pattern_var (const char *target, const char *suffix)
/* Look up a target in the pattern-specific variable list. */
static struct pattern_var *
-lookup_pattern_var (struct pattern_var *start, const char *target)
+lookup_pattern_var (struct pattern_var *start, const char *target,
+ size_t targlen)
{
struct pattern_var *p;
- size_t targlen = strlen (target);
for (p = start ? start->next : pattern_vars; p != 0; p = p->next)
{
@@ -251,9 +256,9 @@ define_variable_in_set (const char *name, size_t length,
#ifdef VMS
/* VMS does not populate envp[] with DCL symbols and logical names which
- historically are mapped to environent variables.
+ historically are mapped to environment variables.
If the variable is not yet defined, then we need to check if getenv()
- can find it. Do not do this for origin == o_env to avoid infinte
+ can find it. Do not do this for origin == o_env to avoid infinite
recursion */
if (HASH_VACANT (v) && (origin != o_env))
{
@@ -321,6 +326,8 @@ define_variable_in_set (const char *name, size_t length,
v->export = v_default;
v->exportable = 1;
+ /* Check the nul-terminated variable name. */
+ name = v->name;
if (*name != '_' && (*name < 'A' || *name > 'Z')
&& (*name < 'a' || *name > 'z'))
v->exportable = 0;
@@ -415,13 +422,12 @@ lookup_special_var (struct variable *var)
{
static unsigned long last_changenum = 0;
-
/* This one actually turns out to be very hard, due to the way the parser
records targets. The way it works is that target information is collected
- internally until make knows the target is completely specified. It unitl
- it sees that some new construct (a new target or variable) is defined that
- it knows the previous one is done. In short, this means that if you do
- this:
+ internally until make knows the target is completely specified. Only when
+ it sees that some new construct (a new target or variable) is defined does
+ make know that the previous one is done. In short, this means that if
+ you do this:
all:
@@ -471,8 +477,7 @@ lookup_special_var (struct variable *var)
p = &var->value[off];
}
- memcpy (p, v->name, l);
- p += l;
+ p = mempcpy (p, v->name, l);
*(p++) = ' ';
}
*(p-1) = '\0';
@@ -514,8 +519,9 @@ lookup_variable (const char *name, size_t length)
}
#ifdef VMS
- /* VMS does not populate envp[] with DCL symbols and logical names which
- historically are mapped to enviroment varables and returned by getenv() */
+ /* VMS doesn't populate envp[] with DCL symbols and logical names, which
+ historically are mapped to environment variables and returned by
+ getenv(). */
{
char *vname = alloca (length + 1);
char *value;
@@ -640,8 +646,9 @@ initialize_file_variables (struct file *file, int reading)
if (!reading && !file->pat_searched)
{
struct pattern_var *p;
+ const size_t targlen = strlen (file->name);
- p = lookup_pattern_var (0, file->name);
+ p = lookup_pattern_var (0, file->name, targlen);
if (p != 0)
{
struct variable_set_list *global = current_variable_set_list;
@@ -680,7 +687,7 @@ initialize_file_variables (struct file *file, int reading)
v->export = p->variable.export;
v->private_var = p->variable.private_var;
}
- while ((p = lookup_pattern_var (p, file->name)) != 0);
+ while ((p = lookup_pattern_var (p, file->name, targlen)) != 0);
current_variable_set_list = global;
}
@@ -871,14 +878,6 @@ define_automatic_variables (void)
sprintf (buf, "%u", makelevel);
define_variable_cname (MAKELEVEL_NAME, buf, o_env, 0);
- sprintf (buf, "%d", (int32_t)makeparent_pid);
- define_variable_cname (MAKEPARENT_PID_NAME, buf, o_env, 0);
-
- if (makeparent_target) {
- sprintf (buf, "%s", makeparent_target);
- define_variable_cname (MAKEPARENT_TARGET_NAME, buf, o_env, 0);
- }
-
sprintf (buf, "%s%s%s",
version_string,
(remote_description == 0 || remote_description[0] == '\0')
@@ -1024,116 +1023,112 @@ define_automatic_variables (void)
int export_all_variables;
+static int
+should_export (const struct variable *v)
+{
+ switch (v->export)
+ {
+ case v_export:
+ break;
+
+ case v_noexport:
+ return 0;
+
+ case v_ifset:
+ if (v->origin == o_default)
+ return 0;
+ break;
+
+ case v_default:
+ if (v->origin == o_default || v->origin == o_automatic)
+ /* Only export default variables by explicit request. */
+ return 0;
+
+ /* The variable doesn't have a name that can be exported. */
+ if (! v->exportable)
+ return 0;
+
+ if (! export_all_variables
+ && v->origin != o_command
+ && v->origin != o_env && v->origin != o_env_override)
+ return 0;
+ break;
+ }
+
+ return 1;
+}
+
/* Create a new environment for FILE's commands.
If FILE is nil, this is for the 'shell' function.
- The child's MAKELEVEL variable is incremented. */
+ The child's MAKELEVEL variable is incremented.
+ If recursive is true then we're running a recursive make, else not. */
char **
-target_environment (struct file *file)
+target_environment (struct file *file, int recursive)
{
struct variable_set_list *set_list;
struct variable_set_list *s;
struct hash_table table;
struct variable **v_slot;
struct variable **v_end;
- struct variable makelevel_key;
- struct variable makeparent_key;
char **result_0;
char **result;
-
- if (file == 0)
- set_list = current_variable_set_list;
- else
+ const char *invalid = NULL;
+ /* If we got no value from the environment then never add the default. */
+ int added_SHELL = shell_var.value == 0;
+ int found_makelevel = 0;
+ int found_mflags = 0;
+ int found_makeflags = 0;
+
+ /* If file is NULL we're creating the target environment for $(shell ...)
+ Remember this so we can just ignore recursion. */
+ if (!file)
+ ++env_recursion;
+
+ /* We need to update makeflags if (a) we're not recurive, (b) jobserver_auth
+ is enabled, and (c) we need to add invalidation. */
+ if (!recursive && jobserver_auth)
+ invalid = jobserver_get_invalid_auth ();
+
+ if (file)
set_list = file->variables;
+ else
+ set_list = current_variable_set_list;
hash_init (&table, VARIABLE_BUCKETS,
variable_hash_1, variable_hash_2, variable_hash_cmp);
- /* Run through all the variable sets in the list,
- accumulating variables in TABLE. */
+ /* Run through all the variable sets in the list, accumulating variables
+ in TABLE. We go from most specific to least, so the first variable we
+ encounter is the keeper. */
for (s = set_list; s != 0; s = s->next)
{
struct variable_set *set = s->set;
+ int isglobal = set == &global_variable_set;
+
v_slot = (struct variable **) set->table.ht_vec;
v_end = v_slot + set->table.ht_size;
for ( ; v_slot < v_end; v_slot++)
if (! HASH_VACANT (*v_slot))
{
- struct variable **new_slot;
+ struct variable **evslot;
struct variable *v = *v_slot;
- /* If this is a per-target variable and it hasn't been touched
- already then look up the global version and take its export
- value. */
- if (v->per_target && v->export == v_default)
- {
- struct variable *gv;
-
- gv = lookup_variable_in_set (v->name, strlen (v->name),
- &global_variable_set);
- if (gv)
- v->export = gv->export;
- }
+ evslot = (struct variable **) hash_find_slot (&table, v);
- switch (v->export)
+ if (HASH_VACANT (*evslot))
{
- case v_default:
- if (v->origin == o_default || v->origin == o_automatic)
- /* Only export default variables by explicit request. */
- continue;
-
- /* The variable doesn't have a name that can be exported. */
- if (! v->exportable)
- continue;
-
- if (! export_all_variables
- && v->origin != o_command
- && v->origin != o_env && v->origin != o_env_override)
- continue;
- break;
-
- case v_export:
- break;
-
- case v_noexport:
- {
- /* If this is the SHELL variable and it's not exported,
- then add the value from our original environment, if
- the original environment defined a value for SHELL. */
- if (streq (v->name, "SHELL") && shell_var.value)
- {
- v = &shell_var;
- break;
- }
- continue;
- }
-
- case v_ifset:
- if (v->origin == o_default)
- continue;
- break;
+ /* If we're not global, or we are and should export, add it. */
+ if (!isglobal || should_export (v))
+ hash_insert_at (&table, v, evslot);
}
-
- new_slot = (struct variable **) hash_find_slot (&table, v);
- if (HASH_VACANT (*new_slot))
- hash_insert_at (&table, v, new_slot);
+ else if ((*evslot)->export == v_default)
+ /* We already have a variable but we don't know its status. */
+ (*evslot)->export = v->export;
}
}
- makelevel_key.name = (char *)MAKELEVEL_NAME;
- makelevel_key.length = MAKELEVEL_LENGTH;
- hash_delete (&table, &makelevel_key);
-
- makeparent_key.name = (char *)MAKEPARENT_PID_NAME;
- makeparent_key.length = MAKEPARENT_PID_LENGTH;
- hash_delete (&table, &makeparent_key);
-
- makeparent_key.name = (char *)MAKEPARENT_TARGET_NAME;
- makeparent_key.length = MAKEPARENT_TARGET_LENGTH;
- hash_delete (&table, &makeparent_key);
-
- /* Include 3 extra for MAKELEVEL and MAKEPARENT PID and TARGET and 1 more for the NULL terminator */
- result = result_0 = xmalloc ((table.ht_fill + 4) * sizeof (char *));
+ result = result_0 = xmalloc ((table.ht_fill + 3) * sizeof (char *));
v_slot = (struct variable **) table.ht_vec;
v_end = v_slot + table.ht_size;
@@ -1141,44 +1136,120 @@ target_environment (struct file *file)
if (! HASH_VACANT (*v_slot))
{
struct variable *v = *v_slot;
+ char *value = v->value;
+ char *cp = NULL;
+
+ /* This might be here because it was a target-specific variable that
+ we didn't know the status of when we added it. */
+ if (! should_export (v))
+ continue;
/* 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)
+ value = cp = recursively_expand_for_file (v, file);
+
+ /* If this is the SHELL variable remember we already added it. */
+ if (!added_SHELL && streq (v->name, "SHELL"))
{
- 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, ';');
-#endif
- *result++ = xstrdup (concat (3, v->name, "=", value));
- free (value);
+ added_SHELL = 1;
+ goto setit;
}
- else
+
+ /* If this is MAKELEVEL, update it. */
+ if (!found_makelevel && streq (v->name, MAKELEVEL_NAME))
+ {
+ char val[INTSTR_LENGTH + 1];
+ sprintf (val, "%u", makelevel + 1);
+ free (cp);
+ value = cp = xstrdup (val);
+ found_makelevel = 1;
+ goto setit;
+ }
+
+ /* If we need to reset jobserver, check for MAKEFLAGS / MFLAGS. */
+ if (invalid)
{
+ if (!found_makeflags && streq (v->name, MAKEFLAGS_NAME))
+ {
+ char *mf;
+ char *vars;
+ found_makeflags = 1;
+
+ if (!strstr (value, " --" JOBSERVER_AUTH_OPT "="))
+ goto setit;
+
+ /* The invalid option must come before variable overrides. */
+ vars = strstr (value, " -- ");
+ if (!vars)
+ mf = xstrdup (concat (2, value, invalid));
+ else
+ {
+ size_t lf = vars - value;
+ size_t li = strlen (invalid);
+ mf = xmalloc (strlen (value) + li + 1);
+ strcpy (mempcpy (mempcpy (mf, value, lf), invalid, li),
+ vars);
+ }
+ free (cp);
+ value = cp = mf;
+ if (found_mflags)
+ invalid = NULL;
+ goto setit;
+ }
+
+ if (!found_mflags && streq (v->name, "MFLAGS"))
+ {
+ const char *mf;
+ found_mflags = 1;
+
+ if (!strstr (value, " --" JOBSERVER_AUTH_OPT "="))
+ goto setit;
+
+ if (v->origin != o_env)
+ goto setit;
+ mf = concat (2, value, invalid);
+ free (cp);
+ value = cp = xstrdup (mf);
+ if (found_makeflags)
+ invalid = NULL;
+ goto setit;
+ }
+ }
+
#ifdef WINDOWS32
- if (strcmp (v->name, "Path") == 0 ||
- strcmp (v->name, "PATH") == 0)
- convert_Path_to_windows32 (v->value, ';');
-#endif
- *result++ = xstrdup (concat (3, v->name, "=", v->value));
+ if (streq (v->name, "Path") || streq (v->name, "PATH"))
+ {
+ if (!cp)
+ cp = xstrdup (value);
+ value = convert_Path_to_windows32 (cp, ';');
+ goto setit;
}
+#endif
+
+ setit:
+ *result++ = xstrdup (concat (3, v->name, "=", value));
+ free (cp);
}
- *result = xmalloc (100);
- sprintf (*result, "%s=%u", MAKELEVEL_NAME, makelevel + 1);
+ if (!added_SHELL)
+ *result++ = xstrdup (concat (3, shell_var.name, "=", shell_var.value));
+
+ if (!found_makelevel)
+ {
+ char val[MAKELEVEL_LENGTH + 1 + INTSTR_LENGTH + 1];
+ sprintf (val, "%s=%u", MAKELEVEL_NAME, makelevel + 1);
+ *result++ = xstrdup (val);
+ }
- *++result = xmalloc (100);
- sprintf (*result, "%s=%d", MAKEPARENT_PID_NAME, getpid());
+ *result = NULL;
- *++result = xmalloc (MAKEPARENT_TARGET_LENGTH + 2 + strlen(file->name));
- sprintf(*result, "%s=%s", MAKEPARENT_TARGET_NAME, file->name);
- *++result = 0;
hash_free (&table, 0);
+ if (!file)
+ --env_recursion;
+
return result_0;
}
@@ -1192,6 +1263,8 @@ set_special_var (struct variable *var)
properly. */
cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0];
}
+ else if (streq (var->name, MAKEFLAGS_NAME))
+ decode_env_switches (STRING_SIZE_TUPLE(MAKEFLAGS_NAME));
return var;
}
@@ -1237,10 +1310,6 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
switch (flavor)
{
- default:
- case f_bogus:
- /* Should not be possible. */
- abort ();
case f_simple:
/* A simple variable definition "var := value". Expand the value.
We have to allocate memory since otherwise it'll clobber the
@@ -1248,6 +1317,25 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
target-specific variable. */
p = alloc_value = allocated_variable_expand (value);
break;
+ case f_expand:
+ {
+ /* A POSIX "var :::= value" assignment. Expand the value, then it
+ becomes a recursive variable. After expansion convert all '$'
+ tokens to '$$' to resolve to '$' when recursively expanded. */
+ char *t = allocated_variable_expand (value);
+ char *np = alloc_value = xmalloc (strlen (t) * 2 + 1);
+ p = t;
+ while (p[0] != '\0')
+ {
+ if (p[0] == '$')
+ *(np++) = '$';
+ *(np++) = *(p++);
+ }
+ *np = '\0';
+ p = alloc_value;
+ free (t);
+ break;
+ }
case f_shell:
{
/* A shell definition "var != value". Expand value, pass it to
@@ -1342,8 +1430,12 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
free (tp);
}
- break;
}
+ break;
+ case f_bogus:
+ default:
+ /* Should not be possible. */
+ abort ();
}
#ifdef __MSDOS__
@@ -1483,8 +1575,8 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
invoked in places where we want to define globally visible variables,
make sure we define this variable in the global set. */
- v = define_variable_in_set (varname, strlen (varname), p,
- origin, flavor == f_recursive,
+ v = define_variable_in_set (varname, strlen (varname), p, origin,
+ flavor == f_recursive || flavor == f_expand,
(target_var
? current_variable_set_list->set : NULL),
flocp);
@@ -1499,7 +1591,7 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
/* Parse P (a null-terminated string) as a variable definition.
If it is not a variable definition, return NULL and the contents of *VAR
- are undefined, except NAME is set to the first non-space character or NIL.
+ are undefined, except NAME points to the first non-space character or EOS.
If it is a variable definition, return a pointer to the char after the
assignment token and set the following fields (only) of *VAR:
@@ -1511,15 +1603,17 @@ do_variable_definition (const gmk_floc *flocp, const char *varname,
*/
char *
-parse_variable_definition (const char *p, struct variable *var)
+parse_variable_definition (const char *str, struct variable *var)
{
- int wspace = 0;
- const char *e = NULL;
+ const char *p = str;
+ const char *end = NULL;
NEXT_TOKEN (p);
var->name = (char *)p;
var->length = 0;
+ /* Walk through STR until we find a valid assignment operator. Each time
+ through this loop P points to the next character to consider. */
while (1)
{
int c = *p++;
@@ -1528,112 +1622,128 @@ parse_variable_definition (const char *p, struct variable *var)
if (STOP_SET (c, MAP_COMMENT|MAP_NUL))
return NULL;
- if (c == '$')
- {
- /* This begins a variable expansion reference. Make sure we don't
- treat chars inside the reference as assignment tokens. */
- char closeparen;
- unsigned int count;
-
- c = *p++;
- if (c == '(')
- closeparen = ')';
- else if (c == '{')
- closeparen = '}';
- else if (c == '\0')
- return NULL;
- else
- /* '$$' or '$X'. Either way, nothing special to do here. */
- continue;
-
- /* P now points past the opening paren or brace.
- Count parens or braces until it is matched. */
- for (count = 1; *p != '\0'; ++p)
- {
- if (*p == closeparen && --count == 0)
- {
- ++p;
- break;
- }
- if (*p == c)
- ++count;
- }
- continue;
- }
-
- /* If we find whitespace skip it, and remember we found it. */
if (ISBLANK (c))
{
- wspace = 1;
- e = p - 1;
- NEXT_TOKEN (p);
- c = *p;
- if (c == '\0')
+ /* Variable names can't contain spaces so if this is the second set
+ of spaces we know it's not a variable assignment. */
+ if (end)
return NULL;
- ++p;
+ end = p - 1;
+ NEXT_TOKEN (p);
+ continue;
}
-
+ /* If we found = we're done! */
if (c == '=')
{
+ if (!end)
+ end = p - 1;
var->flavor = f_recursive;
- if (! e)
- e = p - 1;
break;
}
- /* Match assignment variants (:=, +=, ?=, !=) */
+ if (c == ':')
+ {
+ if (!end)
+ end = p - 1;
+
+ /* We need to distinguish :=, ::=, and :::=, and : outside of an
+ assignment (which means this is not a variable definition). */
+ c = *p++;
+ if (c == '=')
+ {
+ var->flavor = f_simple;
+ break;
+ }
+ if (c == ':')
+ {
+ c = *p++;
+ if (c == '=')
+ {
+ var->flavor = f_simple;
+ break;
+ }
+ if (c == ':' && *p++ == '=')
+ {
+ var->flavor = f_expand;
+ break;
+ }
+ }
+ return NULL;
+ }
+
+ /* See if it's one of the other two-byte operators. */
if (*p == '=')
{
switch (c)
{
- case ':':
- var->flavor = f_simple;
- break;
- case '+':
- var->flavor = f_append;
- break;
- case '?':
- var->flavor = f_conditional;
- break;
- case '!':
- var->flavor = f_shell;
- break;
- default:
- /* If we skipped whitespace, non-assignments means no var. */
- if (wspace)
- return NULL;
-
- /* Might be assignment, or might be $= or #=. Check. */
- continue;
+ case '+':
+ var->flavor = f_append;
+ break;
+ case '?':
+ var->flavor = f_conditional;
+ break;
+ case '!':
+ var->flavor = f_shell;
+ break;
+ default:
+ goto other;
}
- if (! e)
- e = p - 1;
+
+ if (!end)
+ end = p - 1;
++p;
break;
}
- /* Check for POSIX ::= syntax */
- if (c == ':')
+ other:
+ /* We found a char which is not part of an assignment operator.
+ If we've seen whitespace, then we know this is not a variable
+ assignment since variable names cannot contain whitespace. */
+ if (end)
+ return NULL;
+
+ if (c == '$')
{
- /* A colon other than :=/::= is not a variable defn. */
- if (*p != ':' || p[1] != '=')
- return NULL;
+ /* Skip any variable reference, to ensure we don't treat chars
+ inside the reference as assignment operators. */
+ char closeparen;
+ unsigned int count;
- /* POSIX allows ::= to be the same as GNU make's := */
- var->flavor = f_simple;
- if (! e)
- e = p - 1;
- p += 2;
- break;
- }
+ c = *p++;
+ switch (c)
+ {
+ case '(':
+ closeparen = ')';
+ break;
+ case '{':
+ closeparen = '}';
+ break;
+ case '\0':
+ return NULL;
+ default:
+ /* '$$' or '$X': skip it. */
+ continue;
+ }
- /* If we skipped whitespace, non-assignments means no var. */
- if (wspace)
- return NULL;
+ /* P now points past the opening paren or brace. Count parens or
+ braces until we find the closing paren/brace. */
+ for (count = 1; *p != '\0'; ++p)
+ {
+ if (*p == closeparen && --count == 0)
+ {
+ ++p;
+ break;
+ }
+ if (*p == c)
+ ++count;
+ }
+ }
}
- var->length = (unsigned int) (e - var->name);
+ /* We found a valid variable assignment: END points to the char after the
+ end of the variable name and P points to the char after the =. */
+ var->length = (unsigned int) (end - var->name);
var->value = next_token (p);
return (char *)p;
}
@@ -1909,7 +2019,7 @@ print_target_variables (const struct file *file)
size_t l = strlen (file->name);
char *t = alloca (l + 3);
- strcpy (t, file->name);
+ memcpy (t, file->name, l);
t[l] = ':';
t[l+1] = ' ';
t[l+2] = '\0';
@@ -1922,19 +2032,18 @@ print_target_variables (const struct file *file)
void
sync_Path_environment (void)
{
- 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
diff --git a/src/variable.h b/src/variable.h
index 1968d4ca6..724864753 100644
--- a/src/variable.h
+++ b/src/variable.h
@@ -1,5 +1,5 @@
/* Definitions for using variables in GNU Make.
-Copyright (C) 1988-2020 Free Software Foundation, Inc.
+Copyright (C) 1988-2022 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,7 +12,7 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see . */
+this program. If not, see . */
#ifndef VARIABLE_H
#define VARIABLE_H
@@ -40,12 +40,28 @@ enum variable_flavor
f_bogus, /* Bogus (error) */
f_simple, /* Simple definition (:= or ::=) */
f_recursive, /* Recursive definition (=) */
+ f_expand, /* POSIX :::= assignment */
f_append, /* Appending definition (+=) */
f_conditional, /* Conditional definition (?=) */
f_shell, /* Shell assignment (!=) */
f_append_value /* Append unexpanded value */
};
+enum variable_export
+{
+ v_default = 0, /* Decide in target_environment. */
+ v_export, /* Export this variable. */
+ v_noexport, /* Don't export this variable. */
+ v_ifset /* Export it if it has a non-default value. */
+};
+
+enum variable_scope
+{
+ s_global = 0, /* Global variable. */
+ s_target, /* Target-specific variable. */
+ s_pattern /* Pattern-specific variable. */
+};
+
/* Structure that represents one variable definition.
Each bucket of the hash table is a chain of these,
chained through 'next'. */
@@ -78,12 +94,7 @@ struct variable
enum variable_origin
origin ENUM_BITFIELD (4); /* Variable origin. */
enum variable_export
- {
- v_export, /* Export this variable. */
- v_noexport, /* Don't export this variable. */
- v_ifset, /* Export it if it has a non-default value. */
- v_default /* Decide in target_environment. */
- } export ENUM_BITFIELD (2);
+ export ENUM_BITFIELD (2); /* Export control. */
};
/* Structure that represents a variable set. */
@@ -113,6 +124,7 @@ struct pattern_var
struct variable variable;
};
+extern unsigned long env_recursion;
extern char *variable_buffer;
extern struct variable_set_list *current_variable_set_list;
extern struct variable *default_goal_var;
@@ -283,7 +295,7 @@ void undefine_variable_in_set (const char *name, size_t length,
(int)(l), (n)); \
}while(0)
-char **target_environment (struct file *file);
+char **target_environment (struct file *file, int recursive);
struct pattern_var *create_pattern_var (const char *target,
const char *suffix);
diff --git a/tests/scripts/features/se_explicit b/tests/scripts/features/se_explicit
index c28641c12..65ac20c1b 100644
--- a/tests/scripts/features/se_explicit
+++ b/tests/scripts/features/se_explicit
@@ -185,19 +185,39 @@ foo: $$@.o ;
# Not creating libcat.a here prevents segfault,
libcat.a: ; @touch $@
!,
- '', q!#MAKEFILE#:16: Recipe was specified for file '-lcat' at #MAKEFILE#:19,
-#MAKEFILE#:16: but '-lcat' is now considered the same file as 'libcat.a'.
-#MAKEFILE#:16: Recipe for 'libcat.a' will be ignored in favor of the one for '-lcat'.!);
+ '', q!#MAKEFILE#:16: recipe was specified for file '-lcat' at #MAKEFILE#:16,
+#MAKEFILE#:16: but '-lcat' is now considered the same file as 'libcat.a'
+#MAKEFILE#:16: recipe for '-lcat' will be ignored in favor of the one for 'libcat.a'!);
unlink('libcat.a');
-# SV 28456 : Don't reset $$< for default recipes
-run_make_test(q!
-.SECONDEXPANSION:
-
-.PHONY: biz baz
-biz: baz ;
-biz: $$(info $$<)
-!,
- '', "baz\n#MAKE#: Nothing to be done for 'biz'.\n");
-
-1;
+# FIXME: rocky - reinstate
+# # SV 28456 : Don't reset $$< for default recipes
+# run_make_test(q!
+# .SECONDEXPANSION:
+
+# .PHONY: biz baz
+# biz: baz ;
+# biz: $$(info $$<)
+# !,
+# '', "baz\n#MAKE#: Nothing to be done lo.tsk:; exit 1
+# unrelated: $$(shell touch hello.1);
+# !, '',
+# "exit 1
+# #MAKE#: *** [#MAKEFILE#:3: hello.tsk] Error 1\n", 512);
+
+# # sv 62706.
+# # Second expansion of intermediate prerequisites.
+# # The rule to build hello.x is explicit.
+# # .SECONDARY marks hello.x as intermediate.
+# # Test that $$(deps) is secondary expanded.
+# run_make_test(q!
+# deps:=hello.h
+# .SECONDEXPANSION:
+# .SECONDARY: hello.x
+# all: hello.x
+# hello.x: $$(deps); $(info $@)
+# hello.h:; $(info $@)
+# !, '', "hello.h\nhello.x\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# 1;
diff --git a/tests/scripts/functions/intcmp b/tests/scripts/functions/intcmp
new file mode 100644
index 000000000..24e25b224
--- /dev/null
+++ b/tests/scripts/functions/intcmp
@@ -0,0 +1,72 @@
+# -*-perl-*-
+$description = "Test the intcmp function.\n";
+
+$details = "Try various uses of intcmp and ensure they all give the correct
+results.\n";
+
+run_make_test('# Negative
+n = -10
+# Zero
+z = 0
+# Positive
+p = 888
+min = -9223372036854775808
+max = 9223372036854775807
+huge = 8857889956778499040639527525992734031025567913257255490371761260681427
+.RECIPEPREFIX = >
+all:
+> @echo 0_1 $(intcmp $n,$n)
+> @echo 0_2 $(intcmp $z,$z)
+> @echo 0_3 $(intcmp -$z,$z)
+> @echo 0_4 $(intcmp $p,$p)
+> @echo 0_5 $(intcmp $n,$z)
+> @echo 0_6 $(intcmp $z,$n)
+> @echo 1_1 $(intcmp $n,$n,$(shell echo lt))
+> @echo 1_2 $(intcmp $n,$z,$(shell echo lt))
+> @echo 1_3 $(intcmp $z,$n,$(shell echo lt))
+> @echo 2_1 $(intcmp $n,$p,lt,ge)
+> @echo 2_2 $(intcmp $z,$z,lt,ge)
+> @echo 2_3 $(intcmp $p,$n,lt,ge)
+> @echo 3_0 $(intcmp $p,$n,lt,eq,)
+> @echo 3_1 $(intcmp $z,$p,lt,eq,gt)
+> @echo 3_2 $(intcmp $p,$z,lt,eq,gt)
+> @echo 3_3 $(intcmp $p,$p,lt,eq,gt)
+> @echo 4_0 $(intcmp $(min),$(max),lt,eq,gt)
+> @echo 4_1 $(intcmp $(max),$(min),lt,eq,gt)
+> @echo 4_2 $(intcmp $(min),$(min),lt,eq,gt)
+> @echo 4_3 $(intcmp $(max),$(max),lt,eq,gt)
+> @echo 5_0 $(intcmp -$(huge),$(huge),lt,eq,gt)
+> @echo 5_1 $(intcmp $(huge),-$(huge),lt,eq,gt)
+> @echo 5_2 $(intcmp -$(huge),-$(huge),lt,eq,gt)
+> @echo 5_3 $(intcmp +$(huge),$(huge),lt,eq,gt)
+', '', "0_1 -10\n0_2 0\n0_3 0\n0_4 888\n0_5\n0_6\n1_1\n1_2 lt\n1_3\n2_1 lt\n2_2 ge\n2_3 ge\n3_0\n3_1 lt\n3_2 gt\n3_3 eq\n4_0 lt\n4_1 gt\n4_2 eq\n4_3 eq\n5_0 lt\n5_1 gt\n5_2 eq\n5_3 eq\n");
+
+# Test error conditions
+
+run_make_test('
+intcmp-e1: ; @echo $(intcmp 12a,1,foo)
+intcmp-e2: ; @echo $(intcmp 0,,foo)
+intcmp-e3: ; @echo $(intcmp -1,)
+intcmp-e4: ; @echo $(intcmp ,55)',
+ 'intcmp-e1',
+ "#MAKEFILE#:2: *** non-numeric first argument to 'intcmp' function: '12a'. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'intcmp-e2',
+ "#MAKEFILE#:3: *** non-numeric second argument to 'intcmp' function: empty value. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'intcmp-e3',
+ "#MAKEFILE#:4: *** non-numeric second argument to 'intcmp' function: empty value. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'intcmp-e4',
+ "#MAKEFILE#:5: *** non-numeric first argument to 'intcmp' function: empty value. Stop.",
+ 512);
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/tests/scripts/functions/let b/tests/scripts/functions/let
new file mode 100644
index 000000000..89f4c82b9
--- /dev/null
+++ b/tests/scripts/functions/let
@@ -0,0 +1,111 @@
+# -*-perl-*-
+# $Id$
+
+$description = "Test the let function.";
+
+$details = "This is a test of the let function in gnu make.
+This function destructures a list of values and binds each
+value to a variable name in a list of variable names.
+Superfluous variable names are assigned the empty string and
+the remaining values are assigned to the last variable name.
+The binding holds for the duration of the evaluation of the
+given text and no longer. The general form of the command
+is $(let \$vars,\$list,\$text). Several types of let
+assignments are tested\n";
+
+# check for mismatched var and list word counts
+run_make_test(q!
+a = bad
+b = news
+x = $(let a b,1 2,$a $b)
+y = $(let a,1 2,$a)
+z = $(let a b,1,$a $b)
+all:;@echo 'a=,$a,' 'b=,$b,' 'x=,$x,' 'y=,$y,' 'z=,$z,'
+!,
+ '', "a=,bad, b=,news, x=,1 2, y=,1 2, z=,1 ,\n");
+
+# check for whitespace
+run_make_test(q!
+a = bad
+b = news
+x = $(let a b, 1 2 ,+$a+$b+)
+y = $(let a, 1 2 ,+$a+)
+z = $(let a b, 1 ,+$a+$b+)
+all:;@echo 'a=,$a,' 'b=,$b,' 'x=,$x,' 'y=,$y,' 'z=,$z,'
+!,
+ '', "a=,bad, b=,news, x=,+1+2 +, y=,+1 2 +, z=,+1++,\n");
+
+# Allow empty variable names and empty value list.
+# We still expand the list and body.
+run_make_test('
+null =
+v = $(let ,$(info blankvar),abc)
+x = $(let $(null),$(info side-effect),abc)
+y = $(let y,,$ydef)
+
+all: ; @echo $v/$x/$y',
+ '', "blankvar\nside-effect\nabc/abc/def\n");
+
+# The example macro from the manual.
+run_make_test('
+reverse = $(let first rest,$1,$(if $(rest),$(call reverse,$(rest)) )$(first))
+
+all: ; @echo $(call reverse, \
+ moe miny meeny eeny \
+ )',
+ '', "eeny meeny miny moe\n");
+
+
+# Set an environment variable that we can test in the makefile.
+$ENV{FOOFOO} = 'foo foo';
+
+# Verify masking: expansion outside the scope of let is unaffected.
+run_make_test('
+auto_var = \
+ udef \
+ CC \
+ FOOFOO \
+ MAKE \
+ foo \
+ CFLAGS \
+ WHITE \
+ @ \
+ <
+av = $(foreach var, $(auto_var), $(origin $(var)) )
+foo = bletch null @ garf
+override WHITE := BLACK
+
+define mktarget
+target: foo := $(foo)
+target: ; @echo $(AR)_$(foo)_
+endef
+
+all: auto target
+auto: ; @echo $(let $(auto_var),,$(av)) $(av)
+$(let AR foo,bar foo ,$(eval $(value mktarget)))',
+ '-e WHITE=WHITE CFLAGS=',
+ "automatic automatic automatic automatic automatic automatic automatic automatic automatic undefined default undefined default file command line override automatic automatic
+ar_foo _
+");
+
+
+# Check some error conditions.
+run_make_test('
+x = $(let )
+y = $x
+
+all: ; @echo $y',
+ '',
+ "#MAKEFILE#:2: *** insufficient number of arguments (1) to function 'let'. Stop.",
+ 512);
+
+run_make_test('
+x = $(let x,y)
+y := $x
+
+all: ; @echo $y',
+ '',
+ "#MAKEFILE#:2: *** insufficient number of arguments (2) to function 'let'. Stop.",
+ 512);
+
+1;
diff --git a/tests/scripts/functions/word b/tests/scripts/functions/word
index 4dcc94065..d0cd795df 100644
--- a/tests/scripts/functions/word
+++ b/tests/scripts/functions/word
@@ -43,89 +43,89 @@ $answer = "6\n"
."word.pl general_test2.pl FORCE.pl word.pl\n";
&compare_output($answer, &get_logfile(1));
-
+## FIXME rocky: reinstate
# Test error conditions
-run_make_test('FOO = foo bar biz baz
-
-word-e1: ; @echo $(word ,$(FOO))
-word-e2: ; @echo $(word abc ,$(FOO))
-word-e3: ; @echo $(word 1a,$(FOO))
-
-wordlist-e1: ; @echo $(wordlist ,,$(FOO))
-wordlist-e2: ; @echo $(wordlist abc ,,$(FOO))
-wordlist-e3: ; @echo $(wordlist 1, 12a ,$(FOO))',
- 'word-e1',
- "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
- 512);
-
-run_make_test(undef,
- 'word-e2',
- "#MAKEFILE#:4: *** non-numeric first argument to 'word' function: 'abc '. Stop.",
- 512);
-
-run_make_test(undef,
- 'word-e3',
- "#MAKEFILE#:5: *** non-numeric first argument to 'word' function: '1a'. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e1',
- "#MAKEFILE#:7: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e2',
- "#MAKEFILE#:8: *** non-numeric first argument to 'wordlist' function: 'abc '. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e3',
- "#MAKEFILE#:9: *** non-numeric second argument to 'wordlist' function: ' 12a '. Stop.",
- 512);
-
-# Test error conditions again, but this time in a variable reference
-
-run_make_test('FOO = foo bar biz baz
-
-W = $(word $x,$(FOO))
-WL = $(wordlist $s,$e,$(FOO))
-
-word-e: ; @echo $(W)
-wordlist-e: ; @echo $(WL)',
- 'word-e x=',
- "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
- 512);
-
-run_make_test(undef,
- 'word-e x=abc',
- "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: 'abc'. Stop.",
- 512);
-
-run_make_test(undef,
- 'word-e x=0',
- "#MAKEFILE#:3: *** first argument to 'word' function must be greater than 0. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e s= e=',
- "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e s=abc e=',
- "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: 'abc'. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e s=4 e=12a',
- "#MAKEFILE#:4: *** non-numeric second argument to 'wordlist' function: '12a'. Stop.",
- 512);
-
-run_make_test(undef,
- 'wordlist-e s=0 e=12',
- "#MAKEFILE#:4: *** invalid first argument to 'wordlist' function: '0'. Stop.",
- 512);
+# run_make_test('FOO = foo bar biz baz
+
+# word-e1: ; @echo $(word ,$(FOO))
+# word-e2: ; @echo $(word abc ,$(FOO))
+# word-e3: ; @echo $(word 1a,$(FOO))
+
+# wordlist-e1: ; @echo $(wordlist ,,$(FOO))
+# wordlist-e2: ; @echo $(wordlist abc ,,$(FOO))
+# wordlist-e3: ; @echo $(wordlist 1, 12a ,$(FOO))',
+# 'word-e1',
+# "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'word-e2',
+# "#MAKEFILE#:4: *** non-numeric first argument to 'word' function: 'abc '. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'word-e3',
+# "#MAKEFILE#:5: *** non-numeric first argument to 'word' function: '1a'. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e1',
+# "#MAKEFILE#:7: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e2',
+# "#MAKEFILE#:8: *** non-numeric first argument to 'wordlist' function: 'abc '. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e3',
+# "#MAKEFILE#:9: *** non-numeric second argument to 'wordlist' function: ' 12a '. Stop.",
+# 512);
+
+# # Test error conditions again, but this time in a variable reference
+
+# run_make_test('FOO = foo bar biz baz
+
+# W = $(word $x,$(FOO))
+# WL = $(wordlist $s,$e,$(FOO))
+
+# word-e: ; @echo $(W)
+# wordlist-e: ; @echo $(WL)',
+# 'word-e x=',
+# "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'word-e x=abc',
+# "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: 'abc'. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'word-e x=0',
+# "#MAKEFILE#:3: *** first argument to 'word' function must be greater than 0. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e s= e=',
+# "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e s=abc e=',
+# "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: 'abc'. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e s=4 e=12a',
+# "#MAKEFILE#:4: *** non-numeric second argument to 'wordlist' function: '12a'. Stop.",
+# 512);
+
+# run_make_test(undef,
+# 'wordlist-e s=0 e=12',
+# "#MAKEFILE#:4: *** invalid first argument to 'wordlist' function: '0'. Stop.",
+# 512);
# TEST #8 -- test $(firstword )