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