1/*	$NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $	*/
2
3/*
4 * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Adam de Boor.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35/*
36 * Copyright (c) 1988, 1989 by Adam de Boor
37 * Copyright (c) 1989 by Berkeley Softworks
38 * All rights reserved.
39 *
40 * This code is derived from software contributed to Berkeley by
41 * Adam de Boor.
42 *
43 * Redistribution and use in source and binary forms, with or without
44 * modification, are permitted provided that the following conditions
45 * are met:
46 * 1. Redistributions of source code must retain the above copyright
47 *    notice, this list of conditions and the following disclaimer.
48 * 2. Redistributions in binary form must reproduce the above copyright
49 *    notice, this list of conditions and the following disclaimer in the
50 *    documentation and/or other materials provided with the distribution.
51 * 3. All advertising materials mentioning features or use of this software
52 *    must display the following acknowledgement:
53 *	This product includes software developed by the University of
54 *	California, Berkeley and its contributors.
55 * 4. Neither the name of the University nor the names of its contributors
56 *    may be used to endorse or promote products derived from this software
57 *    without specific prior written permission.
58 *
59 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
60 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
63 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
65 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
66 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
67 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
68 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
69 * SUCH DAMAGE.
70 */
71
72/*
73 * This file implements the full-compatibility mode of make, which makes the
74 * targets without parallelism and without a custom shell.
75 *
76 * Interface:
77 *	Compat_MakeAll	Initialize this module and make the given targets.
78 */
79
80#ifdef HAVE_CONFIG_H
81# include   "config.h"
82#endif
83#include <sys/types.h>
84#include <sys/stat.h>
85#include "wait.h"
86
87#include <errno.h>
88#include <signal.h>
89
90#include "make.h"
91#include "dir.h"
92#include "job.h"
93#include "metachar.h"
94#include "pathnames.h"
95
96/*	"@(#)compat.c	8.2 (Berkeley) 3/19/94"	*/
97MAKE_RCSID("$NetBSD: compat.c,v 1.255 2024/04/20 10:18:55 rillig Exp $");
98
99static GNode *curTarg = NULL;
100static pid_t compatChild;
101static int compatSigno;
102
103/*
104 * Delete the file of a failed, interrupted, or otherwise duffed target,
105 * unless inhibited by .PRECIOUS.
106 */
107static void
108CompatDeleteTarget(GNode *gn)
109{
110	if (gn != NULL && !GNode_IsPrecious(gn) &&
111	    (gn->type & OP_PHONY) == 0) {
112		const char *file = GNode_VarTarget(gn);
113		if (!opts.noExecute && unlink_file(file) == 0)
114			Error("*** %s removed", file);
115	}
116}
117
118/*
119 * Interrupt the creation of the current target and remove it if it ain't
120 * precious. Then exit.
121 *
122 * If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED.
123 *
124 * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've
125 * left the logic alone for now. - dholland 20160826
126 */
127static void
128CompatInterrupt(int signo)
129{
130	CompatDeleteTarget(curTarg);
131
132	if (curTarg != NULL && !GNode_IsPrecious(curTarg)) {
133		/* Run .INTERRUPT only if hit with interrupt signal. */
134		if (signo == SIGINT) {
135			GNode *gn = Targ_FindNode(".INTERRUPT");
136			if (gn != NULL)
137				Compat_Make(gn, gn);
138		}
139	}
140
141	if (signo == SIGQUIT)
142		_exit(signo);
143
144	/*
145	 * If there is a child running, pass the signal on.
146	 * We will exist after it has exited.
147	 */
148	compatSigno = signo;
149	if (compatChild > 0) {
150		KILLPG(compatChild, signo);
151	} else {
152		bmake_signal(signo, SIG_DFL);
153		kill(myPid, signo);
154	}
155}
156
157static void
158DebugFailedTarget(const char *cmd, const GNode *gn)
159{
160	const char *p = cmd;
161	debug_printf("\n*** Failed target:  %s\n*** Failed command: ",
162	    gn->name);
163
164	/*
165	 * Replace runs of whitespace with a single space, to reduce the
166	 * amount of whitespace for multi-line command lines.
167	 */
168	while (*p != '\0') {
169		if (ch_isspace(*p)) {
170			debug_printf(" ");
171			cpp_skip_whitespace(&p);
172		} else {
173			debug_printf("%c", *p);
174			p++;
175		}
176	}
177	debug_printf("\n");
178}
179
180static bool
181UseShell(const char *cmd MAKE_ATTR_UNUSED)
182{
183#if defined(FORCE_USE_SHELL) || !defined(MAKE_NATIVE)
184	/*
185	 * In a non-native build, the host environment might be weird enough
186	 * that it's necessary to go through a shell to get the correct
187	 * behaviour.  Or perhaps the shell has been replaced with something
188	 * that does extra logging, and that should not be bypassed.
189	 */
190	return true;
191#else
192	/*
193	 * Search for meta characters in the command. If there are no meta
194	 * characters, there's no need to execute a shell to execute the
195	 * command.
196	 *
197	 * Additionally variable assignments and empty commands
198	 * go to the shell. Therefore treat '=' and ':' like shell
199	 * meta characters as documented in make(1).
200	 */
201
202	return needshell(cmd);
203#endif
204}
205
206/*
207 * Execute the next command for a target. If the command returns an error,
208 * the node's made field is set to ERROR and creation stops.
209 *
210 * Input:
211 *	cmdp		Command to execute
212 *	gn		Node from which the command came
213 *	ln		List node that contains the command
214 *
215 * Results:
216 *	true if the command succeeded.
217 */
218bool
219Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
220{
221	char *cmdStart;		/* Start of expanded command */
222	char *volatile bp;
223	bool silent;		/* Don't print command */
224	bool doIt;		/* Execute even if -n */
225	volatile bool errCheck;	/* Check errors */
226	WAIT_T reason;		/* Reason for child's death */
227	WAIT_T status;		/* Description of child's death */
228	pid_t cpid;		/* Child actually found */
229	pid_t retstat;		/* Result of wait */
230	const char **volatile av; /* Argument vector for thing to exec */
231	char **volatile mav;	/* Copy of the argument vector for freeing */
232	bool useShell;		/* True if command should be executed using a
233				 * shell */
234	const char *volatile cmd = cmdp;
235
236	silent = (gn->type & OP_SILENT) != OP_NONE;
237	errCheck = !(gn->type & OP_IGNORE);
238	doIt = false;
239
240	EvalStack_Push(gn->name, NULL, NULL);
241	cmdStart = Var_Subst(cmd, gn, VARE_WANTRES);
242	EvalStack_Pop();
243	/* TODO: handle errors */
244
245	if (cmdStart[0] == '\0') {
246		free(cmdStart);
247		return true;
248	}
249	cmd = cmdStart;
250	LstNode_Set(ln, cmdStart);
251
252	if (gn->type & OP_SAVE_CMDS) {
253		GNode *endNode = Targ_GetEndNode();
254		if (gn != endNode) {
255			/*
256			 * Append the expanded command, to prevent the
257			 * local variables from being interpreted in the
258			 * scope of the .END node.
259			 *
260			 * A probably unintended side effect of this is that
261			 * the expanded command will be expanded again in the
262			 * .END node.  Therefore, a literal '$' in these
263			 * commands must be written as '$$$$' instead of the
264			 * usual '$$'.
265			 */
266			Lst_Append(&endNode->commands, cmdStart);
267			return true;
268		}
269	}
270	if (strcmp(cmdStart, "...") == 0) {
271		gn->type |= OP_SAVE_CMDS;
272		return true;
273	}
274
275	for (;;) {
276		if (*cmd == '@')
277			silent = !DEBUG(LOUD);
278		else if (*cmd == '-')
279			errCheck = false;
280		else if (*cmd == '+')
281			doIt = true;
282		else if (!ch_isspace(*cmd))
283			/* Ignore whitespace for compatibility with gnu make */
284			break;
285		cmd++;
286	}
287
288	while (ch_isspace(*cmd))
289		cmd++;
290	if (cmd[0] == '\0')
291		return true;
292
293	useShell = UseShell(cmd);
294
295	if (!silent || !GNode_ShouldExecute(gn)) {
296		printf("%s\n", cmd);
297		fflush(stdout);
298	}
299
300	if (!doIt && !GNode_ShouldExecute(gn))
301		return true;
302
303	DEBUG1(JOB, "Execute: '%s'\n", cmd);
304
305	if (useShell && shellPath == NULL)
306		Shell_Init();		/* we need shellPath */
307
308	if (useShell) {
309		static const char *shargv[5];
310
311		/* The following work for any of the builtin shell specs. */
312		int shargc = 0;
313		shargv[shargc++] = shellPath;
314		if (errCheck && shellErrFlag != NULL)
315			shargv[shargc++] = shellErrFlag;
316		shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
317		shargv[shargc++] = cmd;
318		shargv[shargc] = NULL;
319		av = shargv;
320		bp = NULL;
321		mav = NULL;
322	} else {
323		Words words = Str_Words(cmd, false);
324		mav = words.words;
325		bp = words.freeIt;
326		av = (void *)mav;
327	}
328
329#ifdef USE_META
330	if (useMeta)
331		meta_compat_start();
332#endif
333
334	Var_ReexportVars(gn);
335
336	compatChild = cpid = vfork();
337	if (cpid < 0)
338		Fatal("Could not fork");
339
340	if (cpid == 0) {
341#ifdef USE_META
342		if (useMeta)
343			meta_compat_child();
344#endif
345		(void)execvp(av[0], (char *const *)UNCONST(av));
346		execDie("exec", av[0]);
347	}
348
349	free(mav);
350	free(bp);
351
352	/* XXX: Memory management looks suspicious here. */
353	/* XXX: Setting a list item to NULL is unexpected. */
354	LstNode_SetNull(ln);
355
356#ifdef USE_META
357	if (useMeta)
358		meta_compat_parent(cpid);
359#endif
360
361	/* The child is off and running. Now all we can do is wait... */
362	while ((retstat = wait(&reason)) != cpid) {
363		if (retstat > 0)
364			JobReapChild(retstat, reason, false); /* not ours? */
365		if (retstat == -1 && errno != EINTR)
366			break;
367	}
368
369	if (retstat < 0)
370		Fatal("error in wait: %d: %s", retstat, strerror(errno));
371
372	if (WIFSTOPPED(reason)) {
373		status = WSTOPSIG(reason);
374	} else if (WIFEXITED(reason)) {
375		status = WEXITSTATUS(reason);
376#if defined(USE_META) && defined(USE_FILEMON_ONCE)
377		if (useMeta)
378			meta_cmd_finish(NULL);
379#endif
380		if (status != 0) {
381			if (DEBUG(ERROR))
382				DebugFailedTarget(cmd, gn);
383			printf("*** Error code %d", status);
384		}
385	} else {
386		status = WTERMSIG(reason);
387		printf("*** Signal %d", status);
388	}
389
390
391	if (!WIFEXITED(reason) || status != 0) {
392		if (errCheck) {
393#ifdef USE_META
394			if (useMeta)
395				meta_job_error(NULL, gn, false, status);
396#endif
397			gn->made = ERROR;
398			if (WIFEXITED(reason))
399				gn->exit_status = status;
400			if (opts.keepgoing) {
401				/*
402				 * Abort the current target,
403				 * but let others continue.
404				 */
405				printf(" (continuing)\n");
406			} else {
407				printf("\n");
408			}
409			if (deleteOnError)
410				CompatDeleteTarget(gn);
411		} else {
412			/*
413			 * Continue executing commands for this target.
414			 * If we return 0, this will happen...
415			 */
416			printf(" (ignored)\n");
417			status = 0;
418		}
419		fflush(stdout);
420	}
421
422	free(cmdStart);
423	compatChild = 0;
424	if (compatSigno != 0) {
425		bmake_signal(compatSigno, SIG_DFL);
426		kill(myPid, compatSigno);
427	}
428
429	return status == 0;
430}
431
432static void
433RunCommands(GNode *gn)
434{
435	StringListNode *ln;
436
437	for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
438		const char *cmd = ln->datum;
439		if (!Compat_RunCommand(cmd, gn, ln))
440			break;
441	}
442}
443
444static void
445MakeInRandomOrder(GNode **gnodes, GNode **end, GNode *pgn)
446{
447	GNode **it;
448	size_t r;
449
450	for (r = (size_t)(end - gnodes); r >= 2; r--) {
451		/* Biased, but irrelevant in practice. */
452		size_t i = (size_t)random() % r;
453		GNode *t = gnodes[r - 1];
454		gnodes[r - 1] = gnodes[i];
455		gnodes[i] = t;
456	}
457
458	for (it = gnodes; it != end; it++)
459		Compat_Make(*it, pgn);
460}
461
462static void
463MakeWaitGroupsInRandomOrder(GNodeList *gnodes, GNode *pgn)
464{
465	Vector vec;
466	GNodeListNode *ln;
467	GNode **nodes;
468	size_t i, n, start;
469
470	Vector_Init(&vec, sizeof(GNode *));
471	for (ln = gnodes->first; ln != NULL; ln = ln->next)
472		*(GNode **)Vector_Push(&vec) = ln->datum;
473	nodes = vec.items;
474	n = vec.len;
475
476	start = 0;
477	for (i = 0; i < n; i++) {
478		if (nodes[i]->type & OP_WAIT) {
479			MakeInRandomOrder(nodes + start, nodes + i, pgn);
480			Compat_Make(nodes[i], pgn);
481			start = i + 1;
482		}
483	}
484	MakeInRandomOrder(nodes + start, nodes + i, pgn);
485
486	Vector_Done(&vec);
487}
488
489static void
490MakeNodes(GNodeList *gnodes, GNode *pgn)
491{
492	GNodeListNode *ln;
493
494	if (Lst_IsEmpty(gnodes))
495		return;
496	if (opts.randomizeTargets) {
497		MakeWaitGroupsInRandomOrder(gnodes, pgn);
498		return;
499	}
500
501	for (ln = gnodes->first; ln != NULL; ln = ln->next) {
502		GNode *cgn = ln->datum;
503		Compat_Make(cgn, pgn);
504	}
505}
506
507static bool
508MakeUnmade(GNode *gn, GNode *pgn)
509{
510
511	assert(gn->made == UNMADE);
512
513	/*
514	 * First mark ourselves to be made, then apply whatever transformations
515	 * the suffix module thinks are necessary. Once that's done, we can
516	 * descend and make all our children. If any of them has an error
517	 * but the -k flag was given, our 'make' field will be set to false
518	 * again. This is our signal to not attempt to do anything but abort
519	 * our parent as well.
520	 */
521	gn->flags.remake = true;
522	gn->made = BEINGMADE;
523
524	if (!(gn->type & OP_MADE))
525		Suff_FindDeps(gn);
526
527	MakeNodes(&gn->children, gn);
528
529	if (!gn->flags.remake) {
530		gn->made = ABORTED;
531		pgn->flags.remake = false;
532		return false;
533	}
534
535	if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL)
536		Var_Set(pgn, IMPSRC, GNode_VarTarget(gn));
537
538	/*
539	 * All the children were made ok. Now youngestChild->mtime contains the
540	 * modification time of the newest child, we need to find out if we
541	 * exist and when we were modified last. The criteria for datedness
542	 * are defined by GNode_IsOODate.
543	 */
544	DEBUG1(MAKE, "Examining %s...", gn->name);
545	if (!GNode_IsOODate(gn)) {
546		gn->made = UPTODATE;
547		DEBUG0(MAKE, "up-to-date.\n");
548		return false;
549	}
550
551	/*
552	 * If the user is just seeing if something is out-of-date, exit now
553	 * to tell him/her "yes".
554	 */
555	DEBUG0(MAKE, "out-of-date.\n");
556	if (opts.query && gn != Targ_GetEndNode())
557		exit(1);
558
559	/*
560	 * We need to be re-made.
561	 * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set.
562	 */
563	GNode_SetLocalVars(gn);
564
565	/*
566	 * Alter our type to tell if errors should be ignored or things
567	 * should not be printed so Compat_RunCommand knows what to do.
568	 */
569	if (opts.ignoreErrors)
570		gn->type |= OP_IGNORE;
571	if (opts.silent)
572		gn->type |= OP_SILENT;
573
574	if (Job_CheckCommands(gn, Fatal)) {
575		if (!opts.touch || (gn->type & OP_MAKE)) {
576			curTarg = gn;
577#ifdef USE_META
578			if (useMeta && GNode_ShouldExecute(gn))
579				meta_job_start(NULL, gn);
580#endif
581			RunCommands(gn);
582			curTarg = NULL;
583		} else {
584			Job_Touch(gn, (gn->type & OP_SILENT) != OP_NONE);
585		}
586	} else {
587		gn->made = ERROR;
588	}
589#ifdef USE_META
590	if (useMeta && GNode_ShouldExecute(gn)) {
591		if (meta_job_finish(NULL) != 0)
592			gn->made = ERROR;
593	}
594#endif
595
596	if (gn->made != ERROR) {
597		/*
598		 * If the node was made successfully, mark it so, update
599		 * its modification time and timestamp all its parents.
600		 * This is to keep its state from affecting that of its parent.
601		 */
602		gn->made = MADE;
603		if (Make_Recheck(gn) == 0)
604			pgn->flags.force = true;
605		if (!(gn->type & OP_EXEC)) {
606			pgn->flags.childMade = true;
607			GNode_UpdateYoungestChild(pgn, gn);
608		}
609	} else if (opts.keepgoing) {
610		pgn->flags.remake = false;
611	} else {
612		PrintOnError(gn, "\nStop.\n");
613		exit(1);
614	}
615	return true;
616}
617
618static void
619MakeOther(GNode *gn, GNode *pgn)
620{
621
622	if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) {
623		const char *target = GNode_VarTarget(gn);
624		Var_Set(pgn, IMPSRC, target != NULL ? target : "");
625	}
626
627	switch (gn->made) {
628	case BEINGMADE:
629		Error("Graph cycles through %s", gn->name);
630		gn->made = ERROR;
631		pgn->flags.remake = false;
632		break;
633	case MADE:
634		if (!(gn->type & OP_EXEC)) {
635			pgn->flags.childMade = true;
636			GNode_UpdateYoungestChild(pgn, gn);
637		}
638		break;
639	case UPTODATE:
640		if (!(gn->type & OP_EXEC))
641			GNode_UpdateYoungestChild(pgn, gn);
642		break;
643	default:
644		break;
645	}
646}
647
648/*
649 * Make a target.
650 *
651 * If an error is detected and not being ignored, the process exits.
652 *
653 * Input:
654 *	gn		The node to make
655 *	pgn		Parent to abort if necessary
656 *
657 * Output:
658 *	gn->made
659 *		UPTODATE	gn was already up-to-date.
660 *		MADE		gn was recreated successfully.
661 *		ERROR		An error occurred while gn was being created,
662 *				either due to missing commands or in -k mode.
663 *		ABORTED		gn was not remade because one of its
664 *				dependencies could not be made due to errors.
665 */
666void
667Compat_Make(GNode *gn, GNode *pgn)
668{
669	if (shellName == NULL)	/* we came here from jobs */
670		Shell_Init();
671
672	if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
673		if (!MakeUnmade(gn, pgn))
674			goto cohorts;
675
676		/* XXX: Replace with GNode_IsError(gn) */
677	} else if (gn->made == ERROR) {
678		/*
679		 * Already had an error when making this.
680		 * Tell the parent to abort.
681		 */
682		pgn->flags.remake = false;
683	} else {
684		MakeOther(gn, pgn);
685	}
686
687cohorts:
688	MakeNodes(&gn->cohorts, pgn);
689}
690
691static void
692MakeBeginNode(void)
693{
694	GNode *gn = Targ_FindNode(".BEGIN");
695	if (gn == NULL)
696		return;
697
698	Compat_Make(gn, gn);
699	if (GNode_IsError(gn)) {
700		PrintOnError(gn, "\nStop.\n");
701		exit(1);
702	}
703}
704
705static void
706InitSignals(void)
707{
708	if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN)
709		bmake_signal(SIGINT, CompatInterrupt);
710	if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN)
711		bmake_signal(SIGTERM, CompatInterrupt);
712	if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN)
713		bmake_signal(SIGHUP, CompatInterrupt);
714	if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN)
715		bmake_signal(SIGQUIT, CompatInterrupt);
716}
717
718void
719Compat_MakeAll(GNodeList *targs)
720{
721	GNode *errorNode = NULL;
722
723	if (shellName == NULL)
724		Shell_Init();
725
726	InitSignals();
727
728	/*
729	 * Create the .END node now, to keep the (debug) output of the
730	 * counter.mk test the same as before 2020-09-23.  This
731	 * implementation detail probably doesn't matter though.
732	 */
733	(void)Targ_GetEndNode();
734
735	if (!opts.query)
736		MakeBeginNode();
737
738	/*
739	 * Expand .USE nodes right now, because they can modify the structure
740	 * of the tree.
741	 */
742	Make_ExpandUse(targs);
743
744	while (!Lst_IsEmpty(targs)) {
745		GNode *gn = Lst_Dequeue(targs);
746		Compat_Make(gn, gn);
747
748		if (gn->made == UPTODATE) {
749			printf("`%s' is up to date.\n", gn->name);
750		} else if (gn->made == ABORTED) {
751			printf("`%s' not remade because of errors.\n",
752			    gn->name);
753		}
754		if (GNode_IsError(gn) && errorNode == NULL)
755			errorNode = gn;
756	}
757
758	if (errorNode == NULL) {
759		GNode *endNode = Targ_GetEndNode();
760		Compat_Make(endNode, endNode);
761		if (GNode_IsError(endNode))
762			errorNode = endNode;
763	}
764
765	if (errorNode != NULL) {
766		if (DEBUG(GRAPH2))
767			Targ_PrintGraph(2);
768		else if (DEBUG(GRAPH3))
769			Targ_PrintGraph(3);
770		PrintOnError(errorNode, "\nStop.\n");
771		exit(1);
772	}
773}
774