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