[SV 13862] Implement the .WAIT special target

The next version of the POSIX standard defines parallel execution
and requires the .WAIT special target as is implemented in some other
versions of make.

This implementation behaves similarly to others in that it does not
create a relationship between targets in the dependency graph, so
that the same two targets may be run in parallel if they appear as
prerequisites elsewhere without .WAIT between them.

Now that we support .WAIT it's trivial to also support prerequisites
of the .NOTPARALLEL special target, which forces the prerequisites of
those targets to be run serially (as if .WAIT was specified between
each one).

* NEWS: Announce the new .WAIT and .NOTPARALLEL support.
* doc/make.texi (Parallel Disable): A new section to discuss ways in
which parallel execution can be controlled.  Modify cross-refs to
refer to this section.
* src/dep.h (struct dep): Add a new wait_here boolean.
(parse_file_seq): Add PARSEFS_WAIT to check for .WAIT dependencies.
* src/file.c (split_prereqs): Use PARSEFS_WAIT.
(snap_deps): If .NOTPARALLEL has prerequisites, set .WAIT between
each of _their_ prerequisites.
(print_prereqs): Add back in .WAIT when printing prerequisites.
* src/implicit.c (struct patdeps): Preserve wait_here.
(pattern_search): Ditto.  Use PARSEFS_WAIT when parsing prereqs for
pattern rule expansion.
* src/read.c (check_specials): Don't give up early: remembering to
update these options is not worth the rare speedup.
(check_special_file): If .WAIT is given as a target show an error--
once--if it has prereqs or commands.
(record_files): Call check_special_file on each target.
(parse_file_seq): If PARSEFS_WAIT is given, look for .WAIT prereqs.
If we see one assume that we are building a struct dep chain and set
the wait_here option while not putting it into the list.
* src/remake.c (update_file_1): If wait_here is set and we are still
running, then stop trying to build this target's prerequisites.
* src/rule.c (get_rule_defn): Add .WAIT to the prerequisite list.
* src/shuffle.c (shuffle_deps): Don't shuffle the prerequisite list
if .WAIT appears anywhere in it.
* tests/scripts/targets/WAIT: Add a test suite for this feature.
This commit is contained in:
Paul Smith 2022-09-12 18:18:49 -04:00
parent ee861a4e9f
commit f6ea899d83
10 changed files with 445 additions and 51 deletions

15
NEWS
View file

@ -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.

View file

@ -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:

View file

@ -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))

View file

@ -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)
{
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');

View file

@ -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;

View file

@ -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;
struct nameseq *t;
/* Unlikely but ... */
if (posix_pedantic && second_expansion && one_shell
&& (!set_default || default_goal_var->value[0] == '\0'))
return;
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 { \
struct nameseq *_ns = xcalloc (size); \
const char *__n = (_n); \
*newp = xcalloc (size); \
(*newp)->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \
newp = &(*newp)->next; \
_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

View file

@ -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;

View file

@ -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)
{
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;

View file

@ -157,7 +157,13 @@ shuffle_deps (struct dep *deps)
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;
@ -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;

193
tests/scripts/targets/WAIT Normal file
View file

@ -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: