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