summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorapb <apb@NetBSD.org>2006-02-26 22:45:46 +0000
committerapb <apb@NetBSD.org>2006-02-26 22:45:46 +0000
commitf34c12e9891a798cdb3bedffa2ee43aed79adbd7 (patch)
treefa06d1a840e8ecb1c0cf47e3e36732edc0b3a31c /usr.bin
parent3ea7f78b5754c90382f880088ad622295fe20eb8 (diff)
Make ".WAIT" apply recursively to all children of nodes on the right
hand side of the .WAIT, except when the recursive interpretation would cause a cycle in the dependency graph. Discussed in tech-toolchain. Reviewed by christos, sjg.
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/make/main.c8
-rw-r--r--usr.bin/make/make.111
-rw-r--r--usr.bin/make/make.h26
-rw-r--r--usr.bin/make/parse.c9
-rw-r--r--usr.bin/make/targ.c330
-rw-r--r--usr.bin/make/unit-tests/Makefile4
-rw-r--r--usr.bin/make/unit-tests/dotwait61
-rw-r--r--usr.bin/make/unit-tests/test.exp29
8 files changed, 454 insertions, 24 deletions
diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c
index 12fa1301a9a..571e1887328 100644
--- a/usr.bin/make/main.c
+++ b/usr.bin/make/main.c
@@ -1,4 +1,4 @@
-/* $NetBSD: main.c,v 1.119 2006/02/18 04:10:05 tsutsui Exp $ */
+/* $NetBSD: main.c,v 1.120 2006/02/26 22:45:46 apb Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -69,7 +69,7 @@
*/
#ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: main.c,v 1.119 2006/02/18 04:10:05 tsutsui Exp $";
+static char rcsid[] = "$NetBSD: main.c,v 1.120 2006/02/26 22:45:46 apb Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
@@ -81,7 +81,7 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\n\
#if 0
static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94";
#else
-__RCSID("$NetBSD: main.c,v 1.119 2006/02/18 04:10:05 tsutsui Exp $");
+__RCSID("$NetBSD: main.c,v 1.120 2006/02/26 22:45:46 apb Exp $");
#endif
#endif /* not lint */
#endif
@@ -994,6 +994,8 @@ main(int argc, char **argv)
/*
* Propagate attributes through :: dependency lists.
+ *
+ * Also propagate recursive dependencies for .WAIT.
*/
Targ_Propagate();
diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1
index 58349d45670..0344872423f 100644
--- a/usr.bin/make/make.1
+++ b/usr.bin/make/make.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.121 2006/02/26 22:40:50 wiz Exp $
+.\" $NetBSD: make.1,v 1.122 2006/02/26 22:45:46 apb Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -1430,6 +1430,11 @@ appears in a dependency line, the sources that precede it are
made before the sources that succeed it in the line.
Loops are not
detected and targets that form loops will be silently ignored.
+The ordering imposed by
+.Ic .WAIT
+applies recursively to dependents of the named targets,
+except where recursive interpretation of the ordering
+would cause a cycle in the ordering graph.
.El
.Sh SPECIAL TARGETS
Special targets may not be included with other targets, i.e. they must be
@@ -1495,6 +1500,10 @@ Synonym for
for compatibility with other pmake variants.
.It Ic .ORDER
The named targets are made in sequence.
+The ordering imposed by
+.Ic .ORDER
+applies only to the named targets themselves,
+not to dependents of the named targets.
.\" XXX: NOT YET!!!!
.\" .It Ic .PARALLEL
.\" The named targets are executed in parallel mode.
diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h
index 34ed67c5252..8a247fdc40e 100644
--- a/usr.bin/make/make.h
+++ b/usr.bin/make/make.h
@@ -129,20 +129,25 @@
* 7) the number of its children that are, as yet, unmade
* 8) its modification time
* 9) the modification time of its youngest child (qv. make.c)
- * 10) a list of nodes for which this is a source
- * 11) a list of nodes on which this depends
+ * 10) a list of nodes for which this is a source (parents)
+ * 11) a list of nodes on which this depends (children)
* 12) a list of nodes that depend on this, as gleaned from the
- * transformation rules.
- * 13) a list of nodes of the same name created by the :: operator
- * 14) a list of nodes that must be made (if they're made) before
- * this node can be, but that do no enter into the datedness of
+ * transformation rules (iParents)
+ * 13) a list of ancestor nodes, which includes parents, iParents,
+ * and recursive parents of parents
+ * 14) a list of nodes of the same name created by the :: operator
+ * 15) a list of nodes that must be made (if they're made) before
+ * this node can be, but that do not enter into the datedness of
* this node.
- * 15) a list of nodes that must be made (if they're made) after
+ * 16) a list of nodes that must be made (if they're made) before
+ * this node or any child of this node can be, but that do not
+ * enter into the datedness of this node.
+ * 17) a list of nodes that must be made (if they're made) after
* this node is, but that do not depend on this node, in the
* normal sense.
- * 16) a Lst of ``local'' variables that are specific to this target
+ * 18) a Lst of ``local'' variables that are specific to this target
* and this target only (qv. var.c [$@ $< $?, etc.])
- * 17) a Lst of strings that are commands to be given to a shell
+ * 19) a Lst of strings that are commands to be given to a shell
* to create this target.
*/
typedef struct GNode {
@@ -189,9 +194,12 @@ typedef struct GNode {
* implied source, if any */
Lst cohorts; /* Other nodes for the :: operator */
Lst parents; /* Nodes that depend on this one */
+ Lst ancestors; /* Parents, parents of parents, ... */
Lst children; /* Nodes on which this one depends */
Lst successors; /* Nodes that must be made after this one */
Lst preds; /* Nodes that must be made before this one */
+ Lst recpreds; /* Nodes that must be added to preds
+ * recursively for all child nodes */
int unmade_cohorts;/* # of unmade instances on the
cohorts list */
struct GNode *centurion; /* Pointer to the first instance of a ::
diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c
index 64ef9007027..b31d0d43bbd 100644
--- a/usr.bin/make/parse.c
+++ b/usr.bin/make/parse.c
@@ -1,4 +1,4 @@
-/* $NetBSD: parse.c,v 1.109 2006/02/11 20:59:49 dsl Exp $ */
+/* $NetBSD: parse.c,v 1.110 2006/02/26 22:45:46 apb Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -69,14 +69,14 @@
*/
#ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: parse.c,v 1.109 2006/02/11 20:59:49 dsl Exp $";
+static char rcsid[] = "$NetBSD: parse.c,v 1.110 2006/02/26 22:45:46 apb Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94";
#else
-__RCSID("$NetBSD: parse.c,v 1.109 2006/02/11 20:59:49 dsl Exp $");
+__RCSID("$NetBSD: parse.c,v 1.110 2006/02/26 22:45:46 apb Exp $");
#endif
#endif /* not lint */
#endif
@@ -613,6 +613,8 @@ ParseDoOp(ClientData gnp, ClientData opp)
*
* Side Effects:
* A dependency can be added between the two nodes.
+ * The dependency is tagged for recursive addition to
+ * all children of sp.
*
*---------------------------------------------------------------------
*/
@@ -635,6 +637,7 @@ ParseAddDep(ClientData pp, ClientData sp)
*/
(void)Lst_AtEnd(p->successors, (ClientData)s);
(void)Lst_AtEnd(s->preds, (ClientData)p);
+ (void)Lst_AtEnd(s->recpreds, (ClientData)p);
if (DEBUG(PARSE)) {
printf("# ParseAddDep: added .WAIT dependency %s - %s\n",
p->name, s->name);
diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c
index c4cf6d429ec..e1810d5cef3 100644
--- a/usr.bin/make/targ.c
+++ b/usr.bin/make/targ.c
@@ -1,4 +1,4 @@
-/* $NetBSD: targ.c,v 1.41 2006/02/11 20:19:36 dsl Exp $ */
+/* $NetBSD: targ.c,v 1.42 2006/02/26 22:45:46 apb Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -69,14 +69,14 @@
*/
#ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: targ.c,v 1.41 2006/02/11 20:19:36 dsl Exp $";
+static char rcsid[] = "$NetBSD: targ.c,v 1.42 2006/02/26 22:45:46 apb Exp $";
#else
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94";
#else
-__RCSID("$NetBSD: targ.c,v 1.41 2006/02/11 20:19:36 dsl Exp $");
+__RCSID("$NetBSD: targ.c,v 1.42 2006/02/26 22:45:46 apb Exp $");
#endif
#endif /* not lint */
#endif
@@ -118,6 +118,11 @@ __RCSID("$NetBSD: targ.c,v 1.41 2006/02/11 20:19:36 dsl Exp $");
* Targ_Precious Return TRUE if the target is precious and
* should not be removed if we are interrupted.
*
+ * Targ_Propagate Propagate information between related
+ * nodes. Should be called after the
+ * makefiles are parsed but before any
+ * action is taken.
+ *
* Debugging:
* Targ_PrintGraph Print out the entire graphm all variables
* and statistics for the directory cache. Should
@@ -247,9 +252,11 @@ Targ_NewGN(const char *name)
gn->iParents = Lst_Init(FALSE);
gn->cohorts = Lst_Init(FALSE);
gn->parents = Lst_Init(FALSE);
+ gn->ancestors = Lst_Init(FALSE);
gn->children = Lst_Init(FALSE);
gn->successors = Lst_Init(FALSE);
gn->preds = Lst_Init(FALSE);
+ gn->recpreds = Lst_Init(FALSE);
Hash_InitTable(&gn->context, 0);
gn->commands = Lst_Init(FALSE);
gn->suffix = NULL;
@@ -295,9 +302,11 @@ TargFreeGN(ClientData gnp)
Lst_Destroy(gn->iParents, NOFREE);
Lst_Destroy(gn->cohorts, NOFREE);
Lst_Destroy(gn->parents, NOFREE);
+ Lst_Destroy(gn->ancestors, NOFREE);
Lst_Destroy(gn->children, NOFREE);
Lst_Destroy(gn->successors, NOFREE);
Lst_Destroy(gn->preds, NOFREE);
+ Lst_Destroy(gn->recpreds, NOFREE);
Hash_DeleteTable(&gn->context);
Lst_Destroy(gn->commands, NOFREE);
free(gn);
@@ -665,11 +674,21 @@ Targ_PrintNode(ClientData gnp, ClientData passp)
Lst_ForEach(gn->parents, TargPrintName, (ClientData)0);
fputc('\n', stdout);
}
+ if (!Lst_IsEmpty (gn->children)) {
+ printf("# children: ");
+ Lst_ForEach(gn->children, TargPrintName, (ClientData)0);
+ fputc('\n', stdout);
+ }
if (!Lst_IsEmpty (gn->preds)) {
printf("# preds: ");
Lst_ForEach(gn->preds, TargPrintName, (ClientData)0);
fputc('\n', stdout);
}
+ if (!Lst_IsEmpty (gn->recpreds)) {
+ printf("# recpreds: ");
+ Lst_ForEach(gn->recpreds, TargPrintName, (ClientData)0);
+ fputc('\n', stdout);
+ }
if (!Lst_IsEmpty (gn->successors)) {
printf("# successors: ");
Lst_ForEach(gn->successors, TargPrintName, (ClientData)0);
@@ -754,25 +773,322 @@ Targ_PrintGraph(int pass)
Suff_PrintAll();
}
+/*-
+ *-----------------------------------------------------------------------
+ * TargAppendAncestor -
+ * Appends a single ancestor to a node's list of ancestors,
+ * ignoring duplicates.
+ *
+ * Input:
+ * ancestorgnp An ancestor to be added to a list.
+ * thisgnp The node whose ancestor list will be changed.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * May modify the ancestors list of the node we are
+ * examining.
+ *-----------------------------------------------------------------------
+ */
static int
-TargPropagateCohort(ClientData cgnp, ClientData pgnp)
+TargAppendAncestor(ClientData ancestorgnp, ClientData thisgnp)
{
- GNode *cgn = (GNode *)cgnp;
- GNode *pgn = (GNode *)pgnp;
+ GNode *ancestorgn = (GNode *)ancestorgnp;
+ GNode *thisgn = (GNode *)thisgnp;
- cgn->type |= pgn->type & ~OP_OPMASK;
+ if (Lst_Member(thisgn->ancestors, ancestorgn) == NILLNODE) {
+ (void)Lst_AtEnd(thisgn->ancestors, (ClientData)ancestorgn);
+ }
return (0);
}
+/*-
+ *-----------------------------------------------------------------------
+ * TargAppendParentAncestors -
+ * Appends all ancestors of a parent node to the ancestor list of a
+ * given node, ignoring duplicates.
+ *
+ * Input:
+ * parentgnp A parent node whose ancestor list will be
+ * propagated to another node.
+ * thisgnp The node whose ancestor list will be changed.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * May modify the ancestors list of the node we are
+ * examining.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargAppendParentAncestors(ClientData parentgnp, ClientData thisgnp)
+{
+ GNode *parentgn = (GNode *)parentgnp;
+ GNode *thisgn = (GNode *)thisgnp;
+
+ Lst_ForEach(parentgn->ancestors, TargAppendAncestor, thisgn);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargInitAncestors -
+ * Initialises the ancestor list of a node and all the
+ * node's ancestors.
+ *
+ * Input:
+ * thisgnp The node that we are examining.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * May initialise the ancestors list of the node we are
+ * examining and all its ancestors. Does nothing if the
+ * list has already been initialised.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargInitAncestors(ClientData thisgnp, ClientData junk __unused)
+{
+ GNode *thisgn = (GNode *)thisgnp;
+
+ if (Lst_IsEmpty (thisgn->ancestors)) {
+ /*
+ * Add our parents to our ancestor list before recursing, to
+ * ensure that loops in the dependency graph will not result in
+ * infinite recursion.
+ */
+ Lst_ForEach(thisgn->parents, TargAppendAncestor, thisgn);
+ Lst_ForEach(thisgn->iParents, TargAppendAncestor, thisgn);
+ /* Recursively initialise our parents' ancestor lists */
+ Lst_ForEach(thisgn->parents, TargInitAncestors, (ClientData)0);
+ Lst_ForEach(thisgn->iParents, TargInitAncestors, (ClientData)0);
+ /* Our parents' ancestors are also our ancestors */
+ Lst_ForEach(thisgn->parents, TargAppendParentAncestors, thisgn);
+ Lst_ForEach(thisgn->iParents, TargAppendParentAncestors, thisgn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargHasAncestor -
+ * Checks whether one node is an ancestor of another node.
+ *
+ * If called with both arguments pointing to the
+ * same node, checks whether the node is part of a cycle
+ * in the graph.
+ *
+ * Input:
+ * thisgnp The node whose ancestor list we are examining.
+ * seekgnp The node that we are seeking in the
+ * ancestor list.
+ *
+ * Results:
+ * TRUE if seekgn is an ancestor of thisgn; FALSE if not.
+ *
+ * Side Effects:
+ * Initialises the ancestors list in thisgnp and all its ancestors.
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+TargHasAncestor(ClientData thisgnp, ClientData seekgnp)
+{
+ GNode *thisgn = (GNode *)thisgnp;
+ GNode *seekgn = (GNode *)seekgnp;
+
+ TargInitAncestors(thisgn, (ClientData)0);
+ if (Lst_Member(thisgn->ancestors, seekgn) != NILLNODE) {
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateRecpredChild --
+ * Create a new predecessor/successor relationship between a pair
+ * of nodes, and recursively propagate the relationship to all
+ * children of the successor node.
+ *
+ * If there is already a predecessor/successor relationship
+ * in the opposite direction, or if there is already an
+ * ancestor/descendent relationship in the oposite direction, then
+ * we avoid adding the new relationship, because that would cause a
+ * cycle in the graph.
+ *
+ * Input:
+ * succgnp Successor node.
+ * predgnp Predecessor node.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * preds/successors information is modified for predgnp, succgnp,
+ * and recursively for all children of succgnp.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateRecpredChild(ClientData succgnp, ClientData predgnp)
+{
+ GNode *succgn = (GNode *)succgnp;
+ GNode *predgn = (GNode *)predgnp;
+ Boolean debugmore = FALSE; /* how much debugging? */
+
+ /* Ignore if succgn == predgn */
+ if (succgn == predgn) {
+ if (DEBUG(TARG) && debugmore) {
+ printf("# TargPropagateRecpredChild: not propagating %s - %s (identical)\n",
+ predgn->name, succgn->name);
+ }
+ return (0);
+ }
+ /* Pre-existing pred/successor relationship
+ * in the opposite direction takes precedence. */
+ if (Lst_Member(succgn->successors, predgn) != NILLNODE) {
+ if (DEBUG(TARG) && debugmore) {
+ printf("# TargPropagateRecpredChild: not propagating %s - %s (opposite)\n",
+ predgn->name, succgn->name);
+ }
+ return (0);
+ }
+ /* Pre-existing descendent/ancestor relationship in the opposite
+ * direction takes precedence. */
+ if (TargHasAncestor(succgn, predgn)) {
+ if (DEBUG(TARG) && debugmore) {
+ printf("# TargPropagateRecpredChild: not propagating %s - %s (ancestor)\n",
+ predgn->name, succgn->name);
+ }
+ return (0);
+ }
+ /* Note the new pred/successor relationship. */
+ if (DEBUG(TARG)) {
+ printf("# TargPropagateRecpredChild: propagating %s - %s\n",
+ predgn->name, succgn->name);
+ }
+ if (Lst_Member(succgn->preds, predgn) == NILLNODE) {
+ (void)Lst_AtEnd(succgn->preds, (ClientData)predgn);
+ (void)Lst_AtEnd(predgn->successors, (ClientData)succgn);
+ }
+ /* Recurse, provided there's not a cycle. */
+ if (! TargHasAncestor(succgn, succgn)) {
+ Lst_ForEach(succgn->children, TargPropagateRecpredChild, predgn);
+ }
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateRecpred --
+ * Recursively propagate information about a single predecessor
+ * node, from a single successor node to all children of the
+ * successor node.
+ *
+ * Input:
+ * predgnp Predecessor node.
+ * succgnp Successor node.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * preds/successors information is modified for predgnp, succgnp,
+ * and recursively for all children of succgnp.
+ *
+ * The real work is done by TargPropagateRecpredChild(), which
+ * will be called for each child of the successor node.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateRecpred(ClientData predgnp, ClientData succgnp)
+{
+ GNode *predgn = (GNode *)predgnp;
+ GNode *succgn = (GNode *)succgnp;
+
+ Lst_ForEach(succgn->children, TargPropagateRecpredChild, predgn);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateNode --
+ * Propagate information from a single node to related nodes if
+ * appropriate.
+ *
+ * Input:
+ * gnp The node that we are processing.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * Information is propagated from this node to cohort or child
+ * nodes.
+ *
+ * If the node was defined with "::", then TargPropagateCohort()
+ * will be called for each cohort node.
+ *
+ * If the node has recursive predecessors, then
+ * TargPropagateRecpred() will be called for each recursive
+ * predecessor.
+ *-----------------------------------------------------------------------
+ */
static int
TargPropagateNode(ClientData gnp, ClientData junk __unused)
{
GNode *gn = (GNode *)gnp;
if (gn->type & OP_DOUBLEDEP)
Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp);
+ Lst_ForEach(gn->recpreds, TargPropagateRecpred, gnp);
+ return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateCohort --
+ * Propagate some bits in the type mask from a node to
+ * a related cohort node.
+ *
+ * Input:
+ * cnp The node that we are processing.
+ * gnp Another node that has cnp as a cohort.
+ *
+ * Results:
+ * Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ * cnp's type bitmask is modified to incorporate some of the
+ * bits from gnp's type bitmask. (XXX need a better explanation.)
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateCohort(ClientData cgnp, ClientData pgnp)
+{
+ GNode *cgn = (GNode *)cgnp;
+ GNode *pgn = (GNode *)pgnp;
+
+ cgn->type |= pgn->type & ~OP_OPMASK;
return (0);
}
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Propagate --
+ * Propagate information between related nodes. Should be called
+ * after the makefiles are parsed but before any action is taken.
+ *
+ * Results:
+ * none
+ *
+ * Side Effects:
+ * Information is propagated between related nodes throughout the
+ * graph.
+ *-----------------------------------------------------------------------
+ */
void
Targ_Propagate(void)
{
diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile
index ac1719701cc..ef05c8a7027 100644
--- a/usr.bin/make/unit-tests/Makefile
+++ b/usr.bin/make/unit-tests/Makefile
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.18 2006/02/26 21:43:01 sjg Exp $
+# $NetBSD: Makefile,v 1.19 2006/02/26 22:45:46 apb Exp $
#
# Unit tests for make(1)
# The main targets are:
@@ -21,6 +21,7 @@ UNIT_TESTS:= ${.PARSEDIR}
SUBFILES= \
comment \
cond1 \
+ dotwait \
modmatch \
modmisc \
modorder \
@@ -34,6 +35,7 @@ all: ${SUBFILES}
# the tests are actually done with sub-makes.
.PHONY: ${SUBFILES}
+.PRECIOUS: ${SUBFILES}
${SUBFILES}:
-@${.MAKE} -k -f ${UNIT_TESTS}/$@
diff --git a/usr.bin/make/unit-tests/dotwait b/usr.bin/make/unit-tests/dotwait
new file mode 100644
index 00000000000..43706afe64b
--- /dev/null
+++ b/usr.bin/make/unit-tests/dotwait
@@ -0,0 +1,61 @@
+# $NetBSD: dotwait,v 1.1 2006/02/26 22:45:46 apb Exp $
+
+THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE}
+
+TESTS= simple recursive shared cycle
+PAUSE= sleep 1
+
+# Use a .for loop rather than dependencies here, to ensure
+# that the tests are run one by one, with parallelism
+# only within tests.
+# Ignore "--- target ---" lines printed by parallel make.
+all:
+.for t in ${TESTS}
+ @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- "
+.endfor
+
+#
+# Within each test, the names of the sub-targets follow these
+# conventions:
+# * If it's expected that two or more targets may be made in parallel,
+# then the target names will differ only in an alphabetic component
+# such as ".a" or ".b".
+# * If it's expected that two or more targets should be made in sequence
+# then the target names will differ in numeric components, such that
+# lexical ordering of the target names matches the expected order
+# in which the targets should be made.
+#
+# Targets may echo ${PARALLEL_TARG} to print a modified version
+# of their own name, in which alphabetic components like ".a" or ".b"
+# are converted to ".*". Two targets that are expected to
+# be made in parallel will thus print the same strings, so that the
+# output is independent of the order in which these targets are made.
+#
+PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q}
+.DEFAULT:
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+_ECHOUSE: .USE
+ @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+
+# simple: no recursion, no cycles
+simple: simple.1 .WAIT simple.2
+
+# recursive: all children of the left hand side of the .WAIT
+# must be made before any child of the right hand side.
+recursive: recursive.1.99 .WAIT recursive.2.99
+recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE
+recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE
+
+# shared: both shared.1.99 and shared.2.99 depend on shared.0.
+# shared.0 must be made first, even though it is a child of
+# the right hand side of the .WAIT.
+shared: shared.1.99 .WAIT shared.2.99
+shared.1.99: shared.0 _ECHOUSE
+shared.2.99: shared.2.1 shared.0 _ECHOUSE
+
+# cycle: the cyclic dependency must not cause infinite recursion
+# leading to stack overflow and a crash.
+cycle: cycle.1.99 .WAIT cycle.2.99
+cycle.2.99: cycle.2.98 _ECHOUSE
+cycle.2.98: cycle.2.97 _ECHOUSE
+cycle.2.97: cycle.2.99 _ECHOUSE
diff --git a/usr.bin/make/unit-tests/test.exp b/usr.bin/make/unit-tests/test.exp
index 0b285db97ad..be5e203c326 100644
--- a/usr.bin/make/unit-tests/test.exp
+++ b/usr.bin/make/unit-tests/test.exp
@@ -20,6 +20,35 @@ Passed:
4 is not prime
5 is prime
+simple.1
+simple.1
+simple.2
+simple.2
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.99
+recursive.1.99
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.99
+recursive.2.99
+shared.0
+shared.0
+shared.1.99
+shared.1.99
+shared.2.1
+shared.2.1
+shared.2.99
+shared.2.99
+make: Graph cycles through `cycle.2.99'
+make: Graph cycles through `cycle.2.98'
+make: Graph cycles through `cycle.2.97'
+cycle.1.99
+cycle.1.99
LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"