compat.c revision 292068
1/*	$NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg 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#ifndef MAKE_NATIVE
73static char rcsid[] = "$NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg Exp $";
74#else
75#include <sys/cdefs.h>
76#ifndef lint
77#if 0
78static char sccsid[] = "@(#)compat.c	8.2 (Berkeley) 3/19/94";
79#else
80__RCSID("$NetBSD: compat.c,v 1.101 2015/10/11 04:51:24 sjg Exp $");
81#endif
82#endif /* not lint */
83#endif
84
85/*-
86 * compat.c --
87 *	The routines in this file implement the full-compatibility
88 *	mode of PMake. Most of the special functionality of PMake
89 *	is available in this mode. Things not supported:
90 *	    - different shells.
91 *	    - friendly variable substitution.
92 *
93 * Interface:
94 *	Compat_Run	    Initialize things for this module and recreate
95 *	    	  	    thems as need creatin'
96 */
97
98#ifdef HAVE_CONFIG_H
99# include   "config.h"
100#endif
101#include    <sys/types.h>
102#include    <sys/stat.h>
103#include    "wait.h"
104
105#include    <ctype.h>
106#include    <errno.h>
107#include    <signal.h>
108#include    <stdio.h>
109
110#include    "make.h"
111#include    "hash.h"
112#include    "dir.h"
113#include    "job.h"
114#include    "metachar.h"
115#include    "pathnames.h"
116
117
118static GNode	    *curTarg = NULL;
119static GNode	    *ENDNode;
120static void CompatInterrupt(int);
121
122/*-
123 *-----------------------------------------------------------------------
124 * CompatInterrupt --
125 *	Interrupt the creation of the current target and remove it if
126 *	it ain't precious.
127 *
128 * Results:
129 *	None.
130 *
131 * Side Effects:
132 *	The target is removed and the process exits. If .INTERRUPT exists,
133 *	its commands are run first WITH INTERRUPTS IGNORED..
134 *
135 *-----------------------------------------------------------------------
136 */
137static void
138CompatInterrupt(int signo)
139{
140    GNode   *gn;
141
142    if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
143	char	  *p1;
144	char 	  *file = Var_Value(TARGET, curTarg, &p1);
145
146	if (!noExecute && eunlink(file) != -1) {
147	    Error("*** %s removed", file);
148	}
149	if (p1)
150	    free(p1);
151
152	/*
153	 * Run .INTERRUPT only if hit with interrupt signal
154	 */
155	if (signo == SIGINT) {
156	    gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
157	    if (gn != NULL) {
158		Compat_Make(gn, gn);
159	    }
160	}
161
162    }
163    if (signo == SIGQUIT)
164	_exit(signo);
165    bmake_signal(signo, SIG_DFL);
166    kill(myPid, signo);
167}
168
169/*-
170 *-----------------------------------------------------------------------
171 * CompatRunCommand --
172 *	Execute the next command for a target. If the command returns an
173 *	error, the node's made field is set to ERROR and creation stops.
174 *
175 * Input:
176 *	cmdp		Command to execute
177 *	gnp		Node from which the command came
178 *
179 * Results:
180 *	0 if the command succeeded, 1 if an error occurred.
181 *
182 * Side Effects:
183 *	The node's 'made' field may be set to ERROR.
184 *
185 *-----------------------------------------------------------------------
186 */
187int
188CompatRunCommand(void *cmdp, void *gnp)
189{
190    char    	  *cmdStart;	/* Start of expanded command */
191    char 	  *cp, *bp;
192    Boolean 	  silent,   	/* Don't print command */
193	    	  doIt;		/* Execute even if -n */
194    volatile Boolean errCheck; 	/* Check errors */
195    WAIT_T 	  reason;   	/* Reason for child's death */
196    int	    	  status;   	/* Description of child's death */
197    pid_t	  cpid;	    	/* Child actually found */
198    pid_t	  retstat;    	/* Result of wait */
199    LstNode 	  cmdNode;  	/* Node where current command is located */
200    const char  ** volatile av;	/* Argument vector for thing to exec */
201    char	** volatile mav;/* Copy of the argument vector for freeing */
202    int	    	  argc;	    	/* Number of arguments in av or 0 if not
203				 * dynamically allocated */
204    Boolean 	  local;    	/* TRUE if command should be executed
205				 * locally */
206    Boolean 	  useShell;    	/* TRUE if command should be executed
207				 * using a shell */
208    char	  * volatile cmd = (char *)cmdp;
209    GNode	  *gn = (GNode *)gnp;
210
211    silent = gn->type & OP_SILENT;
212    errCheck = !(gn->type & OP_IGNORE);
213    doIt = FALSE;
214
215    cmdNode = Lst_Member(gn->commands, cmd);
216    cmdStart = Var_Subst(NULL, cmd, gn, FALSE, TRUE);
217
218    /*
219     * brk_string will return an argv with a NULL in av[0], thus causing
220     * execvp to choke and die horribly. Besides, how can we execute a null
221     * command? In any case, we warn the user that the command expanded to
222     * nothing (is this the right thing to do?).
223     */
224
225    if (*cmdStart == '\0') {
226	free(cmdStart);
227	return(0);
228    }
229    cmd = cmdStart;
230    Lst_Replace(cmdNode, cmdStart);
231
232    if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
233	(void)Lst_AtEnd(ENDNode->commands, cmdStart);
234	return(0);
235    }
236    if (strcmp(cmdStart, "...") == 0) {
237	gn->type |= OP_SAVE_CMDS;
238	return(0);
239    }
240
241    while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
242	switch (*cmd) {
243	case '@':
244	    silent = DEBUG(LOUD) ? FALSE : TRUE;
245	    break;
246	case '-':
247	    errCheck = FALSE;
248	    break;
249	case '+':
250	    doIt = TRUE;
251	    if (!shellName)		/* we came here from jobs */
252		Shell_Init();
253	    break;
254	}
255	cmd++;
256    }
257
258    while (isspace((unsigned char)*cmd))
259	cmd++;
260
261    /*
262     * If we did not end up with a command, just skip it.
263     */
264    if (!*cmd)
265	return (0);
266
267#if !defined(MAKE_NATIVE)
268    /*
269     * In a non-native build, the host environment might be weird enough
270     * that it's necessary to go through a shell to get the correct
271     * behaviour.  Or perhaps the shell has been replaced with something
272     * that does extra logging, and that should not be bypassed.
273     */
274    useShell = TRUE;
275#else
276    /*
277     * Search for meta characters in the command. If there are no meta
278     * characters, there's no need to execute a shell to execute the
279     * command.
280     *
281     * Additionally variable assignments and empty commands
282     * go to the shell. Therefore treat '=' and ':' like shell
283     * meta characters as documented in make(1).
284     */
285
286    useShell = needshell(cmd, FALSE);
287#endif
288
289    /*
290     * Print the command before echoing if we're not supposed to be quiet for
291     * this one. We also print the command if -n given.
292     */
293    if (!silent || NoExecute(gn)) {
294	printf("%s\n", cmd);
295	fflush(stdout);
296    }
297
298    /*
299     * If we're not supposed to execute any commands, this is as far as
300     * we go...
301     */
302    if (!doIt && NoExecute(gn)) {
303	return (0);
304    }
305    if (DEBUG(JOB))
306	fprintf(debug_file, "Execute: '%s'\n", cmd);
307
308again:
309    if (useShell) {
310	/*
311	 * We need to pass the command off to the shell, typically
312	 * because the command contains a "meta" character.
313	 */
314	static const char *shargv[5];
315	int shargc;
316
317	shargc = 0;
318	shargv[shargc++] = shellPath;
319	/*
320	 * The following work for any of the builtin shell specs.
321	 */
322	if (errCheck && shellErrFlag) {
323	    shargv[shargc++] = shellErrFlag;
324	}
325	if (DEBUG(SHELL))
326		shargv[shargc++] = "-xc";
327	else
328		shargv[shargc++] = "-c";
329	shargv[shargc++] = cmd;
330	shargv[shargc++] = NULL;
331	av = shargv;
332	argc = 0;
333	bp = NULL;
334	mav = NULL;
335    } else {
336	/*
337	 * No meta-characters, so no need to exec a shell. Break the command
338	 * into words to form an argument vector we can execute.
339	 */
340	mav = brk_string(cmd, &argc, TRUE, &bp);
341	if (mav == NULL) {
342		useShell = 1;
343		goto again;
344	}
345	av = (void *)mav;
346    }
347
348    local = TRUE;
349
350#ifdef USE_META
351    if (useMeta) {
352	meta_compat_start();
353    }
354#endif
355
356    /*
357     * Fork and execute the single command. If the fork fails, we abort.
358     */
359    cpid = vFork();
360    if (cpid < 0) {
361	Fatal("Could not fork");
362    }
363    if (cpid == 0) {
364	Var_ExportVars();
365#ifdef USE_META
366	if (useMeta) {
367	    meta_compat_child();
368	}
369#endif
370	if (local)
371	    (void)execvp(av[0], (char *const *)UNCONST(av));
372	else
373	    (void)execv(av[0], (char *const *)UNCONST(av));
374	execError("exec", av[0]);
375	_exit(1);
376    }
377    if (mav)
378	free(mav);
379    if (bp)
380	free(bp);
381    Lst_Replace(cmdNode, NULL);
382
383#ifdef USE_META
384    if (useMeta) {
385	meta_compat_parent();
386    }
387#endif
388
389    /*
390     * The child is off and running. Now all we can do is wait...
391     */
392    while (1) {
393
394	while ((retstat = wait(&reason)) != cpid) {
395	    if (retstat > 0)
396		JobReapChild(retstat, reason, FALSE); /* not ours? */
397	    if (retstat == -1 && errno != EINTR) {
398		break;
399	    }
400	}
401
402	if (retstat > -1) {
403	    if (WIFSTOPPED(reason)) {
404		status = WSTOPSIG(reason);		/* stopped */
405	    } else if (WIFEXITED(reason)) {
406		status = WEXITSTATUS(reason);		/* exited */
407#if defined(USE_META) && defined(USE_FILEMON_ONCE)
408		if (useMeta) {
409		    meta_cmd_finish(NULL);
410		}
411#endif
412		if (status != 0) {
413		    if (DEBUG(ERROR)) {
414		        fprintf(debug_file, "\n*** Failed target:  %s\n*** Failed command: ",
415			    gn->name);
416		        for (cp = cmd; *cp; ) {
417    			    if (isspace((unsigned char)*cp)) {
418				fprintf(debug_file, " ");
419			        while (isspace((unsigned char)*cp))
420				    cp++;
421			    } else {
422				fprintf(debug_file, "%c", *cp);
423			        cp++;
424			    }
425		        }
426			fprintf(debug_file, "\n");
427		    }
428		    printf("*** Error code %d", status);
429		}
430	    } else {
431		status = WTERMSIG(reason);		/* signaled */
432		printf("*** Signal %d", status);
433	    }
434
435
436	    if (!WIFEXITED(reason) || (status != 0)) {
437		if (errCheck) {
438#ifdef USE_META
439		    if (useMeta) {
440			meta_job_error(NULL, gn, 0, status);
441		    }
442#endif
443		    gn->made = ERROR;
444		    if (keepgoing) {
445			/*
446			 * Abort the current target, but let others
447			 * continue.
448			 */
449			printf(" (continuing)\n");
450		    }
451		} else {
452		    /*
453		     * Continue executing commands for this target.
454		     * If we return 0, this will happen...
455		     */
456		    printf(" (ignored)\n");
457		    status = 0;
458		}
459	    }
460	    break;
461	} else {
462	    Fatal("error in wait: %d: %s", retstat, strerror(errno));
463	    /*NOTREACHED*/
464	}
465    }
466    free(cmdStart);
467
468    return (status);
469}
470
471/*-
472 *-----------------------------------------------------------------------
473 * Compat_Make --
474 *	Make a target.
475 *
476 * Input:
477 *	gnp		The node to make
478 *	pgnp		Parent to abort if necessary
479 *
480 * Results:
481 *	0
482 *
483 * Side Effects:
484 *	If an error is detected and not being ignored, the process exits.
485 *
486 *-----------------------------------------------------------------------
487 */
488int
489Compat_Make(void *gnp, void *pgnp)
490{
491    GNode *gn = (GNode *)gnp;
492    GNode *pgn = (GNode *)pgnp;
493
494    if (!shellName)		/* we came here from jobs */
495	Shell_Init();
496    if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
497	/*
498	 * First mark ourselves to be made, then apply whatever transformations
499	 * the suffix module thinks are necessary. Once that's done, we can
500	 * descend and make all our children. If any of them has an error
501	 * but the -k flag was given, our 'make' field will be set FALSE again.
502	 * This is our signal to not attempt to do anything but abort our
503	 * parent as well.
504	 */
505	gn->flags |= REMAKE;
506	gn->made = BEINGMADE;
507	if ((gn->type & OP_MADE) == 0)
508	    Suff_FindDeps(gn);
509	Lst_ForEach(gn->children, Compat_Make, gn);
510	if ((gn->flags & REMAKE) == 0) {
511	    gn->made = ABORTED;
512	    pgn->flags &= ~REMAKE;
513	    goto cohorts;
514	}
515
516	if (Lst_Member(gn->iParents, pgn) != NULL) {
517	    char *p1;
518	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
519	    if (p1)
520		free(p1);
521	}
522
523	/*
524	 * All the children were made ok. Now cmgn->mtime contains the
525	 * modification time of the newest child, we need to find out if we
526	 * exist and when we were modified last. The criteria for datedness
527	 * are defined by the Make_OODate function.
528	 */
529	if (DEBUG(MAKE)) {
530	    fprintf(debug_file, "Examining %s...", gn->name);
531	}
532	if (! Make_OODate(gn)) {
533	    gn->made = UPTODATE;
534	    if (DEBUG(MAKE)) {
535		fprintf(debug_file, "up-to-date.\n");
536	    }
537	    goto cohorts;
538	} else if (DEBUG(MAKE)) {
539	    fprintf(debug_file, "out-of-date.\n");
540	}
541
542	/*
543	 * If the user is just seeing if something is out-of-date, exit now
544	 * to tell him/her "yes".
545	 */
546	if (queryFlag) {
547	    exit(1);
548	}
549
550	/*
551	 * We need to be re-made. We also have to make sure we've got a $?
552	 * variable. To be nice, we also define the $> variable using
553	 * Make_DoAllVar().
554	 */
555	Make_DoAllVar(gn);
556
557	/*
558	 * Alter our type to tell if errors should be ignored or things
559	 * should not be printed so CompatRunCommand knows what to do.
560	 */
561	if (Targ_Ignore(gn)) {
562	    gn->type |= OP_IGNORE;
563	}
564	if (Targ_Silent(gn)) {
565	    gn->type |= OP_SILENT;
566	}
567
568	if (Job_CheckCommands(gn, Fatal)) {
569	    /*
570	     * Our commands are ok, but we still have to worry about the -t
571	     * flag...
572	     */
573	    if (!touchFlag || (gn->type & OP_MAKE)) {
574		curTarg = gn;
575#ifdef USE_META
576		if (useMeta && !NoExecute(gn)) {
577		    meta_job_start(NULL, gn);
578		}
579#endif
580		Lst_ForEach(gn->commands, CompatRunCommand, gn);
581		curTarg = NULL;
582	    } else {
583		Job_Touch(gn, gn->type & OP_SILENT);
584	    }
585	} else {
586	    gn->made = ERROR;
587	}
588#ifdef USE_META
589	if (useMeta && !NoExecute(gn)) {
590	    meta_job_finish(NULL);
591	}
592#endif
593
594	if (gn->made != ERROR) {
595	    /*
596	     * If the node was made successfully, mark it so, update
597	     * its modification time and timestamp all its parents. Note
598	     * that for .ZEROTIME targets, the timestamping isn't done.
599	     * This is to keep its state from affecting that of its parent.
600	     */
601	    gn->made = MADE;
602	    pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
603	    if (!(gn->type & OP_EXEC)) {
604		pgn->flags |= CHILDMADE;
605		Make_TimeStamp(pgn, gn);
606	    }
607	} else if (keepgoing) {
608	    pgn->flags &= ~REMAKE;
609	} else {
610	    PrintOnError(gn, "\n\nStop.");
611	    exit(1);
612	}
613    } else if (gn->made == ERROR) {
614	/*
615	 * Already had an error when making this beastie. Tell the parent
616	 * to abort.
617	 */
618	pgn->flags &= ~REMAKE;
619    } else {
620	if (Lst_Member(gn->iParents, pgn) != NULL) {
621	    char *p1;
622	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
623	    if (p1)
624		free(p1);
625	}
626	switch(gn->made) {
627	    case BEINGMADE:
628		Error("Graph cycles through %s", gn->name);
629		gn->made = ERROR;
630		pgn->flags &= ~REMAKE;
631		break;
632	    case MADE:
633		if ((gn->type & OP_EXEC) == 0) {
634		    pgn->flags |= CHILDMADE;
635		    Make_TimeStamp(pgn, gn);
636		}
637		break;
638	    case UPTODATE:
639		if ((gn->type & OP_EXEC) == 0) {
640		    Make_TimeStamp(pgn, gn);
641		}
642		break;
643	    default:
644		break;
645	}
646    }
647
648cohorts:
649    Lst_ForEach(gn->cohorts, Compat_Make, pgnp);
650    return (0);
651}
652
653/*-
654 *-----------------------------------------------------------------------
655 * Compat_Run --
656 *	Initialize this mode and start making.
657 *
658 * Input:
659 *	targs		List of target nodes to re-create
660 *
661 * Results:
662 *	None.
663 *
664 * Side Effects:
665 *	Guess what?
666 *
667 *-----------------------------------------------------------------------
668 */
669void
670Compat_Run(Lst targs)
671{
672    GNode   	  *gn = NULL;/* Current root target */
673    int	    	  errors;   /* Number of targets not remade due to errors */
674
675    if (!shellName)
676	Shell_Init();
677
678    if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) {
679	bmake_signal(SIGINT, CompatInterrupt);
680    }
681    if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) {
682	bmake_signal(SIGTERM, CompatInterrupt);
683    }
684    if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) {
685	bmake_signal(SIGHUP, CompatInterrupt);
686    }
687    if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
688	bmake_signal(SIGQUIT, CompatInterrupt);
689    }
690
691    ENDNode = Targ_FindNode(".END", TARG_CREATE);
692    ENDNode->type = OP_SPECIAL;
693    /*
694     * If the user has defined a .BEGIN target, execute the commands attached
695     * to it.
696     */
697    if (!queryFlag) {
698	gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
699	if (gn != NULL) {
700	    Compat_Make(gn, gn);
701            if (gn->made == ERROR) {
702                PrintOnError(gn, "\n\nStop.");
703                exit(1);
704            }
705	}
706    }
707
708    /*
709     * Expand .USE nodes right now, because they can modify the structure
710     * of the tree.
711     */
712    Make_ExpandUse(targs);
713
714    /*
715     * For each entry in the list of targets to create, call Compat_Make on
716     * it to create the thing. Compat_Make will leave the 'made' field of gn
717     * in one of several states:
718     *	    UPTODATE	    gn was already up-to-date
719     *	    MADE  	    gn was recreated successfully
720     *	    ERROR 	    An error occurred while gn was being created
721     *	    ABORTED	    gn was not remade because one of its inferiors
722     *	    	  	    could not be made due to errors.
723     */
724    errors = 0;
725    while (!Lst_IsEmpty (targs)) {
726	gn = (GNode *)Lst_DeQueue(targs);
727	Compat_Make(gn, gn);
728
729	if (gn->made == UPTODATE) {
730	    printf("`%s' is up to date.\n", gn->name);
731	} else if (gn->made == ABORTED) {
732	    printf("`%s' not remade because of errors.\n", gn->name);
733	    errors += 1;
734	}
735    }
736
737    /*
738     * If the user has defined a .END target, run its commands.
739     */
740    if (errors == 0) {
741	Compat_Make(ENDNode, ENDNode);
742	if (gn->made == ERROR) {
743	    PrintOnError(gn, "\n\nStop.");
744	    exit(1);
745	}
746    }
747}
748