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