1 /* $NetBSD: compat.c,v 1.196 2020/11/28 19:22:32 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 * compat.c -- 74 * The routines in this file implement the full-compatibility 75 * mode of PMake. Most of the special functionality of PMake 76 * is available in this mode. Things not supported: 77 * - different shells. 78 * - friendly variable substitution. 79 * 80 * Interface: 81 * Compat_Run Initialize things for this module and recreate 82 * thems as need creatin' 83 */ 84 85 #include <sys/types.h> 86 #include <sys/stat.h> 87 #include <sys/wait.h> 88 89 #include <errno.h> 90 #include <signal.h> 91 92 #include "make.h" 93 #include "dir.h" 94 #include "job.h" 95 #include "metachar.h" 96 #include "pathnames.h" 97 98 /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ 99 MAKE_RCSID("$NetBSD: compat.c,v 1.196 2020/11/28 19:22:32 rillig Exp $"); 100 101 static GNode *curTarg = NULL; 102 static pid_t compatChild; 103 static int compatSigno; 104 105 /* 106 * CompatDeleteTarget -- delete the file of a failed, interrupted, or 107 * otherwise duffed target if not inhibited by .PRECIOUS. 108 */ 109 static void 110 CompatDeleteTarget(GNode *gn) 111 { 112 if (gn != NULL && !Targ_Precious(gn)) { 113 const char *file = GNode_VarTarget(gn); 114 115 if (!opts.noExecute && eunlink(file) != -1) { 116 Error("*** %s removed", file); 117 } 118 } 119 } 120 121 /* Interrupt the creation of the current target and remove it if it ain't 122 * precious. Then exit. 123 * 124 * If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED. 125 * 126 * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've 127 * left the logic alone for now. - dholland 20160826 128 */ 129 static void 130 CompatInterrupt(int signo) 131 { 132 CompatDeleteTarget(curTarg); 133 134 if (curTarg != NULL && !Targ_Precious(curTarg)) { 135 /* 136 * Run .INTERRUPT only if hit with interrupt signal 137 */ 138 if (signo == SIGINT) { 139 GNode *gn = Targ_FindNode(".INTERRUPT"); 140 if (gn != NULL) { 141 Compat_Make(gn, gn); 142 } 143 } 144 } 145 146 if (signo == SIGQUIT) 147 _exit(signo); 148 149 /* 150 * If there is a child running, pass the signal on. 151 * We will exist after it has exited. 152 */ 153 compatSigno = signo; 154 if (compatChild > 0) { 155 KILLPG(compatChild, signo); 156 } else { 157 bmake_signal(signo, SIG_DFL); 158 kill(myPid, signo); 159 } 160 } 161 162 static void 163 DebugFailedTarget(const char *cmd, GNode *gn) 164 { 165 const char *p = cmd; 166 debug_printf("\n*** Failed target: %s\n*** Failed command: ", 167 gn->name); 168 169 /* Replace runs of whitespace with a single space, to reduce 170 * the amount of whitespace for multi-line command lines. */ 171 while (*p != '\0') { 172 if (ch_isspace(*p)) { 173 debug_printf(" "); 174 cpp_skip_whitespace(&p); 175 } else { 176 debug_printf("%c", *p); 177 p++; 178 } 179 } 180 debug_printf("\n"); 181 } 182 183 /* Execute the next command for a target. If the command returns an error, 184 * the node's made field is set to ERROR and creation stops. 185 * 186 * Input: 187 * cmdp Command to execute 188 * gnp Node from which the command came 189 * 190 * Results: 191 * 0 if the command succeeded, 1 if an error occurred. 192 */ 193 int 194 Compat_RunCommand(const char *cmdp, GNode *gn) 195 { 196 char *cmdStart; /* Start of expanded command */ 197 char *bp; 198 Boolean silent; /* Don't print command */ 199 Boolean doIt; /* Execute even if -n */ 200 volatile Boolean errCheck; /* Check errors */ 201 int reason; /* Reason for child's death */ 202 int status; /* Description of child's death */ 203 pid_t cpid; /* Child actually found */ 204 pid_t retstat; /* Result of wait */ 205 StringListNode *cmdNode; /* Node where current command is located */ 206 const char **volatile av; /* Argument vector for thing to exec */ 207 char **volatile mav; /* Copy of the argument vector for freeing */ 208 Boolean useShell; /* TRUE if command should be executed 209 * using a shell */ 210 const char *volatile cmd = cmdp; 211 212 silent = (gn->type & OP_SILENT) != 0; 213 errCheck = !(gn->type & OP_IGNORE); 214 doIt = FALSE; 215 216 /* Luckily the commands don't end up in a string pool, otherwise 217 * this comparison could match too early, in a dependency using "..." 218 * for delayed commands, run in parallel mode, using the same shell 219 * command line more than once; see JobPrintCommand. 220 * TODO: write a unit-test to protect against this potential bug. */ 221 cmdNode = Lst_FindDatum(&gn->commands, cmd); 222 (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); 223 /* TODO: handle errors */ 224 225 if (cmdStart[0] == '\0') { 226 free(cmdStart); 227 return 0; 228 } 229 cmd = cmdStart; 230 LstNode_Set(cmdNode, cmdStart); 231 232 if (gn->type & OP_SAVE_CMDS) { 233 GNode *endNode = Targ_GetEndNode(); 234 if (gn != endNode) { 235 Lst_Append(&endNode->commands, cmdStart); 236 return 0; 237 } 238 } 239 if (strcmp(cmdStart, "...") == 0) { 240 gn->type |= OP_SAVE_CMDS; 241 return 0; 242 } 243 244 for (;;) { 245 if (*cmd == '@') 246 silent = !DEBUG(LOUD); 247 else if (*cmd == '-') 248 errCheck = FALSE; 249 else if (*cmd == '+') { 250 doIt = TRUE; 251 if (!shellName) /* we came here from jobs */ 252 Shell_Init(); 253 } else 254 break; 255 cmd++; 256 } 257 258 while (ch_isspace(*cmd)) 259 cmd++; 260 261 /* 262 * If we did not end up with a command, just skip it. 263 */ 264 if (cmd[0] == '\0') 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); 287 #endif 288 289 /* 290 * Print the command before echoing if we're not supposed to be quiet 291 * for this one. We also print the command if -n given. 292 */ 293 if (!silent || !GNode_ShouldExecute(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 && !GNode_ShouldExecute(gn)) 303 return 0; 304 305 DEBUG1(JOB, "Execute: '%s'\n", cmd); 306 307 if (useShell) { 308 /* 309 * We need to pass the command off to the shell, typically 310 * because the command contains a "meta" character. 311 */ 312 static const char *shargv[5]; 313 314 /* The following work for any of the builtin shell specs. */ 315 int shargc = 0; 316 shargv[shargc++] = shellPath; 317 if (errCheck && shellErrFlag) 318 shargv[shargc++] = shellErrFlag; 319 shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c"; 320 shargv[shargc++] = cmd; 321 shargv[shargc] = NULL; 322 av = shargv; 323 bp = NULL; 324 mav = NULL; 325 } else { 326 /* 327 * No meta-characters, so no need to exec a shell. Break the 328 * command into words to form an argument vector we can 329 * execute. 330 */ 331 Words words = Str_Words(cmd, FALSE); 332 mav = words.words; 333 bp = words.freeIt; 334 av = (void *)mav; 335 } 336 337 #ifdef USE_META 338 if (useMeta) { 339 meta_compat_start(); 340 } 341 #endif 342 343 /* 344 * Fork and execute the single command. If the fork fails, we abort. 345 */ 346 compatChild = cpid = vFork(); 347 if (cpid < 0) { 348 Fatal("Could not fork"); 349 } 350 if (cpid == 0) { 351 Var_ExportVars(); 352 #ifdef USE_META 353 if (useMeta) { 354 meta_compat_child(); 355 } 356 #endif 357 (void)execvp(av[0], (char *const *)UNCONST(av)); 358 execDie("exec", av[0]); 359 } 360 361 free(mav); 362 free(bp); 363 364 /* XXX: Memory management looks suspicious here. */ 365 /* XXX: Setting a list item to NULL is unexpected. */ 366 LstNode_SetNull(cmdNode); 367 368 #ifdef USE_META 369 if (useMeta) { 370 meta_compat_parent(cpid); 371 } 372 #endif 373 374 /* 375 * The child is off and running. Now all we can do is wait... 376 */ 377 while ((retstat = wait(&reason)) != cpid) { 378 if (retstat > 0) 379 JobReapChild(retstat, reason, FALSE); /* not ours? */ 380 if (retstat == -1 && errno != EINTR) { 381 break; 382 } 383 } 384 385 if (retstat < 0) 386 Fatal("error in wait: %d: %s", retstat, strerror(errno)); 387 388 if (WIFSTOPPED(reason)) { 389 status = WSTOPSIG(reason); /* stopped */ 390 } else if (WIFEXITED(reason)) { 391 status = WEXITSTATUS(reason); /* exited */ 392 #if defined(USE_META) && defined(USE_FILEMON_ONCE) 393 if (useMeta) { 394 meta_cmd_finish(NULL); 395 } 396 #endif 397 if (status != 0) { 398 if (DEBUG(ERROR)) 399 DebugFailedTarget(cmd, gn); 400 printf("*** Error code %d", status); 401 } 402 } else { 403 status = WTERMSIG(reason); /* signaled */ 404 printf("*** Signal %d", status); 405 } 406 407 408 if (!WIFEXITED(reason) || status != 0) { 409 if (errCheck) { 410 #ifdef USE_META 411 if (useMeta) { 412 meta_job_error(NULL, gn, 0, status); 413 } 414 #endif 415 gn->made = ERROR; 416 if (opts.keepgoing) { 417 /* 418 * Abort the current target, 419 * but let others continue. 420 */ 421 printf(" (continuing)\n"); 422 } else { 423 printf("\n"); 424 } 425 if (deleteOnError) 426 CompatDeleteTarget(gn); 427 } else { 428 /* 429 * Continue executing commands for this target. 430 * If we return 0, this will happen... 431 */ 432 printf(" (ignored)\n"); 433 status = 0; 434 } 435 } 436 437 free(cmdStart); 438 compatChild = 0; 439 if (compatSigno != 0) { 440 bmake_signal(compatSigno, SIG_DFL); 441 kill(myPid, compatSigno); 442 } 443 444 return status; 445 } 446 447 static void 448 RunCommands(GNode *gn) 449 { 450 StringListNode *ln; 451 452 for (ln = gn->commands.first; ln != NULL; ln = ln->next) { 453 const char *cmd = ln->datum; 454 if (Compat_RunCommand(cmd, gn) != 0) 455 break; 456 } 457 } 458 459 static void 460 MakeNodes(GNodeList *gnodes, GNode *pgn) 461 { 462 GNodeListNode *ln; 463 464 for (ln = gnodes->first; ln != NULL; ln = ln->next) { 465 GNode *cohort = ln->datum; 466 Compat_Make(cohort, pgn); 467 } 468 } 469 470 static Boolean 471 MakeUnmade(GNode *const gn, GNode *const pgn) 472 { 473 474 assert(gn->made == UNMADE); 475 476 /* 477 * First mark ourselves to be made, then apply whatever transformations 478 * the suffix module thinks are necessary. Once that's done, we can 479 * descend and make all our children. If any of them has an error 480 * but the -k flag was given, our 'make' field will be set to FALSE 481 * again. This is our signal to not attempt to do anything but abort 482 * our parent as well. 483 */ 484 gn->flags |= REMAKE; 485 gn->made = BEINGMADE; 486 487 if (!(gn->type & OP_MADE)) 488 Suff_FindDeps(gn); 489 490 MakeNodes(&gn->children, gn); 491 492 if (!(gn->flags & REMAKE)) { 493 gn->made = ABORTED; 494 pgn->flags &= ~(unsigned)REMAKE; 495 return FALSE; 496 } 497 498 if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) 499 Var_Set(IMPSRC, GNode_VarTarget(gn), pgn); 500 501 /* 502 * All the children were made ok. Now youngestChild->mtime contains the 503 * modification time of the newest child, we need to find out if we 504 * exist and when we were modified last. The criteria for datedness 505 * are defined by GNode_IsOODate. 506 */ 507 DEBUG1(MAKE, "Examining %s...", gn->name); 508 if (!GNode_IsOODate(gn)) { 509 gn->made = UPTODATE; 510 DEBUG0(MAKE, "up-to-date.\n"); 511 return FALSE; 512 } 513 514 /* 515 * If the user is just seeing if something is out-of-date, exit now 516 * to tell him/her "yes". 517 */ 518 DEBUG0(MAKE, "out-of-date.\n"); 519 if (opts.queryFlag) 520 exit(1); 521 522 /* 523 * We need to be re-made. 524 * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set. 525 */ 526 Make_DoAllVar(gn); 527 528 /* 529 * Alter our type to tell if errors should be ignored or things 530 * should not be printed so CompatRunCommand knows what to do. 531 */ 532 if (Targ_Ignore(gn)) 533 gn->type |= OP_IGNORE; 534 if (Targ_Silent(gn)) 535 gn->type |= OP_SILENT; 536 537 if (Job_CheckCommands(gn, Fatal)) { 538 /* 539 * Our commands are ok, but we still have to worry about 540 * the -t flag. 541 */ 542 if (!opts.touchFlag || (gn->type & OP_MAKE)) { 543 curTarg = gn; 544 #ifdef USE_META 545 if (useMeta && GNode_ShouldExecute(gn)) { 546 meta_job_start(NULL, gn); 547 } 548 #endif 549 RunCommands(gn); 550 curTarg = NULL; 551 } else { 552 Job_Touch(gn, (gn->type & OP_SILENT) != 0); 553 } 554 } else { 555 gn->made = ERROR; 556 } 557 #ifdef USE_META 558 if (useMeta && GNode_ShouldExecute(gn)) { 559 if (meta_job_finish(NULL) != 0) 560 gn->made = ERROR; 561 } 562 #endif 563 564 if (gn->made != ERROR) { 565 /* 566 * If the node was made successfully, mark it so, update 567 * its modification time and timestamp all its parents. 568 * This is to keep its state from affecting that of its parent. 569 */ 570 gn->made = MADE; 571 if (Make_Recheck(gn) == 0) 572 pgn->flags |= FORCE; 573 if (!(gn->type & OP_EXEC)) { 574 pgn->flags |= CHILDMADE; 575 GNode_UpdateYoungestChild(pgn, gn); 576 } 577 } else if (opts.keepgoing) { 578 pgn->flags &= ~(unsigned)REMAKE; 579 } else { 580 PrintOnError(gn, "\nStop."); 581 exit(1); 582 } 583 return TRUE; 584 } 585 586 static void 587 MakeOther(GNode *gn, GNode *pgn) 588 { 589 590 if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) { 591 const char *target = GNode_VarTarget(gn); 592 Var_Set(IMPSRC, target != NULL ? target : "", pgn); 593 } 594 595 switch (gn->made) { 596 case BEINGMADE: 597 Error("Graph cycles through %s", gn->name); 598 gn->made = ERROR; 599 pgn->flags &= ~(unsigned)REMAKE; 600 break; 601 case MADE: 602 if (!(gn->type & OP_EXEC)) { 603 pgn->flags |= CHILDMADE; 604 GNode_UpdateYoungestChild(pgn, gn); 605 } 606 break; 607 case UPTODATE: 608 if (!(gn->type & OP_EXEC)) 609 GNode_UpdateYoungestChild(pgn, gn); 610 break; 611 default: 612 break; 613 } 614 } 615 616 /* Make a target. 617 * 618 * If an error is detected and not being ignored, the process exits. 619 * 620 * Input: 621 * gn The node to make 622 * pgn Parent to abort if necessary 623 * 624 * Output: 625 * gn->made 626 * UPTODATE gn was already up-to-date. 627 * MADE gn was recreated successfully. 628 * ERROR An error occurred while gn was being created, 629 * either due to missing commands or in -k mode. 630 * ABORTED gn was not remade because one of its 631 * dependencies could not be made due to errors. 632 */ 633 void 634 Compat_Make(GNode *gn, GNode *pgn) 635 { 636 if (shellName == NULL) /* we came here from jobs */ 637 Shell_Init(); 638 639 if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { 640 if (!MakeUnmade(gn, pgn)) 641 goto cohorts; 642 643 /* XXX: Replace with GNode_IsError(gn) */ 644 } else if (gn->made == ERROR) { 645 /* 646 * Already had an error when making this. 647 * Tell the parent to abort. 648 */ 649 pgn->flags &= ~(unsigned)REMAKE; 650 } else { 651 MakeOther(gn, pgn); 652 } 653 654 cohorts: 655 MakeNodes(&gn->cohorts, pgn); 656 } 657 658 /* Initialize this module and start making. 659 * 660 * Input: 661 * targs The target nodes to re-create 662 */ 663 void 664 Compat_Run(GNodeList *targs) 665 { 666 GNode *gn = NULL; /* Current root target */ 667 int indirectErrors; /* Number of targets not remade due to errors */ 668 669 if (!shellName) 670 Shell_Init(); 671 672 if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) 673 bmake_signal(SIGINT, CompatInterrupt); 674 if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) 675 bmake_signal(SIGTERM, CompatInterrupt); 676 if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) 677 bmake_signal(SIGHUP, CompatInterrupt); 678 if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) 679 bmake_signal(SIGQUIT, CompatInterrupt); 680 681 /* Create the .END node now, to keep the (debug) output of the 682 * counter.mk test the same as before 2020-09-23. This implementation 683 * detail probably doesn't matter though. */ 684 (void)Targ_GetEndNode(); 685 686 /* 687 * If the user has defined a .BEGIN target, execute the commands 688 * attached to it. 689 */ 690 if (!opts.queryFlag) { 691 gn = Targ_FindNode(".BEGIN"); 692 if (gn != NULL) { 693 Compat_Make(gn, gn); 694 if (GNode_IsError(gn)) { 695 PrintOnError(gn, "\nStop."); 696 exit(1); 697 } 698 } 699 } 700 701 /* 702 * Expand .USE nodes right now, because they can modify the structure 703 * of the tree. 704 */ 705 Make_ExpandUse(targs); 706 707 indirectErrors = 0; 708 while (!Lst_IsEmpty(targs)) { 709 gn = Lst_Dequeue(targs); 710 Compat_Make(gn, gn); 711 712 if (gn->made == UPTODATE) { 713 printf("`%s' is up to date.\n", gn->name); 714 } else if (gn->made == ABORTED) { 715 printf("`%s' not remade because of errors.\n", 716 gn->name); 717 indirectErrors++; 718 } 719 } 720 721 /* 722 * If the user has defined a .END target, run its commands. 723 */ 724 if (indirectErrors == 0) { 725 GNode *endNode = Targ_GetEndNode(); 726 Compat_Make(endNode, endNode); 727 if (GNode_IsError(gn) || GNode_IsError(endNode)) { 728 PrintOnError(gn, "\nStop."); 729 exit(1); 730 } 731 } 732 } 733