Annotation of src/usr.bin/make/compat.c, Revision 1.10
1.10 ! deraadt 1: /* $OpenBSD: compat.c,v 1.9 1998/01/02 05:56:33 deraadt Exp $ */
1.8 millert 2: /* $NetBSD: compat.c,v 1.14 1996/11/06 17:59:01 christos Exp $ */
1.1 deraadt 3:
4: /*
5: * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
6: * Copyright (c) 1988, 1989 by Adam de Boor
7: * Copyright (c) 1989 by Berkeley Softworks
8: * All rights reserved.
9: *
10: * This code is derived from software contributed to Berkeley by
11: * Adam de Boor.
12: *
13: * Redistribution and use in source and binary forms, with or without
14: * modification, are permitted provided that the following conditions
15: * are met:
16: * 1. Redistributions of source code must retain the above copyright
17: * notice, this list of conditions and the following disclaimer.
18: * 2. Redistributions in binary form must reproduce the above copyright
19: * notice, this list of conditions and the following disclaimer in the
20: * documentation and/or other materials provided with the distribution.
21: * 3. All advertising materials mentioning features or use of this software
22: * must display the following acknowledgement:
23: * This product includes software developed by the University of
24: * California, Berkeley and its contributors.
25: * 4. Neither the name of the University nor the names of its contributors
26: * may be used to endorse or promote products derived from this software
27: * without specific prior written permission.
28: *
29: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39: * SUCH DAMAGE.
40: */
41:
42: #ifndef lint
43: #if 0
1.4 millert 44: static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94";
1.1 deraadt 45: #else
1.10 ! deraadt 46: static char rcsid[] = "$OpenBSD: compat.c,v 1.9 1998/01/02 05:56:33 deraadt Exp $";
1.1 deraadt 47: #endif
48: #endif /* not lint */
49:
50: /*-
51: * compat.c --
52: * The routines in this file implement the full-compatibility
53: * mode of PMake. Most of the special functionality of PMake
54: * is available in this mode. Things not supported:
55: * - different shells.
56: * - friendly variable substitution.
57: *
58: * Interface:
59: * Compat_Run Initialize things for this module and recreate
60: * thems as need creatin'
61: */
62:
63: #include <stdio.h>
64: #include <sys/types.h>
65: #include <sys/stat.h>
66: #include <sys/wait.h>
67: #include <ctype.h>
68: #include <errno.h>
69: #include <signal.h>
70: #include "make.h"
71: #include "hash.h"
72: #include "dir.h"
73: #include "job.h"
74: extern int errno;
75:
76: /*
77: * The following array is used to make a fast determination of which
78: * characters are interpreted specially by the shell. If a command
79: * contains any of these characters, it is executed by the shell, not
80: * directly by us.
81: */
82:
83: static char meta[256];
84:
85: static GNode *curTarg = NILGNODE;
86: static GNode *ENDNode;
87: static void CompatInterrupt __P((int));
88: static int CompatRunCommand __P((ClientData, ClientData));
89: static int CompatMake __P((ClientData, ClientData));
90:
91: /*-
92: *-----------------------------------------------------------------------
93: * CompatInterrupt --
94: * Interrupt the creation of the current target and remove it if
95: * it ain't precious.
96: *
97: * Results:
98: * None.
99: *
100: * Side Effects:
101: * The target is removed and the process exits. If .INTERRUPT exists,
102: * its commands are run first WITH INTERRUPTS IGNORED..
103: *
104: *-----------------------------------------------------------------------
105: */
106: static void
107: CompatInterrupt (signo)
108: int signo;
109: {
110: GNode *gn;
1.4 millert 111:
1.1 deraadt 112: if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) {
113: char *p1;
114: char *file = Var_Value (TARGET, curTarg, &p1);
115:
1.2 deraadt 116: if (!noExecute && eunlink(file) != -1) {
1.1 deraadt 117: printf ("*** %s removed\n", file);
118: }
119: if (p1)
120: free(p1);
121:
122: /*
123: * Run .INTERRUPT only if hit with interrupt signal
124: */
125: if (signo == SIGINT) {
126: gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
127: if (gn != NILGNODE) {
128: Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
129: }
130: }
131:
132: }
133: exit (signo);
134: }
135:
136: /*-
137: *-----------------------------------------------------------------------
1.10 ! deraadt 138: * shellneed --
! 139: *
! 140: * Results:
! 141: * Returns 1 if a specified line must be executed by the shell,
! 142: * 0 if it can be run via execve, and -1 if the command is a no-op.
! 143: *
! 144: * Side Effects:
! 145: * None.
! 146: *
! 147: *-----------------------------------------------------------------------
! 148: */
! 149: static int
! 150: shellneed (cmd)
! 151: char *cmd;
! 152: {
! 153: char **av;
! 154: int ac;
! 155:
! 156: av = brk_string(cmd, &ac, TRUE);
! 157: if (strcmp(av[1], "exit") == 0)
! 158: return (1);
! 159: if (strcmp(av[1], "umask") == 0) {
! 160: long umi;
! 161: char *ep = NULL;
! 162: mode_t um;
! 163:
! 164: if (av[2] != NULL) {
! 165: umi = strtol(av[2], &ep, 8);
! 166: if (ep == NULL)
! 167: return (1);
! 168: um = umi;
! 169: (void) umask(um);
! 170: return (-1);
! 171: }
! 172: um = umask(0);
! 173: (void) umask(um);
! 174: printf("%o\n", um);
! 175: return (-1);
! 176: }
! 177:
! 178: return (0);
! 179: }
! 180:
! 181: /*-
! 182: *-----------------------------------------------------------------------
1.1 deraadt 183: * CompatRunCommand --
184: * Execute the next command for a target. If the command returns an
185: * error, the node's made field is set to ERROR and creation stops.
186: *
187: * Results:
188: * 0 if the command succeeded, 1 if an error occurred.
189: *
190: * Side Effects:
191: * The node's 'made' field may be set to ERROR.
192: *
193: *-----------------------------------------------------------------------
194: */
195: static int
196: CompatRunCommand (cmdp, gnp)
197: ClientData cmdp; /* Command to execute */
198: ClientData gnp; /* Node from which the command came */
199: {
200: char *cmdStart; /* Start of expanded command */
201: register char *cp;
202: Boolean silent, /* Don't print command */
203: errCheck; /* Check errors */
1.2 deraadt 204: int reason; /* Reason for child's death */
1.1 deraadt 205: int status; /* Description of child's death */
206: int cpid; /* Child actually found */
207: ReturnStatus stat; /* Status of fork */
208: LstNode cmdNode; /* Node where current command is located */
209: char **av; /* Argument vector for thing to exec */
210: int argc; /* Number of arguments in av or 0 if not
211: * dynamically allocated */
212: Boolean local; /* TRUE if command should be executed
213: * locally */
1.10 ! deraadt 214: int internal; /* Various values.. */
1.1 deraadt 215: char *cmd = (char *) cmdp;
216: GNode *gn = (GNode *) gnp;
217:
1.4 millert 218: /*
1.1 deraadt 219: * Avoid clobbered variable warnings by forcing the compiler
220: * to ``unregister'' variables
221: */
222: #if __GNUC__
223: (void) &av;
224: (void) &errCheck;
225: #endif
226: silent = gn->type & OP_SILENT;
227: errCheck = !(gn->type & OP_IGNORE);
228:
229: cmdNode = Lst_Member (gn->commands, (ClientData)cmd);
230: cmdStart = Var_Subst (NULL, cmd, gn, FALSE);
231:
232: /*
233: * brk_string will return an argv with a NULL in av[1], thus causing
234: * execvp to choke and die horribly. Besides, how can we execute a null
235: * command? In any case, we warn the user that the command expanded to
236: * nothing (is this the right thing to do?).
237: */
1.4 millert 238:
1.1 deraadt 239: if (*cmdStart == '\0') {
240: free(cmdStart);
241: Error("%s expands to empty string", cmd);
242: return(0);
243: } else {
244: cmd = cmdStart;
245: }
246: Lst_Replace (cmdNode, (ClientData)cmdStart);
247:
248: if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
249: (void)Lst_AtEnd(ENDNode->commands, (ClientData)cmdStart);
250: return(0);
251: } else if (strcmp(cmdStart, "...") == 0) {
252: gn->type |= OP_SAVE_CMDS;
253: return(0);
254: }
255:
256: while ((*cmd == '@') || (*cmd == '-')) {
257: if (*cmd == '@') {
258: silent = TRUE;
259: } else {
260: errCheck = FALSE;
261: }
262: cmd++;
263: }
264:
265: while (isspace((unsigned char)*cmd))
266: cmd++;
1.4 millert 267:
1.1 deraadt 268: /*
269: * Search for meta characters in the command. If there are no meta
270: * characters, there's no need to execute a shell to execute the
271: * command.
272: */
273: for (cp = cmd; !meta[(unsigned char)*cp]; cp++) {
274: continue;
275: }
276:
277: /*
278: * Print the command before echoing if we're not supposed to be quiet for
279: * this one. We also print the command if -n given.
280: */
281: if (!silent || noExecute) {
282: printf ("%s\n", cmd);
283: fflush(stdout);
284: }
285:
286: /*
287: * If we're not supposed to execute any commands, this is as far as
288: * we go...
289: */
290: if (noExecute) {
291: return (0);
292: }
1.4 millert 293:
1.1 deraadt 294: if (*cp != '\0') {
295: /*
296: * If *cp isn't the null character, we hit a "meta" character and
297: * need to pass the command off to the shell. We give the shell the
298: * -e flag as well as -c if it's supposed to exit when it hits an
299: * error.
300: */
301: static char *shargv[4] = { "/bin/sh" };
1.10 ! deraadt 302:
! 303: shargv[1] = (errCheck ? "-ec" : "-c");
! 304: shargv[2] = cmd;
! 305: shargv[3] = (char *)NULL;
! 306: av = shargv;
! 307: argc = 0;
! 308: } else if ((internal = shellneed(cmd))) {
! 309: /*
! 310: * This command must be passed by the shell for other reasons..
! 311: * or.. possibly not at all.
! 312: */
! 313: static char *shargv[4] = { "/bin/sh" };
! 314:
! 315: if (internal == -1) {
! 316: /* Command does not need to be executed */
! 317: return (0);
! 318: }
1.1 deraadt 319:
320: shargv[1] = (errCheck ? "-ec" : "-c");
321: shargv[2] = cmd;
322: shargv[3] = (char *)NULL;
323: av = shargv;
324: argc = 0;
325: } else {
326: /*
327: * No meta-characters, so no need to exec a shell. Break the command
328: * into words to form an argument vector we can execute.
329: * brk_string sticks our name in av[0], so we have to
330: * skip over it...
331: */
332: av = brk_string(cmd, &argc, TRUE);
333: av += 1;
334: }
1.4 millert 335:
1.1 deraadt 336: local = TRUE;
337:
338: /*
339: * Fork and execute the single command. If the fork fails, we abort.
340: */
341: cpid = vfork();
342: if (cpid < 0) {
343: Fatal("Could not fork");
344: }
345: if (cpid == 0) {
346: if (local) {
347: execvp(av[0], av);
348: (void) write (2, av[0], strlen (av[0]));
349: (void) write (2, ": not found\n", sizeof(": not found"));
350: } else {
351: (void)execv(av[0], av);
352: }
1.9 deraadt 353: _exit(1);
1.1 deraadt 354: }
355: free(cmdStart);
356: Lst_Replace (cmdNode, (ClientData) NULL);
1.4 millert 357:
1.1 deraadt 358: /*
359: * The child is off and running. Now all we can do is wait...
360: */
361: while (1) {
362:
1.2 deraadt 363: while ((stat = wait(&reason)) != cpid) {
1.1 deraadt 364: if (stat == -1 && errno != EINTR) {
365: break;
366: }
367: }
1.4 millert 368:
1.1 deraadt 369: if (stat > -1) {
370: if (WIFSTOPPED(reason)) {
1.2 deraadt 371: status = WSTOPSIG(reason); /* stopped */
1.1 deraadt 372: } else if (WIFEXITED(reason)) {
1.2 deraadt 373: status = WEXITSTATUS(reason); /* exited */
1.1 deraadt 374: if (status != 0) {
375: printf ("*** Error code %d", status);
376: }
377: } else {
1.2 deraadt 378: status = WTERMSIG(reason); /* signaled */
1.1 deraadt 379: printf ("*** Signal %d", status);
1.4 millert 380: }
381:
1.1 deraadt 382:
383: if (!WIFEXITED(reason) || (status != 0)) {
384: if (errCheck) {
385: gn->made = ERROR;
386: if (keepgoing) {
387: /*
388: * Abort the current target, but let others
389: * continue.
390: */
391: printf (" (continuing)\n");
392: }
393: } else {
394: /*
395: * Continue executing commands for this target.
396: * If we return 0, this will happen...
397: */
398: printf (" (ignored)\n");
399: status = 0;
400: }
401: }
402: break;
403: } else {
404: Fatal ("error in wait: %d", stat);
405: /*NOTREACHED*/
406: }
407: }
408:
409: return (status);
410: }
411:
412: /*-
413: *-----------------------------------------------------------------------
414: * CompatMake --
415: * Make a target.
416: *
417: * Results:
418: * 0
419: *
420: * Side Effects:
421: * If an error is detected and not being ignored, the process exits.
422: *
423: *-----------------------------------------------------------------------
424: */
425: static int
426: CompatMake (gnp, pgnp)
427: ClientData gnp; /* The node to make */
428: ClientData pgnp; /* Parent to abort if necessary */
429: {
430: GNode *gn = (GNode *) gnp;
431: GNode *pgn = (GNode *) pgnp;
1.5 millert 432:
1.7 deraadt 433: if (pgn->type & OP_MADE) {
434: (void) Dir_MTime(gn);
435: gn->made = UPTODATE;
436: }
437:
1.8 millert 438: if (gn->type & OP_USE) {
439: Make_HandleUse(gn, pgn);
440: } else if (gn->made == UNMADE) {
1.1 deraadt 441: /*
442: * First mark ourselves to be made, then apply whatever transformations
443: * the suffix module thinks are necessary. Once that's done, we can
444: * descend and make all our children. If any of them has an error
445: * but the -k flag was given, our 'make' field will be set FALSE again.
446: * This is our signal to not attempt to do anything but abort our
447: * parent as well.
448: */
449: gn->make = TRUE;
450: gn->made = BEINGMADE;
451: Suff_FindDeps (gn);
452: Lst_ForEach (gn->children, CompatMake, (ClientData)gn);
453: if (!gn->make) {
454: gn->made = ABORTED;
455: pgn->make = FALSE;
456: return (0);
457: }
458:
459: if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
460: char *p1;
461: Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
462: if (p1)
463: free(p1);
464: }
1.4 millert 465:
1.1 deraadt 466: /*
467: * All the children were made ok. Now cmtime contains the modification
468: * time of the newest child, we need to find out if we exist and when
469: * we were modified last. The criteria for datedness are defined by the
470: * Make_OODate function.
471: */
472: if (DEBUG(MAKE)) {
473: printf("Examining %s...", gn->name);
474: }
475: if (! Make_OODate(gn)) {
476: gn->made = UPTODATE;
477: if (DEBUG(MAKE)) {
478: printf("up-to-date.\n");
479: }
480: return (0);
481: } else if (DEBUG(MAKE)) {
482: printf("out-of-date.\n");
483: }
484:
485: /*
486: * If the user is just seeing if something is out-of-date, exit now
487: * to tell him/her "yes".
488: */
489: if (queryFlag) {
490: exit (-1);
491: }
492:
493: /*
494: * We need to be re-made. We also have to make sure we've got a $?
495: * variable. To be nice, we also define the $> variable using
496: * Make_DoAllVar().
497: */
498: Make_DoAllVar(gn);
1.4 millert 499:
1.1 deraadt 500: /*
501: * Alter our type to tell if errors should be ignored or things
502: * should not be printed so CompatRunCommand knows what to do.
503: */
504: if (Targ_Ignore (gn)) {
505: gn->type |= OP_IGNORE;
506: }
507: if (Targ_Silent (gn)) {
508: gn->type |= OP_SILENT;
509: }
510:
511: if (Job_CheckCommands (gn, Fatal)) {
512: /*
513: * Our commands are ok, but we still have to worry about the -t
514: * flag...
515: */
516: if (!touchFlag) {
517: curTarg = gn;
518: Lst_ForEach (gn->commands, CompatRunCommand, (ClientData)gn);
519: curTarg = NILGNODE;
520: } else {
521: Job_Touch (gn, gn->type & OP_SILENT);
522: }
523: } else {
524: gn->made = ERROR;
525: }
526:
527: if (gn->made != ERROR) {
528: /*
529: * If the node was made successfully, mark it so, update
530: * its modification time and timestamp all its parents. Note
531: * that for .ZEROTIME targets, the timestamping isn't done.
532: * This is to keep its state from affecting that of its parent.
533: */
534: gn->made = MADE;
535: #ifndef RECHECK
536: /*
537: * We can't re-stat the thing, but we can at least take care of
538: * rules where a target depends on a source that actually creates
539: * the target, but only if it has changed, e.g.
540: *
541: * parse.h : parse.o
542: *
543: * parse.o : parse.y
544: * yacc -d parse.y
545: * cc -c y.tab.c
546: * mv y.tab.o parse.o
547: * cmp -s y.tab.h parse.h || mv y.tab.h parse.h
548: *
549: * In this case, if the definitions produced by yacc haven't
550: * changed from before, parse.h won't have been updated and
551: * gn->mtime will reflect the current modification time for
552: * parse.h. This is something of a kludge, I admit, but it's a
553: * useful one..
554: *
555: * XXX: People like to use a rule like
556: *
557: * FRC:
558: *
559: * To force things that depend on FRC to be made, so we have to
560: * check for gn->children being empty as well...
561: */
562: if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
563: gn->mtime = now;
564: }
565: #else
566: /*
567: * This is what Make does and it's actually a good thing, as it
568: * allows rules like
569: *
570: * cmp -s y.tab.h parse.h || cp y.tab.h parse.h
571: *
572: * to function as intended. Unfortunately, thanks to the stateless
573: * nature of NFS (and the speed of this program), there are times
574: * when the modification time of a file created on a remote
575: * machine will not be modified before the stat() implied by
576: * the Dir_MTime occurs, thus leading us to believe that the file
577: * is unchanged, wreaking havoc with files that depend on this one.
578: *
579: * I have decided it is better to make too much than to make too
580: * little, so this stuff is commented out unless you're sure it's
581: * ok.
582: * -- ardeb 1/12/88
583: */
584: if (noExecute || Dir_MTime(gn) == 0) {
585: gn->mtime = now;
586: }
587: if (gn->cmtime > gn->mtime)
588: gn->mtime = gn->cmtime;
589: if (DEBUG(MAKE)) {
590: printf("update time: %s\n", Targ_FmtTime(gn->mtime));
591: }
592: #endif
593: if (!(gn->type & OP_EXEC)) {
594: pgn->childMade = TRUE;
595: Make_TimeStamp(pgn, gn);
596: }
597: } else if (keepgoing) {
598: pgn->make = FALSE;
599: } else {
600: printf ("\n\nStop.\n");
601: exit (1);
602: }
603: } else if (gn->made == ERROR) {
604: /*
605: * Already had an error when making this beastie. Tell the parent
606: * to abort.
607: */
608: pgn->make = FALSE;
609: } else {
610: if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
611: char *p1;
612: Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
613: if (p1)
614: free(p1);
615: }
616: switch(gn->made) {
617: case BEINGMADE:
618: Error("Graph cycles through %s\n", gn->name);
619: gn->made = ERROR;
620: pgn->make = FALSE;
621: break;
622: case MADE:
623: if ((gn->type & OP_EXEC) == 0) {
624: pgn->childMade = TRUE;
625: Make_TimeStamp(pgn, gn);
626: }
627: break;
628: case UPTODATE:
629: if ((gn->type & OP_EXEC) == 0) {
630: Make_TimeStamp(pgn, gn);
631: }
632: break;
633: default:
634: break;
635: }
636: }
637:
638: return (0);
639: }
1.4 millert 640:
1.1 deraadt 641: /*-
642: *-----------------------------------------------------------------------
643: * Compat_Run --
644: * Initialize this mode and start making.
645: *
646: * Results:
647: * None.
648: *
649: * Side Effects:
650: * Guess what?
651: *
652: *-----------------------------------------------------------------------
653: */
654: void
655: Compat_Run(targs)
656: Lst targs; /* List of target nodes to re-create */
657: {
658: char *cp; /* Pointer to string of shell meta-characters */
659: GNode *gn = NULL;/* Current root target */
660: int errors; /* Number of targets not remade due to errors */
661:
662: if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
663: signal(SIGINT, CompatInterrupt);
664: }
665: if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
666: signal(SIGTERM, CompatInterrupt);
667: }
668: if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
669: signal(SIGHUP, CompatInterrupt);
670: }
671: if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
672: signal(SIGQUIT, CompatInterrupt);
673: }
674:
675: for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
676: meta[(unsigned char) *cp] = 1;
677: }
678: /*
679: * The null character serves as a sentinel in the string.
680: */
681: meta[0] = 1;
682:
683: ENDNode = Targ_FindNode(".END", TARG_CREATE);
684: /*
685: * If the user has defined a .BEGIN target, execute the commands attached
686: * to it.
687: */
688: if (!queryFlag) {
689: gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
690: if (gn != NILGNODE) {
691: Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
1.4 millert 692: if (gn->made == ERROR) {
693: printf("\n\nStop.\n");
694: exit(1);
695: }
1.1 deraadt 696: }
697: }
698:
699: /*
700: * For each entry in the list of targets to create, call CompatMake on
701: * it to create the thing. CompatMake will leave the 'made' field of gn
702: * in one of several states:
703: * UPTODATE gn was already up-to-date
704: * MADE gn was recreated successfully
705: * ERROR An error occurred while gn was being created
706: * ABORTED gn was not remade because one of its inferiors
707: * could not be made due to errors.
708: */
709: errors = 0;
710: while (!Lst_IsEmpty (targs)) {
711: gn = (GNode *) Lst_DeQueue (targs);
712: CompatMake (gn, gn);
713:
714: if (gn->made == UPTODATE) {
715: printf ("`%s' is up to date.\n", gn->name);
716: } else if (gn->made == ABORTED) {
717: printf ("`%s' not remade because of errors.\n", gn->name);
718: errors += 1;
719: }
720: }
721:
722: /*
723: * If the user has defined a .END target, run its commands.
724: */
725: if (errors == 0) {
726: Lst_ForEach(ENDNode->commands, CompatRunCommand, (ClientData)gn);
727: }
728: }