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