summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS15
-rw-r--r--doc/make.texi134
-rw-r--r--src/dep.h6
-rw-r--r--src/file.c25
-rw-r--r--src/implicit.c10
-rw-r--r--src/read.c69
-rw-r--r--src/remake.c7
-rw-r--r--src/rule.c26
-rw-r--r--src/shuffle.c11
-rw-r--r--tests/scripts/targets/WAIT193
10 files changed, 445 insertions, 51 deletions
diff --git a/NEWS b/NEWS
index 5a8c85ec..4f37ec3f 100644
--- a/NEWS
+++ b/NEWS
@@ -46,6 +46,21 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=109&se
https://www.gnu.org/software/gnulib/manual/html_node/C99-features-assumed.html
The configure script should verify the compiler has these features.
+* New feature: The .WAIT special target
+ If the .WAIT target appears between two prerequisites of a target, then
+ GNU make will wait for all of the targets to the left of .WAIT in the list
+ to complete before starting any of the targets to the right of .WAIT.
+ This feature is available in some other versions of make, and it will be
+ required by an upcoming version of the POSIX standard for make.
+ Different patches were made by Alexey Neyman <alex.neyman@auriga.ru> (2005)
+ and Steffen Nurpmeso <steffen@sdaoden.eu> (2020) that were useful but the
+ result is a different implementation (closer to Alexey's idea).
+
+* New feature: .NOTPARALLEL accepts prerequisites
+ If the .NOTPARALLEL special target has prerequisites then all prerequisites
+ of those targets will be run serially (as if .WAIT was specified between
+ each prerequisite).
+
* New feature: The .NOTINTERMEDIATE special target
.NOTINTERMEDIATE Disables intermediate behavior for specific files, for all
files built using a pattern, or for the entire makefile.
diff --git a/doc/make.texi b/doc/make.texi
index b1ff72ef..14ad2a37 100644
--- a/doc/make.texi
+++ b/doc/make.texi
@@ -224,6 +224,7 @@ Recipe Execution
Parallel Execution
+* Parallel Disable:: Disabling parallel execution
* Parallel Output:: Handling output during parallel execution
* Parallel Input:: Handling input during parallel execution
@@ -3179,11 +3180,15 @@ Variables to a Sub-@code{make}}.
@item .NOTPARALLEL
@cindex parallel execution, overriding
-If @code{.NOTPARALLEL} is mentioned as a target, then this invocation
-of @code{make} will be run serially, even if the @samp{-j} option is
-given. Any recursively invoked @code{make} command will still run
-recipes in parallel (unless its makefile also contains this target).
-Any prerequisites on this target are ignored.
+If @code{.NOTPARALLEL} is mentioned as a target with no prerequisites, all
+targets in this invocation of @code{make} will be run serially, even if the
+@samp{-j} option is given. Any recursively invoked @code{make} command will
+still run recipes in parallel (unless its makefile also contains this target).
+
+If @code{.NOTPARALLEL} has targets as prerequisites, then all the
+prerequisites of those targets will be run serially. This implicitly adds a
+@code{.WAIT} between each prerequisite of the listed targets. @xref{Parallel
+Disable, , Disabling Parallel Execution}.
@findex .ONESHELL
@item .ONESHELL
@@ -3191,8 +3196,8 @@ Any prerequisites on this target are ignored.
If @code{.ONESHELL} is mentioned as a target, then when a target is
built all lines of the recipe will be given to a single invocation of
-the shell rather than each line being invoked separately
-(@pxref{Execution, ,Recipe Execution}).
+the shell rather than each line being invoked separately.
+@xref{Execution, ,Recipe Execution}.
@findex .POSIX
@item .POSIX
@@ -4329,13 +4334,12 @@ directory along your @code{PATH}.
@cindex @code{-j}
@cindex @code{--jobs}
-GNU @code{make} knows how to execute several recipes at once.
-Normally, @code{make} will execute only one recipe at a time, waiting
-for it to finish before executing the next. However, the @samp{-j} or
-@samp{--jobs} option tells @code{make} to execute many recipes
-simultaneously. You can inhibit parallelism in a particular makefile
-with the @code{.NOTPARALLEL} pseudo-target (@pxref{Special
-Targets,Special Built-in Target Names}).
+GNU @code{make} knows how to execute several recipes at once. Normally,
+@code{make} will execute only one recipe at a time, waiting for it to finish
+before executing the next. However, the @samp{-j} or @samp{--jobs} option
+tells @code{make} to execute many recipes simultaneously. You can inhibit
+parallelism for some or all targets from within the makefile (@pxref{Parallel
+Disable, ,Disabling Parallel Execution}).
On MS-DOS, the @samp{-j} option has no effect, since that system doesn't
support multi-processing.
@@ -4389,11 +4393,109 @@ average goes below that limit, or until all the other jobs finish.
By default, there is no load limit.
@menu
+* Parallel Disable:: Disabling parallel execution
* Parallel Output:: Handling output during parallel execution
* Parallel Input:: Handling input during parallel execution
@end menu
-@node Parallel Output, Parallel Input, Parallel, Parallel
+@node Parallel Disable, Parallel Output, Parallel, Parallel
+@subsection Disabling Parallel Execution
+@cindex disabling parallel execution
+@cindex parallel execution, disabling
+
+If a makefile completely and accurately defines the dependency relationships
+between all of its targets, then @code{make} will correctly build the goals
+regardless of whether parallel execution is enabled or not. This is the ideal
+way to write makefiles.
+
+However, sometimes some or all of the targets in a makefile cannot be executed
+in parallel and it's not feasible to add the prerequisites needed to inform
+@code{make}. In that case the makefile can use various methods to disable
+parallel execution.
+
+@cindex .NOTPARALLEL special target
+@findex .NOTPARALLEL
+If the @code{.NOTPARALLEL} special target with no prerequisites is specified
+anywhere then the entire instance of @code{make} will be run serially,
+regardless of the parallel setting. For example:
+
+@example
+@group
+all: one two three
+one two three: ; @@sleep 1; echo $@@
+
+.NOTPARALLEL:
+@end group
+@end example
+
+Regardless of how @code{make} is invoked, the targets @file{one}, @file{two},
+and @file{three} will be run serially.
+
+If the @code{.NOTPARALLEL} special target has prerequisites, then each of
+those prerequisites will be considered a target and all prerequisites of these
+targets will be run serially. Note that only when building this target will
+the prerequisites be run serially: if some other target lists the same
+prerequisites and is not in @code{.NOTPARALLEL} then these prerequisites may
+be run in parallel. For example:
+
+@example
+@group
+all: base notparallel
+
+base: one two three
+notparallel: one two three
+
+one two three: ; @@sleep 1; echo $@@
+
+.NOTPARALLEL: notparallel
+@end group
+@end example
+
+Here @samp{make -j base} will run the targets @file{one}, @file{two}, and
+@file{three} in parallel, while @samp{make -j notparallel} will run them
+serially. If you run @samp{make -j all} then they @emph{will} be run in
+parallel since @file{base} lists them as prerequisites and is not serialized.
+
+The @code{.NOTPARALLEL} target should not have commands.
+
+@cindex .WAIT special target
+@findex .WAIT
+Finally you can control the serialization of specific prerequisites in a
+fine-grained way using the @code{.WAIT} special target. When this target
+appears in a prerequisite list and parallel execution is enabled, @code{make}
+will not build any of the prerequisites to the @emph{right} of @code{.WAIT}
+until all prerequisites to the @emph{left} of @code{.WAIT} have completed.
+For example:
+
+@example
+@group
+all: one two .WAIT three
+one two three: ; @@sleep 1; echo $@@
+@end group
+@end example
+
+If parallel execution is enabled, @code{make} will try to build @file{one} and
+@file{two} in parallel but will not try to build @file{three} until both are
+complete.
+
+As with targets provided to @code{.NOTPARALLEL}, @code{.WAIT} has an effect
+only when building the target in whose prerequisite list it appears. If the
+same prerequisites are present in other targets, without @code{.WAIT}, then
+they may still be run in parallel. Because of this, @code{.WAIT} is an
+unreliable way to impose ordering than defining a prerequisite relationship.
+However it is easy to use and may suffice for simple needs.
+
+The @code{.WAIT} prerequisite will not be present in any of the automatic
+variables for the rule.
+
+You can create an actual target @code{.WAIT} in your makefile for portability
+but this is not required to use this feature. If a @code{.WAIT} target is
+created it should not have prerequisites or commands.
+
+The @code{.WAIT} feature is also implemented in other versions of @code{make}
+and it's specified in the POSIX standard for @code{make}.
+
+@node Parallel Output, Parallel Input, Parallel Disable, Parallel
@subsection Output During Parallel Execution
@cindex output during parallel execution
@cindex parallel execution, output during
@@ -9575,6 +9677,8 @@ The order in which prerequisites are listed in automatic variables is not
changed by this option.
The @code{.NOTPARALLEL} pseudo-target disables shuffling for that makefile.
+Also any prerequisite list which contains @code{.WAIT} will not be shuffled.
+@xref{Parallel Disable, ,Disabling Parallel Execution}.
The @samp{--shuffle=} option accepts these values:
diff --git a/src/dep.h b/src/dep.h
index 80092076..c0b6980a 100644
--- a/src/dep.h
+++ b/src/dep.h
@@ -30,11 +30,11 @@ struct nameseq
These flags are saved in the 'flags' field of each
'struct goaldep' in the chain returned by 'read_all_makefiles'. */
+#define RM_NOFLAG 0
#define RM_NO_DEFAULT_GOAL (1 << 0) /* Do not set default goal. */
#define RM_INCLUDED (1 << 1) /* Search makefile search path. */
#define RM_DONTCARE (1 << 2) /* No error if it doesn't exist. */
#define RM_NO_TILDE (1 << 3) /* Don't expand ~ in file name. */
-#define RM_NOFLAG 0
/* Structure representing one dependency of a file.
Each struct file's 'deps' points to a chain of these, through 'next'.
@@ -54,7 +54,8 @@ struct nameseq
unsigned int staticpattern : 1; \
unsigned int need_2nd_expansion : 1; \
unsigned int ignore_automatic_vars : 1; \
- unsigned int is_explicit : 1
+ unsigned int is_explicit : 1; \
+ unsigned int wait_here : 1
struct dep
{
@@ -81,6 +82,7 @@ struct goaldep
#define PARSEFS_EXISTS 0x0008
#define PARSEFS_NOCACHE 0x0010
#define PARSEFS_ONEWORD 0x0020
+#define PARSEFS_WAIT 0x0040
#define PARSE_FILE_SEQ(_s,_t,_c,_p,_f) \
(_t *)parse_file_seq ((_s),sizeof (_t),(_c),(_p),(_f))
diff --git a/src/file.c b/src/file.c
index 2cb013ef..6b7c52a2 100644
--- a/src/file.c
+++ b/src/file.c
@@ -455,8 +455,7 @@ remove_intermediates (int sig)
struct dep *
split_prereqs (char *p)
{
- struct dep *new = PARSE_FILE_SEQ (&p, struct dep, MAP_PIPE, NULL,
- PARSEFS_NONE);
+ struct dep *new = PARSE_FILE_SEQ (&p, struct dep, MAP_PIPE, NULL, PARSEFS_WAIT);
if (*p)
{
@@ -465,7 +464,7 @@ split_prereqs (char *p)
struct dep *ood;
++p;
- ood = PARSE_SIMPLE_SEQ (&p, struct dep);
+ ood = PARSE_FILE_SEQ (&p, struct dep, MAP_NUL, NULL, PARSEFS_WAIT);
if (! new)
new = ood;
@@ -888,7 +887,19 @@ snap_deps (void)
f = lookup_file (".NOTPARALLEL");
if (f != 0 && f->is_target)
- not_parallel = 1;
+ {
+ struct dep *d2;
+
+ if (!f->deps)
+ not_parallel = 1;
+ else
+ /* Set a wait point between every prerequisite of each target. */
+ for (d = f->deps; d != NULL; d = d->next)
+ for (f2 = d->file; f2 != NULL; f2 = f2->prev)
+ if (f2->deps)
+ for (d2 = f2->deps->next; d2 != NULL; d2 = d2->next)
+ d2->wait_here = 1;
+ }
{
struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS")));
@@ -1039,17 +1050,17 @@ print_prereqs (const struct dep *deps)
/* Print all normal dependencies; note any order-only deps. */
for (; deps != 0; deps = deps->next)
if (! deps->ignore_mtime)
- printf (" %s", dep_name (deps));
+ printf (" %s%s", deps->wait_here ? ".WAIT " : "", dep_name (deps));
else if (! ood)
ood = deps;
/* Print order-only deps, if we have any. */
if (ood)
{
- printf (" | %s", dep_name (ood));
+ printf (" | %s%s", ood->wait_here ? ".WAIT " : "", dep_name (ood));
for (ood = ood->next; ood != 0; ood = ood->next)
if (ood->ignore_mtime)
- printf (" %s", dep_name (ood));
+ printf (" %s%s", ood->wait_here ? ".WAIT " : "", dep_name (ood));
}
putchar ('\n');
diff --git a/src/implicit.c b/src/implicit.c
index d3853726..43f0cd24 100644
--- a/src/implicit.c
+++ b/src/implicit.c
@@ -160,6 +160,7 @@ struct patdeps
unsigned int ignore_mtime : 1;
unsigned int ignore_automatic_vars : 1;
unsigned int is_explicit : 1;
+ unsigned int wait_here : 1;
};
/* This structure stores information about pattern rules that we need
@@ -569,12 +570,14 @@ pattern_search (struct file *file, int archive,
/* Parse the expanded string. It might have wildcards. */
p = depname;
- dl = PARSE_FILE_SEQ (&p, struct dep, MAP_NUL, NULL, PARSEFS_ONEWORD);
+ dl = PARSE_FILE_SEQ (&p, struct dep, MAP_NUL, NULL,
+ PARSEFS_ONEWORD|PARSEFS_WAIT);
for (d = dl; d != NULL; d = d->next)
{
++deps_found;
d->ignore_mtime = dep->ignore_mtime;
d->ignore_automatic_vars = dep->ignore_automatic_vars;
+ d->wait_here |= dep->wait_here;
d->is_explicit = is_explicit;
}
@@ -708,7 +711,8 @@ pattern_search (struct file *file, int archive,
/* Parse the expanded string. */
struct dep *dp = PARSE_FILE_SEQ (&p, struct dep,
order_only ? MAP_NUL : MAP_PIPE,
- add_dir ? pathdir : NULL, PARSEFS_NONE);
+ add_dir ? pathdir : NULL,
+ PARSEFS_WAIT);
*dptr = dp;
for (d = dp; d != NULL; d = d->next)
@@ -773,6 +777,7 @@ pattern_search (struct file *file, int archive,
memset (pat, '\0', sizeof (struct patdeps));
pat->ignore_mtime = d->ignore_mtime;
pat->ignore_automatic_vars = d->ignore_automatic_vars;
+ pat->wait_here = d->wait_here;
pat->is_explicit = d->is_explicit;
DBS (DB_IMPLICIT,
@@ -1021,6 +1026,7 @@ pattern_search (struct file *file, int archive,
dep->ignore_mtime = pat->ignore_mtime;
dep->is_explicit = pat->is_explicit;
dep->ignore_automatic_vars = pat->ignore_automatic_vars;
+ dep->wait_here = pat->wait_here;
s = strcache_add (pat->name);
if (recursions)
dep->name = s;
diff --git a/src/read.c b/src/read.c
index 94e1ce90..e94e75bc 100644
--- a/src/read.c
+++ b/src/read.c
@@ -144,7 +144,8 @@ static void do_undefine (char *name, enum variable_origin origin,
static struct variable *do_define (char *name, enum variable_origin origin,
struct ebuffer *ebuf);
static int conditional_line (char *line, size_t len, const floc *flocp);
-static void check_specials (const struct nameseq *file, int set_default);
+static void check_specials (struct nameseq *filep, int set_default);
+static void check_special_file (struct file *filep, const floc *flocp);
static void record_files (struct nameseq *filenames, int are_also_makes,
const char *pattern,
const char *pattern_percent, char *depstr,
@@ -1883,16 +1884,12 @@ record_target_var (struct nameseq *filenames, char *defn,
and it have been mis-parsed because these special targets haven't been
considered yet. */
-static void check_specials (const struct nameseq* files, int set_default)
+static void
+check_specials (struct nameseq *files, int set_default)
{
- const struct nameseq *t = files;
-
- /* Unlikely but ... */
- if (posix_pedantic && second_expansion && one_shell
- && (!set_default || default_goal_var->value[0] == '\0'))
- return;
+ struct nameseq *t;
- for (; t != 0; t = t->next)
+ for (t = files; t != NULL; t = t->next)
{
const char* nm = t->name;
@@ -1977,6 +1974,34 @@ static void check_specials (const struct nameseq* files, int set_default)
}
}
}
+
+/* Check for special targets. We used to do this in record_files() but that's
+ too late: by the time we get there we'll have already parsed the next line
+ and it have been mis-parsed because these special targets haven't been
+ considered yet. */
+
+static void
+check_special_file (struct file *file, const floc *flocp)
+{
+ if (streq (file->name, ".WAIT"))
+ {
+ static unsigned int wpre = 0, wcmd = 0;
+
+ if (!wpre && file->deps)
+ {
+ O (error, flocp, _(".WAIT should not have prerequisites"));
+ wpre = 1;
+ }
+
+ if (!wcmd && file->cmds)
+ {
+ O (error, flocp, _(".WAIT should not have commands"));
+ wcmd = 1;
+ }
+
+ return;
+ }
+}
/* Record a description line for files FILENAMES,
with dependencies DEPS, commands to execute described
@@ -2265,6 +2290,8 @@ record_files (struct nameseq *filenames, int are_also_makes,
name = f->name;
+ check_special_file (f, flocp);
+
/* All done! Set up for the next one. */
if (nextf == 0)
break;
@@ -3143,6 +3170,8 @@ tilde_expand (const char *name)
PARSEFS_EXISTS - Only return globbed files that actually exist
(cannot also set NOGLOB)
PARSEFS_NOCACHE - Do not add filenames to the strcache (caller frees)
+ PARSEFS_ONEWORD - Don't break the sequence on whitespace
+ PARSEFS_WAIT - Assume struct dep and handle .WAIT
*/
void *
@@ -3158,16 +3187,22 @@ parse_file_seq (char **stringp, size_t size, int stopmap,
struct nameseq *new = 0;
struct nameseq **newp = &new;
#define NEWELT(_n) do { \
- const char *__n = (_n); \
- *newp = xcalloc (size); \
- (*newp)->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \
- newp = &(*newp)->next; \
+ struct nameseq *_ns = xcalloc (size); \
+ const char *__n = (_n); \
+ _ns->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \
+ if (found_wait) { \
+ ((struct dep*)_ns)->wait_here = 1; \
+ found_wait = 0; \
+ } \
+ *newp = _ns; \
+ newp = &_ns->next; \
} while(0)
char *p;
glob_t gl;
char *tp;
int findmap = stopmap|MAP_VMSCOMMA|MAP_NUL;
+ int found_wait = 0;
if (NONE_SET (flags, PARSEFS_ONEWORD))
findmap |= MAP_BLANK;
@@ -3241,6 +3276,14 @@ parse_file_seq (char **stringp, size_t size, int stopmap,
if (!p)
p = s + strlen (s);
+ if (ANY_SET (flags, PARSEFS_WAIT) && p - s == CSTRLEN (".WAIT")
+ && memcmp (s, ".WAIT", CSTRLEN (".WAIT")) == 0)
+ {
+ /* Note that we found a .WAIT for the next dep but skip it. */
+ found_wait = 1;
+ continue;
+ }
+
/* Strip leading "this directory" references. */
if (NONE_SET (flags, PARSEFS_NOSTRIP))
#ifdef VMS
diff --git a/src/remake.c b/src/remake.c
index 228b8af6..59bd644c 100644
--- a/src/remake.c
+++ b/src/remake.c
@@ -551,6 +551,9 @@ update_file_1 (struct file *file, unsigned int depth)
d = du->shuf ? du->shuf : du;
+ if (d->wait_here && running)
+ break;
+
check_renamed (d->file);
mtime = file_mtime (d->file);
@@ -631,6 +634,10 @@ update_file_1 (struct file *file, unsigned int depth)
for (du = file->deps; du != 0; du = du->next)
{
d = du->shuf ? du->shuf : du;
+
+ if (d->wait_here && running)
+ break;
+
if (d->file->intermediate)
{
enum update_status new;
diff --git a/src/rule.c b/src/rule.c
index a491b83d..d4d28fa2 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -65,21 +65,22 @@ static size_t maxsuffix;
space separated rule prerequisites, followed by a pipe, followed by
order-only prerequisites, if present. */
-const char *get_rule_defn (struct rule *r)
+const char *
+get_rule_defn (struct rule *r)
{
if (r->_defn == NULL)
{
+ size_t len = 8; /* Reserve for ":: ", " | ", and nul. */
unsigned int k;
- ptrdiff_t len = 8; // Reserve for ":: ", " | " and the null terminator.
char *p;
const char *sep = "";
const struct dep *dep, *ood = 0;
for (k = 0; k < r->num; ++k)
- len += r->lens[k] + 1; // Add one for a space.
+ len += r->lens[k] + 1;
for (dep = r->deps; dep; dep = dep->next)
- len += strlen (dep_name (dep)) + 1; // Add one for a space.
+ len += strlen (dep_name (dep)) + (dep->wait_here ? CSTRLEN (" .WAIT") : 0) + 1;
p = r->_defn = xmalloc (len);
for (k = 0; k < r->num; ++k, sep = " ")
@@ -91,18 +92,25 @@ const char *get_rule_defn (struct rule *r)
/* Copy all normal dependencies; note any order-only deps. */
for (dep = r->deps; dep; dep = dep->next)
if (dep->ignore_mtime == 0)
- p = mempcpy (mempcpy (p, " ", 1), dep_name (dep),
- strlen (dep_name (dep)));
+ {
+ if (dep->wait_here)
+ p = mempcpy (p, STRING_SIZE_TUPLE (" .WAIT"));
+ p = mempcpy (mempcpy (p, " ", 1), dep_name (dep),
+ strlen (dep_name (dep)));
+ }
else if (ood == 0)
ood = dep;
/* Copy order-only deps, if we have any. */
for (sep = " | "; ood; ood = ood->next, sep = " ")
if (ood->ignore_mtime)
- p = mempcpy (mempcpy (p, sep, strlen (sep)), dep_name (ood),
- strlen (dep_name (ood)));
+ {
+ p = mempcpy (p, sep, strlen (sep));
+ if (ood->wait_here)
+ p = mempcpy (p, STRING_SIZE_TUPLE (".WAIT "));
+ p = mempcpy (p, dep_name (ood), strlen (dep_name (ood)));
+ }
*p = '\0';
- assert (p - r->_defn < len);
}
return r->_defn;
diff --git a/src/shuffle.c b/src/shuffle.c
index ca54e528..95f60bea 100644
--- a/src/shuffle.c
+++ b/src/shuffle.c
@@ -157,7 +157,13 @@ shuffle_deps (struct dep *deps)
void **dp;
for (dep = deps; dep; dep = dep->next)
- ndeps++;
+ {
+ /* Do not reshuffle prerequisites if any .WAIT is present. */
+ if (dep->wait_here)
+ return;
+
+ ndeps++;
+ }
if (ndeps == 0)
return;
@@ -215,8 +221,7 @@ shuffle_deps_recursive (struct dep *deps)
if (config.mode == sm_none)
return;
- /* Do not reshuffle targets if Makefile is explicitly marked as
- problematic for parallelism. */
+ /* Do not reshuffle prerequisites if .NOTPARALLEL was specified. */
if (not_parallel)
return;
diff --git a/tests/scripts/targets/WAIT b/tests/scripts/targets/WAIT
new file mode 100644
index 00000000..24cf9179
--- /dev/null
+++ b/tests/scripts/targets/WAIT
@@ -0,0 +1,193 @@
+# -*-perl-*-
+
+$description = "Test the behaviour of the .WAIT target.";
+
+$details = "";
+
+# Ensure .WAIT doesn't appear in any automatic variables
+
+run_make_test(q!
+all: .WAIT pre1 .WAIT pre2 | .WAIT pre3 ; @echo '<=$< ^=$^ ?=$? +=$+ |=$|'
+pre1 pre2 pre3:;
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '', '<=pre1 ^=pre1 pre2 ?=pre1 pre2 +=pre1 pre2 |=pre3');
+
+run_make_test(q!
+.SECONDEXPANSION:
+all: $$(pre) ; @echo '<=$< ^=$^ ?=$? +=$+ |=$|'
+pre1 pre2 pre3:;
+
+pre = .WAIT pre1 .WAIT pre2 | .WAIT pre3
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '', '<=pre1 ^=pre1 pre2 ?=pre1 pre2 +=pre1 pre2 |=pre3');
+
+run_make_test(q!
+all: pre
+p% : .WAIT p%1 .WAIT p%2 | .WAIT p%3; @echo '<=$< ^=$^ ?=$? +=$+ |=$|'
+pre1 pre2 pre3: ;
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '', "<=pre1 ^=pre1 pre2 ?=pre1 pre2 +=pre1 pre2 |=pre3\n");
+
+# Unfortunately I don't think we can get away from using sleep here; at least
+# I can't think of any way to make sure .WAIT works without it. Even with it,
+# it's not reliable (in that even if .WAIT is not working we MIGHT succeed the
+# test--it shouldn't ever be the case that we fail the test unexpectedly).
+# That makes this test suite slow to run :-/.
+
+run_make_test(q!
+all : pre1 .WAIT pre2
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\n");
+
+# Ensure .WAIT doesn't add extra a dependency between its targets
+
+run_make_test(undef, '-j10 pre2', "pre2\n");
+
+# Ensure .WAIT doesn't wait between all targets
+
+run_make_test(q!
+all : pre1 .WAIT pre2 pre3
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out start-$@ file TWO wait THREE out end-$@
+pre3: ; @#HELPER# -q wait TWO out $@ file THREE
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\nstart-pre2\npre3\nend-pre2\n");
+
+unlink(qw(TWO THREE));
+
+# Ensure .WAIT waits for ALL targets on the left before ANY targets on the right
+
+run_make_test(q!
+all : pre1 pre2 .WAIT post1 post2
+pre1: ; @#HELPER# -q out start-$@ file PRE1 wait PRE2 sleep 1 out end-$@
+pre2: ; @#HELPER# -q wait PRE1 out $@ file PRE2
+
+post1: ; @#HELPER# -q wait POST2 out $@ file POST1
+post2: ; @#HELPER# -q file POST2 wait POST1 out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\npre2\nend-pre1\npost1\npost2\n");
+
+unlink(qw(PRE1 PRE2 POST1 POST2));
+
+# See if .WAIT takes effect between different lists of prereqs
+# In the current implementation, .WAIT waits only between two prerequisites
+# in a given target. These same two targets might be run in a different
+# order if they appear as prerequisites of another target. This is the way
+# other implementations of .WAIT work. I personally think it's gross and
+# makes .WAIT just a toy when it comes to ordering, but it's much simpler
+# to implement than creating an actual edge in the DAG to represent .WAIT
+# and since that's what users expect, we'll do the same for now.
+
+run_make_test(q!
+all : one two
+one: pre1 .WAIT pre2
+two: pre2 pre1
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\npre2\nend-pre1\n");
+
+# Check that .WAIT works with pattern rules
+
+run_make_test(q!
+all: pre
+p% : p%1 .WAIT p%2;
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\n");
+
+# Check that .WAIT works with secondarily expanded rules
+
+run_make_test(q!
+.SECONDEXPANSION:
+all: $$(pre)
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+pre3: ; @#HELPER# -q out $@
+
+pre = .WAIT pre1 .WAIT pre2 | .WAIT pre3
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\npre3\n");
+
+# Verify NOTPARALLEL works
+
+run_make_test(q!
+all : pre1 pre2
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+.NOTPARALLEL:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\n");
+
+run_make_test(q!
+all : p1 .WAIT np1
+
+p1: pre1 pre2
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+np1: npre1 npre2
+npre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+npre2: ; @#HELPER# -q out $@
+
+.NOTPARALLEL: np1
+!,
+ '-j10', "start-pre1\npre2\nend-pre1\nstart-npre1\nend-npre1\nnpre2\n");
+
+# Ensure we don't shuffle if .WAIT is set
+
+run_make_test(q!
+all : pre1 .WAIT pre2
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10 --shuffle=reverse', "start-pre1\nend-pre1\npre2\n");
+
+# Warn about invalid .WAIT definitions
+
+run_make_test(q!
+.WAIT: foo
+.WAIT: ; echo oops
+all:;@:
+!,
+ '', "#MAKEFILE#:2: .WAIT should not have prerequisites\n#MAKEFILE#:3: .WAIT should not have commands\n");
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End: