summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Smith <psmith@gnu.org>2022-09-12 18:18:49 -0400
committerPaul Smith <psmith@gnu.org>2022-09-12 18:35:29 -0400
commitf6ea899d83bf00fe9201fde0ca9cf7af8e443677 (patch)
tree62f8077c6c3e9aac1c2a4c18f7ebcc619d6cfbcb
parentee861a4e9f523d06d26ed612ad1a93b6ecb408de (diff)
downloadmake-git-f6ea899d83bf00fe9201fde0ca9cf7af8e443677.tar.gz
[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.
-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: