Import bmake-20201117

o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable
  checks in InitObjdir.  Explicit .OBJDIR target always allows
  read-only directory.

o Fix building and unit-tests on non-BSD.

o More code cleanup and refactoring.

o More unit tests
This commit is contained in:
Simon J. Gerraty 2020-11-20 03:54:37 +00:00
parent 302da1a3d3
commit 1b65f0bd2b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/vendor/NetBSD/bmake/dist/; revision=367860
svn path=/vendor/NetBSD/bmake/20201117/; revision=367861; tag=vendor/NetBSD/bmake/20201117
247 changed files with 6138 additions and 4171 deletions

View file

@ -1,3 +1,78 @@
2020-11-17 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201117
Merge with NetBSD make, pick up
o fix some unit-tests when dash is .SHELL
o rename Targ_NewGN to GNode_New
o make some GNode functions const
o main.c: call Targ_Init before Var_Init
cleanup PrintOnError, getTmpdir and ParseBoolean
o var.c: fix error message of failed :!cmd! modifier
2020-11-14 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201114
Merge with NetBSD make, pick up
o replace a few HashTable_CreateEntry with HashTable_Set
o clean up cached_stats
o rename DEFAULT to defaultNode
o remove redundant struct make_stat
o cond.c: in lint mode, check for ".else <cond>"
use bitset for IfState
replace large switch with if-else in Cond_EvalLine
o job.c: clean up JobExec, JobStart, JobDoOutput
use stderr for error message about failed touch
clean up Job_Touch
replace macro DBPRINTF with JobPrintln
rename JobState to JobStatus
main.c: switch cache for realpath from GNode to HashTable
clean up Fatal
clean up InitDefSysIncPath
use progname instead of hard-coded 'make' in warning
rename Main_SetVarObjdir to SetVarObjdir
make.1: document the -S option
make.c: fix debug output for GNode details
use symbolic names in debug output of GNodes
2020-11-12 Simon J Gerraty <sjg@beast.crufty.net>
* configure.in: fix --with-force-machine-arch
* VERSION (_MAKE_VERSION): 20201112
Merge with NetBSD make, pick up
o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable
checks in InitObjdir. Explicit .OBJDIR target always allows
read-only directory.
o cond.c: clean up Cond_EvalLine
2020-11-11 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201111
Merge with NetBSD make, pick up
o more unit-tests
o style cleanup
remove redundant parentheses from sizeof operator
replace character literal 0 with '\0'.
replace pointer literal 0 with NULL.
remove redundant parentheses.
replace (expr & mask) == 0 with !(expr & mask).
use strict typing in conditions of the form !var
o rename Make_OODate to GNode_IsOODate
o rename Make_TimeStamp to GNode_UpdateYoungestChild
o rename Var_Set_with_flags to Var_SetWithFlags
o rename dieQuietly to shouldDieQuietly
o buf.c: make API of Buf_Init simpler
o compat.c: clean up Compat_Make, Compat_RunCommand,
CompatDeleteTarget and CompatInterrupt
o cond.c: in lint mode, only allow '&&' and '||', not '&' and '|'
clean up CondParser_Comparison
o main.c: rename getBoolean and s2Boolean
rename MAKEFILE_PREFERENCE for consistency
o parse.c: replace strstr in ParseMaybeSubMake with optimized code
o var.c: rename VARE_ASSIGN to VARE_KEEP_DOLLAR
replace emptyString with allocated empty string
error out on unclosed expressions after the colon
2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> 2020-11-01 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201101 * VERSION (_MAKE_VERSION): 20201101

28
FILES
View file

@ -75,8 +75,14 @@ unit-tests/archive-suffix.exp
unit-tests/archive-suffix.mk unit-tests/archive-suffix.mk
unit-tests/archive.exp unit-tests/archive.exp
unit-tests/archive.mk unit-tests/archive.mk
unit-tests/cmd-errors-lint.exp
unit-tests/cmd-errors-lint.mk
unit-tests/cmd-errors.exp
unit-tests/cmd-errors.mk
unit-tests/cmd-interrupt.exp unit-tests/cmd-interrupt.exp
unit-tests/cmd-interrupt.mk unit-tests/cmd-interrupt.mk
unit-tests/cmdline-undefined.exp
unit-tests/cmdline-undefined.mk
unit-tests/cmdline.exp unit-tests/cmdline.exp
unit-tests/cmdline.mk unit-tests/cmdline.mk
unit-tests/comment.exp unit-tests/comment.exp
@ -115,10 +121,14 @@ unit-tests/cond-func.exp
unit-tests/cond-func.mk unit-tests/cond-func.mk
unit-tests/cond-late.exp unit-tests/cond-late.exp
unit-tests/cond-late.mk unit-tests/cond-late.mk
unit-tests/cond-op-and-lint.exp
unit-tests/cond-op-and-lint.mk
unit-tests/cond-op-and.exp unit-tests/cond-op-and.exp
unit-tests/cond-op-and.mk unit-tests/cond-op-and.mk
unit-tests/cond-op-not.exp unit-tests/cond-op-not.exp
unit-tests/cond-op-not.mk unit-tests/cond-op-not.mk
unit-tests/cond-op-or-lint.exp
unit-tests/cond-op-or-lint.mk
unit-tests/cond-op-or.exp unit-tests/cond-op-or.exp
unit-tests/cond-op-or.mk unit-tests/cond-op-or.mk
unit-tests/cond-op-parentheses.exp unit-tests/cond-op-parentheses.exp
@ -287,6 +297,8 @@ unit-tests/directive-for.exp
unit-tests/directive-for.mk unit-tests/directive-for.mk
unit-tests/directive-hyphen-include.exp unit-tests/directive-hyphen-include.exp
unit-tests/directive-hyphen-include.mk unit-tests/directive-hyphen-include.mk
unit-tests/directive-if-nested.exp
unit-tests/directive-if-nested.mk
unit-tests/directive-if.exp unit-tests/directive-if.exp
unit-tests/directive-if.mk unit-tests/directive-if.mk
unit-tests/directive-ifdef.exp unit-tests/directive-ifdef.exp
@ -315,8 +327,6 @@ unit-tests/directive-warning.exp
unit-tests/directive-warning.mk unit-tests/directive-warning.mk
unit-tests/directive.exp unit-tests/directive.exp
unit-tests/directive.mk unit-tests/directive.mk
unit-tests/directives.exp
unit-tests/directives.mk
unit-tests/dollar.exp unit-tests/dollar.exp
unit-tests/dollar.mk unit-tests/dollar.mk
unit-tests/doterror.exp unit-tests/doterror.exp
@ -341,6 +351,8 @@ unit-tests/forloop.exp
unit-tests/forloop.mk unit-tests/forloop.mk
unit-tests/forsubst.exp unit-tests/forsubst.exp
unit-tests/forsubst.mk unit-tests/forsubst.mk
unit-tests/gnode-submake.exp
unit-tests/gnode-submake.mk
unit-tests/hanoi-include.exp unit-tests/hanoi-include.exp
unit-tests/hanoi-include.mk unit-tests/hanoi-include.mk
unit-tests/impsrc.exp unit-tests/impsrc.exp
@ -349,6 +361,8 @@ unit-tests/include-main.exp
unit-tests/include-main.mk unit-tests/include-main.mk
unit-tests/include-sub.mk unit-tests/include-sub.mk
unit-tests/include-subsub.mk unit-tests/include-subsub.mk
unit-tests/job-flags.exp
unit-tests/job-flags.mk
unit-tests/job-output-long-lines.exp unit-tests/job-output-long-lines.exp
unit-tests/job-output-long-lines.mk unit-tests/job-output-long-lines.mk
unit-tests/lint.exp unit-tests/lint.exp
@ -365,6 +379,8 @@ unit-tests/modts.exp
unit-tests/modts.mk unit-tests/modts.mk
unit-tests/modword.exp unit-tests/modword.exp
unit-tests/modword.mk unit-tests/modword.mk
unit-tests/objdir-writable.exp
unit-tests/objdir-writable.mk
unit-tests/opt-backwards.exp unit-tests/opt-backwards.exp
unit-tests/opt-backwards.mk unit-tests/opt-backwards.mk
unit-tests/opt-chdir.exp unit-tests/opt-chdir.exp
@ -447,6 +463,8 @@ unit-tests/opt-raw.exp
unit-tests/opt-raw.mk unit-tests/opt-raw.mk
unit-tests/opt-silent.exp unit-tests/opt-silent.exp
unit-tests/opt-silent.mk unit-tests/opt-silent.mk
unit-tests/opt-touch-jobs.exp
unit-tests/opt-touch-jobs.mk
unit-tests/opt-touch.exp unit-tests/opt-touch.exp
unit-tests/opt-touch.mk unit-tests/opt-touch.mk
unit-tests/opt-tracefile.exp unit-tests/opt-tracefile.exp
@ -517,6 +535,8 @@ unit-tests/suff-main.exp
unit-tests/suff-main.mk unit-tests/suff-main.mk
unit-tests/suff-rebuild.exp unit-tests/suff-rebuild.exp
unit-tests/suff-rebuild.mk unit-tests/suff-rebuild.mk
unit-tests/suff-self.exp
unit-tests/suff-self.mk
unit-tests/suff-transform-endless.exp unit-tests/suff-transform-endless.exp
unit-tests/suff-transform-endless.mk unit-tests/suff-transform-endless.mk
unit-tests/suff-transform-expand.exp unit-tests/suff-transform-expand.exp
@ -737,14 +757,14 @@ unit-tests/varname.exp
unit-tests/varname.mk unit-tests/varname.mk
unit-tests/varparse-dynamic.exp unit-tests/varparse-dynamic.exp
unit-tests/varparse-dynamic.mk unit-tests/varparse-dynamic.mk
unit-tests/varparse-errors.exp
unit-tests/varparse-errors.mk
unit-tests/varparse-mod.exp unit-tests/varparse-mod.exp
unit-tests/varparse-mod.mk unit-tests/varparse-mod.mk
unit-tests/varparse-undef-partial.exp unit-tests/varparse-undef-partial.exp
unit-tests/varparse-undef-partial.mk unit-tests/varparse-undef-partial.mk
unit-tests/varquote.exp unit-tests/varquote.exp
unit-tests/varquote.mk unit-tests/varquote.mk
unit-tests/varshell.exp
unit-tests/varshell.mk
util.c util.c
var.c var.c
wait.h wait.h

View file

@ -1,4 +1,4 @@
# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $ # $Id: Makefile,v 1.114 2020/11/13 21:47:25 sjg Exp $
PROG= bmake PROG= bmake
@ -49,6 +49,12 @@ CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE
CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}}
COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\"" COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\""
.for x in FORCE_MACHINE FORCE_MACHINE_ARCH
.ifdef $x
COPTS.main.c+= "-D$x=\"${$x}\""
.endif
.endfor
# meta mode can be useful even without filemon # meta mode can be useful even without filemon
# should be set by now # should be set by now
USE_FILEMON ?= no USE_FILEMON ?= no

View file

@ -5,8 +5,8 @@ _MAKE_VERSION?=@_MAKE_VERSION@
prefix?= @prefix@ prefix?= @prefix@
srcdir= @srcdir@ srcdir= @srcdir@
CC?= @CC@ CC?= @CC@
MACHINE?= @machine@ @force_machine@MACHINE?= @machine@
MACHINE_ARCH?= @machine_arch@ @force_machine_arch@MACHINE_ARCH?= @machine_arch@
DEFAULT_SYS_PATH?= @default_sys_path@ DEFAULT_SYS_PATH?= @default_sys_path@
CPPFLAGS+= @CPPFLAGS@ CPPFLAGS+= @CPPFLAGS@

View file

@ -1,2 +1,2 @@
# keep this compatible with sh and make # keep this compatible with sh and make
_MAKE_VERSION=20201101 _MAKE_VERSION=20201117

471
arch.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $ */ /* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -68,38 +68,38 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
/*- /* Manipulate libraries, archives and their members.
* arch.c --
* Functions to manipulate libraries, archives and their members.
* *
* Once again, cacheing/hashing comes into play in the manipulation * The first time an archive is referenced, all of its members' headers are
* of archives. The first time an archive is referenced, all of its members' * read and cached and the archive closed again. All cached archives are kept
* headers are read and hashed and the archive closed again. All hashed * on a list which is searched each time an archive member is referenced.
* archives are kept on a list which is searched each time an archive member
* is referenced.
* *
* The interface to this module is: * The interface to this module is:
*
* Arch_Init Initialize this module.
*
* Arch_End Clean up this module.
*
* Arch_ParseArchive * Arch_ParseArchive
* Given an archive specification, return a list * Parse an archive specification such as
* of GNode's, one for each member in the spec. * "archive.a(member1 member2)".
* FALSE is returned if the specification is
* invalid for some reason.
* *
* Arch_Touch Alter the modification time of the archive * Arch_Touch Alter the modification time of the archive
* member described by the given node to be * member described by the given node to be
* the current time. * the time when make was started.
* *
* Arch_TouchLib Update the modification time of the library * Arch_TouchLib Update the modification time of the library
* described by the given node. This is special * described by the given node. This is special
* because it also updates the modification time * because it also updates the modification time
* of the library's table of contents. * of the library's table of contents.
* *
* Arch_MTime Find the modification time of a member of * Arch_UpdateMTime
* an archive *in the archive*. The time is also * Find the modification time of a member of
* placed in the member's GNode. Returns the * an archive *in the archive* and place it in the
* modification time. * member's GNode.
* *
* Arch_MemTime Find the modification time of a member of * Arch_UpdateMemberMTime
* Find the modification time of a member of
* an archive. Called when the member doesn't * an archive. Called when the member doesn't
* already exist. Looks in the archive for the * already exist. Looks in the archive for the
* modification time. Returns the modification * modification time. Returns the modification
@ -109,12 +109,7 @@
* library name in the GNode should be in * library name in the GNode should be in
* -l<name> format. * -l<name> format.
* *
* Arch_LibOODate Special function to decide if a library node * Arch_LibOODate Decide if a library node is out-of-date.
* is out-of-date.
*
* Arch_Init Initialize this module.
*
* Arch_End Clean up this module.
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -151,16 +146,7 @@ struct ar_hdr {
#include "dir.h" #include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $"); MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $");
#ifdef TARGET_MACHINE
#undef MAKE_MACHINE
#define MAKE_MACHINE TARGET_MACHINE
#endif
#ifdef TARGET_MACHINE_ARCH
#undef MAKE_MACHINE_ARCH
#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
#endif
typedef struct List ArchList; typedef struct List ArchList;
typedef struct ListNode ArchListNode; typedef struct ListNode ArchListNode;
@ -230,38 +216,36 @@ ArchFree(void *ap)
#endif #endif
/*- /*
*----------------------------------------------------------------------- * Parse an archive specification such as "archive.a(member1 member2.${EXT})",
* Arch_ParseArchive -- * adding nodes for the expanded members to nodeLst. Nodes are created as
* Parse the archive specification in the given line and find/create * necessary.
* the nodes for the specified archive members, placing their nodes
* on the given list.
* *
* Input: * Input:
* linePtr Pointer to start of specification * pp The start of the specification.
* nodeLst Lst on which to place the nodes * nodeLst The list on which to place the nodes.
* ctxt Context in which to expand variables * ctxt The context in which to expand variables.
* *
* Results: * Output:
* TRUE if it was a valid specification. The linePtr is updated * return TRUE if it was a valid specification.
* to point to the first non-space after the archive spec. The * *pp Points to the first non-space after the archive spec.
* nodes for the members are placed on the given list. * *nodeLst Nodes for the members have been added.
*-----------------------------------------------------------------------
*/ */
Boolean Boolean
Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt) Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
{ {
char *cp; /* Pointer into line */ char *cp; /* Pointer into line */
GNode *gn; /* New node */ GNode *gn; /* New node */
char *libName; /* Library-part of specification */ char *libName; /* Library-part of specification */
char *libName_freeIt = NULL;
char *memName; /* Member-part of specification */ char *memName; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */ char saveChar; /* Ending delimiter of member-name */
Boolean subLibName; /* TRUE if libName should have/had Boolean expandLibName; /* Whether the parsed libName contains
* variable substitution performed on it */ * variable expressions that need to be
* expanded */
libName = *linePtr; libName = *pp;
expandLibName = FALSE;
subLibName = FALSE;
for (cp = libName; *cp != '(' && *cp != '\0';) { for (cp = libName; *cp != '(' && *cp != '\0';) {
if (*cp == '$') { if (*cp == '$') {
@ -274,7 +258,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
const char *result; const char *result;
Boolean isError; Boolean isError;
(void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, /* XXX: is expanded twice: once here and once below */
(void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &result_freeIt); &result, &result_freeIt);
/* TODO: handle errors */ /* TODO: handle errors */
isError = result == var_Error; isError = result == var_Error;
@ -282,16 +267,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
if (isError) if (isError)
return FALSE; return FALSE;
subLibName = TRUE; expandLibName = TRUE;
cp += nested_p - cp; cp += nested_p - cp;
} else } else
cp++; cp++;
} }
*cp++ = '\0'; *cp++ = '\0';
if (subLibName) { if (expandLibName) {
(void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName); (void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName);
/* TODO: handle errors */ /* TODO: handle errors */
libName_freeIt = libName;
} }
@ -317,7 +303,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
Boolean isError; Boolean isError;
const char *nested_p = cp; const char *nested_p = cp;
(void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES, (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &freeIt); &result, &freeIt);
/* TODO: handle errors */ /* TODO: handle errors */
isError = result == var_Error; isError = result == var_Error;
@ -339,7 +325,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
* so it's better to return failure than allow such things to happen * so it's better to return failure than allow such things to happen
*/ */
if (*cp == '\0') { if (*cp == '\0') {
printf("No closing parenthesis in archive specification\n"); Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification");
return FALSE; return FALSE;
} }
@ -370,7 +356,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
char *sacrifice; char *sacrifice;
char *oldMemName = memName; char *oldMemName = memName;
(void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES, (void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&memName); &memName);
/* TODO: handle errors */ /* TODO: handle errors */
@ -381,7 +367,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*/ */
buf = sacrifice = str_concat4(libName, "(", memName, ")"); buf = sacrifice = str_concat4(libName, "(", memName, ")");
if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { if (strchr(memName, '$') != NULL &&
strcmp(memName, oldMemName) == 0) {
/* /*
* Must contain dynamic sources, so we can't deal with it now. * Must contain dynamic sources, so we can't deal with it now.
* Just create an ARCHV node for the thing and let * Just create an ARCHV node for the thing and let
@ -437,17 +424,12 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*cp = saveChar; *cp = saveChar;
} }
/* free(libName_freeIt);
* If substituted libName, free it now, since we need it no longer.
*/
if (subLibName) {
free(libName);
}
cp++; /* skip the ')' */ cp++; /* skip the ')' */
/* We promised that linePtr would be set up at the next non-space. */ /* We promised that pp would be set up at the next non-space. */
pp_skip_whitespace(&cp); pp_skip_whitespace(&cp);
*linePtr = cp; *pp = cp;
return TRUE; return TRUE;
} }
@ -457,15 +439,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
* Input: * Input:
* archive Path to the archive * archive Path to the archive
* member Name of member; only its basename is used. * member Name of member; only its basename is used.
* hash TRUE if archive should be hashed if not already so. * addToCache TRUE if archive should be cached if not already so.
* *
* Results: * Results:
* The ar_hdr for the member. * The ar_hdr for the member, or NULL.
*
* See ArchFindMember for an almost identical copy of this code.
*/ */
static struct ar_hdr * static struct ar_hdr *
ArchStatMember(const char *archive, const char *member, Boolean hash) ArchStatMember(const char *archive, const char *member, Boolean addToCache)
{ {
#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME) - 1) #define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1)
FILE *arch; /* Stream to archive */ FILE *arch; /* Stream to archive */
size_t size; /* Size of archive member */ size_t size; /* Size of archive member */
char magic[SARMAG]; char magic[SARMAG];
@ -484,8 +468,8 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
member = lastSlash + 1; member = lastSlash + 1;
for (ln = archives->first; ln != NULL; ln = ln->next) { for (ln = archives->first; ln != NULL; ln = ln->next) {
const Arch *archPtr = ln->datum; const Arch *a = ln->datum;
if (strcmp(archPtr->name, archive) == 0) if (strcmp(a->name, archive) == 0)
break; break;
} }
@ -505,17 +489,17 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
if (len > AR_MAX_NAME_LEN) { if (len > AR_MAX_NAME_LEN) {
len = AR_MAX_NAME_LEN; len = AR_MAX_NAME_LEN;
snprintf(copy, sizeof copy, "%s", member); snprintf(copy, sizeof copy, "%s", member);
}
hdr = HashTable_FindValue(&ar->members, copy); hdr = HashTable_FindValue(&ar->members, copy);
}
return hdr; return hdr;
} }
} }
if (!hash) { if (!addToCache) {
/* /*
* Caller doesn't want the thing hashed, just use ArchFindMember * Caller doesn't want the thing cached, just use ArchFindMember
* to read the header for the member out and close down the stream * to read the header for the member out and close down the stream
* again. Since the archive is not to be hashed, we assume there's * again. Since the archive is not to be cached, we assume there's
* no need to allocate extra room for the header we're returning, * no need to allocate extra room for the header we're returning,
* so just declare it static. * so just declare it static.
*/ */
@ -541,43 +525,39 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
* We use the ARMAG string to make sure this is an archive we * We use the ARMAG string to make sure this is an archive we
* can handle... * can handle...
*/ */
if ((fread(magic, SARMAG, 1, arch) != 1) || if (fread(magic, SARMAG, 1, arch) != 1 ||
(strncmp(magic, ARMAG, SARMAG) != 0)) { strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch); (void)fclose(arch);
return NULL; return NULL;
} }
ar = bmake_malloc(sizeof(Arch)); ar = bmake_malloc(sizeof *ar);
ar->name = bmake_strdup(archive); ar->name = bmake_strdup(archive);
ar->fnametab = NULL; ar->fnametab = NULL;
ar->fnamesize = 0; ar->fnamesize = 0;
HashTable_Init(&ar->members); HashTable_Init(&ar->members);
memName[AR_MAX_NAME_LEN] = '\0'; memName[AR_MAX_NAME_LEN] = '\0';
while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { while (fread(&arh, sizeof arh, 1, arch) == 1) {
if (strncmp(arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) {
/*
* The header is bogus, so the archive is bad
* and there's no way we can recover...
*/
goto badarch;
} else {
char *nameend; char *nameend;
/* If the header is bogus, there's no way we can recover. */
if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0)
goto badarch;
/* /*
* We need to advance the stream's pointer to the start of the * We need to advance the stream's pointer to the start of the
* next header. Files are padded with newlines to an even-byte * next header. Files are padded with newlines to an even-byte
* boundary, so we need to extract the size of the file from the * boundary, so we need to extract the size of the file from the
* 'size' field of the header and round it up during the seek. * 'size' field of the header and round it up during the seek.
*/ */
arh.AR_SIZE[sizeof(arh.AR_SIZE) - 1] = '\0'; arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
size = (size_t)strtol(arh.ar_size, NULL, 10); size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
memcpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME)); memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME);
nameend = memName + AR_MAX_NAME_LEN; nameend = memName + AR_MAX_NAME_LEN;
while (*nameend == ' ') { while (nameend > memName && *nameend == ' ')
nameend--; nameend--;
}
nameend[1] = '\0'; nameend[1] = '\0';
#ifdef SVR4ARCHIVES #ifdef SVR4ARCHIVES
@ -607,10 +587,10 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
* BSD 4.4 extended AR format: #1/<namelen>, with name as the * BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file * first <namelen> bytes of the file
*/ */
if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) { ch_isdigit(memName[sizeof AR_EFMT1 - 1])) {
int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]); int elen = atoi(memName + sizeof AR_EFMT1 - 1);
if ((unsigned int)elen > MAXPATHLEN) if ((unsigned int)elen > MAXPATHLEN)
goto badarch; goto badarch;
@ -619,20 +599,18 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
memName[elen] = '\0'; memName[elen] = '\0';
if (fseek(arch, -elen, SEEK_CUR) != 0) if (fseek(arch, -elen, SEEK_CUR) != 0)
goto badarch; goto badarch;
if (DEBUG(ARCH) || DEBUG(MAKE)) { if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchStat: Extended format entry for %s\n", debug_printf("ArchStatMember: Extended format entry for %s\n",
memName); memName);
} }
}
#endif #endif
{ {
HashEntry *he; struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr);
he = HashTable_CreateEntry(&ar->members, memName, NULL); memcpy(cached_hdr, &arh, sizeof arh);
HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr))); HashTable_Set(&ar->members, memName, cached_hdr);
memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr));
}
} }
if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0)
goto badarch; goto badarch;
} }
@ -643,7 +621,7 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
/* /*
* Now that the archive has been read and cached, we can look into * Now that the archive has been read and cached, we can look into
* the hash table to find the desired member's header. * the addToCache table to find the desired member's header.
*/ */
return HashTable_FindValue(&ar->members, member); return HashTable_FindValue(&ar->members, member);
@ -674,15 +652,15 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
*----------------------------------------------------------------------- *-----------------------------------------------------------------------
*/ */
static int static int
ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
{ {
#define ARLONGNAMES1 "//" #define ARLONGNAMES1 "//"
#define ARLONGNAMES2 "/ARFILENAMES" #define ARLONGNAMES2 "/ARFILENAMES"
size_t entry; size_t entry;
char *ptr, *eptr; char *ptr, *eptr;
if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 ||
strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
if (ar->fnametab != NULL) { if (ar->fnametab != NULL) {
DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n"); DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n");
@ -711,51 +689,74 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
return 0; return 0;
} }
if (name[1] == ' ' || name[1] == '\0') if (inout_name[1] == ' ' || inout_name[1] == '\0')
return 2; return 2;
entry = (size_t)strtol(&name[1], &eptr, 0); entry = (size_t)strtol(&inout_name[1], &eptr, 0);
if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) {
DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name); DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name);
return 2; return 2;
} }
if (entry >= ar->fnamesize) { if (entry >= ar->fnamesize) {
DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
name, (unsigned long)ar->fnamesize); inout_name, (unsigned long)ar->fnamesize);
return 2; return 2;
} }
DEBUG2(ARCH, "Replaced %s with %s\n", name, &ar->fnametab[entry]); DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]);
snprintf(name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]);
return 1; return 1;
} }
#endif #endif
/*- static Boolean
*----------------------------------------------------------------------- ArchiveMember_HasName(const struct ar_hdr *hdr,
* ArchFindMember -- const char *name, size_t namelen)
* Locate a member of an archive, given the path of the archive and {
* the path of the desired member. If the archive is to be modified, const size_t ar_name_len = sizeof hdr->AR_NAME;
* the mode should be "r+", if not, it should be "r". const char *ar_name = hdr->AR_NAME;
* The passed struct ar_hdr structure is filled in.
if (strncmp(ar_name, name, namelen) != 0)
return FALSE;
if (namelen >= ar_name_len)
return namelen == ar_name_len;
/* hdr->AR_NAME is space-padded to the right. */
if (ar_name[namelen] == ' ')
return TRUE;
/* In archives created by GNU binutils 2.27, the member names end with
* a slash. */
if (ar_name[namelen] == '/' &&
(namelen == ar_name_len || ar_name[namelen + 1] == ' '))
return TRUE;
return FALSE;
}
/* Locate a member of an archive, given the path of the archive and the path
* of the desired member.
* *
* Input: * Input:
* archive Path to the archive * archive Path to the archive
* member Name of member. If it is a path, only the last * member Name of member. If it is a path, only the last
* component is used. * component is used.
* arhPtr Pointer to header structure to be filled in * out_arh Archive header to be filled in
* mode The mode for opening the stream * mode "r" for read-only access, "r+" for read-write access
* *
* Results: * Output:
* An FILE *, opened for reading and writing, positioned at the * return The archive file, positioned at the start of the
* start of the member's struct ar_hdr, or NULL if the member was * member's struct ar_hdr, or NULL if the member doesn't
* nonexistent. The current struct ar_hdr for member. * exist.
*----------------------------------------------------------------------- * *out_arh The current struct ar_hdr for member.
*
* See ArchStatMember for an almost identical copy of this code.
*/ */
static FILE * static FILE *
ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr, ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
const char *mode) const char *mode)
{ {
FILE *arch; /* Stream to archive */ FILE *arch; /* Stream to archive */
@ -772,8 +773,8 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
* We use the ARMAG string to make sure this is an archive we * We use the ARMAG string to make sure this is an archive we
* can handle... * can handle...
*/ */
if ((fread(magic, SARMAG, 1, arch) != 1) || if (fread(magic, SARMAG, 1, arch) != 1 ||
(strncmp(magic, ARMAG, SARMAG) != 0)) { strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch); fclose(arch);
return NULL; return NULL;
} }
@ -787,13 +788,13 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
member = lastSlash + 1; member = lastSlash + 1;
len = tlen = strlen(member); len = tlen = strlen(member);
if (len > sizeof(arhPtr->AR_NAME)) { if (len > sizeof out_arh->AR_NAME) {
tlen = sizeof(arhPtr->AR_NAME); tlen = sizeof out_arh->AR_NAME;
} }
while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) {
if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG)) != 0) { if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) {
/* /*
* The header is bogus, so the archive is bad * The header is bogus, so the archive is bad
* and there's no way we can recover... * and there's no way we can recover...
@ -802,25 +803,21 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
return NULL; return NULL;
} }
if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) { DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
/* archive,
* If the member's name doesn't take up the entire 'name' field, (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
* we have to be careful of matching prefixes. Names are space- (int)sizeof out_arh->ar_date, out_arh->ar_date);
* padded to the right, so if the character in 'name' at the end
* of the matched string is anything but a space, this isn't the
* member we sought.
*/
if (tlen != sizeof arhPtr->AR_NAME && arhPtr->AR_NAME[tlen] != ' ')
goto skip;
if (ArchiveMember_HasName(out_arh, member, len)) {
/* /*
* To make life easier, we reposition the file at the start * To make life easier for callers that want to update the
* archive, we reposition the file at the start
* of the header we just read before we return the stream. * of the header we just read before we return the stream.
* In a more general situation, it might be better to leave * In a more general situation, it might be better to leave
* the file at the actual member, rather than its header, but * the file at the actual member, rather than its header, but
* not here... * not here.
*/ */
if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) { if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) {
fclose(arch); fclose(arch);
return NULL; return NULL;
} }
@ -832,10 +829,10 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
* BSD 4.4 extended AR format: #1/<namelen>, with name as the * BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file * first <namelen> bytes of the file
*/ */
if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1])) ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))
{ {
int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]); int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]);
char ename[MAXPATHLEN + 1]; char ename[MAXPATHLEN + 1];
if ((unsigned int)elen > MAXPATHLEN) { if ((unsigned int)elen > MAXPATHLEN) {
@ -847,9 +844,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
return NULL; return NULL;
} }
ename[elen] = '\0'; ename[elen] = '\0';
if (DEBUG(ARCH) || DEBUG(MAKE)) { if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchFind: Extended format entry for %s\n", ename); debug_printf("ArchFindMember: Extended format entry for %s\n",
} ename);
if (strncmp(ename, member, len) == 0) { if (strncmp(ename, member, len) == 0) {
/* Found as extended name */ /* Found as extended name */
if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen, if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen,
@ -866,7 +863,6 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
} }
#endif #endif
skip:
/* /*
* This isn't the member we're after, so we need to advance the * This isn't the member we're after, so we need to advance the
* stream's pointer to the start of the next header. Files are * stream's pointer to the start of the next header. Files are
@ -874,113 +870,89 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
* extract the size of the file from the 'size' field of the * extract the size of the file from the 'size' field of the
* header and round it up during the seek. * header and round it up during the seek.
*/ */
arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0'; out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0';
size = (int)strtol(arhPtr->ar_size, NULL, 10); size = (int)strtol(out_arh->AR_SIZE, NULL, 10);
if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
fclose(arch); fclose(arch);
return NULL; return NULL;
} }
} }
/*
* We've looked everywhere, but the member is not to be found. Close the
* archive and return NULL -- an error.
*/
fclose(arch); fclose(arch);
return NULL; return NULL;
} }
/*- /* Touch a member of an archive, on disk.
*----------------------------------------------------------------------- * The GNode's modification time is left as-is.
* Arch_Touch -- *
* Touch a member of an archive. * The st_mtime of the entire archive is also changed.
* The modification time of the entire archive is also changed. * For a library, it may be required to run ranlib after this.
* For a library, this could necessitate the re-ranlib'ing of the
* whole thing.
* *
* Input: * Input:
* gn Node of member to touch * gn Node of member to touch
* *
* Results: * Results:
* The 'time' field of the member's header is updated. * The 'time' field of the member's header is updated.
*-----------------------------------------------------------------------
*/ */
void void
Arch_Touch(GNode *gn) Arch_Touch(GNode *gn)
{ {
FILE *arch; /* Stream open to archive, positioned properly */ FILE *f;
struct ar_hdr arh; /* Current header describing member */ struct ar_hdr arh;
arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+");
&arh, "r+"); if (f == NULL)
return;
snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now); snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
(void)fwrite(&arh, sizeof arh, 1, f);
if (arch != NULL) { fclose(f); /* TODO: handle errors */
(void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
fclose(arch);
}
} }
/* Given a node which represents a library, touch the thing, making sure that /* Given a node which represents a library, touch the thing, making sure that
* the table of contents also is touched. * the table of contents is also touched.
* *
* Both the modification time of the library and of the RANLIBMAG member are * Both the modification time of the library and of the RANLIBMAG member are
* set to 'now'. * set to 'now'. */
*
* Input:
* gn The node of the library to touch
*/
void void
Arch_TouchLib(GNode *gn) Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
{ {
#ifdef RANLIBMAG #ifdef RANLIBMAG
FILE * arch; /* Stream open to archive */ FILE *f;
struct ar_hdr arh; /* Header describing table of contents */ struct ar_hdr arh; /* Header describing table of contents */
struct utimbuf times; /* Times for utime() call */ struct utimbuf times;
arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now); if (f == NULL)
return;
if (arch != NULL) { snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
(void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); (void)fwrite(&arh, sizeof arh, 1, f);
fclose(arch); fclose(f); /* TODO: handle errors */
times.actime = times.modtime = now; times.actime = times.modtime = now;
utime(gn->path, &times); utime(gn->path, &times); /* TODO: handle errors */
}
#else
(void)gn;
#endif #endif
} }
/* Return the modification time of a member of an archive. The mtime field /* Update the mtime of the GNode with the mtime from the archive member on
* of the given node is filled in with the value returned by the function. * disk (or in the cache). */
* void
* Input: Arch_UpdateMTime(GNode *gn)
* gn Node describing archive member
*/
time_t
Arch_MTime(GNode *gn)
{ {
struct ar_hdr *arhPtr; /* Header of desired member */ struct ar_hdr *arh;
time_t modTime; /* Modification time as an integer */
arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
if (arhPtr != NULL) { if (arh != NULL)
modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10); gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10);
} else { else
modTime = 0; gn->mtime = 0;
} }
gn->mtime = modTime; /* Given a non-existent archive member's node, update gn->mtime from its
return modTime; * archived form, if it exists. */
} void
Arch_UpdateMemberMTime(GNode *gn)
/* Given a non-existent archive member's node, get its modification time from
* its archived form, if it exists. gn->mtime is filled in as well. */
time_t
Arch_MemMTime(GNode *gn)
{ {
GNodeListNode *ln; GNodeListNode *ln;
@ -1001,7 +973,8 @@ Arch_MemMTime(GNode *gn)
if ((pgn->flags & REMAKE) && if ((pgn->flags & REMAKE) &&
strncmp(nameStart, gn->name, nameLen) == 0) { strncmp(nameStart, gn->name, nameLen) == 0) {
gn->mtime = Arch_MTime(pgn); Arch_UpdateMTime(pgn);
gn->mtime = pgn->mtime;
} }
} else if (pgn->flags & REMAKE) { } else if (pgn->flags & REMAKE) {
/* /*
@ -1012,8 +985,6 @@ Arch_MemMTime(GNode *gn)
break; break;
} }
} }
return gn->mtime;
} }
/* Search for a library along the given search path. /* Search for a library along the given search path.
@ -1045,13 +1016,15 @@ Arch_FindLib(GNode *gn, SearchPath *path)
} }
/* Decide if a node with the OP_LIB attribute is out-of-date. Called from /* Decide if a node with the OP_LIB attribute is out-of-date. Called from
* Make_OODate to make its life easier. * GNode_IsOODate to make its life easier.
* The library will be hashed if it hasn't been already. * The library is cached if it hasn't been already.
* *
* There are several ways for a library to be out-of-date that are * There are several ways for a library to be out-of-date that are
* not available to ordinary files. In addition, there are ways * not available to ordinary files. In addition, there are ways
* that are open to regular files that are not available to * that are open to regular files that are not available to
* libraries. A library that is only used as a source is never * libraries.
*
* A library that is only used as a source is never
* considered out-of-date by itself. This does not preclude the * considered out-of-date by itself. This does not preclude the
* library's modification time from making its parent be out-of-date. * library's modification time from making its parent be out-of-date.
* A library will be considered out-of-date for any of these reasons, * A library will be considered out-of-date for any of these reasons,
@ -1066,16 +1039,10 @@ Arch_FindLib(GNode *gn, SearchPath *path)
* *
* The modification time of one of its sources is greater than the one * The modification time of one of its sources is greater than the one
* of its RANLIBMAG member (i.e. its table of contents is out-of-date). * of its RANLIBMAG member (i.e. its table of contents is out-of-date).
* We don't compare of the archive time vs. TOC time because they can be * We don't compare the archive time vs. TOC time because they can be
* too close. In my opinion we should not bother with the TOC at all * too close. In my opinion we should not bother with the TOC at all
* since this is used by 'ar' rules that affect the data contents of the * since this is used by 'ar' rules that affect the data contents of the
* archive, not by ranlib rules, which affect the TOC. * archive, not by ranlib rules, which affect the TOC.
*
* Input:
* gn The library's graph node
*
* Results:
* TRUE if the library is out-of-date. FALSE otherwise.
*/ */
Boolean Boolean
Arch_LibOODate(GNode *gn) Arch_LibOODate(GNode *gn)
@ -1093,25 +1060,23 @@ Arch_LibOODate(GNode *gn)
oodate = TRUE; oodate = TRUE;
} else { } else {
#ifdef RANLIBMAG #ifdef RANLIBMAG
struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ struct ar_hdr *arh; /* Header for __.SYMDEF */
int modTimeTOC; /* The table-of-contents's mod time */ int modTimeTOC; /* The table-of-contents's mod time */
arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
if (arhPtr != NULL) { if (arh != NULL) {
modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10); modTimeTOC = (int)strtol(arh->ar_date, NULL, 10);
if (DEBUG(ARCH) || DEBUG(MAKE)) { if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); debug_printf("%s modified %s...",
} RANLIBMAG, Targ_FmtTime(modTimeTOC));
oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC); oodate = gn->youngestChild == NULL ||
gn->youngestChild->mtime > modTimeTOC;
} else { } else {
/* /* A library without a table of contents is out-of-date. */
* A library w/o a table of contents is out-of-date if (DEBUG(ARCH) || DEBUG(MAKE))
*/ debug_printf("no toc...");
if (DEBUG(ARCH) || DEBUG(MAKE)) {
debug_printf("No t.o.c....");
}
oodate = TRUE; oodate = TRUE;
} }
#else #else

19
bmake.1
View file

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ .\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
.\" .\"
.\" Copyright (c) 1990, 1993 .\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved. .\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\" .\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\" .\"
.Dd November 1, 2020 .Dd November 14, 2020
.Dt BMAKE 1 .Dt BMAKE 1
.Os .Os
.Sh NAME .Sh NAME
@ -37,7 +37,7 @@
.Nd maintain program dependencies .Nd maintain program dependencies
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl BeikNnqrstWwX .Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory .Op Fl C Ar directory
.Op Fl D Ar variable .Op Fl D Ar variable
.Op Fl d Ar flags .Op Fl d Ar flags
@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are
up-to-date and 1, otherwise. up-to-date and 1, otherwise.
.It Fl r .It Fl r
Do not use the built-in rules specified in the system makefile. Do not use the built-in rules specified in the system makefile.
.It Fl S
Stop processing if an error is encountered.
This is the default behavior and the opposite of
.Fl k .
.It Fl s .It Fl s
Do not echo any commands as they are executed. Do not echo any commands as they are executed.
Equivalent to specifying Equivalent to specifying
@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and and
.Ql Ev PWD .Ql Ev PWD
to that directory before executing any targets. to that directory before executing any targets.
.Pp
Except in the case of an explicit
.Ql Ic .OBJDIR
target,
.Nm
will check that the specified directory is writable and ignore it if not.
This check can be skipped by setting the environment variable
.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE
to "no".
. .
.It Va .PARSEDIR .It Va .PARSEDIR
A path to the directory of the current A path to the directory of the current

View file

@ -4,7 +4,7 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
bmake -- maintain program dependencies bmake -- maintain program dependencies
SYNOPSIS SYNOPSIS
bmake [-BeikNnqrstWwX] [-C directory] [-D variable] [-d flags] bmake [-BeikNnqrSstWwX] [-C directory] [-D variable] [-d flags]
[-f makefile] [-I directory] [-J private] [-j max_jobs] [-f makefile] [-I directory] [-J private] [-j max_jobs]
[-m directory] [-T file] [-V variable] [-v variable] [-m directory] [-T file] [-V variable] [-v variable]
[variable=value] [target ...] [variable=value] [target ...]
@ -205,6 +205,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
-r Do not use the built-in rules specified in the system makefile. -r Do not use the built-in rules specified in the system makefile.
-S Stop processing if an error is encountered. This is the default
behavior and the opposite of -k.
-s Do not echo any commands as they are executed. Equivalent to -s Do not echo any commands as they are executed. Equivalent to
specifying `@' before each command line in the makefile. specifying `@' before each command line in the makefile.
@ -718,6 +721,12 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
the specified directory if it exists, and set `.OBJDIR' the specified directory if it exists, and set `.OBJDIR'
and `PWD' to that directory before executing any targets. and `PWD' to that directory before executing any targets.
Except in the case of an explicit `.OBJDIR' target, bmake
will check that the specified directory is writable and
ignore it if not. This check can be skipped by setting
the environment variable `MAKE_OBJDIR_CHECK_WRITABLE' to
"no".
.PARSEDIR A path to the directory of the current `Makefile' being .PARSEDIR A path to the directory of the current `Makefile' being
parsed. parsed.
@ -1568,4 +1577,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
There is no way of escaping a space character in a filename. There is no way of escaping a space character in a filename.
FreeBSD 11.3 November 1, 2020 FreeBSD 11.3 FreeBSD 11.3 November 14, 2020 FreeBSD 11.3

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View file

@ -107,6 +107,10 @@
# set "machine_arch" to override that determined by # set "machine_arch" to override that determined by
# machine.sh # machine.sh
# #
# --with-force_machine_arch="machine_arch"
# force "machine_arch" to override that determined by
# machine.sh
#
# --with-default-sys-path="syspath" # --with-default-sys-path="syspath"
# set an explicit default "syspath" which is where bmake # set an explicit default "syspath" which is where bmake
# will look for sys.mk and friends. # will look for sys.mk and friends.
@ -115,7 +119,7 @@
# Simon J. Gerraty <sjg@crufty.net> # Simon J. Gerraty <sjg@crufty.net>
# RCSid: # RCSid:
# $Id: boot-strap,v 1.53 2020/09/16 02:12:01 sjg Exp $ # $Id: boot-strap,v 1.54 2020/11/13 21:47:25 sjg Exp $
# #
# @(#) Copyright (c) 2001 Simon J. Gerraty # @(#) Copyright (c) 2001 Simon J. Gerraty
# #

17
buf.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $ */ /* $NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -75,7 +75,7 @@
#include "make.h" #include "make.h"
/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $"); MAKE_RCSID("$NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $");
/* Make space in the buffer for adding a single byte. */ /* Make space in the buffer for adding a single byte. */
void void
@ -155,19 +155,22 @@ Buf_Empty(Buffer *buf)
buf->data[0] = '\0'; buf->data[0] = '\0';
} }
/* Initialize a buffer. /* Initialize a buffer. */
* If the given initial capacity is 0, a reasonable default is used. */
void void
Buf_Init(Buffer *buf, size_t cap) Buf_InitSize(Buffer *buf, size_t cap)
{ {
if (cap <= 0)
cap = 256;
buf->cap = cap; buf->cap = cap;
buf->len = 0; buf->len = 0;
buf->data = bmake_malloc(cap); buf->data = bmake_malloc(cap);
buf->data[0] = '\0'; buf->data[0] = '\0';
} }
void
Buf_Init(Buffer *buf)
{
Buf_InitSize(buf, 256);
}
/* Reset the buffer. /* Reset the buffer.
* If freeData is TRUE, the data from the buffer is freed as well. * If freeData is TRUE, the data from the buffer is freed as well.
* Otherwise it is kept and returned. */ * Otherwise it is kept and returned. */

11
buf.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: buf.h,v 1.34 2020/09/27 16:59:02 rillig Exp $ */ /* $NetBSD: buf.h,v 1.36 2020/11/10 00:32:12 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -94,7 +94,7 @@ typedef struct Buffer {
void Buf_Expand_1(Buffer *); void Buf_Expand_1(Buffer *);
/* Buf_AddByte adds a single byte to a buffer. */ /* Buf_AddByte adds a single byte to a buffer. */
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
Buf_AddByte(Buffer *buf, char byte) Buf_AddByte(Buffer *buf, char byte)
{ {
size_t old_len = buf->len++; size_t old_len = buf->len++;
@ -106,13 +106,13 @@ Buf_AddByte(Buffer *buf, char byte)
end[1] = '\0'; end[1] = '\0';
} }
static inline MAKE_ATTR_UNUSED size_t MAKE_INLINE size_t
Buf_Len(const Buffer *buf) Buf_Len(const Buffer *buf)
{ {
return buf->len; return buf->len;
} }
static inline MAKE_ATTR_UNUSED Boolean MAKE_INLINE Boolean
Buf_EndsWith(const Buffer *buf, char ch) Buf_EndsWith(const Buffer *buf, char ch)
{ {
return buf->len > 0 && buf->data[buf->len - 1] == ch; return buf->len > 0 && buf->data[buf->len - 1] == ch;
@ -124,7 +124,8 @@ void Buf_AddStr(Buffer *, const char *);
void Buf_AddInt(Buffer *, int); void Buf_AddInt(Buffer *, int);
char *Buf_GetAll(Buffer *, size_t *); char *Buf_GetAll(Buffer *, size_t *);
void Buf_Empty(Buffer *); void Buf_Empty(Buffer *);
void Buf_Init(Buffer *, size_t); void Buf_Init(Buffer *);
void Buf_InitSize(Buffer *, size_t);
char *Buf_Destroy(Buffer *, Boolean); char *Buf_Destroy(Buffer *, Boolean);
char *Buf_DestroyCompact(Buffer *); char *Buf_DestroyCompact(Buffer *);

View file

@ -1,4 +1,4 @@
/* $NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $ */ /* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -99,15 +99,15 @@
#include "pathnames.h" #include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $"); MAKE_RCSID("$NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $");
static GNode *curTarg = NULL; static GNode *curTarg = NULL;
static pid_t compatChild; static pid_t compatChild;
static int compatSigno; static int compatSigno;
/* /*
* CompatDeleteTarget -- delete a failed, interrupted, or otherwise * CompatDeleteTarget -- delete the file of a failed, interrupted, or
* duffed target if not inhibited by .PRECIOUS. * otherwise duffed target if not inhibited by .PRECIOUS.
*/ */
static void static void
CompatDeleteTarget(GNode *gn) CompatDeleteTarget(GNode *gn)
@ -132,8 +132,6 @@ CompatDeleteTarget(GNode *gn)
static void static void
CompatInterrupt(int signo) CompatInterrupt(int signo)
{ {
GNode *gn;
CompatDeleteTarget(curTarg); CompatDeleteTarget(curTarg);
if (curTarg != NULL && !Targ_Precious(curTarg)) { if (curTarg != NULL && !Targ_Precious(curTarg)) {
@ -141,7 +139,7 @@ CompatInterrupt(int signo)
* Run .INTERRUPT only if hit with interrupt signal * Run .INTERRUPT only if hit with interrupt signal
*/ */
if (signo == SIGINT) { if (signo == SIGINT) {
gn = Targ_FindNode(".INTERRUPT"); GNode *gn = Targ_FindNode(".INTERRUPT");
if (gn != NULL) { if (gn != NULL) {
Compat_Make(gn, gn); Compat_Make(gn, gn);
} }
@ -206,7 +204,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
(void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
/* TODO: handle errors */ /* TODO: handle errors */
if (*cmdStart == '\0') { if (cmdStart[0] == '\0') {
free(cmdStart); free(cmdStart);
return 0; return 0;
} }
@ -225,20 +223,17 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
return 0; return 0;
} }
while (*cmd == '@' || *cmd == '-' || *cmd == '+') { for (;;) {
switch (*cmd) { if (*cmd == '@')
case '@':
silent = !DEBUG(LOUD); silent = !DEBUG(LOUD);
break; else if (*cmd == '-')
case '-':
errCheck = FALSE; errCheck = FALSE;
break; else if (*cmd == '+') {
case '+':
doIt = TRUE; doIt = TRUE;
if (!shellName) /* we came here from jobs */ if (!shellName) /* we came here from jobs */
Shell_Init(); Shell_Init();
} else
break; break;
}
cmd++; cmd++;
} }
@ -248,7 +243,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
/* /*
* If we did not end up with a command, just skip it. * If we did not end up with a command, just skip it.
*/ */
if (!*cmd) if (cmd[0] == '\0')
return 0; return 0;
#if !defined(MAKE_NATIVE) #if !defined(MAKE_NATIVE)
@ -286,9 +281,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
* If we're not supposed to execute any commands, this is as far as * If we're not supposed to execute any commands, this is as far as
* we go... * we go...
*/ */
if (!doIt && !GNode_ShouldExecute(gn)) { if (!doIt && !GNode_ShouldExecute(gn))
return 0; return 0;
}
DEBUG1(JOB, "Execute: '%s'\n", cmd); DEBUG1(JOB, "Execute: '%s'\n", cmd);
if (useShell) { if (useShell) {
@ -297,20 +292,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
* because the command contains a "meta" character. * because the command contains a "meta" character.
*/ */
static const char *shargv[5]; static const char *shargv[5];
int shargc;
shargc = 0; /* The following work for any of the builtin shell specs. */
int shargc = 0;
shargv[shargc++] = shellPath; shargv[shargc++] = shellPath;
/* if (errCheck && shellErrFlag)
* The following work for any of the builtin shell specs.
*/
if (errCheck && shellErrFlag) {
shargv[shargc++] = shellErrFlag; shargv[shargc++] = shellErrFlag;
} shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
if (DEBUG(SHELL))
shargv[shargc++] = "-xc";
else
shargv[shargc++] = "-c";
shargv[shargc++] = cmd; shargv[shargc++] = cmd;
shargv[shargc] = NULL; shargv[shargc] = NULL;
av = shargv; av = shargv;
@ -389,17 +377,19 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
#endif #endif
if (status != 0) { if (status != 0) {
if (DEBUG(ERROR)) { if (DEBUG(ERROR)) {
const char *cp; const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ", debug_printf("\n*** Failed target: %s\n*** Failed command: ",
gn->name); gn->name);
for (cp = cmd; *cp; ) {
if (ch_isspace(*cp)) { /* Replace runs of whitespace with a single space, to reduce
* the amount of whitespace for multi-line command lines. */
while (*p != '\0') {
if (ch_isspace(*p)) {
debug_printf(" "); debug_printf(" ");
while (ch_isspace(*cp)) cpp_skip_whitespace(&p);
cp++;
} else { } else {
debug_printf("%c", *cp); debug_printf("%c", *p);
cp++; p++;
} }
} }
debug_printf("\n"); debug_printf("\n");
@ -480,16 +470,17 @@ MakeNodes(GNodeList *gnodes, GNode *pgn)
void void
Compat_Make(GNode *gn, GNode *pgn) Compat_Make(GNode *gn, GNode *pgn)
{ {
if (!shellName) /* we came here from jobs */ if (shellName == NULL) /* we came here from jobs */
Shell_Init(); Shell_Init();
if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
/* /*
* First mark ourselves to be made, then apply whatever transformations * First mark ourselves to be made, then apply whatever transformations
* the suffix module thinks are necessary. Once that's done, we can * the suffix module thinks are necessary. Once that's done, we can
* descend and make all our children. If any of them has an error * descend and make all our children. If any of them has an error
* but the -k flag was given, our 'make' field will be set FALSE again. * but the -k flag was given, our 'make' field will be set to FALSE
* This is our signal to not attempt to do anything but abort our * again. This is our signal to not attempt to do anything but abort
* parent as well. * our parent as well.
*/ */
gn->flags |= REMAKE; gn->flags |= REMAKE;
gn->made = BEINGMADE; gn->made = BEINGMADE;
@ -509,10 +500,10 @@ Compat_Make(GNode *gn, GNode *pgn)
* All the children were made ok. Now youngestChild->mtime contains the * All the children were made ok. Now youngestChild->mtime contains the
* modification time of the newest child, we need to find out if we * modification time of the newest child, we need to find out if we
* exist and when we were modified last. The criteria for datedness * exist and when we were modified last. The criteria for datedness
* are defined by the Make_OODate function. * are defined by GNode_IsOODate.
*/ */
DEBUG1(MAKE, "Examining %s...", gn->name); DEBUG1(MAKE, "Examining %s...", gn->name);
if (!Make_OODate(gn)) { if (!GNode_IsOODate(gn)) {
gn->made = UPTODATE; gn->made = UPTODATE;
DEBUG0(MAKE, "up-to-date.\n"); DEBUG0(MAKE, "up-to-date.\n");
goto cohorts; goto cohorts;
@ -523,9 +514,8 @@ Compat_Make(GNode *gn, GNode *pgn)
* If the user is just seeing if something is out-of-date, exit now * If the user is just seeing if something is out-of-date, exit now
* to tell him/her "yes". * to tell him/her "yes".
*/ */
if (opts.queryFlag) { if (opts.queryFlag)
exit(1); exit(1);
}
/* /*
* We need to be re-made. We also have to make sure we've got a $? * We need to be re-made. We also have to make sure we've got a $?
@ -573,15 +563,15 @@ Compat_Make(GNode *gn, GNode *pgn)
if (gn->made != ERROR) { if (gn->made != ERROR) {
/* /*
* If the node was made successfully, mark it so, update * If the node was made successfully, mark it so, update
* its modification time and timestamp all its parents. Note * its modification time and timestamp all its parents.
* that for .ZEROTIME targets, the timestamping isn't done.
* This is to keep its state from affecting that of its parent. * This is to keep its state from affecting that of its parent.
*/ */
gn->made = MADE; gn->made = MADE;
pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; if (Make_Recheck(gn) == 0)
pgn->flags |= FORCE;
if (!(gn->type & OP_EXEC)) { if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE; pgn->flags |= CHILDMADE;
Make_TimeStamp(pgn, gn); GNode_UpdateYoungestChild(pgn, gn);
} }
} else if (opts.keepgoing) { } else if (opts.keepgoing) {
pgn->flags &= ~(unsigned)REMAKE; pgn->flags &= ~(unsigned)REMAKE;
@ -604,15 +594,14 @@ Compat_Make(GNode *gn, GNode *pgn)
pgn->flags &= ~(unsigned)REMAKE; pgn->flags &= ~(unsigned)REMAKE;
break; break;
case MADE: case MADE:
if ((gn->type & OP_EXEC) == 0) { if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE; pgn->flags |= CHILDMADE;
Make_TimeStamp(pgn, gn); GNode_UpdateYoungestChild(pgn, gn);
} }
break; break;
case UPTODATE: case UPTODATE:
if ((gn->type & OP_EXEC) == 0) { if (!(gn->type & OP_EXEC))
Make_TimeStamp(pgn, gn); GNode_UpdateYoungestChild(pgn, gn);
}
break; break;
default: default:
break; break;

559
cond.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */ /* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -72,7 +72,8 @@
/* Handling of conditionals in a makefile. /* Handling of conditionals in a makefile.
* *
* Interface: * Interface:
* Cond_EvalLine Evaluate the conditional. * Cond_EvalLine Evaluate the conditional directive, such as
* '.if <cond>', '.elifnmake <cond>', '.else', '.endif'.
* *
* Cond_EvalCondition * Cond_EvalCondition
* Evaluate the conditional, which is either the argument * Evaluate the conditional, which is either the argument
@ -93,7 +94,7 @@
#include "dir.h" #include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $"); MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $");
/* /*
* The parsing of conditional expressions is based on this grammar: * The parsing of conditional expressions is based on this grammar:
@ -162,7 +163,7 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
* *
* TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc)
* FALSE when CondEvalExpression is called from ApplyModifier_IfElse * FALSE when CondEvalExpression is called from ApplyModifier_IfElse
* since lhs is already expanded and we cannot tell if * since lhs is already expanded, and at that point we cannot tell if
* it was a variable reference or not. * it was a variable reference or not.
*/ */
static Boolean lhsStrict; static Boolean lhsStrict;
@ -173,6 +174,12 @@ is_token(const char *str, const char *tok, size_t len)
return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
} }
static Token
ToToken(Boolean cond)
{
return cond ? TOK_TRUE : TOK_FALSE;
}
/* Push back the most recent token read. We only need one level of this. */ /* Push back the most recent token read. We only need one level of this. */
static void static void
CondParser_PushBack(CondParser *par, Token t) CondParser_PushBack(CondParser *par, Token t)
@ -200,7 +207,7 @@ CondParser_SkipWhitespace(CondParser *par)
* func says whether the argument belongs to an actual function, or * func says whether the argument belongs to an actual function, or
* whether the parsed argument is passed to the default function. * whether the parsed argument is passed to the default function.
* *
* Return the length of the argument. */ * Return the length of the argument, or 0 on error. */
static size_t static size_t
ParseFuncArg(const char **pp, Boolean doEval, const char *func, ParseFuncArg(const char **pp, Boolean doEval, const char *func,
char **out_arg) { char **out_arg) {
@ -213,26 +220,18 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
p++; /* Skip opening '(' - verified by caller */ p++; /* Skip opening '(' - verified by caller */
if (*p == '\0') { if (*p == '\0') {
/* *out_arg = NULL; /* Missing closing parenthesis: */
* No arguments whatsoever. Because 'make' and 'defined' aren't really return 0; /* .if defined( */
* "reserved words", we don't print a message. I think this is better
* than hitting the user with a warning message every time s/he uses
* the word 'make' or 'defined' at the beginning of a symbol...
*/
*out_arg = NULL;
return 0;
} }
while (*p == ' ' || *p == '\t') { cpp_skip_hspace(&p);
p++;
}
Buf_Init(&argBuf, 16); Buf_InitSize(&argBuf, 16);
paren_depth = 0; paren_depth = 0;
for (;;) { for (;;) {
char ch = *p; char ch = *p;
if (ch == 0 || ch == ' ' || ch == '\t') if (ch == '\0' || ch == ' ' || ch == '\t')
break; break;
if ((ch == '&' || ch == '|') && paren_depth == 0) if ((ch == '&' || ch == '|') && paren_depth == 0)
break; break;
@ -244,7 +243,8 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
* though perhaps we should... * though perhaps we should...
*/ */
void *nestedVal_freeIt; void *nestedVal_freeIt;
VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0); VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR
: VARE_NONE;
const char *nestedVal; const char *nestedVal;
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal, (void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal,
&nestedVal_freeIt); &nestedVal_freeIt);
@ -264,9 +264,7 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
*out_arg = Buf_GetAll(&argBuf, &argLen); *out_arg = Buf_GetAll(&argBuf, &argLen);
Buf_Destroy(&argBuf, FALSE); Buf_Destroy(&argBuf, FALSE);
while (*p == ' ' || *p == '\t') { cpp_skip_hspace(&p);
p++;
}
if (func != NULL && *p++ != ')') { if (func != NULL && *p++ != ')') {
Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
@ -309,13 +307,10 @@ FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
char *path; char *path;
path = Dir_FindFile(arg, dirSearchPath); path = Dir_FindFile(arg, dirSearchPath);
DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : ""); DEBUG2(COND, "exists(%s) result is \"%s\"\n",
if (path != NULL) { arg, path != NULL ? path : "");
result = TRUE; result = path != NULL;
free(path); free(path);
} else {
result = FALSE;
}
return result; return result;
} }
@ -336,40 +331,41 @@ FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands); return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands);
} }
/*- /*
* Convert the given number into a double. * Convert the given number into a double.
* We try a base 10 or 16 integer conversion first, if that fails * We try a base 10 or 16 integer conversion first, if that fails
* then we try a floating point conversion instead. * then we try a floating point conversion instead.
* *
* Results: * Results:
* Sets 'value' to double value of string.
* Returns TRUE if the conversion succeeded. * Returns TRUE if the conversion succeeded.
* Sets 'out_value' to the converted number.
*/ */
static Boolean static Boolean
TryParseNumber(const char *str, double *value) TryParseNumber(const char *str, double *out_value)
{ {
char *eptr, ech; char *end;
unsigned long l_val; unsigned long ul_val;
double d_val; double dbl_val;
errno = 0; errno = 0;
if (!*str) { if (str[0] == '\0') { /* XXX: why is an empty string a number? */
*value = 0.0; *out_value = 0.0;
return TRUE; return TRUE;
} }
l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
ech = *eptr; ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
if (ech == '\0' && errno != ERANGE) { if (*end == '\0' && errno != ERANGE) {
d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
} else { return TRUE;
if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E')
return FALSE;
d_val = strtod(str, &eptr);
if (*eptr)
return FALSE;
} }
*value = d_val; if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
return FALSE; /* skip the expensive strtod call */
dbl_val = strtod(str, &end);
if (*end != '\0')
return FALSE;
*out_value = dbl_val;
return TRUE; return TRUE;
} }
@ -385,31 +381,31 @@ is_separator(char ch)
* *
* Results: * Results:
* Returns the string, absent any quotes, or NULL on error. * Returns the string, absent any quotes, or NULL on error.
* Sets quoted if the string was quoted. * Sets out_quoted if the string was quoted.
* Sets freeIt if needed. * Sets out_freeIt.
*/ */
/* coverity:[+alloc : arg-*4] */ /* coverity:[+alloc : arg-*4] */
static const char * static const char *
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
Boolean *quoted, void **freeIt) Boolean *out_quoted, void **out_freeIt)
{ {
Buffer buf; Buffer buf;
const char *str; const char *str;
Boolean atStart; Boolean atStart;
const char *nested_p; const char *nested_p;
Boolean qt; Boolean quoted;
const char *start; const char *start;
VarEvalFlags eflags; VarEvalFlags eflags;
VarParseResult parseResult; VarParseResult parseResult;
Buf_Init(&buf, 0); Buf_Init(&buf);
str = NULL; str = NULL;
*freeIt = NULL; *out_freeIt = NULL;
*quoted = qt = par->p[0] == '"' ? 1 : 0; *out_quoted = quoted = par->p[0] == '"';
start = par->p; start = par->p;
if (qt) if (quoted)
par->p++; par->p++;
while (par->p[0] && str == NULL) { while (par->p[0] != '\0' && str == NULL) {
switch (par->p[0]) { switch (par->p[0]) {
case '\\': case '\\':
par->p++; par->p++;
@ -419,40 +415,44 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
} }
continue; continue;
case '"': case '"':
if (qt) { if (quoted) {
par->p++; /* we don't want the quotes */ par->p++; /* skip the closing quote */
goto got_str; goto got_str;
} }
Buf_AddByte(&buf, par->p[0]); /* likely? */ Buf_AddByte(&buf, par->p[0]); /* likely? */
par->p++; par->p++;
continue; continue;
case ')': case ')': /* see is_separator */
case '!': case '!':
case '=': case '=':
case '>': case '>':
case '<': case '<':
case ' ': case ' ':
case '\t': case '\t':
if (!qt) if (!quoted)
goto got_str; goto got_str;
Buf_AddByte(&buf, par->p[0]); Buf_AddByte(&buf, par->p[0]);
par->p++; par->p++;
continue; continue;
case '$': case '$':
/* if we are in quotes, an undefined variable is ok */ /* if we are in quotes, an undefined variable is ok */
eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) | eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
(doEval ? VARE_WANTRES : 0); doEval ? VARE_WANTRES :
VARE_NONE;
nested_p = par->p; nested_p = par->p;
atStart = nested_p == start; atStart = nested_p == start;
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str, parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str,
freeIt); out_freeIt);
/* TODO: handle errors */ /* TODO: handle errors */
if (str == var_Error) { if (str == var_Error) {
if (parseResult & VPR_ANY_MSG) if (parseResult & VPR_ANY_MSG)
par->printedError = TRUE; par->printedError = TRUE;
if (*freeIt) { if (*out_freeIt != NULL) {
free(*freeIt); /* XXX: Can there be any situation in which a returned
*freeIt = NULL; * var_Error requires freeIt? */
free(*out_freeIt);
*out_freeIt = NULL;
} }
/* /*
* Even if !doEval, we still report syntax errors, which * Even if !doEval, we still report syntax errors, which
@ -473,19 +473,15 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
goto cleanup; goto cleanup;
Buf_AddStr(&buf, str); Buf_AddStr(&buf, str);
if (*freeIt) { if (*out_freeIt) {
free(*freeIt); free(*out_freeIt);
*freeIt = NULL; *out_freeIt = NULL;
} }
str = NULL; /* not finished yet */ str = NULL; /* not finished yet */
continue; continue;
default: default:
if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) { if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) {
/* lhs must be quoted, a variable reference or number */ /* lhs must be quoted, a variable reference or number */
if (*freeIt) {
free(*freeIt);
*freeIt = NULL;
}
str = NULL; str = NULL;
goto cleanup; goto cleanup;
} }
@ -495,20 +491,22 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
} }
} }
got_str: got_str:
*freeIt = Buf_GetAll(&buf, NULL); *out_freeIt = Buf_GetAll(&buf, NULL);
str = *freeIt; str = *out_freeIt;
cleanup: cleanup:
Buf_Destroy(&buf, FALSE); Buf_Destroy(&buf, FALSE);
return str; return str;
} }
/* The different forms of .if directives. */ struct If {
static const struct If {
const char *form; /* Form of if */ const char *form; /* Form of if */
size_t formlen; /* Length of form */ size_t formlen; /* Length of form */
Boolean doNot; /* TRUE if default function should be negated */ Boolean doNot; /* TRUE if default function should be negated */
Boolean (*defProc)(size_t, const char *); /* Default function to apply */ Boolean (*defProc)(size_t, const char *); /* Default function to apply */
} ifs[] = { };
/* The different forms of .if directives. */
static const struct If ifs[] = {
{ "def", 3, FALSE, FuncDefined }, { "def", 3, FALSE, FuncDefined },
{ "ndef", 4, TRUE, FuncDefined }, { "ndef", 4, TRUE, FuncDefined },
{ "make", 4, FALSE, FuncMake }, { "make", 4, FALSE, FuncMake },
@ -516,28 +514,39 @@ static const struct If {
{ "", 0, FALSE, FuncDefined }, { "", 0, FALSE, FuncDefined },
{ NULL, 0, FALSE, NULL } { NULL, 0, FALSE, NULL }
}; };
enum { PLAIN_IF_INDEX = 4 };
static Boolean
If_Eval(const struct If *if_info, const char *arg, size_t arglen)
{
Boolean res = if_info->defProc(arglen, arg);
return if_info->doNot ? !res : res;
}
/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or /* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0". */ * ".if 0". */
static Token static Boolean
EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted) EvalNotEmpty(CondParser *par, const char *value, Boolean quoted)
{ {
double left; double num;
/* For .ifxxx "..." check for non-empty string. */ /* For .ifxxx "...", check for non-empty string. */
if (lhsQuoted) if (quoted)
return lhs[0] != '\0'; return value[0] != '\0';
/* For .ifxxx <number> compare against zero */ /* For .ifxxx <number>, compare against zero */
if (TryParseNumber(lhs, &left)) if (TryParseNumber(value, &num))
return left != 0.0; return num != 0.0;
/* For .if ${...} check for non-empty string (defProc is ifdef). */ /* For .if ${...}, check for non-empty string. This is different from
* the evaluation function from that .if variant, which would test
* whether a variable of the given name were defined. */
/* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */
if (par->if_info->form[0] == '\0') if (par->if_info->form[0] == '\0')
return lhs[0] != 0; return value[0] != '\0';
/* Otherwise action default test ... */ /* For the other variants of .ifxxx ${...}, use its default function. */
return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot; return If_Eval(par->if_info, value, strlen(value));
} }
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
@ -553,18 +562,18 @@ EvalCompareNum(double lhs, const char *op, double rhs)
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
return TOK_ERROR; return TOK_ERROR;
} }
return lhs != rhs; return ToToken(lhs != rhs);
case '=': case '=':
if (op[1] != '=') { if (op[1] != '=') {
Parse_Error(PARSE_WARNING, "Unknown operator"); Parse_Error(PARSE_WARNING, "Unknown operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
return TOK_ERROR; return TOK_ERROR;
} }
return lhs == rhs; return ToToken(lhs == rhs);
case '<': case '<':
return op[1] == '=' ? lhs <= rhs : lhs < rhs; return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs);
case '>': case '>':
return op[1] == '=' ? lhs >= rhs : lhs > rhs; return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs);
} }
return TOK_ERROR; return TOK_ERROR;
} }
@ -580,7 +589,7 @@ EvalCompareStr(const char *lhs, const char *op, const char *rhs)
} }
DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op); DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op);
return (*op == '=') == (strcmp(lhs, rhs) == 0); return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0));
} }
/* Evaluate a comparison, such as "${VAR} == 12345". */ /* Evaluate a comparison, such as "${VAR} == 12345". */
@ -609,43 +618,34 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
{ {
Token t = TOK_ERROR; Token t = TOK_ERROR;
const char *lhs, *op, *rhs; const char *lhs, *op, *rhs;
void *lhsFree, *rhsFree; void *lhs_freeIt, *rhs_freeIt;
Boolean lhsQuoted, rhsQuoted; Boolean lhsQuoted, rhsQuoted;
rhs = NULL;
lhsFree = rhsFree = NULL;
lhsQuoted = rhsQuoted = FALSE;
/* /*
* Parse the variable spec and skip over it, saving its * Parse the variable spec and skip over it, saving its
* value in lhs. * value in lhs.
*/ */
lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree); lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt);
if (!lhs) if (lhs == NULL)
goto done; goto done_lhs;
CondParser_SkipWhitespace(par); CondParser_SkipWhitespace(par);
/*
* Make sure the operator is a valid one. If it isn't a
* known relational operator, pretend we got a
* != 0 comparison.
*/
op = par->p; op = par->p;
switch (par->p[0]) { switch (par->p[0]) {
case '!': case '!':
case '=': case '=':
case '<': case '<':
case '>': case '>':
if (par->p[1] == '=') { if (par->p[1] == '=')
par->p += 2; par->p += 2;
} else { else
par->p++; par->p++;
}
break; break;
default: default:
t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE; /* Unknown operator, compare against an empty string or 0. */
goto done; t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted));
goto done_lhs;
} }
CondParser_SkipWhitespace(par); CondParser_SkipWhitespace(par);
@ -653,42 +653,45 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
if (par->p[0] == '\0') { if (par->p[0] == '\0') {
Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */ /* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
goto done; goto done_lhs;
} }
rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree); rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt);
if (rhs == NULL) if (rhs == NULL)
goto done; goto done_rhs;
if (!doEval) { if (!doEval) {
t = TOK_FALSE; t = TOK_FALSE;
goto done; goto done_rhs;
} }
t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted); t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted);
done: done_rhs:
free(lhsFree); free(rhs_freeIt);
free(rhsFree); done_lhs:
free(lhs_freeIt);
return t; return t;
} }
/* The argument to empty() is a variable name, optionally followed by
* variable modifiers. */
static size_t static size_t
ParseEmptyArg(const char **linePtr, Boolean doEval, ParseEmptyArg(const char **pp, Boolean doEval,
const char *func MAKE_ATTR_UNUSED, char **argPtr) const char *func MAKE_ATTR_UNUSED, char **out_arg)
{ {
void *val_freeIt; void *val_freeIt;
const char *val; const char *val;
size_t magic_res; size_t magic_res;
/* We do all the work here and return the result as the length */ /* We do all the work here and return the result as the length */
*argPtr = NULL; *out_arg = NULL;
(*linePtr)--; /* Make (*linePtr)[1] point to the '('. */ (*pp)--; /* Make (*pp)[1] point to the '('. */
(void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0, (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
&val, &val_freeIt); &val, &val_freeIt);
/* TODO: handle errors */ /* TODO: handle errors */
/* If successful, *linePtr points beyond the closing ')' now. */ /* If successful, *pp points beyond the closing ')' now. */
if (val == var_Error) { if (val == var_Error) {
free(val_freeIt); free(val_freeIt);
@ -714,54 +717,71 @@ FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED)
return arglen == 1; return arglen == 1;
} }
static Token static Boolean
CondParser_Func(CondParser *par, Boolean doEval) CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
{ {
static const struct fn_def { static const struct fn_def {
const char *fn_name; const char *fn_name;
size_t fn_name_len; size_t fn_name_len;
size_t (*fn_parse)(const char **, Boolean, const char *, char **); size_t (*fn_parse)(const char **, Boolean, const char *, char **);
Boolean (*fn_eval)(size_t, const char *); Boolean (*fn_eval)(size_t, const char *);
} fn_defs[] = { } fns[] = {
{ "defined", 7, ParseFuncArg, FuncDefined }, { "defined", 7, ParseFuncArg, FuncDefined },
{ "make", 4, ParseFuncArg, FuncMake }, { "make", 4, ParseFuncArg, FuncMake },
{ "exists", 6, ParseFuncArg, FuncExists }, { "exists", 6, ParseFuncArg, FuncExists },
{ "empty", 5, ParseEmptyArg, FuncEmpty }, { "empty", 5, ParseEmptyArg, FuncEmpty },
{ "target", 6, ParseFuncArg, FuncTarget }, { "target", 6, ParseFuncArg, FuncTarget },
{ "commands", 8, ParseFuncArg, FuncCommands }, { "commands", 8, ParseFuncArg, FuncCommands }
{ NULL, 0, NULL, NULL },
}; };
const struct fn_def *fn_def; const struct fn_def *fn;
char *arg = NULL;
size_t arglen;
const char *cp = par->p;
const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0];
for (fn = fns; fn != fns_end; fn++) {
if (!is_token(cp, fn->fn_name, fn->fn_name_len))
continue;
cp += fn->fn_name_len;
cpp_skip_whitespace(&cp);
if (*cp != '(')
break;
arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg);
if (arglen == 0 || arglen == (size_t)-1) {
par->p = cp;
*out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR;
return TRUE;
}
/* Evaluate the argument using the required function. */
*out_token = ToToken(!doEval || fn->fn_eval(arglen, arg));
free(arg);
par->p = cp;
return TRUE;
}
return FALSE;
}
/* Parse a function call, a number, a variable expression or a string
* literal. */
static Token
CondParser_LeafToken(CondParser *par, Boolean doEval)
{
Token t; Token t;
char *arg = NULL; char *arg = NULL;
size_t arglen; size_t arglen;
const char *cp = par->p; const char *cp = par->p;
const char *cp1; const char *cp1;
for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { if (CondParser_Func(par, doEval, &t))
if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len))
continue;
cp += fn_def->fn_name_len;
/* There can only be whitespace before the '(' */
cpp_skip_whitespace(&cp);
if (*cp != '(')
break;
arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg);
if (arglen == 0 || arglen == (size_t)-1) {
par->p = cp;
return arglen == 0 ? TOK_FALSE : TOK_ERROR;
}
/* Evaluate the argument using the required function. */
t = !doEval || fn_def->fn_eval(arglen, arg);
free(arg);
par->p = cp;
return t; return t;
}
/* Push anything numeric through the compare expression */ /* Push anything numeric through the compare expression */
cp = par->p; cp = par->p;
if (ch_isdigit(cp[0]) || strchr("+-", cp[0])) if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+')
return CondParser_Comparison(par, doEval); return CondParser_Comparison(par, doEval);
/* /*
@ -785,7 +805,7 @@ CondParser_Func(CondParser *par, Boolean doEval)
* after .if must have been taken literally, so the argument cannot * after .if must have been taken literally, so the argument cannot
* be empty - even if it contained a variable expansion. * be empty - even if it contained a variable expansion.
*/ */
t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot; t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen));
free(arg); free(arg);
return t; return t;
} }
@ -802,9 +822,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return t; return t;
} }
while (par->p[0] == ' ' || par->p[0] == '\t') { cpp_skip_hspace(&par->p);
par->p++;
}
switch (par->p[0]) { switch (par->p[0]) {
@ -818,15 +836,23 @@ CondParser_Token(CondParser *par, Boolean doEval)
case '|': case '|':
par->p++; par->p++;
if (par->p[0] == '|') { if (par->p[0] == '|')
par->p++; par->p++;
else if (opts.lint) {
Parse_Error(PARSE_FATAL, "Unknown operator '|'");
par->printedError = TRUE;
return TOK_ERROR;
} }
return TOK_OR; return TOK_OR;
case '&': case '&':
par->p++; par->p++;
if (par->p[0] == '&') { if (par->p[0] == '&')
par->p++; par->p++;
else if (opts.lint) {
Parse_Error(PARSE_FATAL, "Unknown operator '&'");
par->printedError = TRUE;
return TOK_ERROR;
} }
return TOK_AND; return TOK_AND;
@ -834,8 +860,9 @@ CondParser_Token(CondParser *par, Boolean doEval)
par->p++; par->p++;
return TOK_NOT; return TOK_NOT;
case '#': case '#': /* XXX: see unit-tests/cond-token-plain.mk */
case '\n': case '\n': /* XXX: why should this end the condition? */
/* Probably obsolete now, from 1993-03-21. */
case '\0': case '\0':
return TOK_EOF; return TOK_EOF;
@ -844,7 +871,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return CondParser_Comparison(par, doEval); return CondParser_Comparison(par, doEval);
default: default:
return CondParser_Func(par, doEval); return CondParser_LeafToken(par, doEval);
} }
} }
@ -1003,25 +1030,14 @@ static CondEvalResult
CondEvalExpression(const struct If *info, const char *cond, Boolean *value, CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
Boolean eprint, Boolean strictLHS) Boolean eprint, Boolean strictLHS)
{ {
static const struct If *dflt_info;
CondParser par; CondParser par;
int rval; CondEvalResult rval;
lhsStrict = strictLHS; lhsStrict = strictLHS;
while (*cond == ' ' || *cond == '\t') cpp_skip_hspace(&cond);
cond++;
if (info == NULL && (info = dflt_info) == NULL) { par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX;
/* Scan for the entry for .if - it can't be first */
for (info = ifs;; info++)
if (info->form[0] == 0)
break;
dflt_info = info;
}
assert(info != NULL);
par.if_info = info;
par.p = cond; par.p = cond;
par.curr = TOK_NONE; par.curr = TOK_NONE;
par.printedError = FALSE; par.printedError = FALSE;
@ -1034,123 +1050,154 @@ CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
return rval; return rval;
} }
/* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}. */
CondEvalResult CondEvalResult
Cond_EvalCondition(const char *cond, Boolean *out_value) Cond_EvalCondition(const char *cond, Boolean *out_value)
{ {
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE); return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE);
} }
/* Evaluate the conditional in the passed line. The line looks like this: /* Evaluate the conditional directive in the line, which is one of:
* .<cond-type> <expr>
* In this line, <cond-type> is any of if, ifmake, ifnmake, ifdef, ifndef,
* elif, elifmake, elifnmake, elifdef, elifndef.
* In this line, <expr> consists of &&, ||, !, function(arg), comparisons
* and parenthetical groupings thereof.
* *
* Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order * .if <cond>
* to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF), * .ifmake <cond>
* otherwise .else could be treated as '.elif 1'. * .ifnmake <cond>
* .ifdef <cond>
* .ifndef <cond>
* .elif <cond>
* .elifmake <cond>
* .elifnmake <cond>
* .elifdef <cond>
* .elifndef <cond>
* .else
* .endif
*
* In these directives, <cond> consists of &&, ||, !, function(arg),
* comparisons, expressions, bare words, numbers and strings, and
* parenthetical groupings thereof.
* *
* Results: * Results:
* COND_PARSE to continue parsing the lines after the conditional * COND_PARSE to continue parsing the lines that follow the
* (when .if or .else returns TRUE) * conditional (when <cond> evaluates to TRUE)
* COND_SKIP to skip the lines after the conditional * COND_SKIP to skip the lines after the conditional
* (when .if or .elif returns FALSE, or when a previous * (when <cond> evaluates to FALSE, or when a previous
* branch has already been taken) * branch has already been taken)
* COND_INVALID if the conditional was not valid, either because of * COND_INVALID if the conditional was not valid, either because of
* a syntax error or because some variable was undefined * a syntax error or because some variable was undefined
* or because the condition could not be evaluated * or because the condition could not be evaluated
*/ */
CondEvalResult CondEvalResult
Cond_EvalLine(const char *line) Cond_EvalLine(const char *const line)
{ {
enum { MAXIF = 128 }; /* maximum depth of .if'ing */ typedef enum IfState {
enum { MAXIF_BUMP = 32 }; /* how much to grow by */
enum if_states { /* None of the previous <cond> evaluated to TRUE. */
IF_ACTIVE, /* .if or .elif part active */ IFS_INITIAL = 0,
ELSE_ACTIVE, /* .else part active */
SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ /* The previous <cond> evaluated to TRUE.
SKIP_TO_ELSE, /* has been true, but not seen '.else' */ * The lines following this condition are interpreted. */
SKIP_TO_ENDIF /* nothing else to execute */ IFS_ACTIVE = 1 << 0,
};
static enum if_states *cond_state = NULL; /* The previous directive was an '.else'. */
static unsigned int max_if_depth = MAXIF; IFS_SEEN_ELSE = 1 << 1,
/* One of the previous <cond> evaluated to TRUE. */
IFS_WAS_ACTIVE = 1 << 2
} IfState;
static enum IfState *cond_states = NULL;
static unsigned int cond_states_cap = 128;
const struct If *ifp; const struct If *ifp;
Boolean isElif; Boolean isElif;
Boolean value; Boolean value;
enum if_states state; IfState state;
const char *p = line;
if (!cond_state) { if (cond_states == NULL) {
cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states);
cond_state[0] = IF_ACTIVE; cond_states[0] = IFS_ACTIVE;
} }
/* skip leading character (the '.') and any whitespace */
for (line++; *line == ' ' || *line == '\t'; line++)
continue;
/* Find what type of if we're dealing with. */ p++; /* skip the leading '.' */
if (line[0] == 'e') { cpp_skip_hspace(&p);
if (line[1] != 'l') {
if (!is_token(line + 1, "ndif", 4)) /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
if (p[0] == 'e') {
if (p[1] != 'l') {
if (!is_token(p + 1, "ndif", 4)) {
/* Unknown directive. It might still be a transformation
* rule like '.elisp.scm', therefore no error message here. */
return COND_INVALID; return COND_INVALID;
/* End of conditional section */ }
/* It is an '.endif'. */
/* TODO: check for extraneous <cond> */
if (cond_depth == cond_min_depth) { if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif"); Parse_Error(PARSE_FATAL, "if-less endif");
return COND_PARSE; return COND_PARSE;
} }
/* Return state for previous conditional */ /* Return state for previous conditional */
cond_depth--; cond_depth--;
return cond_state[cond_depth] <= ELSE_ACTIVE return cond_states[cond_depth] & IFS_ACTIVE
? COND_PARSE : COND_SKIP; ? COND_PARSE : COND_SKIP;
} }
/* Quite likely this is 'else' or 'elif' */ /* Quite likely this is 'else' or 'elif' */
line += 2; p += 2;
if (is_token(line, "se", 2)) { if (is_token(p, "se", 2)) { /* It is an 'else'. */
/* It is else... */
if (opts.lint && p[2] != '\0')
Parse_Error(PARSE_FATAL,
"The .else directive does not take arguments.");
if (cond_depth == cond_min_depth) { if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else"); Parse_Error(PARSE_FATAL, "if-less else");
return COND_PARSE; return COND_PARSE;
} }
state = cond_state[cond_depth]; state = cond_states[cond_depth];
switch (state) { if (state == IFS_INITIAL) {
case SEARCH_FOR_ELIF: state = IFS_ACTIVE | IFS_SEEN_ELSE;
state = ELSE_ACTIVE; } else {
break; if (state & IFS_SEEN_ELSE)
case ELSE_ACTIVE:
case SKIP_TO_ENDIF:
Parse_Error(PARSE_WARNING, "extra else"); Parse_Error(PARSE_WARNING, "extra else");
/* FALLTHROUGH */ state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
default:
case IF_ACTIVE:
case SKIP_TO_ELSE:
state = SKIP_TO_ENDIF;
break;
} }
cond_state[cond_depth] = state; cond_states[cond_depth] = state;
return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
} }
/* Assume for now it is an elif */ /* Assume for now it is an elif */
isElif = TRUE; isElif = TRUE;
} else } else
isElif = FALSE; isElif = FALSE;
if (line[0] != 'i' || line[1] != 'f') if (p[0] != 'i' || p[1] != 'f') {
/* Not an ifxxx or elifxxx line */ /* Unknown directive. It might still be a transformation rule like
return COND_INVALID; * '.elisp.scm', therefore no error message here. */
return COND_INVALID; /* Not an ifxxx or elifxxx line */
}
/* /*
* Figure out what sort of conditional it is -- what its default * Figure out what sort of conditional it is -- what its default
* function is, etc. -- by looking in the table of valid "ifs" * function is, etc. -- by looking in the table of valid "ifs"
*/ */
line += 2; p += 2;
for (ifp = ifs;; ifp++) { for (ifp = ifs;; ifp++) {
if (ifp->form == NULL) if (ifp->form == NULL) {
/* TODO: Add error message about unknown directive,
* since there is no other known directive that starts with 'el'
* or 'if'.
* Example: .elifx 123 */
return COND_INVALID; return COND_INVALID;
if (is_token(ifp->form, line, ifp->formlen)) { }
line += ifp->formlen; if (is_token(p, ifp->form, ifp->formlen)) {
p += ifp->formlen;
break; break;
} }
} }
@ -1162,51 +1209,51 @@ Cond_EvalLine(const char *line)
Parse_Error(PARSE_FATAL, "if-less elif"); Parse_Error(PARSE_FATAL, "if-less elif");
return COND_PARSE; return COND_PARSE;
} }
state = cond_state[cond_depth]; state = cond_states[cond_depth];
if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { if (state & IFS_SEEN_ELSE) {
Parse_Error(PARSE_WARNING, "extra elif"); Parse_Error(PARSE_WARNING, "extra elif");
cond_state[cond_depth] = SKIP_TO_ENDIF; cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
return COND_SKIP; return COND_SKIP;
} }
if (state != SEARCH_FOR_ELIF) { if (state != IFS_INITIAL) {
/* Either just finished the 'true' block, or already SKIP_TO_ELSE */ cond_states[cond_depth] = IFS_WAS_ACTIVE;
cond_state[cond_depth] = SKIP_TO_ELSE;
return COND_SKIP; return COND_SKIP;
} }
} else { } else {
/* Normal .if */ /* Normal .if */
if (cond_depth + 1 >= max_if_depth) { if (cond_depth + 1 >= cond_states_cap) {
/* /*
* This is rare, but not impossible. * This is rare, but not impossible.
* In meta mode, dirdeps.mk (only runs at level 0) * In meta mode, dirdeps.mk (only runs at level 0)
* can need more than the default. * can need more than the default.
*/ */
max_if_depth += MAXIF_BUMP; cond_states_cap += 32;
cond_state = bmake_realloc(cond_state, cond_states = bmake_realloc(cond_states,
max_if_depth * sizeof(*cond_state)); cond_states_cap * sizeof *cond_states);
} }
state = cond_state[cond_depth]; state = cond_states[cond_depth];
cond_depth++; cond_depth++;
if (state > ELSE_ACTIVE) { if (!(state & IFS_ACTIVE)) {
/* If we aren't parsing the data, treat as always false */ /* If we aren't parsing the data, treat as always false */
cond_state[cond_depth] = SKIP_TO_ELSE; cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP; return COND_SKIP;
} }
} }
/* And evaluate the conditional expression */ /* And evaluate the conditional expression */
if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) { if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) {
/* Syntax error in conditional, error message already output. */ /* Syntax error in conditional, error message already output. */
/* Skip everything to matching .endif */ /* Skip everything to matching .endif */
cond_state[cond_depth] = SKIP_TO_ELSE; /* XXX: An extra '.else' is not detected in this case. */
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP; return COND_SKIP;
} }
if (!value) { if (!value) {
cond_state[cond_depth] = SEARCH_FOR_ELIF; cond_states[cond_depth] = IFS_INITIAL;
return COND_SKIP; return COND_SKIP;
} }
cond_state[cond_depth] = IF_ACTIVE; cond_states[cond_depth] = IFS_ACTIVE;
return COND_PARSE; return COND_PARSE;
} }

24
configure vendored
View file

@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for bmake 20201018. # Generated by GNU Autoconf 2.69 for bmake 20201112.
# #
# Report bugs to <sjg@NetBSD.org>. # Report bugs to <sjg@NetBSD.org>.
# #
@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='bmake' PACKAGE_NAME='bmake'
PACKAGE_TARNAME='bmake' PACKAGE_TARNAME='bmake'
PACKAGE_VERSION='20201018' PACKAGE_VERSION='20201112'
PACKAGE_STRING='bmake 20201018' PACKAGE_STRING='bmake 20201112'
PACKAGE_BUGREPORT='sjg@NetBSD.org' PACKAGE_BUGREPORT='sjg@NetBSD.org'
PACKAGE_URL='' PACKAGE_URL=''
@ -631,6 +631,7 @@ GCC
INSTALL INSTALL
default_sys_path default_sys_path
mksrc mksrc
force_machine_arch
machine_arch machine_arch
force_machine force_machine
machine machine
@ -1254,7 +1255,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
\`configure' configures bmake 20201018 to adapt to many kinds of systems. \`configure' configures bmake 20201112 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1315,7 +1316,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of bmake 20201018:";; short | recursive ) echo "Configuration of bmake 20201112:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@ -1421,7 +1422,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
bmake configure 20201018 bmake configure 20201112
generated by GNU Autoconf 2.69 generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc. Copyright (C) 2012 Free Software Foundation, Inc.
@ -2001,7 +2002,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by bmake $as_me 20201018, which was It was created by bmake $as_me 20201112, which was
generated by GNU Autoconf 2.69. Invocation command line was generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@ $ $0 $@
@ -6045,7 +6046,7 @@ if test "${with_force_machine_arch+set}" = set; then :
withval=$with_force_machine_arch; case "${withval}" in withval=$with_force_machine_arch; case "${withval}" in
yes) force_machine_arch=FORCE_;; yes) force_machine_arch=FORCE_;;
no) ;; no) ;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;; *) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac esac
fi fi
@ -6059,7 +6060,7 @@ no) ;;
esac esac
fi fi
echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6
default_sys_path=\${prefix}/share/mk default_sys_path=\${prefix}/share/mk
# Check whether --with-default-sys-path was given. # Check whether --with-default-sys-path was given.
@ -6151,6 +6152,7 @@ fi
bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh" bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh"
if test $use_makefile = yes; then if test $use_makefile = yes; then
bm_outfiles="makefile $bm_outfiles" bm_outfiles="makefile $bm_outfiles"
@ -6664,7 +6666,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by bmake $as_me 20201018, which was This file was extended by bmake $as_me 20201112, which was
generated by GNU Autoconf 2.69. Invocation command line was generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@ -6726,7 +6728,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\ ac_cs_version="\\
bmake config.status 20201018 bmake config.status 20201112
configured by $0, generated by GNU Autoconf 2.69, configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"

View file

@ -1,11 +1,11 @@
dnl dnl
dnl RCSid: dnl RCSid:
dnl $Id: configure.in,v 1.67 2020/10/19 19:47:50 sjg Exp $ dnl $Id: configure.in,v 1.69 2020/11/14 07:40:43 sjg Exp $
dnl dnl
dnl Process this file with autoconf to produce a configure script dnl Process this file with autoconf to produce a configure script
dnl dnl
AC_PREREQ(2.50) AC_PREREQ(2.50)
AC_INIT([bmake], [20201018], [sjg@NetBSD.org]) AC_INIT([bmake], [20201112], [sjg@NetBSD.org])
AC_CONFIG_HEADERS(config.h) AC_CONFIG_HEADERS(config.h)
dnl make srcdir absolute dnl make srcdir absolute
@ -308,7 +308,7 @@ AC_ARG_WITH(force_machine_arch,
[case "${withval}" in [case "${withval}" in
yes) force_machine_arch=FORCE_;; yes) force_machine_arch=FORCE_;;
no) ;; no) ;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;; *) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac]) esac])
dnl dnl
AC_ARG_WITH(machine_arch, AC_ARG_WITH(machine_arch,
@ -321,7 +321,7 @@ esac])
dnl dnl
dnl Tell them what we ended up with dnl Tell them what we ended up with
dnl dnl
echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6 echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6
dnl dnl
dnl Allow folk to control _PATH_DEFSYSPATH dnl Allow folk to control _PATH_DEFSYSPATH
dnl dnl
@ -407,6 +407,7 @@ dnl
AC_SUBST(machine) AC_SUBST(machine)
AC_SUBST(force_machine) AC_SUBST(force_machine)
AC_SUBST(machine_arch) AC_SUBST(machine_arch)
AC_SUBST(force_machine_arch)
AC_SUBST(mksrc) AC_SUBST(mksrc)
AC_SUBST(default_sys_path) AC_SUBST(default_sys_path)
AC_SUBST(INSTALL) AC_SUBST(INSTALL)

222
dir.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $ */ /* $NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -100,9 +100,9 @@
* then all the directories above it in turn until * then all the directories above it in turn until
* the path is found or we reach the root ("/"). * the path is found or we reach the root ("/").
* *
* Dir_MTime Return the modification time of a node. The file * Dir_UpdateMTime
* is searched for along the default search path. * Update the modification time and path of a node with
* The path and mtime fields of the node are filled in. * data from the file corresponding to the node.
* *
* Dir_AddDir Add a directory to a search path. * Dir_AddDir Add a directory to a search path.
* *
@ -134,7 +134,7 @@
#include "job.h" #include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $"); MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $");
#define DIR_DEBUG0(text) DEBUG0(DIR, text) #define DIR_DEBUG0(text) DEBUG0(DIR, text)
#define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1) #define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1)
@ -168,9 +168,9 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* the process too much, it could severely affect the amount of * the process too much, it could severely affect the amount of
* parallelism available as each directory open would take another file * parallelism available as each directory open would take another file
* descriptor out of play for handling I/O for another job. Given that * descriptor out of play for handling I/O for another job. Given that
* it is only recently that UNIX OS's have taken to allowing more than * it is only recently (as of 1993 or earlier) that UNIX OS's have taken
* 20 or 32 file descriptors for a process, this doesn't seem acceptable * to allowing more than 20 or 32 file descriptors for a process, this
* to me. * doesn't seem acceptable to me.
* *
* 3) record the mtime of the directory in the CachedDir structure and * 3) record the mtime of the directory in the CachedDir structure and
* verify the directory hasn't changed since the contents were cached. * verify the directory hasn't changed since the contents were cached.
@ -184,11 +184,11 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* resort to using stat in its place. * resort to using stat in its place.
* *
* An additional thing to consider is that pmake is used primarily to create * An additional thing to consider is that pmake is used primarily to create
* C programs and until recently pcc-based compilers refused to allow you to * C programs and until recently (as of 1993 or earlier) pcc-based compilers
* specify where the resulting object file should be placed. This forced all * refused to allow you to specify where the resulting object file should be
* objects to be created in the current directory. This isn't meant as a full * placed. This forced all objects to be created in the current directory.
* excuse, just an explanation of some of the reasons for the caching used * This isn't meant as a full excuse, just an explanation of some of the
* here. * reasons for the caching used here.
* *
* One more note: the location of a target's file is only performed on the * One more note: the location of a target's file is only performed on the
* downward traversal of the graph and then only for terminal nodes in the * downward traversal of the graph and then only for terminal nodes in the
@ -204,7 +204,7 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* Given that an access() is essentially a stat() without the copyout() call, * Given that an access() is essentially a stat() without the copyout() call,
* and that the same filesystem overhead would have to be incurred in * and that the same filesystem overhead would have to be incurred in
* Dir_MTime, it made sense to replace the access() with a stat() and record * Dir_MTime, it made sense to replace the access() with a stat() and record
* the mtime in a cache for when Dir_MTime was actually called. * the mtime in a cache for when Dir_UpdateMTime was actually called.
*/ */
typedef List CachedDirList; typedef List CachedDirList;
@ -253,12 +253,10 @@ OpenDirs_Find(OpenDirs *odirs, const char *name)
static void static void
OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir) OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir)
{ {
HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name); if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL)
if (he != NULL)
return; return;
he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL);
Lst_Append(odirs->list, cdir); Lst_Append(odirs->list, cdir);
HashEntry_Set(he, odirs->list->last); HashTable_Set(&odirs->table, cdir->name, odirs->list->last);
} }
static void static void
@ -273,10 +271,10 @@ OpenDirs_Remove(OpenDirs *odirs, const char *name)
Lst_Remove(odirs->list, ln); Lst_Remove(odirs->list, ln);
} }
static OpenDirs openDirs; /* the list of all open directories */ static OpenDirs openDirs; /* all cached directories */
/* /*
* Variables for gathering statistics on the efficiency of the cashing * Variables for gathering statistics on the efficiency of the caching
* mechanism. * mechanism.
*/ */
static int hits; /* Found in directory cache */ static int hits; /* Found in directory cache */
@ -300,74 +298,50 @@ static HashTable mtimes;
static HashTable lmtimes; /* same as mtimes but for lstat */ static HashTable lmtimes; /* same as mtimes but for lstat */
/*
* We use stat(2) a lot, cache the results.
* mtime and mode are all we care about.
*/
struct cache_st {
time_t lmtime; /* lstat */
time_t mtime; /* stat */
mode_t mode;
};
/* minimize changes below */
typedef enum CachedStatsFlags { typedef enum CachedStatsFlags {
CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */ CST_NONE = 0,
CST_UPDATE = 0x02 /* ignore existing cached entry */ CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */
CST_UPDATE = 1 << 1 /* ignore existing cached entry */
} CachedStatsFlags; } CachedStatsFlags;
/* Returns 0 and the result of stat(2) or lstat(2) in *mst, or -1 on error. */ /* Returns 0 and the result of stat(2) or lstat(2) in *out_cst,
* or -1 on error. */
static int static int
cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst, cached_stats(const char *pathname, struct cached_stat *out_cst,
CachedStatsFlags flags) CachedStatsFlags flags)
{ {
HashEntry *entry; HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes;
struct stat sys_st; struct stat sys_st;
struct cache_st *cst; struct cached_stat *cst;
int rc; int rc;
if (!pathname || !pathname[0]) if (pathname == NULL || pathname[0] == '\0')
return -1; return -1; /* This can happen in meta mode. */
entry = HashTable_FindEntry(htp, pathname); cst = HashTable_FindValue(tbl, pathname);
if (cst != NULL && !(flags & CST_UPDATE)) {
if (entry && !(flags & CST_UPDATE)) { *out_cst = *cst;
cst = HashEntry_Get(entry);
mst->mst_mode = cst->mode;
mst->mst_mtime = (flags & CST_LSTAT) ? cst->lmtime : cst->mtime;
if (mst->mst_mtime) {
DIR_DEBUG2("Using cached time %s for %s\n", DIR_DEBUG2("Using cached time %s for %s\n",
Targ_FmtTime(mst->mst_mtime), pathname); Targ_FmtTime(cst->cst_mtime), pathname);
return 0; return 0;
} }
}
rc = (flags & CST_LSTAT) rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st);
? lstat(pathname, &sys_st)
: stat(pathname, &sys_st);
if (rc == -1) if (rc == -1)
return -1; return -1; /* don't cache negative lookups */
if (sys_st.st_mtime == 0) if (sys_st.st_mtime == 0)
sys_st.st_mtime = 1; /* avoid confusion with missing file */ sys_st.st_mtime = 1; /* avoid confusion with missing file */
mst->mst_mode = sys_st.st_mode; if (cst == NULL) {
mst->mst_mtime = sys_st.st_mtime; cst = bmake_malloc(sizeof *cst);
HashTable_Set(tbl, pathname, cst);
}
if (entry == NULL) cst->cst_mtime = sys_st.st_mtime;
entry = HashTable_CreateEntry(htp, pathname, NULL); cst->cst_mode = sys_st.st_mode;
if (HashEntry_Get(entry) == NULL) {
HashEntry_Set(entry, bmake_malloc(sizeof(*cst))); *out_cst = *cst;
memset(HashEntry_Get(entry), 0, sizeof(*cst));
}
cst = HashEntry_Get(entry);
if (flags & CST_LSTAT) {
cst->lmtime = sys_st.st_mtime;
} else {
cst->mtime = sys_st.st_mtime;
}
cst->mode = sys_st.st_mode;
DIR_DEBUG2(" Caching %s for %s\n", DIR_DEBUG2(" Caching %s for %s\n",
Targ_FmtTime(sys_st.st_mtime), pathname); Targ_FmtTime(sys_st.st_mtime), pathname);
@ -375,15 +349,15 @@ cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst,
} }
int int
cached_stat(const char *pathname, struct make_stat *st) cached_stat(const char *pathname, struct cached_stat *cst)
{ {
return cached_stats(&mtimes, pathname, st, 0); return cached_stats(pathname, cst, CST_NONE);
} }
int int
cached_lstat(const char *pathname, struct make_stat *st) cached_lstat(const char *pathname, struct cached_stat *cst)
{ {
return cached_stats(&lmtimes, pathname, st, CST_LSTAT); return cached_stats(pathname, cst, CST_LSTAT);
} }
/* Initialize the directories module. */ /* Initialize the directories module. */
@ -401,7 +375,7 @@ Dir_InitDir(const char *cdname)
{ {
Dir_InitCur(cdname); Dir_InitCur(cdname);
dotLast = bmake_malloc(sizeof(CachedDir)); dotLast = bmake_malloc(sizeof *dotLast);
dotLast->refCount = 1; dotLast->refCount = 1;
dotLast->hits = 0; dotLast->hits = 0;
dotLast->name = bmake_strdup(".DOTLAST"); dotLast->name = bmake_strdup(".DOTLAST");
@ -416,14 +390,24 @@ Dir_InitCur(const char *cdname)
{ {
CachedDir *dir; CachedDir *dir;
if (cdname != NULL) { if (cdname == NULL)
return;
/* /*
* Our build directory is not the same as our source directory. * Our build directory is not the same as our source directory.
* Keep this one around too. * Keep this one around too.
*/ */
if ((dir = Dir_AddDir(NULL, cdname))) { dir = Dir_AddDir(NULL, cdname);
if (dir == NULL)
return;
/* XXX: Reference counting is wrong here.
* If this function is called repeatedly with the same directory name,
* its reference count increases each time even though the number of
* actual references stays the same. */
dir->refCount++; dir->refCount++;
if (cur && cur != dir) { if (cur != NULL && cur != dir) {
/* /*
* We've been here before, clean up. * We've been here before, clean up.
*/ */
@ -432,8 +416,6 @@ Dir_InitCur(const char *cdname)
} }
cur = dir; cur = dir;
} }
}
}
/* (Re)initialize "dot" (current/object directory) path hash. /* (Re)initialize "dot" (current/object directory) path hash.
* Some directories may be opened. */ * Some directories may be opened. */
@ -588,6 +570,9 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; Boolean isDot = dirName[0] == '.' && dirName[1] == '\0';
HashIter hi; HashIter hi;
/* XXX: Iterating over all hash entries is inefficient. If the pattern
* is a plain string without any wildcards, a direct lookup is faster. */
HashIter_Init(&hi, &dir->files); HashIter_Init(&hi, &dir->files);
while (HashIter_Next(&hi) != NULL) { while (HashIter_Next(&hi) != NULL) {
const char *base = hi.entry->key; const char *base = hi.entry->key;
@ -879,13 +864,13 @@ DirLookup(CachedDir *dir, const char *base)
static char * static char *
DirLookupSubdir(CachedDir *dir, const char *name) DirLookupSubdir(CachedDir *dir, const char *name)
{ {
struct make_stat mst; struct cached_stat cst;
char *file = dir == dot ? bmake_strdup(name) char *file = dir == dot ? bmake_strdup(name)
: str_concat3(dir->name, "/", name); : str_concat3(dir->name, "/", name);
DIR_DEBUG1("checking %s ...\n", file); DIR_DEBUG1("checking %s ...\n", file);
if (cached_stat(file, &mst) == 0) { if (cached_stat(file, &cst) == 0) {
nearmisses++; nearmisses++;
return file; return file;
} }
@ -974,7 +959,7 @@ Dir_FindFile(const char *name, SearchPath *path)
const char *base; /* Terminal name of file */ const char *base; /* Terminal name of file */
Boolean hasLastDot = FALSE; /* true if we should search dot last */ Boolean hasLastDot = FALSE; /* true if we should search dot last */
Boolean hasSlash; /* true if 'name' contains a / */ Boolean hasSlash; /* true if 'name' contains a / */
struct make_stat mst; /* Buffer for stat, if necessary */ struct cached_stat cst; /* Buffer for stat, if necessary */
const char *trailing_dot = "."; const char *trailing_dot = ".";
/* /*
@ -1176,7 +1161,7 @@ Dir_FindFile(const char *name, SearchPath *path)
* When searching for $(FILE), we will find it in $(INSTALLDIR) * When searching for $(FILE), we will find it in $(INSTALLDIR)
* b/c we added it here. This is not good... * b/c we added it here. This is not good...
*/ */
#ifdef notdef #if 0
if (base == trailing_dot) { if (base == trailing_dot) {
base = strrchr(name, '/'); base = strrchr(name, '/');
base++; base++;
@ -1198,17 +1183,17 @@ Dir_FindFile(const char *name, SearchPath *path)
} else { } else {
return NULL; return NULL;
} }
#else /* !notdef */ #else
DIR_DEBUG1(" Looking for \"%s\" ...\n", name); DIR_DEBUG1(" Looking for \"%s\" ...\n", name);
bigmisses++; bigmisses++;
if (cached_stat(name, &mst) == 0) { if (cached_stat(name, &cst) == 0) {
return bmake_strdup(name); return bmake_strdup(name);
} }
DIR_DEBUG0(" failed. Returning NULL\n"); DIR_DEBUG0(" failed. Returning NULL\n");
return NULL; return NULL;
#endif /* notdef */ #endif
} }
@ -1225,7 +1210,7 @@ Dir_FindFile(const char *name, SearchPath *path)
char * char *
Dir_FindHereOrAbove(const char *here, const char *search_path) Dir_FindHereOrAbove(const char *here, const char *search_path)
{ {
struct make_stat mst; struct cached_stat cst;
char *dirbase, *dirbase_end; char *dirbase, *dirbase_end;
char *try, *try_end; char *try, *try_end;
@ -1238,12 +1223,12 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
/* try and stat(2) it ... */ /* try and stat(2) it ... */
try = str_concat3(dirbase, "/", search_path); try = str_concat3(dirbase, "/", search_path);
if (cached_stat(try, &mst) != -1) { if (cached_stat(try, &cst) != -1) {
/* /*
* success! if we found a file, chop off * success! if we found a file, chop off
* the filename so we return a directory. * the filename so we return a directory.
*/ */
if ((mst.mst_mode & S_IFMT) != S_IFDIR) { if ((cst.cst_mode & S_IFMT) != S_IFDIR) {
try_end = try + strlen(try); try_end = try + strlen(try);
while (try_end > try && *try_end != '/') while (try_end > try && *try_end != '/')
try_end--; try_end--;
@ -1275,36 +1260,27 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
return NULL; return NULL;
} }
/*- /* Search gn along dirSearchPath and store its modification time in gn->mtime.
*----------------------------------------------------------------------- * If no file is found, store 0 instead.
* Dir_MTime --
* Find the modification time of the file described by gn along the
* search path dirSearchPath.
* *
* Input: * The found file is stored in gn->path, unless the node already had a path. */
* gn the file whose modification time is desired void
* Dir_UpdateMTime(GNode *gn, Boolean recheck)
* Results:
* The modification time or 0 if it doesn't exist
*
* Side Effects:
* The modification time is placed in the node's mtime slot.
* If the node didn't have a path entry before, and Dir_FindFile
* found one for it, the full name is placed in the path slot.
*-----------------------------------------------------------------------
*/
time_t
Dir_MTime(GNode *gn, Boolean recheck)
{ {
char *fullName; /* the full pathname of name */ char *fullName;
struct make_stat mst; /* buffer for finding the mod time */ struct cached_stat cst;
if (gn->type & OP_ARCHV) { if (gn->type & OP_ARCHV) {
return Arch_MTime(gn); Arch_UpdateMTime(gn);
} else if (gn->type & OP_PHONY) { return;
}
if (gn->type & OP_PHONY) {
gn->mtime = 0; gn->mtime = 0;
return 0; return;
} else if (gn->path == NULL) { }
if (gn->path == NULL) {
if (gn->type & OP_NOPATH) if (gn->type & OP_NOPATH)
fullName = NULL; fullName = NULL;
else { else {
@ -1344,25 +1320,24 @@ Dir_MTime(GNode *gn, Boolean recheck)
fullName = gn->path; fullName = gn->path;
} }
if (fullName == NULL) { if (fullName == NULL)
fullName = bmake_strdup(gn->name); fullName = bmake_strdup(gn->name);
}
if (cached_stats(&mtimes, fullName, &mst, recheck ? CST_UPDATE : 0) < 0) { if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) {
if (gn->type & OP_MEMBER) { if (gn->type & OP_MEMBER) {
if (fullName != gn->path) if (fullName != gn->path)
free(fullName); free(fullName);
return Arch_MemMTime(gn); Arch_UpdateMemberMTime(gn);
} else { return;
mst.mst_mtime = 0;
} }
cst.cst_mtime = 0;
} }
if (fullName != NULL && gn->path == NULL) if (fullName != NULL && gn->path == NULL)
gn->path = fullName; gn->path = fullName;
gn->mtime = mst.mst_mtime; gn->mtime = cst.cst_mtime;
return gn->mtime;
} }
/* Read the list of filenames in the directory and store the result /* Read the list of filenames in the directory and store the result
@ -1387,6 +1362,7 @@ Dir_AddDir(SearchPath *path, const char *name)
if (path != NULL && strcmp(name, ".DOTLAST") == 0) { if (path != NULL && strcmp(name, ".DOTLAST") == 0) {
SearchPathNode *ln; SearchPathNode *ln;
/* XXX: Linear search gets slow with thousands of entries. */
for (ln = path->first; ln != NULL; ln = ln->next) { for (ln = path->first; ln != NULL; ln = ln->next) {
CachedDir *pathDir = ln->datum; CachedDir *pathDir = ln->datum;
if (strcmp(pathDir->name, name) == 0) if (strcmp(pathDir->name, name) == 0)
@ -1410,7 +1386,7 @@ Dir_AddDir(SearchPath *path, const char *name)
DIR_DEBUG1("Caching %s ...", name); DIR_DEBUG1("Caching %s ...", name);
if ((d = opendir(name)) != NULL) { if ((d = opendir(name)) != NULL) {
dir = bmake_malloc(sizeof(CachedDir)); dir = bmake_malloc(sizeof *dir);
dir->name = bmake_strdup(name); dir->name = bmake_strdup(name);
dir->hits = 0; dir->hits = 0;
dir->refCount = 1; dir->refCount = 1;
@ -1480,7 +1456,7 @@ Dir_MakeFlags(const char *flag, SearchPath *path)
Buffer buf; Buffer buf;
SearchPathNode *ln; SearchPathNode *ln;
Buf_Init(&buf, 0); Buf_Init(&buf);
if (path != NULL) { if (path != NULL) {
for (ln = path->first; ln != NULL; ln = ln->next) { for (ln = path->first; ln != NULL; ln = ln->next) {

14
dir.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: dir.h,v 1.32 2020/10/25 10:00:20 rillig Exp $ */ /* $NetBSD: dir.h,v 1.34 2020/11/14 19:24:24 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -101,7 +101,7 @@ Boolean Dir_HasWildcards(const char *);
void Dir_Expand(const char *, SearchPath *, StringList *); void Dir_Expand(const char *, SearchPath *, StringList *);
char *Dir_FindFile(const char *, SearchPath *); char *Dir_FindFile(const char *, SearchPath *);
char *Dir_FindHereOrAbove(const char *, const char *); char *Dir_FindHereOrAbove(const char *, const char *);
time_t Dir_MTime(GNode *, Boolean); void Dir_UpdateMTime(GNode *, Boolean);
CachedDir *Dir_AddDir(SearchPath *, const char *); CachedDir *Dir_AddDir(SearchPath *, const char *);
char *Dir_MakeFlags(const char *, SearchPath *); char *Dir_MakeFlags(const char *, SearchPath *);
void Dir_ClearPath(SearchPath *); void Dir_ClearPath(SearchPath *);
@ -112,12 +112,12 @@ void Dir_Destroy(void *);
SearchPath *Dir_CopyDirSearchPath(void); SearchPath *Dir_CopyDirSearchPath(void);
/* Stripped-down variant of struct stat. */ /* Stripped-down variant of struct stat. */
struct make_stat { struct cached_stat {
time_t mst_mtime; time_t cst_mtime;
mode_t mst_mode; mode_t cst_mode;
}; };
int cached_lstat(const char *, struct make_stat *); int cached_lstat(const char *, struct cached_stat *);
int cached_stat(const char *, struct make_stat *); int cached_stat(const char *, struct cached_stat *);
#endif /* MAKE_DIR_H */ #endif /* MAKE_DIR_H */

View file

@ -1,4 +1,4 @@
/* $NetBSD: filemon_dev.c,v 1.3 2020/07/10 15:53:30 sjg Exp $ */ /* $NetBSD: filemon_dev.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
/*- /*-
* Copyright (c) 2020 The NetBSD Foundation, Inc. * Copyright (c) 2020 The NetBSD Foundation, Inc.
@ -65,7 +65,7 @@ filemon_open(void)
int error; int error;
/* Allocate and zero a struct filemon object. */ /* Allocate and zero a struct filemon object. */
F = calloc(1, sizeof(*F)); F = calloc(1, sizeof *F);
if (F == NULL) if (F == NULL)
return NULL; return NULL;

View file

@ -1,4 +1,4 @@
/* $NetBSD: filemon_ktrace.c,v 1.3 2020/10/18 11:54:43 rillig Exp $ */ /* $NetBSD: filemon_ktrace.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
/*- /*-
* Copyright (c) 2019 The NetBSD Foundation, Inc. * Copyright (c) 2019 The NetBSD Foundation, Inc.
@ -198,7 +198,7 @@ filemon_open(void)
int error; int error;
/* Allocate and zero a struct filemon object. */ /* Allocate and zero a struct filemon object. */
F = calloc(1, sizeof(*F)); F = calloc(1, sizeof *F);
if (F == NULL) if (F == NULL)
return NULL; return NULL;

68
for.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */ /* $NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $ */
/* /*
* Copyright (c) 1992, The Regents of the University of California. * Copyright (c) 1992, The Regents of the University of California.
@ -60,15 +60,7 @@
#include "make.h" #include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $"); MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $");
/* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped. */
typedef enum ForEscapes {
FOR_SUB_ESCAPE_CHAR = 0x0001,
FOR_SUB_ESCAPE_BRACE = 0x0002,
FOR_SUB_ESCAPE_PAREN = 0x0004
} ForEscapes;
static int forLevel = 0; /* Nesting level */ static int forLevel = 0; /* Nesting level */
@ -120,30 +112,6 @@ For_Free(For *f)
free(f); free(f);
} }
static ForEscapes
GetEscapes(const char *word)
{
const char *p;
ForEscapes escapes = 0;
for (p = word; *p != '\0'; p++) {
switch (*p) {
case ':':
case '$':
case '\\':
escapes |= FOR_SUB_ESCAPE_CHAR;
break;
case ')':
escapes |= FOR_SUB_ESCAPE_PAREN;
break;
case '}':
escapes |= FOR_SUB_ESCAPE_BRACE;
break;
}
}
return escapes;
}
static Boolean static Boolean
IsFor(const char *p) IsFor(const char *p)
{ {
@ -191,11 +159,11 @@ For_Eval(const char *line)
*/ */
f = bmake_malloc(sizeof *f); f = bmake_malloc(sizeof *f);
Buf_Init(&f->body, 0); Buf_Init(&f->body);
Vector_Init(&f->vars, sizeof(ForVar)); Vector_Init(&f->vars, sizeof(ForVar));
f->items.words = NULL; f->items.words = NULL;
f->items.freeIt = NULL; f->items.freeIt = NULL;
Buf_Init(&f->curBody, 0); Buf_Init(&f->curBody);
f->short_var = FALSE; f->short_var = FALSE;
f->sub_next = 0; f->sub_next = 0;
@ -302,7 +270,7 @@ for_var_len(const char *var)
size_t len; size_t len;
var_start = *var; var_start = *var;
if (var_start == 0) if (var_start == '\0')
/* just escape the $ */ /* just escape the $ */
return 0; return 0;
@ -315,7 +283,7 @@ for_var_len(const char *var)
return 1; return 1;
depth = 1; depth = 1;
for (len = 1; (ch = var[len++]) != 0;) { for (len = 1; (ch = var[len++]) != '\0';) {
if (ch == var_start) if (ch == var_start)
depth++; depth++;
else if (ch == var_end && --depth == 0) else if (ch == var_end && --depth == 0)
@ -326,18 +294,30 @@ for_var_len(const char *var)
return 0; return 0;
} }
/* The .for loop substitutes the items as ${:U<value>...}, which means
* that characters that break this syntax must be backslash-escaped. */
static Boolean
NeedsEscapes(const char *word, char endc)
{
const char *p;
for (p = word; *p != '\0'; p++) {
if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
return TRUE;
}
return FALSE;
}
/* While expanding the body of a .for loop, write the item in the ${:U...} /* While expanding the body of a .for loop, write the item in the ${:U...}
* expression, escaping characters as needed. See ApplyModifier_Defined. */ * expression, escaping characters as needed.
*
* The result is later unescaped by ApplyModifier_Defined. */
static void static void
Buf_AddEscaped(Buffer *cmds, const char *item, char ech) Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
{ {
ForEscapes escapes = GetEscapes(item);
char ch; char ch;
/* If there were no escapes, or the only escape is the other variable if (!NeedsEscapes(item, ech)) {
* terminator, then just substitute the full string */
if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE
: ~(unsigned)FOR_SUB_ESCAPE_PAREN))) {
Buf_AddStr(cmds, item); Buf_AddStr(cmds, item);
return; return;
} }

18
hash.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $ */ /* $NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -74,7 +74,7 @@
#include "make.h" #include "make.h"
/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $"); MAKE_RCSID("$NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $");
/* /*
* The ratio of # entries to # buckets at which we rebuild the table to * The ratio of # entries to # buckets at which we rebuild the table to
@ -128,7 +128,7 @@ void
HashTable_Init(HashTable *t) HashTable_Init(HashTable *t)
{ {
unsigned int n = 16, i; unsigned int n = 16, i;
HashEntry **buckets = bmake_malloc(sizeof(*buckets) * n); HashEntry **buckets = bmake_malloc(sizeof *buckets * n);
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
buckets[i] = NULL; buckets[i] = NULL;
@ -195,7 +195,7 @@ HashTable_Enlarge(HashTable *t)
HashEntry **oldBuckets = t->buckets; HashEntry **oldBuckets = t->buckets;
unsigned int newSize = 2 * oldSize; unsigned int newSize = 2 * oldSize;
unsigned int newMask = newSize - 1; unsigned int newMask = newSize - 1;
HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize); HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize);
size_t i; size_t i;
for (i = 0; i < newSize; i++) for (i = 0; i < newSize; i++)
@ -239,7 +239,7 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
if (t->numEntries >= rebuildLimit * t->bucketsSize) if (t->numEntries >= rebuildLimit * t->bucketsSize)
HashTable_Enlarge(t); HashTable_Enlarge(t);
he = bmake_malloc(sizeof(*he) + keylen); he = bmake_malloc(sizeof *he + keylen);
he->value = NULL; he->value = NULL;
he->key_hash = h; he->key_hash = h;
memcpy(he->key, key, keylen + 1); memcpy(he->key, key, keylen + 1);
@ -253,6 +253,14 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
return he; return he;
} }
HashEntry *
HashTable_Set(HashTable *t, const char *key, void *value)
{
HashEntry *he = HashTable_CreateEntry(t, key, NULL);
HashEntry_Set(he, value);
return he;
}
/* Delete the entry from the table and free the associated memory. */ /* Delete the entry from the table and free the associated memory. */
void void
HashTable_DeleteEntry(HashTable *t, HashEntry *he) HashTable_DeleteEntry(HashTable *t, HashEntry *he)

7
hash.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: hash.h,v 1.31 2020/10/25 19:19:07 rillig Exp $ */ /* $NetBSD: hash.h,v 1.33 2020/11/14 21:29:44 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -103,13 +103,13 @@ typedef struct HashIter {
HashEntry *entry; /* Next entry to check in current bucket. */ HashEntry *entry; /* Next entry to check in current bucket. */
} HashIter; } HashIter;
static inline MAKE_ATTR_UNUSED void * MAKE_INLINE void *
HashEntry_Get(HashEntry *h) HashEntry_Get(HashEntry *h)
{ {
return h->value; return h->value;
} }
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
HashEntry_Set(HashEntry *h, void *datum) HashEntry_Set(HashEntry *h, void *datum)
{ {
h->value = datum; h->value = datum;
@ -122,6 +122,7 @@ void *HashTable_FindValue(HashTable *, const char *);
unsigned int Hash_Hash(const char *); unsigned int Hash_Hash(const char *);
void *HashTable_FindValueHash(HashTable *, const char *, unsigned int); void *HashTable_FindValueHash(HashTable *, const char *, unsigned int);
HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *); HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *);
HashEntry *HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *); void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *); void HashTable_DebugStats(HashTable *, const char *);

430
job.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $ */ /* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -156,7 +156,7 @@
#include "trace.h" #include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $"); MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $");
/* A shell defines how the commands are run. All commands for a target are /* A shell defines how the commands are run. All commands for a target are
* written into a single file, which is then given to the shell to execute * written into a single file, which is then given to the shell to execute
@ -388,8 +388,8 @@ static char *shellArgv = NULL; /* Custom shell args */
static Job *job_table; /* The structures that describe them */ static Job *job_table; /* The structures that describe them */
static Job *job_table_end; /* job_table + maxJobs */ static Job *job_table_end; /* job_table + maxJobs */
static unsigned int wantToken; /* we want a token */ static unsigned int wantToken; /* we want a token */
static int lurking_children = 0; static Boolean lurking_children = FALSE;
static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */
/* /*
* Set of descriptors of pipes connected to * Set of descriptors of pipes connected to
@ -443,7 +443,7 @@ job_table_dump(const char *where)
debug_printf("job table @ %s\n", where); debug_printf("job table @ %s\n", where);
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
debug_printf("job %d, status %d, flags %d, pid %d\n", debug_printf("job %d, status %d, flags %d, pid %d\n",
(int)(job - job_table), job->job_state, job->flags, job->pid); (int)(job - job_table), job->status, job->flags, job->pid);
} }
} }
@ -539,7 +539,7 @@ JobCondPassSig(int signo)
DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo);
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if (job->job_state != JOB_ST_RUNNING) if (job->status != JOB_ST_RUNNING)
continue; continue;
DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n",
signo, job->pid); signo, job->pid);
@ -596,7 +596,7 @@ JobPassSig_suspend(int signo)
struct sigaction act; struct sigaction act;
/* Suppress job started/continued messages */ /* Suppress job started/continued messages */
make_suspended = 1; make_suspended = TRUE;
/* Pass the signal onto every job */ /* Pass the signal onto every job */
JobCondPassSig(signo); JobCondPassSig(signo);
@ -646,12 +646,12 @@ JobPassSig_suspend(int signo)
} }
static Job * static Job *
JobFindPid(int pid, JobState status, Boolean isJobs) JobFindPid(int pid, JobStatus status, Boolean isJobs)
{ {
Job *job; Job *job;
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if ((job->job_state == status) && job->pid == pid) if (job->status == status && job->pid == pid)
return job; return job;
} }
if (DEBUG(JOB) && isJobs) if (DEBUG(JOB) && isJobs)
@ -705,6 +705,22 @@ EscapeShellDblQuot(const char *cmd)
return esc; return esc;
} }
static void
JobPrintf(Job *job, const char *fmt, const char *arg)
{
if (DEBUG(JOB))
debug_printf(fmt, arg);
(void)fprintf(job->cmdFILE, fmt, arg);
(void)fflush(job->cmdFILE);
}
static void
JobPrintln(Job *job, const char *line)
{
JobPrintf(job, "%s\n", line);
}
/*- /*-
*----------------------------------------------------------------------- *-----------------------------------------------------------------------
* JobPrintCommand -- * JobPrintCommand --
@ -717,7 +733,7 @@ EscapeShellDblQuot(const char *cmd)
* If the command is just "..." we take all future commands for this * If the command is just "..." we take all future commands for this
* job to be commands to be executed once the entire graph has been * job to be commands to be executed once the entire graph has been
* made and return non-zero to signal that the end of the commands * made and return non-zero to signal that the end of the commands
* was reached. These commands are later attached to the postCommands * was reached. These commands are later attached to the .END
* node and executed by Job_End when all things are done. * node and executed by Job_End when all things are done.
* *
* Side Effects: * Side Effects:
@ -746,12 +762,6 @@ JobPrintCommand(Job *job, char *cmd)
noSpecials = !GNode_ShouldExecute(job->node); noSpecials = !GNode_ShouldExecute(job->node);
#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \
debug_printf(fmt, arg); \
} \
(void)fprintf(job->cmdFILE, fmt, arg); \
(void)fflush(job->cmdFILE);
numCommands++; numCommands++;
Var_Subst(cmd, job->node, VARE_WANTRES, &cmd); Var_Subst(cmd, job->node, VARE_WANTRES, &cmd);
@ -783,8 +793,8 @@ JobPrintCommand(Job *job, char *cmd)
if (shutUp) { if (shutUp) {
if (!(job->flags & JOB_SILENT) && !noSpecials && if (!(job->flags & JOB_SILENT) && !noSpecials &&
commandShell->hasEchoCtl) { (commandShell->hasEchoCtl)) {
DBPRINTF("%s\n", commandShell->echoOff); JobPrintln(job, commandShell->echoOff);
} else { } else {
if (commandShell->hasErrCtl) if (commandShell->hasErrCtl)
shutUp = FALSE; shutUp = FALSE;
@ -803,16 +813,15 @@ JobPrintCommand(Job *job, char *cmd)
* it already is? * it already is?
*/ */
if (!(job->flags & JOB_SILENT) && !shutUp && if (!(job->flags & JOB_SILENT) && !shutUp &&
commandShell->hasEchoCtl) { (commandShell->hasEchoCtl)) {
DBPRINTF("%s\n", commandShell->echoOff); JobPrintln(job, commandShell->echoOff);
DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); JobPrintln(job, commandShell->errOffOrExecIgnore);
DBPRINTF("%s\n", commandShell->echoOn); JobPrintln(job, commandShell->echoOn);
} else { } else {
DBPRINTF("%s\n", commandShell->errOffOrExecIgnore); JobPrintln(job, commandShell->errOffOrExecIgnore);
} }
} else if (commandShell->errOffOrExecIgnore && } else if (commandShell->errOffOrExecIgnore &&
commandShell->errOffOrExecIgnore[0] != '\0') commandShell->errOffOrExecIgnore[0] != '\0') {
{
/* /*
* The shell has no error control, so we need to be * The shell has no error control, so we need to be
* weird to get it to ignore any errors from the command. * weird to get it to ignore any errors from the command.
@ -825,14 +834,13 @@ JobPrintCommand(Job *job, char *cmd)
job->flags |= JOB_IGNERR; job->flags |= JOB_IGNERR;
if (!(job->flags & JOB_SILENT) && !shutUp) { if (!(job->flags & JOB_SILENT) && !shutUp) {
if (commandShell->hasEchoCtl) { if (commandShell->hasEchoCtl) {
DBPRINTF("%s\n", commandShell->echoOff); JobPrintln(job, commandShell->echoOff);
} }
DBPRINTF(commandShell->errOnOrEcho, escCmd); JobPrintf(job, commandShell->errOnOrEcho, escCmd);
shutUp = TRUE; shutUp = TRUE;
} else { } else {
if (!shutUp) { if (!shutUp)
DBPRINTF(commandShell->errOnOrEcho, escCmd); JobPrintf(job, commandShell->errOnOrEcho, escCmd);
}
} }
cmdTemplate = commandShell->errOffOrExecIgnore; cmdTemplate = commandShell->errOffOrExecIgnore;
/* /*
@ -858,15 +866,14 @@ JobPrintCommand(Job *job, char *cmd)
if (!commandShell->hasErrCtl && commandShell->errExit && if (!commandShell->hasErrCtl && commandShell->errExit &&
commandShell->errExit[0] != '\0') { commandShell->errExit[0] != '\0') {
if (!(job->flags & JOB_SILENT) && !shutUp) { if (!(job->flags & JOB_SILENT) && !shutUp) {
if (commandShell->hasEchoCtl) { if (commandShell->hasEchoCtl)
DBPRINTF("%s\n", commandShell->echoOff); JobPrintln(job, commandShell->echoOff);
} JobPrintf(job, commandShell->errOnOrEcho, escCmd);
DBPRINTF(commandShell->errOnOrEcho, escCmd);
shutUp = TRUE; shutUp = TRUE;
} }
/* If it's a comment line or blank, treat as an ignored error */ /* If it's a comment line or blank, treat as an ignored error */
if ((escCmd[0] == commandShell->commentChar) || if (escCmd[0] == commandShell->commentChar ||
(escCmd[0] == 0)) (escCmd[0] == '\0'))
cmdTemplate = commandShell->errOffOrExecIgnore; cmdTemplate = commandShell->errOffOrExecIgnore;
else else
cmdTemplate = commandShell->errExit; cmdTemplate = commandShell->errExit;
@ -875,12 +882,12 @@ JobPrintCommand(Job *job, char *cmd)
} }
if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
(job->flags & JOB_TRACED) == 0) { !(job->flags & JOB_TRACED)) {
DBPRINTF("set -%s\n", "x"); JobPrintln(job, "set -x");
job->flags |= JOB_TRACED; job->flags |= JOB_TRACED;
} }
DBPRINTF(cmdTemplate, cmd); JobPrintf(job, cmdTemplate, cmd);
free(cmdStart); free(cmdStart);
free(escCmd); free(escCmd);
if (errOff) { if (errOff) {
@ -890,14 +897,13 @@ JobPrintCommand(Job *job, char *cmd)
* for the whole command... * for the whole command...
*/ */
if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) { if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) {
DBPRINTF("%s\n", commandShell->echoOff); JobPrintln(job, commandShell->echoOff);
shutUp = TRUE; shutUp = TRUE;
} }
DBPRINTF("%s\n", commandShell->errOnOrEcho); JobPrintln(job, commandShell->errOnOrEcho);
}
if (shutUp && commandShell->hasEchoCtl) {
DBPRINTF("%s\n", commandShell->echoOn);
} }
if (shutUp && commandShell->hasEchoCtl)
JobPrintln(job, commandShell->echoOn);
} }
/* Print all commands to the shell file that is later executed. /* Print all commands to the shell file that is later executed.
@ -914,11 +920,10 @@ JobPrintCommands(Job *job)
if (strcmp(cmd, "...") == 0) { if (strcmp(cmd, "...") == 0) {
job->node->type |= OP_SAVE_CMDS; job->node->type |= OP_SAVE_CMDS;
if ((job->flags & JOB_IGNDOTS) == 0) {
job->tailCmds = ln->next; job->tailCmds = ln->next;
break; break;
} }
} else
JobPrintCommand(job, ln->datum); JobPrintCommand(job, ln->datum);
} }
} }
@ -944,7 +949,7 @@ JobSaveCommands(Job *job)
/* Called to close both input and output pipes when a job is finished. */ /* Called to close both input and output pipes when a job is finished. */
static void static void
JobClose(Job *job) JobClosePipes(Job *job)
{ {
clearfd(job); clearfd(job);
(void)close(job->outPipe); (void)close(job->outPipe);
@ -955,28 +960,17 @@ JobClose(Job *job)
job->inPipe = -1; job->inPipe = -1;
} }
/*- /* Do final processing for the given job including updating parent nodes and
*----------------------------------------------------------------------- * starting new jobs as available/necessary.
* JobFinish -- *
* Do final processing for the given job including updating * Deferred commands for the job are placed on the .END node.
* parents and starting new jobs as available/necessary. Note *
* that we pay no attention to the JOB_IGNERR flag here. * If there was a serious error (errors != 0; not an ignored one), no more
* This is because when we're called because of a noexecute flag * jobs will be started.
* or something, jstat.w_status is 0 and when called from
* Job_CatchChildren, the status is zeroed if it s/b ignored.
* *
* Input: * Input:
* job job to finish * job job to finish
* status sub-why job went away * status sub-why job went away
*
* Side Effects:
* Final commands for the job are placed on postCommands.
*
* If we got an error and are aborting (aborting == ABORT_ERROR) and
* the job list is now empty, we are done for the day.
* If we recognized an error (errors !=0), we set the aborting flag
* to ABORT_ERROR so no more jobs will be started.
*-----------------------------------------------------------------------
*/ */
static void static void
JobFinish (Job *job, WAIT_T status) JobFinish (Job *job, WAIT_T status)
@ -987,7 +981,7 @@ JobFinish (Job *job, WAIT_T status)
job->pid, job->node->name, status); job->pid, job->node->name, status);
if ((WIFEXITED(status) && if ((WIFEXITED(status) &&
(((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || ((WEXITSTATUS(status) != 0 && !(job->flags & JOB_IGNERR)))) ||
WIFSIGNALED(status)) WIFSIGNALED(status))
{ {
/* /*
@ -998,7 +992,7 @@ JobFinish (Job *job, WAIT_T status)
* cases, finish out the job's output before printing the exit * cases, finish out the job's output before printing the exit
* status... * status...
*/ */
JobClose(job); JobClosePipes(job);
if (job->cmdFILE != NULL && job->cmdFILE != stdout) { if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
(void)fclose(job->cmdFILE); (void)fclose(job->cmdFILE);
job->cmdFILE = NULL; job->cmdFILE = NULL;
@ -1007,19 +1001,11 @@ JobFinish (Job *job, WAIT_T status)
} else if (WIFEXITED(status)) { } else if (WIFEXITED(status)) {
/* /*
* Deal with ignored errors in -B mode. We need to print a message * Deal with ignored errors in -B mode. We need to print a message
* telling of the ignored error as well as setting status.w_status * telling of the ignored error as well as to run the next command.
* to 0 so the next command gets run. To do this, we set done to be *
* TRUE if in -B mode and the job exited non-zero.
*/ */
done = WEXITSTATUS(status) != 0; done = WEXITSTATUS(status) != 0;
/* JobClosePipes(job);
* Old comment said: "Note we don't
* want to close down any of the streams until we know we're at the
* end."
* But we do. Otherwise when are we going to print the rest of the
* stuff?
*/
JobClose(job);
} else { } else {
/* /*
* No need to close things down or anything. * No need to close things down or anything.
@ -1041,7 +1027,7 @@ JobFinish (Job *job, WAIT_T status)
meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
} }
#endif #endif
if (!dieQuietly(job->node, -1)) if (!shouldDieQuietly(job->node, -1))
(void)printf("*** [%s] Error code %d%s\n", (void)printf("*** [%s] Error code %d%s\n",
job->node->name, job->node->name,
WEXITSTATUS(status), WEXITSTATUS(status),
@ -1078,11 +1064,9 @@ JobFinish (Job *job, WAIT_T status)
#ifdef USE_META #ifdef USE_META
if (useMeta) { if (useMeta) {
int x; int meta_status = meta_job_finish(job);
if (meta_status != 0 && status == 0)
if ((x = meta_job_finish(job)) != 0 && status == 0) { status = meta_status;
status = x;
}
} }
#endif #endif
@ -1090,13 +1074,12 @@ JobFinish (Job *job, WAIT_T status)
Trace_Log(JOBEND, job); Trace_Log(JOBEND, job);
if (!(job->flags & JOB_SPECIAL)) { if (!(job->flags & JOB_SPECIAL)) {
if ((WAIT_STATUS(status) != 0) || if (WAIT_STATUS(status) != 0 ||
(aborting == ABORT_ERROR) || (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT)
(aborting == ABORT_INTERRUPT))
return_job_token = TRUE; return_job_token = TRUE;
} }
if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT &&
(WAIT_STATUS(status) == 0)) { (WAIT_STATUS(status) == 0)) {
/* /*
* As long as we aren't aborting and the job didn't return a non-zero * As long as we aren't aborting and the job didn't return a non-zero
@ -1108,33 +1091,50 @@ JobFinish (Job *job, WAIT_T status)
if (!(job->flags & JOB_SPECIAL)) if (!(job->flags & JOB_SPECIAL))
return_job_token = TRUE; return_job_token = TRUE;
Make_Update(job->node); Make_Update(job->node);
job->job_state = JOB_ST_FREE; job->status = JOB_ST_FREE;
} else if (WAIT_STATUS(status)) { } else if (WAIT_STATUS(status)) {
errors++; errors++;
job->job_state = JOB_ST_FREE; job->status = JOB_ST_FREE;
} }
/* if (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT)
* Set aborting if any error. aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */
*/
if (errors && !opts.keepgoing && (aborting != ABORT_INTERRUPT)) {
/*
* If we found any errors in this batch of children and the -k flag
* wasn't given, we set the aborting flag so no more jobs get
* started.
*/
aborting = ABORT_ERROR;
}
if (return_job_token) if (return_job_token)
Job_TokenReturn(); Job_TokenReturn();
if (aborting == ABORT_ERROR && jobTokensRunning == 0) { if (aborting == ABORT_ERROR && jobTokensRunning == 0)
/*
* If we are aborting and the job table is now empty, we finish.
*/
Finish(errors); Finish(errors);
} }
static void
TouchRegular(GNode *gn)
{
const char *file = GNode_Path(gn);
struct utimbuf times = { now, now };
int fd;
char c;
if (utime(file, &times) >= 0)
return;
fd = open(file, O_RDWR | O_CREAT, 0666);
if (fd < 0) {
(void)fprintf(stderr, "*** couldn't touch %s: %s\n",
file, strerror(errno));
(void)fflush(stderr);
return; /* XXX: What about propagating the error? */
}
/* Last resort: update the file's time stamps in the traditional way.
* XXX: This doesn't work for empty files, which are sometimes used
* as marker files. */
if (read(fd, &c, 1) == 1) {
(void)lseek(fd, 0, SEEK_SET);
while (write(fd, &c, 1) == -1 && errno == EAGAIN)
continue;
}
(void)close(fd); /* XXX: What about propagating the error? */
} }
/* Touch the given target. Called by JobStart when the -t flag was given. /* Touch the given target. Called by JobStart when the -t flag was given.
@ -1144,15 +1144,9 @@ JobFinish (Job *job, WAIT_T status)
void void
Job_Touch(GNode *gn, Boolean silent) Job_Touch(GNode *gn, Boolean silent)
{ {
int streamID; /* ID of stream opened to do the touch */
struct utimbuf times; /* Times for utime() call */
if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
OP_SPECIAL|OP_PHONY)) { OP_SPECIAL|OP_PHONY)) {
/* /* These are "virtual" targets and should not really be created. */
* .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
* and, as such, shouldn't really be created.
*/
return; return;
} }
@ -1161,42 +1155,20 @@ Job_Touch(GNode *gn, Boolean silent)
(void)fflush(stdout); (void)fflush(stdout);
} }
if (!GNode_ShouldExecute(gn)) { if (!GNode_ShouldExecute(gn))
return; return;
}
if (gn->type & OP_ARCHV) { if (gn->type & OP_ARCHV) {
Arch_Touch(gn); Arch_Touch(gn);
} else if (gn->type & OP_LIB) { return;
}
if (gn->type & OP_LIB) {
Arch_TouchLib(gn); Arch_TouchLib(gn);
} else { return;
const char *file = GNode_Path(gn);
times.actime = times.modtime = now;
if (utime(file, &times) < 0){
streamID = open(file, O_RDWR | O_CREAT, 0666);
if (streamID >= 0) {
char c;
/*
* Read and write a byte to the file to change the
* modification time, then close the file.
*/
if (read(streamID, &c, 1) == 1) {
(void)lseek(streamID, (off_t)0, SEEK_SET);
while (write(streamID, &c, 1) == -1 && errno == EAGAIN)
continue;
} }
(void)close(streamID); TouchRegular(gn);
} else {
(void)fprintf(stdout, "*** couldn't touch %s: %s",
file, strerror(errno));
(void)fflush(stdout);
}
}
}
} }
/* Make sure the given node has all the commands it needs. /* Make sure the given node has all the commands it needs.
@ -1223,25 +1195,25 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
/* /*
* No commands. Look for .DEFAULT rule from which we might infer * No commands. Look for .DEFAULT rule from which we might infer
* commands * commands.
*/ */
if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) &&
(gn->type & OP_SPECIAL) == 0) { !(gn->type & OP_SPECIAL)) {
/* /*
* Make only looks for a .DEFAULT if the node was never the * The traditional Make only looks for a .DEFAULT if the node was
* target of an operator, so that's what we do too. If * never the target of an operator, so that's what we do too.
* a .DEFAULT was given, we substitute its commands for gn's *
* commands and set the IMPSRC variable to be the target's name * The .DEFAULT node acts like a transformation rule, in that
* The DEFAULT node acts like a transformation rule, in that
* gn also inherits any attributes or sources attached to * gn also inherits any attributes or sources attached to
* .DEFAULT itself. * .DEFAULT itself.
*/ */
Make_HandleUse(DEFAULT, gn); Make_HandleUse(defaultNode, gn);
Var_Set(IMPSRC, GNode_VarTarget(gn), gn); Var_Set(IMPSRC, GNode_VarTarget(gn), gn);
return TRUE; return TRUE;
} }
if (Dir_MTime(gn, 0) != 0 || (gn->type & OP_SPECIAL)) Dir_UpdateMTime(gn, FALSE);
if (gn->mtime != 0 || (gn->type & OP_SPECIAL))
return TRUE; return TRUE;
/* /*
@ -1280,9 +1252,7 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
/* Execute the shell for the given job. /* Execute the shell for the given job.
* *
* A shell is executed, its output is altered and the Job structure added * See Job_CatchOutput for handling the output of the shell. */
* to the job table.
*/
static void static void
JobExec(Job *job, char **argv) JobExec(Job *job, char **argv)
{ {
@ -1294,7 +1264,7 @@ JobExec(Job *job, char **argv)
if (DEBUG(JOB)) { if (DEBUG(JOB)) {
int i; int i;
debug_printf("Running %s %sly\n", job->node->name, "local"); debug_printf("Running %s\n", job->node->name);
debug_printf("\tCommand: "); debug_printf("\tCommand: ");
for (i = 0; argv[i] != NULL; i++) { for (i = 0; argv[i] != NULL; i++) {
debug_printf("%s ", argv[i]); debug_printf("%s ", argv[i]);
@ -1317,7 +1287,7 @@ JobExec(Job *job, char **argv)
JobSigLock(&mask); JobSigLock(&mask);
/* Pre-emptively mark job running, pid still zero though */ /* Pre-emptively mark job running, pid still zero though */
job->job_state = JOB_ST_RUNNING; job->status = JOB_ST_RUNNING;
cpid = vFork(); cpid = vFork();
if (cpid == -1) if (cpid == -1)
@ -1351,7 +1321,7 @@ JobExec(Job *job, char **argv)
execDie("dup2", "job->cmdFILE"); execDie("dup2", "job->cmdFILE");
if (fcntl(0, F_SETFD, 0) == -1) if (fcntl(0, F_SETFD, 0) == -1)
execDie("fcntl clear close-on-exec", "stdin"); execDie("fcntl clear close-on-exec", "stdin");
if (lseek(0, (off_t)0, SEEK_SET) == -1) if (lseek(0, 0, SEEK_SET) == -1)
execDie("lseek to 0", "stdin"); execDie("lseek to 0", "stdin");
if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
@ -1458,7 +1428,7 @@ JobMakeArgv(Job *job, char **argv)
* Bourne shell thinks its second argument is a file to source. * Bourne shell thinks its second argument is a file to source.
* Grrrr. Note the ten-character limitation on the combined arguments. * Grrrr. Note the ten-character limitation on the combined arguments.
*/ */
(void)snprintf(args, sizeof(args), "-%s%s", (void)snprintf(args, sizeof args, "-%s%s",
((job->flags & JOB_IGNERR) ? "" : ((job->flags & JOB_IGNERR) ? "" :
(commandShell->exit ? commandShell->exit : "")), (commandShell->exit ? commandShell->exit : "")),
((job->flags & JOB_SILENT) ? "" : ((job->flags & JOB_SILENT) ? "" :
@ -1490,7 +1460,6 @@ JobMakeArgv(Job *job, char **argv)
* Input: * Input:
* gn target to create * gn target to create
* flags flags for the job to override normal ones. * flags flags for the job to override normal ones.
* e.g. JOB_SPECIAL or JOB_IGNDOTS
* previous The previous Job structure for this node, if any. * previous The previous Job structure for this node, if any.
* *
* Results: * Results:
@ -1502,13 +1471,11 @@ JobMakeArgv(Job *job, char **argv)
* A new Job node is created and added to the list of running * A new Job node is created and added to the list of running
* jobs. PMake is forked and a child shell created. * jobs. PMake is forked and a child shell created.
* *
* NB: I'm fairly sure that this code is never called with JOB_SPECIAL set * NB: The return value is ignored by everyone.
* JOB_IGNDOTS is never set (dsl)
* Also the return value is ignored by everyone.
*----------------------------------------------------------------------- *-----------------------------------------------------------------------
*/ */
static JobStartResult static JobStartResult
JobStart(GNode *gn, int flags) JobStart(GNode *gn, JobFlags flags)
{ {
Job *job; /* new job descriptor */ Job *job; /* new job descriptor */
char *argv[10]; /* Argument vector to shell */ char *argv[10]; /* Argument vector to shell */
@ -1517,33 +1484,24 @@ JobStart(GNode *gn, int flags)
int tfd; /* File descriptor to the temp file */ int tfd; /* File descriptor to the temp file */
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if (job->job_state == JOB_ST_FREE) if (job->status == JOB_ST_FREE)
break; break;
} }
if (job >= job_table_end) if (job >= job_table_end)
Punt("JobStart no job slots vacant"); Punt("JobStart no job slots vacant");
memset(job, 0, sizeof *job); memset(job, 0, sizeof *job);
job->job_state = JOB_ST_SETUP;
if (gn->type & OP_SPECIAL)
flags |= JOB_SPECIAL;
job->node = gn; job->node = gn;
job->tailCmds = NULL; job->tailCmds = NULL;
job->status = JOB_ST_SET_UP;
/* if (gn->type & OP_SPECIAL)
* Set the initial value of the flags for this job based on the global flags |= JOB_SPECIAL;
* ones and the node's attributes... Any flags supplied by the caller if (Targ_Ignore(gn))
* are also added to the field. flags |= JOB_IGNERR;
*/ if (Targ_Silent(gn))
job->flags = 0; flags |= JOB_SILENT;
if (Targ_Ignore(gn)) { job->flags = flags;
job->flags |= JOB_IGNERR;
}
if (Targ_Silent(gn)) {
job->flags |= JOB_SILENT;
}
job->flags |= flags;
/* /*
* Check the commands now so any attributes from .DEFAULT have a chance * Check the commands now so any attributes from .DEFAULT have a chance
@ -1583,9 +1541,9 @@ JobStart(GNode *gn, int flags)
JobSigUnlock(&mask); JobSigUnlock(&mask);
job->cmdFILE = fdopen(tfd, "w+"); job->cmdFILE = fdopen(tfd, "w+");
if (job->cmdFILE == NULL) { if (job->cmdFILE == NULL)
Punt("Could not fdopen %s", tfile); Punt("Could not fdopen %s", tfile);
}
(void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC);
/* /*
* Send the commands to the command file, flush all its buffers then * Send the commands to the command file, flush all its buffers then
@ -1596,10 +1554,9 @@ JobStart(GNode *gn, int flags)
#ifdef USE_META #ifdef USE_META
if (useMeta) { if (useMeta) {
meta_job_start(job, gn); meta_job_start(job, gn);
if (Targ_Silent(gn)) { /* might have changed */ if (Targ_Silent(gn)) /* might have changed */
job->flags |= JOB_SILENT; job->flags |= JOB_SILENT;
} }
}
#endif #endif
/* /*
* We can do all the commands at once. hooray for sanity * We can do all the commands at once. hooray for sanity
@ -1660,12 +1617,10 @@ JobStart(GNode *gn, int flags)
/* /*
* Unlink and close the command file if we opened one * Unlink and close the command file if we opened one
*/ */
if (job->cmdFILE != stdout) { if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
if (job->cmdFILE != NULL) {
(void)fclose(job->cmdFILE); (void)fclose(job->cmdFILE);
job->cmdFILE = NULL; job->cmdFILE = NULL;
} }
}
/* /*
* We only want to work our way up the graph if we aren't here because * We only want to work our way up the graph if we aren't here because
@ -1676,7 +1631,7 @@ JobStart(GNode *gn, int flags)
job->node->made = MADE; job->node->made = MADE;
Make_Update(job->node); Make_Update(job->node);
} }
job->job_state = JOB_ST_FREE; job->status = JOB_ST_FREE;
return cmdsOK ? JOB_FINISHED : JOB_ERROR; return cmdsOK ? JOB_FINISHED : JOB_ERROR;
} }
@ -1693,14 +1648,18 @@ JobStart(GNode *gn, int flags)
return JOB_RUNNING; return JOB_RUNNING;
} }
/* Print the output of the shell command, skipping the noPrint command of
* the shell, if any. */
static char * static char *
JobOutput(Job *job, char *cp, char *endp) JobOutput(Job *job, char *cp, char *endp)
{ {
char *ecp; char *ecp;
if (commandShell->noPrint && commandShell->noPrint[0] != '\0') { if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0')
return cp;
while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) {
if (cp != ecp) { if (ecp != cp) {
*ecp = '\0'; *ecp = '\0';
/* /*
* The only way there wouldn't be a newline after * The only way there wouldn't be a newline after
@ -1719,45 +1678,28 @@ JobOutput(Job *job, char *cp, char *endp)
* command.... * command....
*/ */
cp++; cp++;
while (*cp == ' ' || *cp == '\t' || *cp == '\n') { pp_skip_whitespace(&cp);
cp++;
}
} else { } else {
return cp; return cp;
} }
} }
}
return cp; return cp;
} }
/*- /*
*----------------------------------------------------------------------- * This function is called whenever there is something to read on the pipe.
* JobDoOutput -- * We collect more output from the given job and store it in the job's
* This function is called at different times depending on * outBuf. If this makes up a line, we print it tagged by the job's
* whether the user has specified that output is to be collected * identifier, as necessary.
* via pipes or temporary files. In the former case, we are called *
* whenever there is something to read on the pipe. We collect more * In the output of the shell, the 'noPrint' lines are removed. If the
* output from the given job and store it in the job's outBuf. If * command is not alone on the line (the character after it is not \0 or
* this makes up a line, we print it tagged by the job's identifier, * \n), we do print whatever follows it.
* as necessary.
* If output has been collected in a temporary file, we open the
* file and read it line by line, transferring it to our own
* output channel until the file is empty. At which point we
* remove the temporary file.
* In both cases, however, we keep our figurative eye out for the
* 'noPrint' line for the shell from which the output came. If
* we recognize a line, we don't print it. If the command is not
* alone on the line (the character after it is not \0 or \n), we
* do print whatever follows it.
* *
* Input: * Input:
* job the job whose output needs printing * job the job whose output needs printing
* finish TRUE if this is the last time we'll be called * finish TRUE if this is the last time we'll be called
* for this job * for this job
*
* Side Effects:
* curPos may be shifted as may the contents of outBuf.
*-----------------------------------------------------------------------
*/ */
static void static void
JobDoOutput(Job *job, Boolean finish) JobDoOutput(Job *job, Boolean finish)
@ -1772,7 +1714,7 @@ JobDoOutput(Job *job, Boolean finish)
/* /*
* Read as many bytes as will fit in the buffer. * Read as many bytes as will fit in the buffer.
*/ */
end_loop: again:
gotNL = FALSE; gotNL = FALSE;
fbuf = FALSE; fbuf = FALSE;
@ -1795,7 +1737,7 @@ JobDoOutput(Job *job, Boolean finish)
* output remaining in the buffer. * output remaining in the buffer.
* Also clear the 'finish' flag so we stop looping. * Also clear the 'finish' flag so we stop looping.
*/ */
if ((nr == 0) && (job->curPos != 0)) { if (nr == 0 && job->curPos != 0) {
job->outBuf[job->curPos] = '\n'; job->outBuf[job->curPos] = '\n';
nr = 1; nr = 1;
finish = FALSE; finish = FALSE;
@ -1890,7 +1832,7 @@ JobDoOutput(Job *job, Boolean finish)
* we do get an EOF, finish will be set FALSE and we'll fall * we do get an EOF, finish will be set FALSE and we'll fall
* through and out. * through and out.
*/ */
goto end_loop; goto again;
} }
} }
@ -1988,13 +1930,13 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs)
(void)printf("*** [%s] Stopped -- signal %d\n", (void)printf("*** [%s] Stopped -- signal %d\n",
job->node->name, WSTOPSIG(status)); job->node->name, WSTOPSIG(status));
} }
job->job_suspended = 1; job->suspended = TRUE;
} }
(void)fflush(stdout); (void)fflush(stdout);
return; return;
} }
job->job_state = JOB_ST_FINISHED; job->status = JOB_ST_FINISHED;
job->exit_status = WAIT_STATUS(status); job->exit_status = WAIT_STATUS(status);
JobFinish(job, status); JobFinish(job, status);
@ -2038,7 +1980,7 @@ Job_CatchOutput(void)
default: default:
abort(); abort();
} }
--nready; nready--;
} }
Job_CatchChildren(); Job_CatchChildren();
@ -2049,7 +1991,7 @@ Job_CatchOutput(void)
if (!fds[i].revents) if (!fds[i].revents)
continue; continue;
job = jobfds[i]; job = jobfds[i];
if (job->job_state == JOB_ST_RUNNING) if (job->status == JOB_ST_RUNNING)
JobDoOutput(job, FALSE); JobDoOutput(job, FALSE);
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
/* /*
@ -2073,7 +2015,7 @@ Job_CatchOutput(void)
void void
Job_Make(GNode *gn) Job_Make(GNode *gn)
{ {
(void)JobStart(gn, 0); (void)JobStart(gn, JOB_NONE);
} }
void void
@ -2094,7 +2036,7 @@ Shell_Init(void)
#endif #endif
shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName);
} }
Var_Set_with_flags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY);
if (commandShell->exit == NULL) { if (commandShell->exit == NULL) {
commandShell->exit = ""; commandShell->exit = "";
} }
@ -2172,7 +2114,7 @@ Job_Init(void)
if (rval > 0) if (rval > 0)
continue; continue;
if (rval == 0) if (rval == 0)
lurking_children = 1; lurking_children = TRUE;
break; break;
} }
@ -2181,9 +2123,9 @@ Job_Init(void)
JobCreatePipe(&childExitJob, 3); JobCreatePipe(&childExitJob, 3);
/* Preallocate enough for the maximum number of jobs. */ /* Preallocate enough for the maximum number of jobs. */
fds = bmake_malloc(sizeof(*fds) * fds = bmake_malloc(sizeof *fds *
(npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
jobfds = bmake_malloc(sizeof(*jobfds) * jobfds = bmake_malloc(sizeof *jobfds *
(npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
/* These are permanent entries and take slots 0 and 1 */ /* These are permanent entries and take slots 0 and 1 */
@ -2328,7 +2270,7 @@ Job_ParseShell(char *line)
free(shellArgv); free(shellArgv);
memset(&newShell, 0, sizeof(newShell)); memset(&newShell, 0, sizeof newShell);
/* /*
* Parse the specification by keyword * Parse the specification by keyword
@ -2440,7 +2382,7 @@ Job_ParseShell(char *line)
} }
commandShell = sh; commandShell = sh;
} else { } else {
commandShell = bmake_malloc(sizeof(Shell)); commandShell = bmake_malloc(sizeof *commandShell);
*commandShell = newShell; *commandShell = newShell;
} }
/* this will take care of shellErrFlag */ /* this will take care of shellErrFlag */
@ -2491,7 +2433,7 @@ JobInterrupt(int runINTERRUPT, int signo)
JobSigLock(&mask); JobSigLock(&mask);
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if (job->job_state != JOB_ST_RUNNING) if (job->status != JOB_ST_RUNNING)
continue; continue;
gn = job->node; gn = job->node;
@ -2513,7 +2455,7 @@ JobInterrupt(int runINTERRUPT, int signo)
JobRun(interrupt); JobRun(interrupt);
} }
} }
Trace_Log(MAKEINTR, 0); Trace_Log(MAKEINTR, NULL);
exit(signo); exit(signo);
} }
@ -2570,7 +2512,7 @@ Job_AbortAll(void)
if (jobTokensRunning) { if (jobTokensRunning) {
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if (job->job_state != JOB_ST_RUNNING) if (job->status != JOB_ST_RUNNING)
continue; continue;
/* /*
* kill the child process with increasingly drastic signals to make * kill the child process with increasingly drastic signals to make
@ -2596,23 +2538,23 @@ JobRestartJobs(void)
Job *job; Job *job;
for (job = job_table; job < job_table_end; job++) { for (job = job_table; job < job_table_end; job++) {
if (job->job_state == JOB_ST_RUNNING && if (job->status == JOB_ST_RUNNING &&
(make_suspended || job->job_suspended)) { (make_suspended || job->suspended)) {
DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid);
if (job->job_suspended) { if (job->suspended) {
(void)printf("*** [%s] Continued\n", job->node->name); (void)printf("*** [%s] Continued\n", job->node->name);
(void)fflush(stdout); (void)fflush(stdout);
} }
job->job_suspended = 0; job->suspended = FALSE;
if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
debug_printf("Failed to send SIGCONT to %d\n", job->pid); debug_printf("Failed to send SIGCONT to %d\n", job->pid);
} }
} }
if (job->job_state == JOB_ST_FINISHED) if (job->status == JOB_ST_FINISHED)
/* Job exit deferred after calling waitpid() in a signal handler */ /* Job exit deferred after calling waitpid() in a signal handler */
JobFinish(job, job->exit_status); JobFinish(job, job->exit_status);
} }
make_suspended = 0; make_suspended = FALSE;
} }
static void static void
@ -2716,7 +2658,7 @@ Job_ServerStart(int max_tokens, int jp_0, int jp_1)
JobCreatePipe(&tokenWaitJob, 15); JobCreatePipe(&tokenWaitJob, 15);
snprintf(jobarg, sizeof(jobarg), "%d,%d", snprintf(jobarg, sizeof jobarg, "%d,%d",
tokenWaitJob.inPipe, tokenWaitJob.outPipe); tokenWaitJob.inPipe, tokenWaitJob.outPipe);
Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
@ -2784,7 +2726,7 @@ Job_TokenWithdraw(void)
/* And put the stopper back */ /* And put the stopper back */
while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
continue; continue;
if (dieQuietly(NULL, 1)) if (shouldDieQuietly(NULL, 1))
exit(2); exit(2);
Fatal("A failure has been detected in another branch of the parallel make"); Fatal("A failure has been detected in another branch of the parallel make");
} }
@ -2860,7 +2802,7 @@ emul_poll(struct pollfd *fd, int nfd, int timeout)
tvp = &tv; tvp = &tv;
} }
nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp);
if (nselect <= 0) if (nselect <= 0)
return nselect; return nselect;

24
job.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: job.h,v 1.58 2020/10/26 21:34:10 rillig Exp $ */ /* $NetBSD: job.h,v 1.63 2020/11/14 13:27:01 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -117,25 +117,25 @@ struct pollfd;
# include "meta.h" # include "meta.h"
#endif #endif
typedef enum JobState { typedef enum JobStatus {
JOB_ST_FREE = 0, /* Job is available */ JOB_ST_FREE = 0, /* Job is available */
JOB_ST_SETUP = 1, /* Job is allocated but otherwise invalid */ JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */
/* XXX: What about the 2? */
JOB_ST_RUNNING = 3, /* Job is running, pid valid */ JOB_ST_RUNNING = 3, /* Job is running, pid valid */
JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */
} JobState; } JobStatus;
typedef enum JobFlags { typedef enum JobFlags {
JOB_NONE = 0,
/* Ignore non-zero exits */ /* Ignore non-zero exits */
JOB_IGNERR = 0x001, JOB_IGNERR = 1 << 0,
/* no output */ /* no output */
JOB_SILENT = 0x002, JOB_SILENT = 1 << 1,
/* Target is a special one. i.e. run it locally /* Target is a special one. i.e. run it locally
* if we can't export it and maxLocal is 0 */ * if we can't export it and maxLocal is 0 */
JOB_SPECIAL = 0x004, JOB_SPECIAL = 1 << 2,
/* Ignore "..." lines when processing commands */
JOB_IGNDOTS = 0x008,
/* we've sent 'set -x' */ /* we've sent 'set -x' */
JOB_TRACED = 0x400 JOB_TRACED = 1 << 10
} JobFlags; } JobFlags;
/* A Job manages the shell commands that are run to create a single target. /* A Job manages the shell commands that are run to create a single target.
@ -167,9 +167,9 @@ typedef struct Job {
int exit_status; /* from wait4() in signal handler */ int exit_status; /* from wait4() in signal handler */
JobState job_state; /* status of the job entry */ JobStatus status;
char job_suspended; Boolean suspended;
JobFlags flags; /* Flags to control treatment of job */ JobFlags flags; /* Flags to control treatment of job */

144
lst.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $ */ /* $NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -34,7 +34,7 @@
#include "make.h" #include "make.h"
MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $"); MAKE_RCSID("$NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $");
#ifdef HAVE_INTTYPES_H #ifdef HAVE_INTTYPES_H
#include <inttypes.h> #include <inttypes.h>
@ -45,11 +45,11 @@ MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $");
static ListNode * static ListNode *
LstNodeNew(ListNode *prev, ListNode *next, void *datum) LstNodeNew(ListNode *prev, ListNode *next, void *datum)
{ {
ListNode *node = bmake_malloc(sizeof *node); ListNode *ln = bmake_malloc(sizeof *ln);
node->prev = prev; ln->prev = prev;
node->next = next; ln->next = next;
node->datum = datum; ln->datum = datum;
return node; return ln;
} }
/* Create and initialize a new, empty list. */ /* Create and initialize a new, empty list. */
@ -68,12 +68,11 @@ Lst_New(void)
void void
Lst_Free(List *list) Lst_Free(List *list)
{ {
ListNode *node; ListNode *ln, *next;
ListNode *next;
for (node = list->first; node != NULL; node = next) { for (ln = list->first; ln != NULL; ln = next) {
next = node->next; next = ln->next;
free(node); free(ln);
} }
free(list); free(list);
@ -84,37 +83,32 @@ Lst_Free(List *list)
void void
Lst_Destroy(List *list, LstFreeProc freeProc) Lst_Destroy(List *list, LstFreeProc freeProc)
{ {
ListNode *node; ListNode *ln, *next;
ListNode *next;
for (node = list->first; node != NULL; node = next) { for (ln = list->first; ln != NULL; ln = next) {
next = node->next; next = ln->next;
freeProc(node->datum); freeProc(ln->datum);
free(node); free(ln);
} }
free(list); free(list);
} }
/*
* Functions to modify a list
*/
/* Insert a new node with the datum before the given node. */ /* Insert a new node with the datum before the given node. */
void void
Lst_InsertBefore(List *list, ListNode *node, void *datum) Lst_InsertBefore(List *list, ListNode *ln, void *datum)
{ {
ListNode *newNode; ListNode *newNode;
assert(datum != NULL); assert(datum != NULL);
newNode = LstNodeNew(node->prev, node, datum); newNode = LstNodeNew(ln->prev, ln, datum);
if (node->prev != NULL) if (ln->prev != NULL)
node->prev->next = newNode; ln->prev->next = newNode;
node->prev = newNode; ln->prev = newNode;
if (node == list->first) if (ln == list->first)
list->first = newNode; list->first = newNode;
} }
@ -122,18 +116,18 @@ Lst_InsertBefore(List *list, ListNode *node, void *datum)
void void
Lst_Prepend(List *list, void *datum) Lst_Prepend(List *list, void *datum)
{ {
ListNode *node; ListNode *ln;
assert(datum != NULL); assert(datum != NULL);
node = LstNodeNew(NULL, list->first, datum); ln = LstNodeNew(NULL, list->first, datum);
if (list->first == NULL) { if (list->first == NULL) {
list->first = node; list->first = ln;
list->last = node; list->last = ln;
} else { } else {
list->first->prev = node; list->first->prev = ln;
list->first = node; list->first = ln;
} }
} }
@ -141,71 +135,69 @@ Lst_Prepend(List *list, void *datum)
void void
Lst_Append(List *list, void *datum) Lst_Append(List *list, void *datum)
{ {
ListNode *node; ListNode *ln;
assert(datum != NULL); assert(datum != NULL);
node = LstNodeNew(list->last, NULL, datum); ln = LstNodeNew(list->last, NULL, datum);
if (list->last == NULL) { if (list->last == NULL) {
list->first = node; list->first = ln;
list->last = node; list->last = ln;
} else { } else {
list->last->next = node; list->last->next = ln;
list->last = node; list->last = ln;
} }
} }
/* Remove the given node from the given list. /* Remove the given node from the given list.
* The datum stored in the node must be freed by the caller, if necessary. */ * The datum stored in the node must be freed by the caller, if necessary. */
void void
Lst_Remove(List *list, ListNode *node) Lst_Remove(List *list, ListNode *ln)
{ {
/* unlink it from its neighbors */ /* unlink it from its neighbors */
if (node->next != NULL) if (ln->next != NULL)
node->next->prev = node->prev; ln->next->prev = ln->prev;
if (node->prev != NULL) if (ln->prev != NULL)
node->prev->next = node->next; ln->prev->next = ln->next;
/* unlink it from the list */ /* unlink it from the list */
if (list->first == node) if (list->first == ln)
list->first = node->next; list->first = ln->next;
if (list->last == node) if (list->last == ln)
list->last = node->prev; list->last = ln->prev;
} }
/* Replace the datum in the given node with the new datum. */ /* Replace the datum in the given node with the new datum. */
void void
LstNode_Set(ListNode *node, void *datum) LstNode_Set(ListNode *ln, void *datum)
{ {
assert(datum != NULL); assert(datum != NULL);
node->datum = datum; ln->datum = datum;
} }
/* Replace the datum in the given node to NULL. /* Replace the datum in the given node with NULL.
* Having NULL values in a list is unusual though. */ * Having NULL values in a list is unusual though. */
void void
LstNode_SetNull(ListNode *node) LstNode_SetNull(ListNode *ln)
{ {
node->datum = NULL; ln->datum = NULL;
} }
/* /* Return the first node that contains the given datum, or NULL.
* Functions for entire lists *
*/ * Time complexity: O(length(list)) */
/* Return the first node that contains the given datum, or NULL. */
ListNode * ListNode *
Lst_FindDatum(List *list, const void *datum) Lst_FindDatum(List *list, const void *datum)
{ {
ListNode *node; ListNode *ln;
assert(datum != NULL); assert(datum != NULL);
for (node = list->first; node != NULL; node = node->next) for (ln = list->first; ln != NULL; ln = ln->next)
if (node->datum == datum) if (ln->datum == datum)
return node; return ln;
return NULL; return NULL;
} }
@ -213,32 +205,32 @@ Lst_FindDatum(List *list, const void *datum)
int int
Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData) Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData)
{ {
ListNode *node; ListNode *ln;
int result = 0; int result = 0;
for (node = list->first; node != NULL; node = node->next) { for (ln = list->first; ln != NULL; ln = ln->next) {
result = proc(node->datum, procData); result = proc(ln->datum, procData);
if (result != 0) if (result != 0)
break; break;
} }
return result; return result;
} }
/* Move all nodes from list2 to the end of list1. /* Move all nodes from src to the end of dst.
* List2 is destroyed and freed. */ * The source list is destroyed and freed. */
void void
Lst_MoveAll(List *list1, List *list2) Lst_MoveAll(List *dst, List *src)
{ {
if (list2->first != NULL) { if (src->first != NULL) {
list2->first->prev = list1->last; src->first->prev = dst->last;
if (list1->last != NULL) if (dst->last != NULL)
list1->last->next = list2->first; dst->last->next = src->first;
else else
list1->first = list2->first; dst->first = src->first;
list1->last = list2->last; dst->last = src->last;
} }
free(list2); free(src);
} }
/* Copy the element data from src to the start of dst. */ /* Copy the element data from src to the start of dst. */

6
lst.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: lst.h,v 1.84 2020/10/28 02:43:16 rillig Exp $ */ /* $NetBSD: lst.h,v 1.85 2020/11/10 00:32:12 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -118,7 +118,7 @@ void Lst_Destroy(List *, LstFreeProc);
/* Get information about a list */ /* Get information about a list */
static inline MAKE_ATTR_UNUSED Boolean MAKE_INLINE Boolean
Lst_IsEmpty(List *list) { return list->first == NULL; } Lst_IsEmpty(List *list) { return list->first == NULL; }
/* Find the first node that contains the given datum, or NULL. */ /* Find the first node that contains the given datum, or NULL. */
@ -173,7 +173,7 @@ void Vector_Init(Vector *, size_t);
/* Return the pointer to the given item in the vector. /* Return the pointer to the given item in the vector.
* The returned data is valid until the next modifying operation. */ * The returned data is valid until the next modifying operation. */
static inline MAKE_ATTR_UNUSED void * MAKE_INLINE void *
Vector_Get(Vector *v, size_t i) Vector_Get(Vector *v, size_t i)
{ {
unsigned char *items = v->items; unsigned char *items = v->items;

818
main.c

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,8 @@ CFLAGS="@CFLAGS@ -I. -I${srcdir} @DEFS@ @CPPFLAGS@ -DMAKE_NATIVE ${XDEFS} -DBMAK
MAKE_VERSION=@_MAKE_VERSION@ MAKE_VERSION=@_MAKE_VERSION@
MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \ MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \
-D@force_machine@MACHINE=\"@machine@\" -DMACHINE_ARCH=\"@machine_arch@\" \ -D@force_machine@MACHINE=\"@machine@\" \
-D@force_machine_arch@MACHINE_ARCH=\"@machine_arch@\" \
-D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\"" -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\""
@ -59,7 +60,7 @@ do_link() {
} }
BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \ BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \
lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \ lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o \
suff.o targ.o trace.o var.o util.o" suff.o targ.o trace.o var.o util.o"
LIB_OBJECTS="@LIBOBJS@" LIB_OBJECTS="@LIBOBJS@"

19
make.1
View file

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $ .\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
.\" .\"
.\" Copyright (c) 1990, 1993 .\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved. .\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\" .\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\" .\"
.Dd November 1, 2020 .Dd November 14, 2020
.Dt MAKE 1 .Dt MAKE 1
.Os .Os
.Sh NAME .Sh NAME
@ -37,7 +37,7 @@
.Nd maintain program dependencies .Nd maintain program dependencies
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl BeikNnqrstWwX .Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory .Op Fl C Ar directory
.Op Fl D Ar variable .Op Fl D Ar variable
.Op Fl d Ar flags .Op Fl d Ar flags
@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are
up-to-date and 1, otherwise. up-to-date and 1, otherwise.
.It Fl r .It Fl r
Do not use the built-in rules specified in the system makefile. Do not use the built-in rules specified in the system makefile.
.It Fl S
Stop processing if an error is encountered.
This is the default behavior and the opposite of
.Fl k .
.It Fl s .It Fl s
Do not echo any commands as they are executed. Do not echo any commands as they are executed.
Equivalent to specifying Equivalent to specifying
@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and and
.Ql Ev PWD .Ql Ev PWD
to that directory before executing any targets. to that directory before executing any targets.
.Pp
Except in the case of an explicit
.Ql Ic .OBJDIR
target,
.Nm
will check that the specified directory is writable and ignore it if not.
This check can be skipped by setting the environment variable
.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE
to "no".
. .
.It Va .PARSEDIR .It Va .PARSEDIR
A path to the directory of the current A path to the directory of the current

335
make.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $ */ /* $NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -68,33 +68,28 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
/*- /* Examination of targets and their suitability for creation.
* make.c --
* The functions which perform the examination of targets and
* their suitability for creation
* *
* Interface: * Interface:
* Make_Run Initialize things for the module and recreate * Make_Run Initialize things for the module. Returns TRUE if
* whatever needs recreating. Returns TRUE if * work was (or would have been) done.
* work was (or would have been) done and FALSE
* otherwise.
* *
* Make_Update Update all parents of a given child. Performs * Make_Update After a target is made, update all its parents.
* various bookkeeping chores like the updating * Perform various bookkeeping chores like the updating
* of the youngestChild field of the parent, filling * of the youngestChild field of the parent, filling
* of the IMPSRC context variable, etc. It will * of the IMPSRC context variable, etc. Place the parent
* place the parent on the toBeMade queue if it * on the toBeMade queue if it should be.
* should be.
* *
* Make_TimeStamp Function to set the parent's youngestChild field * GNode_UpdateYoungestChild
* based on a child's modification time. * Update the node's youngestChild field based on the
* child's modification time.
* *
* Make_DoAllVar Set up the various local variables for a * Make_DoAllVar Set up the various local variables for a
* target, including the .ALLSRC variable, making * target, including the .ALLSRC variable, making
* sure that any variable that needs to exist * sure that any variable that needs to exist
* at the very least has the empty value. * at the very least has the empty value.
* *
* Make_OODate Determine if a target is out-of-date. * GNode_IsOODate Determine if a target is out-of-date.
* *
* Make_HandleUse See if a child is a .USE node for a parent * Make_HandleUse See if a child is a .USE node for a parent
* and perform the .USE actions if so. * and perform the .USE actions if so.
@ -107,17 +102,16 @@
#include "job.h" #include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $"); MAKE_RCSID("$NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $");
/* Sequence # to detect recursion. */ /* Sequence # to detect recursion. */
static unsigned int checked = 1; static unsigned int checked_seqno = 1;
/* The current fringe of the graph. /* The current fringe of the graph.
* These are nodes which await examination by MakeOODate. * These are nodes which await examination by MakeOODate.
* It is added to by Make_Update and subtracted from by MakeStartJobs */ * It is added to by Make_Update and subtracted from by MakeStartJobs */
static GNodeList *toBeMade; static GNodeList *toBeMade;
static int MakeCheckOrder(void *, void *);
static int MakeBuildParent(void *, void *); static int MakeBuildParent(void *, void *);
void void
@ -185,11 +179,37 @@ GNode_ShouldExecute(GNode *gn)
/* Update the youngest child of the node, according to the given child. */ /* Update the youngest child of the node, according to the given child. */
void void
Make_TimeStamp(GNode *pgn, GNode *cgn) GNode_UpdateYoungestChild(GNode *gn, GNode *cgn)
{ {
if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) { if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime)
pgn->youngestChild = cgn; gn->youngestChild = cgn;
} }
static Boolean
IsOODateRegular(GNode *gn)
{
/* These rules are inherited from the original Make. */
if (gn->youngestChild != NULL) {
if (gn->mtime < gn->youngestChild->mtime) {
DEBUG1(MAKE, "modified before source \"%s\"...",
GNode_Path(gn->youngestChild));
return TRUE;
}
return FALSE;
}
if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) {
DEBUG0(MAKE, "non-existent and no sources...");
return TRUE;
}
if (gn->type & OP_DOUBLEDEP) {
DEBUG0(MAKE, ":: operator and no sources...");
return TRUE;
}
return FALSE;
} }
/* See if the node is out of date with respect to its sources. /* See if the node is out of date with respect to its sources.
@ -204,7 +224,7 @@ Make_TimeStamp(GNode *pgn, GNode *cgn)
* may be changed. * may be changed.
*/ */
Boolean Boolean
Make_OODate(GNode *gn) GNode_IsOODate(GNode *gn)
{ {
Boolean oodate; Boolean oodate;
@ -212,16 +232,15 @@ Make_OODate(GNode *gn)
* Certain types of targets needn't even be sought as their datedness * Certain types of targets needn't even be sought as their datedness
* doesn't depend on their modification time... * doesn't depend on their modification time...
*/ */
if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) { if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
(void)Dir_MTime(gn, 1); Dir_UpdateMTime(gn, TRUE);
if (DEBUG(MAKE)) { if (DEBUG(MAKE)) {
if (gn->mtime != 0) { if (gn->mtime != 0)
debug_printf("modified %s...", Targ_FmtTime(gn->mtime)); debug_printf("modified %s...", Targ_FmtTime(gn->mtime));
} else { else
debug_printf("non-existent..."); debug_printf("non-existent...");
} }
} }
}
/* /*
* A target is remade in one of the following circumstances: * A target is remade in one of the following circumstances:
@ -244,8 +263,7 @@ Make_OODate(GNode *gn)
*/ */
DEBUG0(MAKE, ".USE node..."); DEBUG0(MAKE, ".USE node...");
oodate = FALSE; oodate = FALSE;
} else if ((gn->type & OP_LIB) && } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) {
((gn->mtime==0) || Arch_IsLib(gn))) {
DEBUG0(MAKE, "library..."); DEBUG0(MAKE, "library...");
/* /*
@ -261,7 +279,7 @@ Make_OODate(GNode *gn)
*/ */
DEBUG0(MAKE, ".JOIN node..."); DEBUG0(MAKE, ".JOIN node...");
DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; oodate = (gn->flags & CHILDMADE) != 0;
} else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
/* /*
* A node which is the object of the force (!) operator or which has * A node which is the object of the force (!) operator or which has
@ -277,30 +295,7 @@ Make_OODate(GNode *gn)
} }
} }
oodate = TRUE; oodate = TRUE;
} else if ((gn->youngestChild != NULL && } else if (IsOODateRegular(gn)) {
gn->mtime < gn->youngestChild->mtime) ||
(gn->youngestChild == NULL &&
((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
|| gn->type & OP_DOUBLEDEP)))
{
/*
* A node whose modification time is less than that of its
* youngest child or that has no children (youngestChild == NULL) and
* either doesn't exist (mtime == 0) and it isn't optional
* or was the object of a * :: operator is out-of-date.
* Why? Because that's the way Make does it.
*/
if (DEBUG(MAKE)) {
if (gn->youngestChild != NULL &&
gn->mtime < gn->youngestChild->mtime) {
debug_printf("modified before source %s...",
GNode_Path(gn->youngestChild));
} else if (gn->mtime == 0) {
debug_printf("non-existent and no sources...");
} else {
debug_printf(":: operator and no sources...");
}
}
oodate = TRUE; oodate = TRUE;
} else { } else {
/* /*
@ -314,7 +309,7 @@ Make_OODate(GNode *gn)
if (gn->flags & FORCE) if (gn->flags & FORCE)
debug_printf("non existing child..."); debug_printf("non existing child...");
} }
oodate = (gn->flags & FORCE) ? TRUE : FALSE; oodate = (gn->flags & FORCE) != 0;
} }
#ifdef USE_META #ifdef USE_META
@ -333,46 +328,24 @@ Make_OODate(GNode *gn)
if (!oodate) { if (!oodate) {
GNodeListNode *ln; GNodeListNode *ln;
for (ln = gn->parents->first; ln != NULL; ln = ln->next) for (ln = gn->parents->first; ln != NULL; ln = ln->next)
Make_TimeStamp(ln->datum, gn); GNode_UpdateYoungestChild(ln->datum, gn);
} }
return oodate; return oodate;
} }
/* Add the node to the list if it needs to be examined. */ static void
static int PretendAllChildrenAreMade(GNode *pgn)
MakeAddChild(void *gnp, void *lp)
{ {
GNode *gn = gnp; GNodeListNode *ln;
GNodeList *l = lp;
if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { for (ln = pgn->children->first; ln != NULL; ln = ln->next) {
DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n", GNode *cgn = ln->datum;
gn->name, gn->cohort_num);
Lst_Enqueue(l, gn);
}
return 0;
}
/* Find the pathname of a child that was already made. Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */
* GNode_UpdateYoungestChild(pgn, cgn);
* The path and mtime of the node and the youngestChild of the parent are
* updated; the unmade children count of the parent is decremented.
*
* Input:
* gnp the node to find
*/
static int
MakeFindChild(void *gnp, void *pgnp)
{
GNode *gn = gnp;
GNode *pgn = pgnp;
(void)Dir_MTime(gn, 0);
Make_TimeStamp(pgn, gn);
pgn->unmade--; pgn->unmade--;
}
return 0;
} }
/* Called by Make_Run and SuffApplyTransform on the downward pass to handle /* Called by Make_Run and SuffApplyTransform on the downward pass to handle
@ -394,9 +367,9 @@ Make_HandleUse(GNode *cgn, GNode *pgn)
GNodeListNode *ln; /* An element in the children list */ GNodeListNode *ln; /* An element in the children list */
#ifdef DEBUG_SRC #ifdef DEBUG_SRC
if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { if (!(cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM))) {
debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name); debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name);
return; return; /* XXX: debug mode should not affect control flow */
} }
#endif #endif
@ -457,10 +430,10 @@ MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln)
{ {
Boolean unmarked; Boolean unmarked;
unmarked = ((cgn->type & OP_MARK) == 0); unmarked = !(cgn->type & OP_MARK);
cgn->type |= OP_MARK; cgn->type |= OP_MARK;
if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) if (!(cgn->type & (OP_USE|OP_USEBEFORE)))
return; return;
if (unmarked) if (unmarked)
@ -493,7 +466,10 @@ HandleUseNodes(GNode *gn)
time_t time_t
Make_Recheck(GNode *gn) Make_Recheck(GNode *gn)
{ {
time_t mtime = Dir_MTime(gn, 1); time_t mtime;
Dir_UpdateMTime(gn, TRUE);
mtime = gn->mtime;
#ifndef RECHECK #ifndef RECHECK
/* /*
@ -512,13 +488,11 @@ Make_Recheck(GNode *gn)
* In this case, if the definitions produced by yacc haven't changed * In this case, if the definitions produced by yacc haven't changed
* from before, parse.h won't have been updated and gn->mtime will * from before, parse.h won't have been updated and gn->mtime will
* reflect the current modification time for parse.h. This is * reflect the current modification time for parse.h. This is
* something of a kludge, I admit, but it's a useful one.. * something of a kludge, I admit, but it's a useful one.
* XXX: People like to use a rule like
* *
* FRC: * XXX: People like to use a rule like "FRC:" to force things that
* * depend on FRC to be made, so we have to check for gn->children
* To force things that depend on FRC to be made, so we have to * being empty as well.
* check for gn->children being empty as well...
*/ */
if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
gn->mtime = now; gn->mtime = now;
@ -535,7 +509,7 @@ Make_Recheck(GNode *gn)
* using the same file from a common server), there are times * using the same file from a common server), there are times
* when the modification time of a file created on a remote * when the modification time of a file created on a remote
* machine will not be modified before the local stat() implied by * machine will not be modified before the local stat() implied by
* the Dir_MTime occurs, thus leading us to believe that the file * the Dir_UpdateMTime occurs, thus leading us to believe that the file
* is unchanged, wreaking havoc with files that depend on this one. * is unchanged, wreaking havoc with files that depend on this one.
* *
* I have decided it is better to make too much than to make too * I have decided it is better to make too much than to make too
@ -543,8 +517,8 @@ Make_Recheck(GNode *gn)
* -- ardeb 1/12/88 * -- ardeb 1/12/88
*/ */
/* /*
* Christos, 4/9/92: If we are saving commands pretend that * Christos, 4/9/92: If we are saving commands, pretend that
* the target is made now. Otherwise archives with ... rules * the target is made now. Otherwise archives with '...' rules
* don't work! * don't work!
*/ */
if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) || if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
@ -552,12 +526,14 @@ Make_Recheck(GNode *gn)
DEBUG2(MAKE, " recheck(%s): update time from %s to now\n", DEBUG2(MAKE, " recheck(%s): update time from %s to now\n",
gn->name, Targ_FmtTime(gn->mtime)); gn->name, Targ_FmtTime(gn->mtime));
gn->mtime = now; gn->mtime = now;
} } else {
else {
DEBUG2(MAKE, " recheck(%s): current update time: %s\n", DEBUG2(MAKE, " recheck(%s): current update time: %s\n",
gn->name, Targ_FmtTime(gn->mtime)); gn->name, Targ_FmtTime(gn->mtime));
} }
#endif #endif
/* XXX: The returned mtime may differ from gn->mtime.
* Intentionally? */
return mtime; return mtime;
} }
@ -581,6 +557,25 @@ UpdateImplicitParentsVars(GNode *cgn, const char *cname)
} }
} }
/* See if a .ORDER rule stops us from building this node. */
static Boolean
IsWaitingForOrder(GNode *gn)
{
GNodeListNode *ln;
for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) {
GNode *ogn = ln->datum;
if (ogn->made >= MADE || !(ogn->flags & REMAKE))
continue;
DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n",
ogn->name, ogn->cohort_num);
return TRUE;
}
return FALSE;
}
/* Perform update on the parents of a node. Used by JobFinish once /* Perform update on the parents of a node. Used by JobFinish once
* a node has been dealt with and by MakeStartJobs if it finds an * a node has been dealt with and by MakeStartJobs if it finds an
* up-to-date node. * up-to-date node.
@ -610,7 +605,7 @@ Make_Update(GNode *cgn)
GNode *centurion; GNode *centurion;
/* It is save to re-examine any nodes again */ /* It is save to re-examine any nodes again */
checked++; checked_seqno++;
cname = GNode_VarTarget(cgn); cname = GNode_VarTarget(cgn);
@ -647,11 +642,11 @@ Make_Update(GNode *cgn)
for (ln = parents->first; ln != NULL; ln = ln->next) { for (ln = parents->first; ln != NULL; ln = ln->next) {
GNode *pgn = ln->datum; GNode *pgn = ln->datum;
if (DEBUG(MAKE)) if (DEBUG(MAKE)) {
debug_printf("inspect parent %s%s: flags %x, " debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num);
"type %x, made %d, unmade %d ", GNode_FprintDetails(opts.debug_file, "", pgn, "");
pgn->name, pgn->cohort_num, pgn->flags, debug_printf(", unmade %d ", pgn->unmade - 1);
pgn->type, pgn->made, pgn->unmade - 1); }
if (!(pgn->flags & REMAKE)) { if (!(pgn->flags & REMAKE)) {
/* This parent isn't needed */ /* This parent isn't needed */
@ -677,7 +672,7 @@ Make_Update(GNode *cgn)
if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) { if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) {
if (cgn->made == MADE) if (cgn->made == MADE)
pgn->flags |= CHILDMADE; pgn->flags |= CHILDMADE;
(void)Make_TimeStamp(pgn, cgn); GNode_UpdateYoungestChild(pgn, cgn);
} }
/* /*
@ -716,11 +711,10 @@ Make_Update(GNode *cgn)
DEBUG0(MAKE, "- not deferred\n"); DEBUG0(MAKE, "- not deferred\n");
continue; continue;
} }
assert(pgn->order_pred != NULL);
if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) { if (IsWaitingForOrder(pgn))
/* A .ORDER rule stops us building this */
continue; continue;
}
if (DEBUG(MAKE)) { if (DEBUG(MAKE)) {
debug_printf("- %s%s made, schedule %s%s (made %d)\n", debug_printf("- %s%s made, schedule %s%s (made %d)\n",
cgn->name, cgn->cohort_num, cgn->name, cgn->cohort_num,
@ -771,7 +765,7 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
return; return;
cgn->type |= OP_MARK; cgn->type |= OP_MARK;
if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { if (!(cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE))) {
const char *child, *allsrc; const char *child, *allsrc;
if (cgn->type & OP_ARCHV) if (cgn->type & OP_ARCHV)
@ -798,9 +792,9 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
* the start of the make. This is to keep pmake from getting * the start of the make. This is to keep pmake from getting
* confused if something else updates the parent after the * confused if something else updates the parent after the
* make starts (shouldn't happen, I know, but sometimes it * make starts (shouldn't happen, I know, but sometimes it
* does). In such a case, if we've updated the kid, the parent * does). In such a case, if we've updated the child, the parent
* is likely to have a modification time later than that of * is likely to have a modification time later than that of
* the kid and anything that relies on the OODATE variable will * the child and anything that relies on the OODATE variable will
* be hosed. * be hosed.
* *
* XXX: This will cause all made children to go in the OODATE * XXX: This will cause all made children to go in the OODATE
@ -838,44 +832,31 @@ Make_DoAllVar(GNode *gn)
for (ln = gn->children->first; ln != NULL; ln = ln->next) for (ln = gn->children->first; ln != NULL; ln = ln->next)
MakeAddAllSrc(ln->datum, gn); MakeAddAllSrc(ln->datum, gn);
if (!Var_Exists(OODATE, gn)) { if (!Var_Exists(OODATE, gn))
Var_Set(OODATE, "", gn); Var_Set(OODATE, "", gn);
} if (!Var_Exists(ALLSRC, gn))
if (!Var_Exists(ALLSRC, gn)) {
Var_Set(ALLSRC, "", gn); Var_Set(ALLSRC, "", gn);
}
if (gn->type & OP_JOIN) if (gn->type & OP_JOIN)
Var_Set(TARGET, GNode_VarAllsrc(gn), gn); Var_Set(TARGET, GNode_VarAllsrc(gn), gn);
gn->flags |= DONE_ALLSRC; gn->flags |= DONE_ALLSRC;
} }
static int
MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED)
{
GNode *bn = v_bn;
if (bn->made >= MADE || !(bn->flags & REMAKE))
return 0;
DEBUG2(MAKE, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
bn->name, bn->cohort_num);
return 1;
}
static int static int
MakeBuildChild(void *v_cn, void *toBeMade_next) MakeBuildChild(void *v_cn, void *toBeMade_next)
{ {
GNode *cn = v_cn; GNode *cn = v_cn;
DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n", if (DEBUG(MAKE)) {
cn->name, cn->cohort_num, cn->made, cn->type); debug_printf("MakeBuildChild: inspect %s%s, ",
cn->name, cn->cohort_num);
GNode_FprintDetails(opts.debug_file, "", cn, "\n");
}
if (cn->made > DEFERRED) if (cn->made > DEFERRED)
return 0; return 0;
/* If this node is on the RHS of a .ORDER, check LHSs. */ /* If this node is on the RHS of a .ORDER, check LHSs. */
assert(cn->order_pred); if (IsWaitingForOrder(cn)) {
if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) {
/* Can't build this (or anything else in this child list) yet */ /* Can't build this (or anything else in this child list) yet */
cn->made = DEFERRED; cn->made = DEFERRED;
return 0; /* but keep looking */ return 0; /* but keep looking */
@ -899,7 +880,7 @@ MakeBuildChild(void *v_cn, void *toBeMade_next)
return cn->type & OP_WAIT && cn->unmade > 0; return cn->type & OP_WAIT && cn->unmade > 0;
} }
/* When a .ORDER LHS node completes we do this on each RHS */ /* When a .ORDER LHS node completes, we do this on each RHS. */
static int static int
MakeBuildParent(void *v_pn, void *toBeMade_next) MakeBuildParent(void *v_pn, void *toBeMade_next)
{ {
@ -918,21 +899,21 @@ MakeBuildParent(void *v_pn, void *toBeMade_next)
/* Start as many jobs as possible, taking them from the toBeMade queue. /* Start as many jobs as possible, taking them from the toBeMade queue.
* *
* If the query flag was given to pmake, no job will be started, * If the -q option was given, no job will be started,
* but as soon as an out-of-date target is found, this function * but as soon as an out-of-date target is found, this function
* returns TRUE. At all other times, this function returns FALSE. * returns TRUE. In all other cases, this function returns FALSE.
*/ */
static Boolean static Boolean
MakeStartJobs(void) MakeStartJobs(void)
{ {
GNode *gn; GNode *gn;
int have_token = 0; Boolean have_token = FALSE;
while (!Lst_IsEmpty(toBeMade)) { while (!Lst_IsEmpty(toBeMade)) {
/* Get token now to avoid cycling job-list when we only have 1 token */ /* Get token now to avoid cycling job-list when we only have 1 token */
if (!have_token && !Job_TokenWithdraw()) if (!have_token && !Job_TokenWithdraw())
break; break;
have_token = 1; have_token = TRUE;
gn = Lst_Dequeue(toBeMade); gn = Lst_Dequeue(toBeMade);
DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num);
@ -943,13 +924,13 @@ MakeStartJobs(void)
make_abort(gn, __LINE__); make_abort(gn, __LINE__);
} }
if (gn->checked_seqno == checked) { if (gn->checked_seqno == checked_seqno) {
/* We've already looked at this node since a job finished... */ /* We've already looked at this node since a job finished... */
DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num); DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num);
gn->made = DEFERRED; gn->made = DEFERRED;
continue; continue;
} }
gn->checked_seqno = checked; gn->checked_seqno = checked_seqno;
if (gn->unmade != 0) { if (gn->unmade != 0) {
/* /*
@ -964,14 +945,13 @@ MakeStartJobs(void)
} }
gn->made = BEINGMADE; gn->made = BEINGMADE;
if (Make_OODate(gn)) { if (GNode_IsOODate(gn)) {
DEBUG0(MAKE, "out-of-date\n"); DEBUG0(MAKE, "out-of-date\n");
if (opts.queryFlag) { if (opts.queryFlag)
return TRUE; return TRUE;
}
Make_DoAllVar(gn); Make_DoAllVar(gn);
Job_Make(gn); Job_Make(gn);
have_token = 0; have_token = FALSE;
} else { } else {
DEBUG0(MAKE, "up-to-date\n"); DEBUG0(MAKE, "up-to-date\n");
gn->made = UPTODATE; gn->made = UPTODATE;
@ -994,6 +974,7 @@ MakeStartJobs(void)
return FALSE; return FALSE;
} }
/* Print the status of a .ORDER node. */
static void static void
MakePrintStatusOrderNode(GNode *ogn, GNode *gn) MakePrintStatusOrderNode(GNode *ogn, GNode *gn)
{ {
@ -1074,7 +1055,7 @@ MakePrintStatus(GNode *gn, int *errors)
* print out the cycle by recursing on its children. * print out the cycle by recursing on its children.
*/ */
if (!(gn->flags & CYCLE)) { if (!(gn->flags & CYCLE)) {
/* Fist time we've seen this node, check all children */ /* First time we've seen this node, check all children */
gn->flags |= CYCLE; gn->flags |= CYCLE;
MakePrintStatusList(gn->children, errors); MakePrintStatusList(gn->children, errors);
/* Mark that this node needn't be processed again */ /* Mark that this node needn't be processed again */
@ -1103,6 +1084,25 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
break; break;
} }
static void
ExamineLater(GNodeList *examine, GNodeList *toBeExamined)
{
ListNode *ln;
for (ln = toBeExamined->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
if (gn->flags & REMAKE)
continue;
if (gn->type & (OP_USE | OP_USEBEFORE))
continue;
DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n",
gn->name, gn->cohort_num);
Lst_Enqueue(examine, gn);
}
}
/* Expand .USE nodes and create a new targets list. /* Expand .USE nodes and create a new targets list.
* *
* Input: * Input:
@ -1111,17 +1111,8 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
void void
Make_ExpandUse(GNodeList *targs) Make_ExpandUse(GNodeList *targs)
{ {
GNodeList *examine; /* List of targets to examine */ GNodeList *examine = Lst_New(); /* Queue of targets to examine */
Lst_AppendAll(examine, targs);
{
/* XXX: Why is it necessary to copy the list? There shouldn't be
* any modifications to the list, at least the function name
* ExpandUse doesn't suggest that. */
GNodeListNode *ln;
examine = Lst_New();
for (ln = targs->first; ln != NULL; ln = ln->next)
Lst_Append(examine, ln->datum);
}
/* /*
* Make an initial downward pass over the graph, marking nodes to be made * Make an initial downward pass over the graph, marking nodes to be made
@ -1151,9 +1142,8 @@ Make_ExpandUse(GNodeList *targs)
* expansions. * expansions.
*/ */
if (gn->type & OP_ARCHV) { if (gn->type & OP_ARCHV) {
char *eoa, *eon; char *eoa = strchr(gn->name, '(');
eoa = strchr(gn->name, '('); char *eon = strchr(gn->name, ')');
eon = strchr(gn->name, ')');
if (eoa == NULL || eon == NULL) if (eoa == NULL || eon == NULL)
continue; continue;
*eoa = '\0'; *eoa = '\0';
@ -1164,23 +1154,22 @@ Make_ExpandUse(GNodeList *targs)
*eon = ')'; *eon = ')';
} }
(void)Dir_MTime(gn, 0); Dir_UpdateMTime(gn, FALSE);
Var_Set(TARGET, GNode_Path(gn), gn); Var_Set(TARGET, GNode_Path(gn), gn);
UnmarkChildren(gn); UnmarkChildren(gn);
HandleUseNodes(gn); HandleUseNodes(gn);
if ((gn->type & OP_MADE) == 0) if (!(gn->type & OP_MADE))
Suff_FindDeps(gn); Suff_FindDeps(gn);
else { else {
/* Pretend we made all this node's children */ PretendAllChildrenAreMade(gn);
Lst_ForEachUntil(gn->children, MakeFindChild, gn);
if (gn->unmade != 0) if (gn->unmade != 0)
printf("Warning: %s%s still has %d unmade children\n", printf("Warning: %s%s still has %d unmade children\n",
gn->name, gn->cohort_num, gn->unmade); gn->name, gn->cohort_num, gn->unmade);
} }
if (gn->unmade != 0) if (gn->unmade != 0)
Lst_ForEachUntil(gn->children, MakeAddChild, examine); ExamineLater(examine, gn->children);
} }
Lst_Free(examine); Lst_Free(examine);
@ -1218,7 +1207,7 @@ Make_ProcessWait(GNodeList *targs)
* Perhaps this should be done earlier... * Perhaps this should be done earlier...
*/ */
pgn = Targ_NewGN(".MAIN"); pgn = GNode_New(".MAIN");
pgn->flags = REMAKE; pgn->flags = REMAKE;
pgn->type = OP_PHONY | OP_DEPENDS; pgn->type = OP_PHONY | OP_DEPENDS;
/* Get it displayed in the diag dumps */ /* Get it displayed in the diag dumps */
@ -1353,9 +1342,9 @@ Make_Run(GNodeList *targs)
MakePrintStatusList(targs, &errors); MakePrintStatusList(targs, &errors);
if (DEBUG(MAKE)) { if (DEBUG(MAKE)) {
debug_printf("done: errors %d\n", errors); debug_printf("done: errors %d\n", errors);
if (errors) if (errors > 0)
Targ_PrintGraph(4); Targ_PrintGraph(4);
} }
} }
return errors != 0; return errors > 0;
} }

262
make.h
View file

@ -1,4 +1,4 @@
/* $NetBSD: make.h,v 1.179 2020/11/01 17:47:26 rillig Exp $ */ /* $NetBSD: make.h,v 1.210 2020/11/16 21:53:10 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -135,6 +135,8 @@
#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
#endif #endif
#define MAKE_INLINE static inline MAKE_ATTR_UNUSED
/* /*
* A boolean type is defined as an integer, not an enum, for historic reasons. * A boolean type is defined as an integer, not an enum, for historic reasons.
* The only allowed values are the constants TRUE and FALSE (1 and 0). * The only allowed values are the constants TRUE and FALSE (1 and 0).
@ -187,7 +189,7 @@ typedef int Boolean;
#define POSIX_SIGNALS #define POSIX_SIGNALS
#endif #endif
typedef enum { typedef enum GNodeMade {
UNMADE, /* Not examined yet */ UNMADE, /* Not examined yet */
DEFERRED, /* Examined once (building child) */ DEFERRED, /* Examined once (building child) */
REQUESTED, /* on toBeMade list */ REQUESTED, /* on toBeMade list */
@ -207,6 +209,8 @@ typedef enum {
* *
* Some of the OP_ constants can be combined, others cannot. */ * Some of the OP_ constants can be combined, others cannot. */
typedef enum GNodeType { typedef enum GNodeType {
OP_NONE = 0,
/* The dependency operator ':' is the most common one. The commands of /* The dependency operator ':' is the most common one. The commands of
* this node are executed if any child is out-of-date. */ * this node are executed if any child is out-of-date. */
OP_DEPENDS = 1 << 0, OP_DEPENDS = 1 << 0,
@ -215,7 +219,8 @@ typedef enum GNodeType {
OP_FORCE = 1 << 1, OP_FORCE = 1 << 1,
/* The dependency operator '::' behaves like ':', except that it allows /* The dependency operator '::' behaves like ':', except that it allows
* multiple dependency groups to be defined. Each of these groups is * multiple dependency groups to be defined. Each of these groups is
* executed on its own, independently from the others. */ * executed on its own, independently from the others. Each individual
* dependency group is called a cohort. */
OP_DOUBLEDEP = 1 << 2, OP_DOUBLEDEP = 1 << 2,
/* Matches the dependency operators ':', '!' and '::'. */ /* Matches the dependency operators ':', '!' and '::'. */
@ -246,7 +251,7 @@ typedef enum GNodeType {
/* Like .USE, only prepend commands */ /* Like .USE, only prepend commands */
OP_USEBEFORE = 1 << 13, OP_USEBEFORE = 1 << 13,
/* The node is invisible to its parents. I.e. it doesn't show up in the /* The node is invisible to its parents. I.e. it doesn't show up in the
* parents' local variables. */ * parents' local variables (.IMPSRC, .ALLSRC). */
OP_INVISIBLE = 1 << 14, OP_INVISIBLE = 1 << 14,
/* The node is exempt from normal 'main target' processing in parse.c */ /* The node is exempt from normal 'main target' processing in parse.c */
OP_NOTMAIN = 1 << 15, OP_NOTMAIN = 1 << 15,
@ -254,7 +259,10 @@ typedef enum GNodeType {
OP_PHONY = 1 << 16, OP_PHONY = 1 << 16,
/* Don't search for file in the path */ /* Don't search for file in the path */
OP_NOPATH = 1 << 17, OP_NOPATH = 1 << 17,
/* .WAIT phony node */ /* In a dependency line "target: source1 .WAIT source2", source1 is made
* first, including its children. Once that is finished, source2 is made,
* including its children. The .WAIT keyword may appear more than once in
* a single dependency declaration. */
OP_WAIT = 1 << 18, OP_WAIT = 1 << 18,
/* .NOMETA do not create a .meta file */ /* .NOMETA do not create a .meta file */
OP_NOMETA = 1 << 19, OP_NOMETA = 1 << 19,
@ -267,7 +275,7 @@ typedef enum GNodeType {
/* Attributes applied by PMake */ /* Attributes applied by PMake */
/* The node is a transformation rule */ /* The node is a transformation rule, such as ".c.o". */
OP_TRANSFORM = 1 << 31, OP_TRANSFORM = 1 << 31,
/* Target is a member of an archive */ /* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */ /* XXX: How does this differ from OP_ARCHV? */
@ -337,7 +345,7 @@ typedef struct GNode {
int unmade; /* The number of unmade children */ int unmade; /* The number of unmade children */
/* The modification time; 0 means the node does not have a corresponding /* The modification time; 0 means the node does not have a corresponding
* file; see Make_OODate. */ * file; see GNode_IsOODate. */
time_t mtime; time_t mtime;
struct GNode *youngestChild; struct GNode *youngestChild;
@ -346,9 +354,6 @@ typedef struct GNode {
* file.c has the node for file.o in this list. */ * file.c has the node for file.o in this list. */
GNodeList *implicitParents; GNodeList *implicitParents;
/* Other nodes of the same name, for the '::' operator. */
GNodeList *cohorts;
/* The nodes that depend on this one, or in other words, the nodes for /* The nodes that depend on this one, or in other words, the nodes for
* which this is a source. */ * which this is a source. */
GNodeList *parents; GNodeList *parents;
@ -364,6 +369,8 @@ typedef struct GNode {
* in the normal sense. */ * in the normal sense. */
GNodeList *order_succ; GNodeList *order_succ;
/* Other nodes of the same name, for the '::' dependency operator. */
GNodeList *cohorts;
/* The "#n" suffix for this cohort, or "" for other nodes */ /* The "#n" suffix for this cohort, or "" for other nodes */
char cohort_num[8]; char cohort_num[8];
/* The number of unmade instances on the cohorts list */ /* The number of unmade instances on the cohorts list */
@ -389,20 +396,20 @@ typedef struct GNode {
* but the Suff module) */ * but the Suff module) */
struct Suff *suffix; struct Suff *suffix;
/* filename where the GNode got defined */ /* Filename where the GNode got defined */
/* XXX: What is the lifetime of this string? */
const char *fname; const char *fname;
/* line number where the GNode got defined */ /* Line number where the GNode got defined */
int lineno; int lineno;
} GNode; } GNode;
/* /* Error levels for diagnostics during parsing. */
* Error levels for parsing. PARSE_FATAL means the process cannot continue
* once the top-level makefile has been parsed. PARSE_WARNING and PARSE_INFO
* mean it can.
*/
typedef enum ParseErrorLevel { typedef enum ParseErrorLevel {
/* Exit when the current top-level makefile has been parsed completely. */
PARSE_FATAL = 1, PARSE_FATAL = 1,
/* Print "warning"; may be upgraded to fatal by the -w option. */
PARSE_WARNING, PARSE_WARNING,
/* Informational, mainly used during development of makefiles. */
PARSE_INFO PARSE_INFO
} ParseErrorLevel; } ParseErrorLevel;
@ -415,9 +422,7 @@ typedef enum CondEvalResult {
COND_INVALID /* Not a conditional statement */ COND_INVALID /* Not a conditional statement */
} CondEvalResult; } CondEvalResult;
/* /* Names of the variables that are "local" to a specific target. */
* Definitions for the "local" variables. Used only for clarity.
*/
#define TARGET "@" /* Target of dependency */ #define TARGET "@" /* Target of dependency */
#define OODATE "?" /* All out-of-date sources */ #define OODATE "?" /* All out-of-date sources */
#define ALLSRC ">" /* All sources */ #define ALLSRC ">" /* All sources */
@ -426,49 +431,68 @@ typedef enum CondEvalResult {
#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ #define ARCHIVE "!" /* Archive in "archive(member)" syntax */
#define MEMBER "%" /* Member in "archive(member)" syntax */ #define MEMBER "%" /* Member in "archive(member)" syntax */
#define FTARGET "@F" /* file part of TARGET */
#define DTARGET "@D" /* directory part of TARGET */
#define FIMPSRC "<F" /* file part of IMPSRC */
#define DIMPSRC "<D" /* directory part of IMPSRC */
#define FPREFIX "*F" /* file part of PREFIX */
#define DPREFIX "*D" /* directory part of PREFIX */
/* /*
* Global Variables * Global Variables
*/ */
extern SearchPath *dirSearchPath;
/* The list of directories to search when
* looking for targets */
extern Boolean allPrecious; /* True if every target is precious */
extern Boolean deleteOnError; /* True if failed targets should be deleted */
extern Boolean doing_depend; /* TRUE if processing .depend */
extern GNode *DEFAULT; /* .DEFAULT rule */ /* True if every target is precious */
extern Boolean allPrecious;
/* True if failed targets should be deleted */
extern Boolean deleteOnError;
/* TRUE while processing .depend */
extern Boolean doing_depend;
/* .DEFAULT rule */
extern GNode *defaultNode;
extern GNode *VAR_INTERNAL; /* Variables defined internally by make /* Variables defined internally by make which should not override those set
* which should not override those set by * by makefiles. */
* makefiles. extern GNode *VAR_INTERNAL;
/* Variables defined in a global context, e.g in the Makefile itself. */
extern GNode *VAR_GLOBAL;
/* Variables defined on the command line. */
extern GNode *VAR_CMDLINE;
/* Value returned by Var_Parse when an error is encountered. It actually
* points to an empty string, so naive callers needn't worry about it. */
extern char var_Error[];
/* The time at the start of this whole process */
extern time_t now;
/*
* If FALSE (the default behavior), undefined subexpressions in a variable
* expression are discarded. If TRUE (only during variable assignments using
* the ':=' assignment operator, no matter how deeply nested), they are
* preserved and possibly expanded later when the variable from the
* subexpression has been defined.
*
* Example for a ':=' assignment:
* CFLAGS = $(.INCLUDES)
* CFLAGS := -I.. $(CFLAGS)
* # If .INCLUDES (an undocumented special variable, by the way) is
* # still undefined, the updated CFLAGS becomes "-I.. $(.INCLUDES)".
*/ */
extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g extern Boolean preserveUndefined;
* in the Makefile itself */
extern GNode *VAR_CMDLINE; /* Variables defined on the command line */
extern char var_Error[]; /* Value returned by Var_Parse when an error
* is encountered. It actually points to
* an empty string, so naive callers needn't
* worry about it. */
extern time_t now; /* The time at the start of this whole /* The list of directories to search when looking for targets (set by the
* process */ * special target .PATH). */
extern SearchPath *dirSearchPath;
/* Used for .include "...". */
extern SearchPath *parseIncPath;
/* Used for .include <...>, for the built-in sys.mk and makefiles from the
* command line arguments. */
extern SearchPath *sysIncPath;
/* The default for sysIncPath. */
extern SearchPath *defSysIncPath;
extern Boolean oldVars; /* Do old-style variable substitution */ /* Startup directory */
extern char curdir[];
extern SearchPath *sysIncPath; /* The system include path. */ /* The basename of the program name, suffixed with [n] for sub-makes. */
extern SearchPath *defSysIncPath; /* The default system include path. */ extern char *progname;
/* Name of the .depend makefile */
extern char curdir[]; /* Startup directory */ extern char *makeDependfile;
extern char *progname; /* The program name */ /* If we replaced environ, this will be non-NULL. */
extern char *makeDependfile; /* .depend */ extern char **savedEnv;
extern char **savedEnv; /* if we replaced environ this will be non-NULL */
extern int makelevel; extern int makelevel;
@ -485,7 +509,7 @@ extern pid_t myPid;
#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */ #define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */ #define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */
#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ #define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" #define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ #define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
#define MAKE_MODE ".MAKE.MODE" #define MAKE_MODE ".MAKE.MODE"
#ifndef MAKE_LEVEL_ENV #ifndef MAKE_LEVEL_ENV
@ -493,29 +517,28 @@ extern pid_t myPid;
#endif #endif
typedef enum DebugFlags { typedef enum DebugFlags {
DEBUG_NONE = 0,
DEBUG_ARCH = 1 << 0, DEBUG_ARCH = 1 << 0,
DEBUG_COND = 1 << 1, DEBUG_COND = 1 << 1,
DEBUG_DIR = 1 << 2, DEBUG_CWD = 1 << 2,
DEBUG_GRAPH1 = 1 << 3, DEBUG_DIR = 1 << 3,
DEBUG_GRAPH2 = 1 << 4, DEBUG_ERROR = 1 << 4,
DEBUG_JOB = 1 << 5, DEBUG_FOR = 1 << 5,
DEBUG_MAKE = 1 << 6, DEBUG_GRAPH1 = 1 << 6,
DEBUG_SUFF = 1 << 7, DEBUG_GRAPH2 = 1 << 7,
DEBUG_TARG = 1 << 8, DEBUG_GRAPH3 = 1 << 8,
DEBUG_VAR = 1 << 9, DEBUG_HASH = 1 << 9,
DEBUG_FOR = 1 << 10, DEBUG_JOB = 1 << 10,
DEBUG_SHELL = 1 << 11, DEBUG_LOUD = 1 << 11,
DEBUG_ERROR = 1 << 12, DEBUG_MAKE = 1 << 12,
DEBUG_LOUD = 1 << 13, DEBUG_META = 1 << 13,
DEBUG_META = 1 << 14, DEBUG_PARSE = 1 << 14,
DEBUG_HASH = 1 << 15, DEBUG_SCRIPT = 1 << 15,
DEBUG_SHELL = 1 << 16,
DEBUG_GRAPH3 = 1 << 16, DEBUG_SUFF = 1 << 17,
DEBUG_SCRIPT = 1 << 17, DEBUG_TARG = 1 << 18,
DEBUG_PARSE = 1 << 18, DEBUG_VAR = 1 << 19,
DEBUG_CWD = 1 << 19, DEBUG_ALL = (1 << 20) - 1
DEBUG_LINT = 1 << 20
} DebugFlags; } DebugFlags;
#define CONCAT(a,b) a##b #define CONCAT(a,b) a##b
@ -549,8 +572,9 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5) else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5)
typedef enum PrintVarsMode { typedef enum PrintVarsMode {
COMPAT_VARS = 1, PVM_NONE,
EXPAND_VARS PVM_UNEXPANDED,
PVM_EXPANDED
} PrintVarsMode; } PrintVarsMode;
/* Command line options */ /* Command line options */
@ -565,6 +589,12 @@ typedef struct CmdOpts {
/* -df: debug output is written here - default stderr */ /* -df: debug output is written here - default stderr */
FILE *debug_file; FILE *debug_file;
/* -dL: lint mode
*
* Runs make in strict mode, with additional checks and better error
* handling. */
Boolean lint;
/* -dV: for the -V option, print unexpanded variable values */ /* -dV: for the -V option, print unexpanded variable values */
Boolean debugVflag; Boolean debugVflag;
@ -630,49 +660,49 @@ extern CmdOpts opts;
#include "nonints.h" #include "nonints.h"
void Make_TimeStamp(GNode *, GNode *); void GNode_UpdateYoungestChild(GNode *, GNode *);
Boolean Make_OODate(GNode *); Boolean GNode_IsOODate(GNode *);
void Make_ExpandUse(GNodeList *); void Make_ExpandUse(GNodeList *);
time_t Make_Recheck(GNode *); time_t Make_Recheck(GNode *);
void Make_HandleUse(GNode *, GNode *); void Make_HandleUse(GNode *, GNode *);
void Make_Update(GNode *); void Make_Update(GNode *);
void Make_DoAllVar(GNode *); void Make_DoAllVar(GNode *);
Boolean Make_Run(GNodeList *); Boolean Make_Run(GNodeList *);
int dieQuietly(GNode *, int); Boolean shouldDieQuietly(GNode *, int);
void PrintOnError(GNode *, const char *); void PrintOnError(GNode *, const char *);
void Main_ExportMAKEFLAGS(Boolean); void Main_ExportMAKEFLAGS(Boolean);
Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
int mkTempFile(const char *, char **); int mkTempFile(const char *, char **);
int str2Lst_Append(StringList *, char *, const char *); int str2Lst_Append(StringList *, char *);
void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
Boolean GNode_ShouldExecute(GNode *gn); Boolean GNode_ShouldExecute(GNode *gn);
/* See if the node was seen on the left-hand side of a dependency operator. */ /* See if the node was seen on the left-hand side of a dependency operator. */
static MAKE_ATTR_UNUSED Boolean MAKE_INLINE Boolean
GNode_IsTarget(const GNode *gn) GNode_IsTarget(const GNode *gn)
{ {
return (gn->type & OP_OPMASK) != 0; return (gn->type & OP_OPMASK) != 0;
} }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_Path(const GNode *gn) GNode_Path(const GNode *gn)
{ {
return gn->path != NULL ? gn->path : gn->name; return gn->path != NULL ? gn->path : gn->name;
} }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); } GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); } GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); } GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); } GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); } GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); } GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); }
static MAKE_ATTR_UNUSED const char * MAKE_INLINE const char *
GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); } GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#ifdef __GNUC__ #ifdef __GNUC__
@ -703,35 +733,49 @@ GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#define KILLPG(pid, sig) killpg((pid), (sig)) #define KILLPG(pid, sig) killpg((pid), (sig))
#endif #endif
static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch) MAKE_INLINE Boolean
{ return isalnum((unsigned char)ch) != 0; } ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch) MAKE_INLINE Boolean
{ return isalpha((unsigned char)ch) != 0; } ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch) MAKE_INLINE Boolean
{ return isdigit((unsigned char)ch) != 0; } ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch) MAKE_INLINE Boolean
{ return isspace((unsigned char)ch) != 0; } ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch) MAKE_INLINE Boolean
{ return isupper((unsigned char)ch) != 0; } ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED char ch_tolower(char ch) MAKE_INLINE char
{ return (char)tolower((unsigned char)ch); } ch_tolower(char ch) { return (char)tolower((unsigned char)ch); }
static inline MAKE_ATTR_UNUSED char ch_toupper(char ch) MAKE_INLINE char
{ return (char)toupper((unsigned char)ch); } ch_toupper(char ch) { return (char)toupper((unsigned char)ch); }
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
cpp_skip_whitespace(const char **pp) cpp_skip_whitespace(const char **pp)
{ {
while (ch_isspace(**pp)) while (ch_isspace(**pp))
(*pp)++; (*pp)++;
} }
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
cpp_skip_hspace(const char **pp)
{
while (**pp == ' ' || **pp == '\t')
(*pp)++;
}
MAKE_INLINE void
pp_skip_whitespace(char **pp) pp_skip_whitespace(char **pp)
{ {
while (ch_isspace(**pp)) while (ch_isspace(**pp))
(*pp)++; (*pp)++;
} }
MAKE_INLINE void
pp_skip_hspace(char **pp)
{
while (**pp == ' ' || **pp == '\t')
(*pp)++;
}
#ifdef MAKE_NATIVE #ifdef MAKE_NATIVE
# include <sys/cdefs.h> # include <sys/cdefs.h>
# ifndef lint # ifndef lint

View file

@ -1,4 +1,4 @@
/* $NetBSD: make_malloc.h,v 1.12 2020/10/19 23:43:55 rillig Exp $ */ /* $NetBSD: make_malloc.h,v 1.13 2020/11/10 00:32:12 rillig Exp $ */
/*- /*-
* Copyright (c) 2009 The NetBSD Foundation, Inc. * Copyright (c) 2009 The NetBSD Foundation, Inc.
@ -46,7 +46,7 @@ char *bmake_strsedup(const char *, const char *);
* *
* The case of a NULL pointer happens especially often after Var_Value, * The case of a NULL pointer happens especially often after Var_Value,
* since only environment variables need to be freed, but not others. */ * since only environment variables need to be freed, but not others. */
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
bmake_free(void *p) bmake_free(void *p)
{ {
if (p != NULL) if (p != NULL)

116
meta.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: meta.c,v 1.136 2020/10/31 12:04:24 rillig Exp $ */ /* $NetBSD: meta.c,v 1.144 2020/11/15 12:02:44 rillig Exp $ */
/* /*
* Implement 'meta' mode. * Implement 'meta' mode.
@ -84,7 +84,6 @@ static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
extern Boolean forceJobs; extern Boolean forceJobs;
extern Boolean comatMake;
extern char **environ; extern char **environ;
#define MAKE_META_PREFIX ".MAKE.META.PREFIX" #define MAKE_META_PREFIX ".MAKE.META.PREFIX"
@ -97,7 +96,7 @@ extern char **environ;
#endif #endif
#if !defined(HAVE_STRSEP) #if !defined(HAVE_STRSEP)
# define strsep(s, d) stresep((s), (d), 0) # define strsep(s, d) stresep((s), (d), '\0')
#endif #endif
/* /*
@ -184,7 +183,7 @@ filemon_read(FILE *mfp, int fd)
error = 0; error = 0;
fprintf(mfp, "\n-- filemon acquired metadata --\n"); fprintf(mfp, "\n-- filemon acquired metadata --\n");
while ((n = read(fd, buf, sizeof(buf))) > 0) { while ((n = read(fd, buf, sizeof buf)) > 0) {
if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n) if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n)
error = EIO; error = EIO;
} }
@ -276,12 +275,12 @@ meta_name(char *mname, size_t mnamelen,
* next time through. * next time through.
*/ */
if (tname[0] == '/') { if (tname[0] == '/') {
strlcpy(buf, tname, sizeof(buf)); strlcpy(buf, tname, sizeof buf);
} else { } else {
snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); snprintf(buf, sizeof buf, "%s/%s", cwd, tname);
} }
eat_dots(buf, sizeof(buf), 1); /* ./ */ eat_dots(buf, sizeof buf, 1); /* ./ */
eat_dots(buf, sizeof(buf), 2); /* ../ */ eat_dots(buf, sizeof buf, 2); /* ../ */
tname = buf; tname = buf;
} }
} }
@ -329,7 +328,7 @@ is_submake(void *cmdp, void *gnp)
char *cp2; char *cp2;
int rc = 0; /* keep looking */ int rc = 0; /* keep looking */
if (!p_make) { if (p_make == NULL) {
void *dontFreeIt; void *dontFreeIt;
p_make = Var_Value(".MAKE", gn, &dontFreeIt); p_make = Var_Value(".MAKE", gn, &dontFreeIt);
p_len = strlen(p_make); p_len = strlen(p_make);
@ -341,7 +340,7 @@ is_submake(void *cmdp, void *gnp)
cmd = mp; cmd = mp;
} }
cp2 = strstr(cmd, p_make); cp2 = strstr(cmd, p_make);
if ((cp2)) { if (cp2 != NULL) {
switch (cp2[p_len]) { switch (cp2[p_len]) {
case '\0': case '\0':
case ' ': case ' ':
@ -415,7 +414,7 @@ static Boolean
meta_needed(GNode *gn, const char *dname, meta_needed(GNode *gn, const char *dname,
char *objdir, int verbose) char *objdir, int verbose)
{ {
struct make_stat mst; struct cached_stat cst;
if (verbose) if (verbose)
verbose = DEBUG(META); verbose = DEBUG(META);
@ -446,7 +445,7 @@ meta_needed(GNode *gn, const char *dname,
} }
/* The object directory may not exist. Check it.. */ /* The object directory may not exist. Check it.. */
if (cached_stat(dname, &mst) != 0) { if (cached_stat(dname, &cst) != 0) {
if (verbose) if (verbose)
debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name);
return FALSE; return FALSE;
@ -513,7 +512,7 @@ meta_create(BuildMon *pbm, GNode *gn)
/* Don't create meta data. */ /* Don't create meta data. */
goto out; goto out;
fname = meta_name(pbm->meta_fname, sizeof(pbm->meta_fname), fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname,
dname, tname, objdir); dname, tname, objdir);
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
@ -529,7 +528,7 @@ meta_create(BuildMon *pbm, GNode *gn)
printCMDs(gn, &mf); printCMDs(gn, &mf);
fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof buf));
fprintf(mf.fp, "TARGET %s\n", tname); fprintf(mf.fp, "TARGET %s\n", tname);
cp = GNode_VarOodate(gn); cp = GNode_VarOodate(gn);
if (cp && *cp) { if (cp && *cp) {
@ -585,7 +584,7 @@ meta_init(void)
#define get_mode_bf(bf, token) \ #define get_mode_bf(bf, token) \
if ((cp = strstr(make_mode, token))) \ if ((cp = strstr(make_mode, token))) \
bf = boolValue(&cp[sizeof(token) - 1]) bf = boolValue(cp + sizeof (token) - 1)
/* /*
* Initialization we need after reading makefiles. * Initialization we need after reading makefiles.
@ -630,7 +629,7 @@ meta_mode_init(const char *make_mode)
if (once) if (once)
return; return;
once = 1; once = 1;
memset(&Mybm, 0, sizeof(Mybm)); memset(&Mybm, 0, sizeof Mybm);
/* /*
* We consider ourselves master of all within ${.MAKE.META.BAILIWICK} * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
*/ */
@ -638,7 +637,7 @@ meta_mode_init(const char *make_mode)
(void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr); VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr);
/* TODO: handle errors */ /* TODO: handle errors */
str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); str2Lst_Append(metaBailiwick, metaBailiwickStr);
/* /*
* We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
*/ */
@ -648,7 +647,7 @@ meta_mode_init(const char *make_mode)
(void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr);
/* TODO: handle errors */ /* TODO: handle errors */
str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr);
/* /*
* We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
@ -784,7 +783,7 @@ meta_job_error(Job *job, GNode *gn, int flags, int status)
if (job != NULL) { if (job != NULL) {
pbm = &job->bm; pbm = &job->bm;
if (!gn) if (gn == NULL)
gn = job->node; gn = job->node;
} else { } else {
pbm = &Mybm; pbm = &Mybm;
@ -798,9 +797,9 @@ meta_job_error(Job *job, GNode *gn, int flags, int status)
if (gn) { if (gn) {
Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL); Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL);
} }
getcwd(cwd, sizeof(cwd)); getcwd(cwd, sizeof cwd);
Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL); Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL);
if (pbm->meta_fname[0]) { if (pbm->meta_fname[0] != '\0') {
Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL); Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL);
} }
meta_job_finish(job); meta_job_finish(job);
@ -821,7 +820,7 @@ meta_job_output(Job *job, char *cp, const char *nl)
static char *meta_prefix = NULL; static char *meta_prefix = NULL;
static size_t meta_prefix_len; static size_t meta_prefix_len;
if (!meta_prefix) { if (meta_prefix == NULL) {
char *cp2; char *cp2;
(void)Var_Subst("${" MAKE_META_PREFIX "}", (void)Var_Subst("${" MAKE_META_PREFIX "}",
@ -851,7 +850,7 @@ meta_cmd_finish(void *pbmp)
int x; int x;
#endif #endif
if (!pbm) if (pbm == NULL)
pbm = &Mybm; pbm = &Mybm;
#ifdef USE_FILEMON #ifdef USE_FILEMON
@ -865,8 +864,10 @@ meta_cmd_finish(void *pbmp)
error = x; error = x;
pbm->mon_fd = -1; pbm->mon_fd = -1;
pbm->filemon = NULL; pbm->filemon = NULL;
} else return error;
}
#endif #endif
fprintf(pbm->mfp, "\n"); /* ensure end with newline */ fprintf(pbm->mfp, "\n"); /* ensure end with newline */
return error; return error;
} }
@ -941,7 +942,7 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
*bufp = buf = p; *bufp = buf = p;
*szp = bufsz = newsz; *szp = bufsz = newsz;
/* fetch the rest */ /* fetch the rest */
if (!fgets(&buf[x], (int)bufsz - x, fp)) if (fgets(&buf[x], (int)bufsz - x, fp) == NULL)
return x; /* truncated! */ return x; /* truncated! */
goto check_newline; goto check_newline;
} }
@ -1012,7 +1013,7 @@ meta_ignore(GNode *gn, const char *p)
char *fm; char *fm;
/* skip if filter result is empty */ /* skip if filter result is empty */
snprintf(fname, sizeof(fname), snprintf(fname, sizeof fname,
"${%s:L:${%s:ts:}}", "${%s:L:${%s:ts:}}",
p, MAKE_META_IGNORE_FILTER); p, MAKE_META_IGNORE_FILTER);
(void)Var_Subst(fname, gn, VARE_WANTRES, &fm); (void)Var_Subst(fname, gn, VARE_WANTRES, &fm);
@ -1090,7 +1091,7 @@ meta_oodate(GNode *gn, Boolean oodate)
FILE *fp; FILE *fp;
Boolean needOODATE = FALSE; Boolean needOODATE = FALSE;
StringList *missingFiles; StringList *missingFiles;
int have_filemon = FALSE; Boolean have_filemon = FALSE;
void *objdir_freeIt; void *objdir_freeIt;
if (oodate) if (oodate)
@ -1114,7 +1115,7 @@ meta_oodate(GNode *gn, Boolean oodate)
*/ */
Make_DoAllVar(gn); Make_DoAllVar(gn);
meta_name(fname, sizeof(fname), dname, tname, dname); meta_name(fname, sizeof fname, dname, tname, dname);
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
DEBUG1(META, "meta_oodate: %s\n", fname); DEBUG1(META, "meta_oodate: %s\n", fname);
@ -1128,22 +1129,22 @@ meta_oodate(GNode *gn, Boolean oodate)
int pid; int pid;
int x; int x;
StringListNode *cmdNode; StringListNode *cmdNode;
struct make_stat mst; struct cached_stat cst;
if (!buf) { if (buf == NULL) {
bufsz = 8 * BUFSIZ; bufsz = 8 * BUFSIZ;
buf = bmake_malloc(bufsz); buf = bmake_malloc(bufsz);
} }
if (!cwdlen) { if (cwdlen == 0) {
if (getcwd(cwd, sizeof(cwd)) == NULL) if (getcwd(cwd, sizeof cwd) == NULL)
err(1, "Could not get current working directory"); err(1, "Could not get current working directory");
cwdlen = strlen(cwd); cwdlen = strlen(cwd);
} }
strlcpy(lcwd, cwd, sizeof(lcwd)); strlcpy(lcwd, cwd, sizeof lcwd);
strlcpy(latestdir, cwd, sizeof(latestdir)); strlcpy(latestdir, cwd, sizeof latestdir);
if (!tmpdir) { if (tmpdir == NULL) {
tmpdir = getTmpdir(); tmpdir = getTmpdir();
tmplen = strlen(tmpdir); tmplen = strlen(tmpdir);
} }
@ -1228,17 +1229,17 @@ meta_oodate(GNode *gn, Boolean oodate)
Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(lcwd_vname, lcwd, VAR_GLOBAL);
Var_Set(ldir_vname, latestdir, VAR_GLOBAL); Var_Set(ldir_vname, latestdir, VAR_GLOBAL);
} }
snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid);
snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid);
lastpid = pid; lastpid = pid;
ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
if (ldir) { if (ldir) {
strlcpy(latestdir, ldir, sizeof(latestdir)); strlcpy(latestdir, ldir, sizeof latestdir);
bmake_free(tp); bmake_free(tp);
} }
ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
if (ldir) { if (ldir) {
strlcpy(lcwd, ldir, sizeof(lcwd)); strlcpy(lcwd, ldir, sizeof lcwd);
bmake_free(tp); bmake_free(tp);
} }
} }
@ -1271,9 +1272,9 @@ meta_oodate(GNode *gn, Boolean oodate)
child = atoi(p); child = atoi(p);
if (child > 0) { if (child > 0) {
snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child);
Var_Set(cldir, lcwd, VAR_GLOBAL); Var_Set(cldir, lcwd, VAR_GLOBAL);
snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child);
Var_Set(cldir, latestdir, VAR_GLOBAL); Var_Set(cldir, latestdir, VAR_GLOBAL);
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
if (DEBUG(META)) if (DEBUG(META))
@ -1288,8 +1289,8 @@ meta_oodate(GNode *gn, Boolean oodate)
case 'C': /* Chdir */ case 'C': /* Chdir */
/* Update lcwd and latest directory. */ /* Update lcwd and latest directory. */
strlcpy(latestdir, p, sizeof(latestdir)); strlcpy(latestdir, p, sizeof latestdir);
strlcpy(lcwd, p, sizeof(lcwd)); strlcpy(lcwd, p, sizeof lcwd);
Var_Set(lcwd_vname, lcwd, VAR_GLOBAL); Var_Set(lcwd_vname, lcwd, VAR_GLOBAL);
Var_Set(ldir_vname, lcwd, VAR_GLOBAL); Var_Set(ldir_vname, lcwd, VAR_GLOBAL);
#ifdef DEBUG_META_MODE #ifdef DEBUG_META_MODE
@ -1382,11 +1383,12 @@ meta_oodate(GNode *gn, Boolean oodate)
break; break;
/* ignore anything containing the string "tmp" */ /* ignore anything containing the string "tmp" */
/* XXX: The arguments to strstr must be swapped. */
if ((strstr("tmp", p))) if ((strstr("tmp", p)))
break; break;
if ((link_src != NULL && cached_lstat(p, &mst) < 0) || if ((link_src != NULL && cached_lstat(p, &cst) < 0) ||
(link_src == NULL && cached_stat(p, &mst) < 0)) { (link_src == NULL && cached_stat(p, &cst) < 0)) {
if (!meta_ignore(gn, p)) if (!meta_ignore(gn, p))
append_if_new(missingFiles, p); append_if_new(missingFiles, p);
} }
@ -1425,17 +1427,17 @@ meta_oodate(GNode *gn, Boolean oodate)
continue; /* no point */ continue; /* no point */
/* Check vs latestdir */ /* Check vs latestdir */
snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p);
sdirs[sdx++] = fname1; sdirs[sdx++] = fname1;
if (strcmp(latestdir, lcwd) != 0) { if (strcmp(latestdir, lcwd) != 0) {
/* Check vs lcwd */ /* Check vs lcwd */
snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); snprintf(fname2, sizeof fname2, "%s/%s", lcwd, p);
sdirs[sdx++] = fname2; sdirs[sdx++] = fname2;
} }
if (strcmp(lcwd, cwd) != 0) { if (strcmp(lcwd, cwd) != 0) {
/* Check vs cwd */ /* Check vs cwd */
snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); snprintf(fname3, sizeof fname3, "%s/%s", cwd, p);
sdirs[sdx++] = fname3; sdirs[sdx++] = fname3;
} }
} }
@ -1446,7 +1448,7 @@ meta_oodate(GNode *gn, Boolean oodate)
DEBUG3(META, "%s: %d: looking for: %s\n", DEBUG3(META, "%s: %d: looking for: %s\n",
fname, lineno, *sdp); fname, lineno, *sdp);
#endif #endif
if (cached_stat(*sdp, &mst) == 0) { if (cached_stat(*sdp, &cst) == 0) {
found = 1; found = 1;
p = *sdp; p = *sdp;
} }
@ -1456,12 +1458,12 @@ meta_oodate(GNode *gn, Boolean oodate)
DEBUG3(META, "%s: %d: found: %s\n", DEBUG3(META, "%s: %d: found: %s\n",
fname, lineno, p); fname, lineno, p);
#endif #endif
if (!S_ISDIR(mst.mst_mode) && if (!S_ISDIR(cst.cst_mode) &&
mst.mst_mtime > gn->mtime) { cst.cst_mtime > gn->mtime) {
DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n",
fname, lineno, p); fname, lineno, p);
oodate = TRUE; oodate = TRUE;
} else if (S_ISDIR(mst.mst_mode)) { } else if (S_ISDIR(cst.cst_mode)) {
/* Update the latest directory. */ /* Update the latest directory. */
cached_realpath(p, latestdir); cached_realpath(p, latestdir);
} }
@ -1476,7 +1478,7 @@ meta_oodate(GNode *gn, Boolean oodate)
} }
if (buf[0] == 'E') { if (buf[0] == 'E') {
/* previous latestdir is no longer relevant */ /* previous latestdir is no longer relevant */
strlcpy(latestdir, lcwd, sizeof(latestdir)); strlcpy(latestdir, lcwd, sizeof latestdir);
} }
break; break;
default: default:
@ -1537,10 +1539,10 @@ meta_oodate(GNode *gn, Boolean oodate)
if (buf[x - 1] == '\n') if (buf[x - 1] == '\n')
buf[x - 1] = '\0'; buf[x - 1] = '\0';
} }
if (p && if (p != NULL &&
!hasOODATE && !hasOODATE &&
!(gn->type & OP_NOMETA_CMP) && !(gn->type & OP_NOMETA_CMP) &&
strcmp(p, cmd) != 0) { (strcmp(p, cmd) != 0)) {
DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n",
fname, lineno, p, cmd); fname, lineno, p, cmd);
if (!metaIgnoreCMDs) if (!metaIgnoreCMDs)
@ -1588,7 +1590,7 @@ meta_oodate(GNode *gn, Boolean oodate)
cp = NULL; /* not in .CURDIR */ cp = NULL; /* not in .CURDIR */
} }
} }
if (!cp) { if (cp == NULL) {
DEBUG1(META, "%s: required but missing\n", fname); DEBUG1(META, "%s: required but missing\n", fname);
oodate = TRUE; oodate = TRUE;
needOODATE = TRUE; /* assume the worst */ needOODATE = TRUE; /* assume the worst */
@ -1686,7 +1688,7 @@ meta_compat_parent(pid_t child)
if (outfd != -1 && FD_ISSET(outfd, &readfds)) do { if (outfd != -1 && FD_ISSET(outfd, &readfds)) do {
/* XXX this is not line-buffered */ /* XXX this is not line-buffered */
ssize_t nread = read(outfd, buf, sizeof(buf) - 1); ssize_t nread = read(outfd, buf, sizeof buf - 1);
if (nread == -1) if (nread == -1)
err(1, "read"); err(1, "read");
if (nread == 0) { if (nread == 0) {

View file

@ -1,4 +1,4 @@
/* $NetBSD: metachar.h,v 1.11 2020/10/31 18:20:00 rillig Exp $ */ /* $NetBSD: metachar.h,v 1.12 2020/11/10 00:32:12 rillig Exp $ */
/*- /*-
* Copyright (c) 2015 The NetBSD Foundation, Inc. * Copyright (c) 2015 The NetBSD Foundation, Inc.
@ -37,7 +37,7 @@ extern unsigned char _metachar[];
#define is_shell_metachar(c) _metachar[(c) & 0x7f] #define is_shell_metachar(c) _metachar[(c) & 0x7f]
static inline MAKE_ATTR_UNUSED int MAKE_INLINE int
needshell(const char *cmd) needshell(const char *cmd)
{ {
while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=')

186
missing/sys/cdefs.h Normal file
View file

@ -0,0 +1,186 @@
/* $NetBSD: cdefs.h,v 1.18 1997/06/18 19:09:50 christos Exp $ */
/*
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Berkeley Software Design, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)cdefs.h 8.7 (Berkeley) 1/21/94
*/
#ifndef _SYS_CDEFS_H_
#if defined(NEED_HOST_CDEFS_H)
/*
* make sure we don't come past here again.
*/
#undef NEED_HOST_CDEFS_H
/*
* Some systems - notably linux, have sys/cdefs.h
* which is not really compatible with our's.
*/
#ifdef __GNUC__
# include_next <sys/cdefs.h>
#else
/*
* It sucks that we have to hard code a path like this.
* But systems that have a sys/cdefs.h that don't use gcc
* should be few.
*/
# include "/usr/include/sys/cdefs.h"
#endif
/*
* We are about to [re]define these
*/
#undef __P
#undef _SYS_CDEFS_H_
#endif
#define _SYS_CDEFS_H_
#ifdef NetBSD
#include <machine/cdefs.h>
#endif
#if defined(__cplusplus)
# ifndef __BEGIN_DECLS
# define __BEGIN_DECLS extern "C" {
# endif
# ifndef __END_DECLS
# define __END_DECLS };
# endif
#else
# ifndef __BEGIN_DECLS
# define __BEGIN_DECLS
# endif
# ifndef __END_DECLS
# define __END_DECLS
# endif
#endif
/*
* The __CONCAT macro is used to concatenate parts of symbol names, e.g.
* with "#define OLD(foo) __CONCAT(old,foo)", OLD(foo) produces oldfoo.
* The __CONCAT macro is a bit tricky -- make sure you don't put spaces
* in between its arguments. __CONCAT can also concatenate double-quoted
* strings produced by the __STRING macro, but this only works with ANSI C.
*/
#if defined(__STDC__) || defined(__cplusplus)
#define __P(protos) protos /* full-blown ANSI C */
#ifndef __CONCAT
#define __CONCAT(x,y) x ## y
#endif
#define __STRING(x) #x
#define __const const /* define reserved names to standard */
#define __signed signed
#define __volatile volatile
#if defined(__cplusplus)
#define __inline inline /* convert to C++ keyword */
#else
#ifndef __GNUC__
#define __inline /* delete GCC keyword */
#endif /* !__GNUC__ */
#endif /* !__cplusplus */
#else /* !(__STDC__ || __cplusplus) */
#define __P(protos) () /* traditional C preprocessor */
#define __CONCAT(x,y) x/**/y
#define __STRING(x) "x"
#ifndef __GNUC__
#define __const /* delete pseudo-ANSI C keywords */
#define __inline
#define __signed
#define __volatile
#endif /* !__GNUC__ */
/*
* In non-ANSI C environments, new programs will want ANSI-only C keywords
* deleted from the program and old programs will want them left alone.
* Programs using the ANSI C keywords const, inline etc. as normal
* identifiers should define -DNO_ANSI_KEYWORDS.
*/
#ifndef NO_ANSI_KEYWORDS
#define const __const /* convert ANSI C keywords */
#define inline __inline
#define signed __signed
#define volatile __volatile
#endif /* !NO_ANSI_KEYWORDS */
#endif /* !(__STDC__ || __cplusplus) */
/*
* GCC1 and some versions of GCC2 declare dead (non-returning) and
* pure (no side effects) functions using "volatile" and "const";
* unfortunately, these then cause warnings under "-ansi -pedantic".
* GCC2 uses a new, peculiar __attribute__((attrs)) style. All of
* these work for GNU C++ (modulo a slight glitch in the C++ grammar
* in the distribution version of 2.5.5).
*/
#if !defined(__GNUC__) || __GNUC__ < 2 || \
(__GNUC__ == 2 && __GNUC_MINOR__ < 5)
#define __attribute__(x) /* delete __attribute__ if non-gcc or gcc1 */
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define __dead __volatile
#define __pure __const
#endif
#endif
#ifdef sun386
# define __attribute__(x)
#endif
#ifdef __KPRINTF_ATTRIBUTE__
#define __kprintf_attribute__(a) __attribute__(a)
#else
#define __kprintf_attribute__(a)
#endif
/* Delete pseudo-keywords wherever they are not available or needed. */
#ifndef __dead
#define __dead
#define __pure
#endif
#define __IDSTRING(name,string) \
static const char name[] __attribute__((__unused__)) = string
#ifndef __RCSID
#define __RCSID(s) __IDSTRING(rcsid,s)
#endif
#ifndef __COPYRIGHT
#define __COPYRIGHT(s) __IDSTRING(copyright,s)
#endif
#endif /* !_SYS_CDEFS_H_ */

View file

@ -1,3 +1,10 @@
2020-11-06 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20201106
* meta.autodep.mk: use OBJ_EXTENSIONS rather than hardcode sed
args to tweak extensions for local deps.
2020-11-01 Simon J Gerraty <sjg@beast.crufty.net> 2020-11-01 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20201101 * install-mk (MK_VERSION): 20201101

View file

@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net> # Simon J. Gerraty <sjg@crufty.net>
# RCSid: # RCSid:
# $Id: install-mk,v 1.183 2020/11/02 16:34:12 sjg Exp $ # $Id: install-mk,v 1.184 2020/11/08 05:47:56 sjg Exp $
# #
# @(#) Copyright (c) 1994 Simon J. Gerraty # @(#) Copyright (c) 1994 Simon J. Gerraty
# #
@ -70,7 +70,7 @@
# sjg@crufty.net # sjg@crufty.net
# #
MK_VERSION=20201101 MK_VERSION=20201106
OWNER= OWNER=
GROUP= GROUP=
MODE=444 MODE=444

View file

@ -1,4 +1,4 @@
# $Id: meta.autodep.mk,v 1.52 2020/07/18 05:57:57 sjg Exp $ # $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 sjg Exp $
# #
# @(#) Copyright (c) 2010, Simon J. Gerraty # @(#) Copyright (c) 2010, Simon J. Gerraty
@ -178,7 +178,8 @@ DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh
@case "${.MAKE.META.FILES:T:M*.po.*}" in \ @case "${.MAKE.META.FILES:T:M*.po.*}" in \
*.po.*) mv $@.${.MAKE.PID} $@;; \ *.po.*) mv $@.${.MAKE.PID} $@;; \
*) { cat $@.${.MAKE.PID}; \ *) { cat $@.${.MAKE.PID}; \
sed 's,\${PICO}:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \ sed ${OBJ_EXTENSIONS:N.o:N.po:@o@-e 's,\$o:,.o:,'@} \
-e 's,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \
rm -f $@.${.MAKE.PID};; \ rm -f $@.${.MAKE.PID};; \
esac esac
.else .else

View file

@ -77,7 +77,7 @@
# RCSid: # RCSid:
# $Id: meta2deps.sh,v 1.14 2020/10/02 03:11:17 sjg Exp $ # $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $
# Copyright (c) 2010-2013, Juniper Networks, Inc. # Copyright (c) 2010-2013, Juniper Networks, Inc.
# All rights reserved. # All rights reserved.

View file

@ -1,4 +1,4 @@
/* $NetBSD: nonints.h,v 1.149 2020/11/01 00:24:57 rillig Exp $ */ /* $NetBSD: nonints.h,v 1.162 2020/11/16 21:48:18 rillig Exp $ */
/*- /*-
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -79,8 +79,8 @@ void Arch_End(void);
Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); Boolean Arch_ParseArchive(char **, GNodeList *, GNode *);
void Arch_Touch(GNode *); void Arch_Touch(GNode *);
void Arch_TouchLib(GNode *); void Arch_TouchLib(GNode *);
time_t Arch_MTime(GNode *); void Arch_UpdateMTime(GNode *gn);
time_t Arch_MemMTime(GNode *); void Arch_UpdateMemberMTime(GNode *gn);
void Arch_FindLib(GNode *, SearchPath *); void Arch_FindLib(GNode *, SearchPath *);
Boolean Arch_LibOODate(GNode *); Boolean Arch_LibOODate(GNode *);
Boolean Arch_IsLib(GNode *); Boolean Arch_IsLib(GNode *);
@ -107,6 +107,7 @@ void JobReapChild(pid_t, WAIT_T, Boolean);
#endif #endif
/* main.c */ /* main.c */
Boolean GetBooleanVar(const char *, Boolean);
void Main_ParseArgLine(const char *); void Main_ParseArgLine(const char *);
void MakeMode(const char *); void MakeMode(const char *);
char *Cmd_Exec(const char *, const char **); char *Cmd_Exec(const char *, const char **);
@ -118,8 +119,7 @@ void Finish(int) MAKE_ATTR_DEAD;
int eunlink(const char *); int eunlink(const char *);
void execDie(const char *, const char *); void execDie(const char *, const char *);
char *getTmpdir(void); char *getTmpdir(void);
Boolean s2Boolean(const char *, Boolean); Boolean ParseBoolean(const char *, Boolean);
Boolean getBoolean(const char *, Boolean);
char *cached_realpath(const char *, char *); char *cached_realpath(const char *, char *);
/* parse.c */ /* parse.c */
@ -159,7 +159,7 @@ typedef struct Words {
} Words; } Words;
Words Str_Words(const char *, Boolean); Words Str_Words(const char *, Boolean);
static inline MAKE_ATTR_UNUSED void MAKE_INLINE void
Words_Free(Words w) { Words_Free(Words w) {
free(w.words); free(w.words);
free(w.freeIt); free(w.freeIt);
@ -199,15 +199,15 @@ void Targ_End(void);
void Targ_Stats(void); void Targ_Stats(void);
GNodeList *Targ_List(void); GNodeList *Targ_List(void);
GNode *Targ_NewGN(const char *); GNode *GNode_New(const char *);
GNode *Targ_FindNode(const char *); GNode *Targ_FindNode(const char *);
GNode *Targ_GetNode(const char *); GNode *Targ_GetNode(const char *);
GNode *Targ_NewInternalNode(const char *); GNode *Targ_NewInternalNode(const char *);
GNode *Targ_GetEndNode(void); GNode *Targ_GetEndNode(void);
GNodeList *Targ_FindList(StringList *); GNodeList *Targ_FindList(StringList *);
Boolean Targ_Ignore(GNode *); Boolean Targ_Ignore(const GNode *);
Boolean Targ_Silent(GNode *); Boolean Targ_Silent(const GNode *);
Boolean Targ_Precious(GNode *); Boolean Targ_Precious(const GNode *);
void Targ_SetMain(GNode *); void Targ_SetMain(GNode *);
void Targ_PrintCmds(GNode *); void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int); void Targ_PrintNode(GNode *, int);
@ -223,21 +223,40 @@ void Var_End(void);
typedef enum VarEvalFlags { typedef enum VarEvalFlags {
VARE_NONE = 0, VARE_NONE = 0,
/* Treat undefined variables as errors. */
VARE_UNDEFERR = 0x01, /* Expand and evaluate variables during parsing.
/* Expand and evaluate variables during parsing. */ *
VARE_WANTRES = 0x02, * TODO: Document what Var_Parse and Var_Subst return when this flag
/* In an assignment using the ':=' operator, keep '$$' as '$$' instead * is not set. */
* of reducing it to a single '$'. */ VARE_WANTRES = 1 << 0,
VARE_ASSIGN = 0x04
/* Treat undefined variables as errors.
* Must only be used in combination with VARE_WANTRES. */
VARE_UNDEFERR = 1 << 1,
/* Keep '$$' as '$$' instead of reducing it to a single '$'.
*
* Used in variable assignments using the ':=' operator. It allows
* multiple such assignments to be chained without accidentally expanding
* '$$file' to '$file' in the first assignment and interpreting it as
* '${f}' followed by 'ile' in the next assignment.
*
* See also preserveUndefined, which preserves subexpressions that are
* based on undefined variables; maybe that can be converted to a flag
* as well. */
VARE_KEEP_DOLLAR = 1 << 2
} VarEvalFlags; } VarEvalFlags;
typedef enum VarSet_Flags { typedef enum VarSetFlags {
VAR_NO_EXPORT = 0x01, /* do not export */ VAR_SET_NONE = 0,
/* do not export */
VAR_SET_NO_EXPORT = 1 << 0,
/* Make the variable read-only. No further modification is possible, /* Make the variable read-only. No further modification is possible,
* except for another call to Var_Set with the same flag. */ * except for another call to Var_Set with the same flag. */
VAR_SET_READONLY = 0x02 VAR_SET_READONLY = 1 << 1
} VarSet_Flags; } VarSetFlags;
/* The state of error handling returned by Var_Parse. /* The state of error handling returned by Var_Parse.
* *
@ -296,7 +315,7 @@ typedef enum VarParseResult {
void Var_Delete(const char *, GNode *); void Var_Delete(const char *, GNode *);
void Var_Set(const char *, const char *, GNode *); void Var_Set(const char *, const char *, GNode *);
void Var_Set_with_flags(const char *, const char *, GNode *, VarSet_Flags); void Var_SetWithFlags(const char *, const char *, GNode *, VarSetFlags);
void Var_Append(const char *, const char *, GNode *); void Var_Append(const char *, const char *, GNode *);
Boolean Var_Exists(const char *, GNode *); Boolean Var_Exists(const char *, GNode *);
const char *Var_Value(const char *, GNode *, void **); const char *Var_Value(const char *, GNode *, void **);

622
parse.c

File diff suppressed because it is too large Load diff

60
str.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $ */ /* $NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $ */
/*- /*-
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -71,7 +71,7 @@
#include "make.h" #include "make.h"
/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ /* "@(#)str.c 5.8 (Berkeley) 6/1/90" */
MAKE_RCSID("$NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $"); MAKE_RCSID("$NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $");
/* Return the concatenation of s1 and s2, freshly allocated. */ /* Return the concatenation of s1 and s2, freshly allocated. */
char * char *
@ -116,15 +116,13 @@ str_concat4(const char *s1, const char *s2, const char *s3, const char *s4)
} }
/* Fracture a string into an array of words (as delineated by tabs or spaces) /* Fracture a string into an array of words (as delineated by tabs or spaces)
* taking quotation marks into account. Leading tabs/spaces are ignored. * taking quotation marks into account.
* *
* If expand is TRUE, quotes are removed and escape sequences such as \r, \t, * If expand is TRUE, quotes are removed and escape sequences such as \r, \t,
* etc... are expanded. In this case, the return value is NULL on parse * etc... are expanded. In this case, return NULL on parse errors.
* errors.
* *
* Returns the fractured words, which must be freed later using Words_Free. * Returns the fractured words, which must be freed later using Words_Free,
* If expand was TRUE and there was a parse error, words is NULL, and in that * unless the returned Words.words was NULL.
* case, nothing needs to be freed.
*/ */
Words Words
Str_Words(const char *str, Boolean expand) Str_Words(const char *str, Boolean expand)
@ -139,9 +137,8 @@ Str_Words(const char *str, Boolean expand)
char *word_end; char *word_end;
const char *str_p; const char *str_p;
/* skip leading space chars. */ /* XXX: why only hspace, not whitespace? */
for (; *str == ' ' || *str == '\t'; ++str) cpp_skip_hspace(&str); /* skip leading space chars. */
continue;
/* words_buf holds the words, separated by '\0'. */ /* words_buf holds the words, separated by '\0'. */
str_len = strlen(str); str_len = strlen(str);
@ -239,7 +236,7 @@ Str_Words(const char *str, Boolean expand)
case '\n': case '\n':
/* hmmm; fix it up as best we can */ /* hmmm; fix it up as best we can */
ch = '\\'; ch = '\\';
--str_p; str_p--;
break; break;
case 'b': case 'b':
ch = '\b'; ch = '\b';
@ -264,21 +261,15 @@ Str_Words(const char *str, Boolean expand)
*word_end++ = ch; *word_end++ = ch;
} }
done: done:
words[words_len] = NULL; words[words_len] = NULL; /* useful for argv */
return (Words){ words, words_len, words_buf }; return (Words){ words, words_len, words_buf };
} }
/* /*
* Str_Match -- Test if a string matches a pattern like "*.[ch]". * Str_Match -- Test if a string matches a pattern like "*.[ch]".
* The following special characters are known *?\[] (as in fnmatch(3)).
* *
* XXX this function does not detect or report malformed patterns. * XXX: this function does not detect or report malformed patterns.
*
* Results:
* Non-zero is returned if string matches the pattern, 0 otherwise. The
* matching operation permits the following special characters in the
* pattern: *?\[] (as in fnmatch(3)).
*
* Side effects: None.
*/ */
Boolean Boolean
Str_Match(const char *str, const char *pat) Str_Match(const char *str, const char *pat)
@ -286,12 +277,12 @@ Str_Match(const char *str, const char *pat)
for (;;) { for (;;) {
/* /*
* See if we're at the end of both the pattern and the * See if we're at the end of both the pattern and the
* string. If, we succeeded. If we're at the end of the * string. If so, we succeeded. If we're at the end of the
* pattern but not at the end of the string, we failed. * pattern but not at the end of the string, we failed.
*/ */
if (*pat == 0) if (*pat == '\0')
return *str == 0; return *str == '\0';
if (*str == 0 && *pat != '*') if (*str == '\0' && *pat != '*')
return FALSE; return FALSE;
/* /*
@ -302,9 +293,9 @@ Str_Match(const char *str, const char *pat)
pat++; pat++;
while (*pat == '*') while (*pat == '*')
pat++; pat++;
if (*pat == 0) if (*pat == '\0')
return TRUE; return TRUE;
while (*str != 0) { while (*str != '\0') {
if (Str_Match(str, pat)) if (Str_Match(str, pat))
return TRUE; return TRUE;
str++; str++;
@ -327,15 +318,18 @@ Str_Match(const char *str, const char *pat)
pat += neg ? 2 : 1; pat += neg ? 2 : 1;
for (;;) { for (;;) {
if (*pat == ']' || *pat == 0) { if (*pat == ']' || *pat == '\0') {
if (neg) if (neg)
break; break;
return FALSE; return FALSE;
} }
/* XXX: This naive comparison makes the parser
* for the pattern dependent on the actual of
* the string. This is unpredictable. */
if (*pat == *str) if (*pat == *str)
break; break;
if (pat[1] == '-') { if (pat[1] == '-') {
if (pat[2] == 0) if (pat[2] == '\0')
return neg; return neg;
if (*pat <= *str && pat[2] >= *str) if (*pat <= *str && pat[2] >= *str)
break; break;
@ -345,11 +339,11 @@ Str_Match(const char *str, const char *pat)
} }
pat++; pat++;
} }
if (neg && *pat != ']' && *pat != 0) if (neg && *pat != ']' && *pat != '\0')
return FALSE; return FALSE;
while (*pat != ']' && *pat != 0) while (*pat != ']' && *pat != '\0')
pat++; pat++;
if (*pat == 0) if (*pat == '\0')
pat--; pat--;
goto thisCharOK; goto thisCharOK;
} }
@ -360,7 +354,7 @@ Str_Match(const char *str, const char *pat)
*/ */
if (*pat == '\\') { if (*pat == '\\') {
pat++; pat++;
if (*pat == 0) if (*pat == '\0')
return FALSE; return FALSE;
} }

531
suff.c

File diff suppressed because it is too large Load diff

164
targ.c
View file

@ -1,4 +1,4 @@
/* $NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $ */ /* $NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $ */
/* /*
* Copyright (c) 1988, 1989, 1990, 1993 * Copyright (c) 1988, 1989, 1990, 1993
@ -68,19 +68,17 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
/*- /*
* targ.c -- * Maintaining the targets and sources, which are both implemented as GNode.
* Functions for maintaining the Lst allTargets. Target nodes are
* kept in two structures: a Lst and a hash table.
* *
* Interface: * Interface:
* Targ_Init Initialization procedure. * Targ_Init Initialize the module.
* *
* Targ_End Clean up the module * Targ_End Clean up the module.
* *
* Targ_List Return the list of all targets so far. * Targ_List Return the list of all targets so far.
* *
* Targ_NewGN Create a new GNode for the passed target * GNode_New Create a new GNode for the passed target
* (string). The node is *not* placed in the * (string). The node is *not* placed in the
* hash table, though all its fields are * hash table, though all its fields are
* initialized. * initialized.
@ -121,23 +119,26 @@
#include "dir.h" #include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ /* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $"); MAKE_RCSID("$NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $");
static GNodeList *allTargets; /* the list of all targets found so far */ /* All target nodes found so far, but not the source nodes. */
#ifdef CLEANUP static GNodeList *allTargets;
static GNodeList *allGNs; /* List of all the GNodes */ static HashTable allTargetsByName;
#endif
static HashTable targets; /* a hash table of same */
#ifdef CLEANUP #ifdef CLEANUP
static void TargFreeGN(void *); static GNodeList *allNodes;
static void GNode_Free(void *);
#endif #endif
void void
Targ_Init(void) Targ_Init(void)
{ {
allTargets = Lst_New(); allTargets = Lst_New();
HashTable_Init(&targets); HashTable_Init(&allTargetsByName);
#ifdef CLEANUP
allNodes = Lst_New();
#endif
} }
void void
@ -146,56 +147,67 @@ Targ_End(void)
Targ_Stats(); Targ_Stats();
#ifdef CLEANUP #ifdef CLEANUP
Lst_Free(allTargets); Lst_Free(allTargets);
if (allGNs != NULL) HashTable_Done(&allTargetsByName);
Lst_Destroy(allGNs, TargFreeGN); Lst_Destroy(allNodes, GNode_Free);
HashTable_Done(&targets);
#endif #endif
} }
void void
Targ_Stats(void) Targ_Stats(void)
{ {
HashTable_DebugStats(&targets, "targets"); HashTable_DebugStats(&allTargetsByName, "targets");
} }
/* Return the list of all targets. */ /*
* Return the list of all targets, which are all nodes that appear on the
* left-hand side of a dependency declaration such as "target: source".
* The returned list does not contain pure sources.
*/
GNodeList * GNodeList *
Targ_List(void) Targ_List(void)
{ {
return allTargets; return allTargets;
} }
/* Create and initialize a new graph node. The gnode is added to the list of /* Create a new graph node, but don't register it anywhere.
* all gnodes.
* *
* Input: * Graph nodes that appear on the left-hand side of a dependency line such
* name the name of the node, such as "clean", "src.c", ".END" * as "target: source" are called targets. XXX: In some cases (like the
* .ALLTARGETS variable), all nodes are called targets as well, even if they
* never appear on the left-hand side. This is a mistake.
*
* Typical names for graph nodes are:
* "src.c" (an ordinary file)
* "clean" (a .PHONY target)
* ".END" (a special hook target)
* "-lm" (a library)
* "libc.a(isspace.o)" (an archive member)
*/ */
GNode * GNode *
Targ_NewGN(const char *name) GNode_New(const char *name)
{ {
GNode *gn; GNode *gn;
gn = bmake_malloc(sizeof(GNode)); gn = bmake_malloc(sizeof *gn);
gn->name = bmake_strdup(name); gn->name = bmake_strdup(name);
gn->uname = NULL; gn->uname = NULL;
gn->path = NULL; gn->path = NULL;
gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0; gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0;
gn->unmade = 0;
gn->unmade_cohorts = 0;
gn->cohort_num[0] = '\0';
gn->centurion = NULL;
gn->made = UNMADE;
gn->flags = 0; gn->flags = 0;
gn->checked_seqno = 0; gn->made = UNMADE;
gn->unmade = 0;
gn->mtime = 0; gn->mtime = 0;
gn->youngestChild = NULL; gn->youngestChild = NULL;
gn->implicitParents = Lst_New(); gn->implicitParents = Lst_New();
gn->cohorts = Lst_New();
gn->parents = Lst_New(); gn->parents = Lst_New();
gn->children = Lst_New(); gn->children = Lst_New();
gn->order_pred = Lst_New(); gn->order_pred = Lst_New();
gn->order_succ = Lst_New(); gn->order_succ = Lst_New();
gn->cohorts = Lst_New();
gn->cohort_num[0] = '\0';
gn->unmade_cohorts = 0;
gn->centurion = NULL;
gn->checked_seqno = 0;
HashTable_Init(&gn->context); HashTable_Init(&gn->context);
gn->commands = Lst_New(); gn->commands = Lst_New();
gn->suffix = NULL; gn->suffix = NULL;
@ -203,9 +215,7 @@ Targ_NewGN(const char *name)
gn->lineno = 0; gn->lineno = 0;
#ifdef CLEANUP #ifdef CLEANUP
if (allGNs == NULL) Lst_Append(allNodes, gn);
allGNs = Lst_New();
Lst_Append(allGNs, gn);
#endif #endif
return gn; return gn;
@ -213,24 +223,29 @@ Targ_NewGN(const char *name)
#ifdef CLEANUP #ifdef CLEANUP
static void static void
TargFreeGN(void *gnp) GNode_Free(void *gnp)
{ {
GNode *gn = gnp; GNode *gn = gnp;
free(gn->name); free(gn->name);
free(gn->uname); free(gn->uname);
free(gn->path); free(gn->path);
/* gn->youngestChild is not owned by this node. */
Lst_Free(gn->implicitParents); Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */
Lst_Free(gn->cohorts); Lst_Free(gn->parents); /* as they are not owned by this node. */
Lst_Free(gn->parents); Lst_Free(gn->children); /* likewise */
Lst_Free(gn->children); Lst_Free(gn->order_pred); /* likewise */
Lst_Free(gn->order_succ); Lst_Free(gn->order_succ); /* likewise */
Lst_Free(gn->order_pred); Lst_Free(gn->cohorts); /* likewise */
HashTable_Done(&gn->context); HashTable_Done(&gn->context); /* ... but not the variables themselves,
Lst_Free(gn->commands); * even though they are owned by this node.
* XXX: they should probably be freed. */
/* XXX: does gn->suffix need to be freed? It is reference-counted. */ Lst_Free(gn->commands); /* ... but not the commands themselves,
* as they may be shared with other nodes. */
/* gn->suffix is not owned by this node. */
/* XXX: gn->suffix should be unreferenced here. This requires a thorough
* check that the reference counting is done correctly in all places,
* otherwise a suffix might be freed too early. */
free(gn); free(gn);
} }
@ -240,7 +255,7 @@ TargFreeGN(void *gnp)
GNode * GNode *
Targ_FindNode(const char *name) Targ_FindNode(const char *name)
{ {
return HashTable_FindValue(&targets, name); return HashTable_FindValue(&allTargetsByName, name);
} }
/* Get the existing global node, or create it. */ /* Get the existing global node, or create it. */
@ -248,7 +263,7 @@ GNode *
Targ_GetNode(const char *name) Targ_GetNode(const char *name)
{ {
Boolean isNew; Boolean isNew;
HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew); HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew);
if (!isNew) if (!isNew)
return HashEntry_Get(he); return HashEntry_Get(he);
@ -259,14 +274,16 @@ Targ_GetNode(const char *name)
} }
} }
/* Create a node, register it in .ALLTARGETS but don't store it in the /*
* Create a node, register it in .ALLTARGETS but don't store it in the
* table of global nodes. This means it cannot be found by name. * table of global nodes. This means it cannot be found by name.
* *
* This is used for internal nodes, such as cohorts or .WAIT nodes. */ * This is used for internal nodes, such as cohorts or .WAIT nodes.
*/
GNode * GNode *
Targ_NewInternalNode(const char *name) Targ_NewInternalNode(const char *name)
{ {
GNode *gn = Targ_NewGN(name); GNode *gn = GNode_New(name);
Var_Append(".ALLTARGETS", name, VAR_GLOBAL); Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
Lst_Append(allTargets, gn); Lst_Append(allTargets, gn);
if (doing_depend) if (doing_depend)
@ -274,8 +291,10 @@ Targ_NewInternalNode(const char *name)
return gn; return gn;
} }
/* Return the .END node, which contains the commands to be executed when /*
* everything else is done. */ * Return the .END node, which contains the commands to be run when
* everything else has been made.
*/
GNode *Targ_GetEndNode(void) GNode *Targ_GetEndNode(void)
{ {
/* Save the node locally to avoid having to search for it all the time. */ /* Save the node locally to avoid having to search for it all the time. */
@ -303,31 +322,33 @@ Targ_FindList(StringList *names)
/* Return true if should ignore errors when creating gn. */ /* Return true if should ignore errors when creating gn. */
Boolean Boolean
Targ_Ignore(GNode *gn) Targ_Ignore(const GNode *gn)
{ {
return opts.ignoreErrors || gn->type & OP_IGNORE; return opts.ignoreErrors || gn->type & OP_IGNORE;
} }
/* Return true if be silent when creating gn. */ /* Return true if be silent when creating gn. */
Boolean Boolean
Targ_Silent(GNode *gn) Targ_Silent(const GNode *gn)
{ {
return opts.beSilent || gn->type & OP_SILENT; return opts.beSilent || gn->type & OP_SILENT;
} }
/* See if the given target is precious. */ /* See if the given target is precious. */
Boolean Boolean
Targ_Precious(GNode *gn) Targ_Precious(const GNode *gn)
{ {
/* XXX: Why are '::' targets precious? */
return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
} }
/******************* DEBUG INFO PRINTING ****************/ /*
* The main target to be made; only for debugging output.
* See mainNode in parse.c for the definitive source.
*/
static GNode *mainTarg;
static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ /* Remember the main target to make; only used for debugging. */
/* Set our idea of the main target we'll be creating. Used for debugging
* output. */
void void
Targ_SetMain(GNode *gn) Targ_SetMain(GNode *gn)
{ {
@ -459,7 +480,7 @@ Targ_PrintNode(GNode *gn, int pass)
debug_printf("# *** MAIN TARGET ***\n"); debug_printf("# *** MAIN TARGET ***\n");
} }
if (pass >= 2) { if (pass >= 2) {
if (gn->unmade) { if (gn->unmade > 0) {
debug_printf("# %d unmade children\n", gn->unmade); debug_printf("# %d unmade children\n", gn->unmade);
} else { } else {
debug_printf("# No unmade children\n"); debug_printf("# No unmade children\n");
@ -532,20 +553,27 @@ Targ_PrintGraph(int pass)
{ {
debug_printf("#*** Input graph:\n"); debug_printf("#*** Input graph:\n");
Targ_PrintNodes(allTargets, pass); Targ_PrintNodes(allTargets, pass);
debug_printf("\n\n"); debug_printf("\n");
debug_printf("#\n# Files that are only sources:\n"); debug_printf("\n");
debug_printf("#\n");
debug_printf("# Files that are only sources:\n");
PrintOnlySources(); PrintOnlySources();
debug_printf("#*** Global Variables:\n"); debug_printf("#*** Global Variables:\n");
Var_Dump(VAR_GLOBAL); Var_Dump(VAR_GLOBAL);
debug_printf("#*** Command-line Variables:\n"); debug_printf("#*** Command-line Variables:\n");
Var_Dump(VAR_CMDLINE); Var_Dump(VAR_CMDLINE);
debug_printf("\n"); debug_printf("\n");
Dir_PrintDirectories(); Dir_PrintDirectories();
debug_printf("\n"); debug_printf("\n");
Suff_PrintAll(); Suff_PrintAll();
} }
/* Propagate some type information to cohort nodes (those from the :: /* Propagate some type information to cohort nodes (those from the '::'
* dependency operator). * dependency operator).
* *
* Should be called after the makefiles are parsed but before any action is * Should be called after the makefiles are parsed but before any action is

View file

@ -1,6 +1,6 @@
# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $ # $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $
# #
# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $ # $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $
# #
# Unit tests for make(1) # Unit tests for make(1)
# #
@ -36,8 +36,11 @@
# src/tests/usr.bin/make/t_make.sh as well. # src/tests/usr.bin/make/t_make.sh as well.
#TESTS+= archive #TESTS+= archive
TESTS+= archive-suffix TESTS+= archive-suffix
TESTS+= cmd-errors
TESTS+= cmd-errors-lint
TESTS+= cmd-interrupt TESTS+= cmd-interrupt
TESTS+= cmdline TESTS+= cmdline
TESTS+= cmdline-undefined
TESTS+= comment TESTS+= comment
TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric
TESTS+= cond-cmp-numeric-eq TESTS+= cond-cmp-numeric-eq
@ -58,8 +61,10 @@ TESTS+= cond-func-target
TESTS+= cond-late TESTS+= cond-late
TESTS+= cond-op TESTS+= cond-op
TESTS+= cond-op-and TESTS+= cond-op-and
TESTS+= cond-op-and-lint
TESTS+= cond-op-not TESTS+= cond-op-not
TESTS+= cond-op-or TESTS+= cond-op-or
TESTS+= cond-op-or-lint
TESTS+= cond-op-parentheses TESTS+= cond-op-parentheses
TESTS+= cond-short TESTS+= cond-short
TESTS+= cond-token-number TESTS+= cond-token-number
@ -144,6 +149,7 @@ TESTS+= directive-for
TESTS+= directive-for-generating-endif TESTS+= directive-for-generating-endif
TESTS+= directive-hyphen-include TESTS+= directive-hyphen-include
TESTS+= directive-if TESTS+= directive-if
TESTS+= directive-if-nested
TESTS+= directive-ifdef TESTS+= directive-ifdef
TESTS+= directive-ifmake TESTS+= directive-ifmake
TESTS+= directive-ifndef TESTS+= directive-ifndef
@ -156,7 +162,6 @@ TESTS+= directive-undef
TESTS+= directive-unexport TESTS+= directive-unexport
TESTS+= directive-unexport-env TESTS+= directive-unexport-env
TESTS+= directive-warning TESTS+= directive-warning
TESTS+= directives
TESTS+= dollar TESTS+= dollar
TESTS+= doterror TESTS+= doterror
TESTS+= dotwait TESTS+= dotwait
@ -169,9 +174,11 @@ TESTS+= export-env
TESTS+= export-variants TESTS+= export-variants
TESTS+= forloop TESTS+= forloop
TESTS+= forsubst TESTS+= forsubst
TESTS+= gnode-submake
TESTS+= hanoi-include TESTS+= hanoi-include
TESTS+= impsrc TESTS+= impsrc
TESTS+= include-main TESTS+= include-main
TESTS+= job-flags
#TESTS+= job-output-long-lines #TESTS+= job-output-long-lines
TESTS+= lint TESTS+= lint
TESTS+= make-exported TESTS+= make-exported
@ -180,6 +187,7 @@ TESTS+= modmatch
TESTS+= modmisc TESTS+= modmisc
TESTS+= modts TESTS+= modts
TESTS+= modword TESTS+= modword
TESTS+= objdir-writable
TESTS+= opt TESTS+= opt
TESTS+= opt-backwards TESTS+= opt-backwards
TESTS+= opt-chdir TESTS+= opt-chdir
@ -223,6 +231,7 @@ TESTS+= opt-query
TESTS+= opt-raw TESTS+= opt-raw
TESTS+= opt-silent TESTS+= opt-silent
TESTS+= opt-touch TESTS+= opt-touch
TESTS+= opt-touch-jobs
TESTS+= opt-tracefile TESTS+= opt-tracefile
TESTS+= opt-var-expanded TESTS+= opt-var-expanded
TESTS+= opt-var-literal TESTS+= opt-var-literal
@ -248,7 +257,9 @@ TESTS+= sh-multi-line
TESTS+= sh-single-line TESTS+= sh-single-line
TESTS+= shell-csh TESTS+= shell-csh
TESTS+= shell-custom TESTS+= shell-custom
.if exists(/bin/ksh)
TESTS+= shell-ksh TESTS+= shell-ksh
.endif
TESTS+= shell-sh TESTS+= shell-sh
TESTS+= suff-add-later TESTS+= suff-add-later
TESTS+= suff-clear-regular TESTS+= suff-clear-regular
@ -256,6 +267,7 @@ TESTS+= suff-clear-single
TESTS+= suff-lookup TESTS+= suff-lookup
TESTS+= suff-main TESTS+= suff-main
TESTS+= suff-rebuild TESTS+= suff-rebuild
TESTS+= suff-self
TESTS+= suff-transform-endless TESTS+= suff-transform-endless
TESTS+= suff-transform-expand TESTS+= suff-transform-expand
TESTS+= suff-transform-select TESTS+= suff-transform-select
@ -366,38 +378,54 @@ TESTS+= varname-makeflags
TESTS+= varname-pwd TESTS+= varname-pwd
TESTS+= varname-vpath TESTS+= varname-vpath
TESTS+= varparse-dynamic TESTS+= varparse-dynamic
TESTS+= varparse-errors
TESTS+= varparse-mod TESTS+= varparse-mod
TESTS+= varparse-undef-partial TESTS+= varparse-undef-partial
TESTS+= varquote TESTS+= varquote
TESTS+= varshell
# Ideas for more tests:
# char-0020-space.mk
# char-005C-backslash.mk
# escape-cond-str.mk
# escape-cond-func-arg.mk
# escape-cond-func-arg.mk
# escape-varmod.mk
# escape-varmod-define.mk
# escape-varmod-match.mk
# escape-varname.mk
# escape-varassign-varname.mk
# escape-varassign-varname-cmdline.mk
# escape-varassign-value.mk
# escape-varassign-value-cmdline.mk
# escape-dependency-source.mk
# escape-dependency-target.mk
# escape-for-varname.mk
# escape-for-item.mk
# posix-*.mk (see posix.mk and posix1.mk)
.if ${.OBJDIR} != ${.CURDIR}
RO_OBJDIR:= ${.OBJDIR}/roobj
.else
RO_OBJDIR:= ${TMPDIR:U/tmp}/roobj
.endif
# Additional environment variables for some of the tests. # Additional environment variables for some of the tests.
# The base environment is -i PATH="$PATH". # The base environment is -i PATH="$PATH".
ENV.depsrc-optional+= TZ=UTC
ENV.envfirst= FROM_ENV=value-from-env ENV.envfirst= FROM_ENV=value-from-env
ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR}
ENV.varmisc= FROM_ENV=env ENV.varmisc= FROM_ENV=env
ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_BEFORE=env
ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmisc+= FROM_ENV_AFTER=env
ENV.varmod-localtime+= TZ=Europe/Berlin ENV.varmod-localtime+= TZ=Europe/Berlin
ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# Override make flags for some of the tests; default is -k. # Override make flags for some of the tests; default is -k.
# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of
# settings FLAGS.test=-dv here, since that is closer to the test code. # settings FLAGS.test=-dv here, since that is closer to the test code.
FLAGS.cond-func-make= via-cmdline FLAGS.cond-func-make= via-cmdline
FLAGS.directive-ifmake= first second FLAGS.directive-ifmake= first second
FLAGS.doterror= # none FLAGS.doterror= # none, especially not -k
FLAGS.envfirst= -e FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
FLAGS.export= # none
FLAGS.opt-ignore= -i
FLAGS.opt-keep-going= -k
FLAGS.opt-no-action= -n
FLAGS.opt-query= -q
FLAGS.opt-var-expanded= -v VAR -v VALUE
FLAGS.opt-var-literal= -V VAR -V VALUE
FLAGS.opt-warnings-as-errors= -W
FLAGS.order= -j1
FLAGS.recursive= -dL
FLAGS.sh-leading-plus= -n
FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain'
# Some tests need extra postprocessing. # Some tests need extra postprocessing.
SED_CMDS.export= \ SED_CMDS.export= \
@ -406,6 +434,9 @@ SED_CMDS.export= \
.for t in export-all export-env .for t in export-all export-env
SED_CMDS.$t= ${SED_CMDS.export} SED_CMDS.$t= ${SED_CMDS.export}
.endfor .endfor
SED_CMDS.directive-export-gmake= \
${:D dash is a pain } \
-e /non-zero/d
SED_CMDS.job-output-long-lines= \ SED_CMDS.job-output-long-lines= \
${:D Job separators on their own line are ok. } \ ${:D Job separators on their own line are ok. } \
-e '/^--- job-[ab] ---$$/d' \ -e '/^--- job-[ab] ---$$/d' \
@ -417,6 +448,7 @@ SED_CMDS.job-output-long-lines= \
${:D marker should always be at the beginning of the line. } \ ${:D marker should always be at the beginning of the line. } \
-e '/^aa*--- job-b ---$$/d' \ -e '/^aa*--- job-b ---$$/d' \
-e '/^bb*--- job-a ---$$/d' -e '/^bb*--- job-a ---$$/d'
SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g'
SED_CMDS.opt-debug-graph1= \ SED_CMDS.opt-debug-graph1= \
-e 's,${.CURDIR},CURDIR,' -e 's,${.CURDIR},CURDIR,'
SED_CMDS.opt-debug-graph1+= \ SED_CMDS.opt-debug-graph1+= \
@ -428,11 +460,14 @@ SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,'
# The "-q" may be there or not, see jobs.c, variable shells. # The "-q" may be there or not, see jobs.c, variable shells.
SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,' SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,'
SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,'
SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }'
SED_CMDS.vardebug= \
${:D canonicalize .SHELL } \
-e 's,${.SHELL},</path/to/shell>,'
SED_CMDS.varmod-subst-regex+= \ SED_CMDS.varmod-subst-regex+= \
-e 's,\(Regex compilation error:\).*,\1 (details omitted),' -e 's,\(Regex compilation error:\).*,\1 (details omitted),'
SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,' SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,'
SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,'
SED_CMDS.varshell+= -e '/command/s,No such.*,not found,'
SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g'
@ -442,7 +477,7 @@ SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g'
# Some tests need an additional round of postprocessing. # Some tests need an additional round of postprocessing.
POSTPROC.deptgt-suffixes= \ POSTPROC.deptgt-suffixes= \
${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p' ${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p'
POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p' POSTPROC.gnode-submake= awk '/Input graph/, /^$$/'
POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p' POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p'
# Some tests reuse other tests, which makes them unnecessarily fragile. # Some tests reuse other tests, which makes them unnecessarily fragile.
@ -519,6 +554,8 @@ MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc
# always pretend .MAKE was called 'make' # always pretend .MAKE was called 'make'
_SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,'
_SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,'
_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,'
_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
# replace anything after 'stopped in' with unit-tests # replace anything after 'stopped in' with unit-tests
_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' _SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
# strip ${.CURDIR}/ from the output # strip ${.CURDIR}/ from the output

View file

@ -1,11 +1,11 @@
# $NetBSD: archive-suffix.mk,v 1.1 2020/08/29 14:47:26 rillig Exp $ # $NetBSD: archive-suffix.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
# #
# Between 2020-08-23 and 2020-08-30, the below code produced an assertion # Between 2020-08-23 and 2020-08-30, the below code produced an assertion
# failure in Var_Set_with_flags, triggered by Compat_Make, when setting the # failure in Var_SetWithFlags, triggered by Compat_Make, when setting the
# .IMPSRC of an archive node to its .TARGET. # .IMPSRC of an archive node to its .TARGET.
# #
# The code assumed that the .TARGET variable of every node would be set, but # The code assumed that the .TARGET variable of every node would be set, but
# but that is not guaranteed. # that is not guaranteed.
# #
# Between 2016-03-15 and 2016-03-16 the behavior of the below code changed. # Between 2016-03-15 and 2016-03-16 the behavior of the below code changed.
# Until 2016-03-15, it remade the target, starting with 2016-03-16 it says # Until 2016-03-15, it remade the target, starting with 2016-03-16 it says

View file

@ -1,18 +1,15 @@
# $NetBSD: archive.mk,v 1.10 2020/10/09 06:44:42 rillig Exp $ # $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $
# #
# Very basic demonstration of handling archives, based on the description # Very basic demonstration of handling archives, based on the description
# in PSD.doc/tutorial.ms. # in PSD.doc/tutorial.ms.
# #
# This test aims at covering the code, not at being an introduction to # This test aims at covering the code, not at being an introduction to
# archive handling. That's why it is more complicated and detailed than # archive handling. That's why it deviates from the tutorial style of
# strictly necessary. # several other tests.
ARCHIVE= libprog.a ARCHIVE= libprog.a
FILES= archive.mk modmisc.mk varmisc.mk FILES= archive.mk modmisc.mk varmisc.mk
MAKE_CMD= ${.MAKE} -f ${MAKEFILE}
RUN?= @set -eu;
all: all:
.if ${.PARSEDIR:tA} != ${.CURDIR:tA} .if ${.PARSEDIR:tA} != ${.CURDIR:tA}
@cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR}
@ -20,13 +17,13 @@ all:
# The following targets create and remove files. The filesystem cache in # The following targets create and remove files. The filesystem cache in
# dir.c would probably not handle this correctly, therefore each of the # dir.c would probably not handle this correctly, therefore each of the
# targets is run in its separate sub-make. # targets is run in its separate sub-make.
${RUN} ${MAKE_CMD} remove-archive @${MAKE} -f ${MAKEFILE} remove-archive
${RUN} ${MAKE_CMD} create-archive @${MAKE} -f ${MAKEFILE} create-archive
${RUN} ${MAKE_CMD} list-archive @${MAKE} -f ${MAKEFILE} list-archive
${RUN} ${MAKE_CMD} list-archive-wildcard @${MAKE} -f ${MAKEFILE} list-archive-wildcard
${RUN} ${MAKE_CMD} depend-on-existing-member @${MAKE} -f ${MAKEFILE} depend-on-existing-member
${RUN} ${MAKE_CMD} depend-on-nonexistent-member @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member
${RUN} ${MAKE_CMD} remove-archive @${MAKE} -f ${MAKEFILE} remove-archive
create-archive: ${ARCHIVE} pre post create-archive: ${ARCHIVE} pre post
@ -43,15 +40,16 @@ list-archive: ${ARCHIVE} pre post
# XXX: I had expected that this dependency would select all *.mk files from # XXX: I had expected that this dependency would select all *.mk files from
# the archive. Instead, the globbing is done in the current directory. # the archive. Instead, the globbing is done in the current directory.
#
# To prevent an overly long file list, the pattern is restricted to [at]*.mk. # To prevent an overly long file list, the pattern is restricted to [at]*.mk.
list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post
${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post
${RUN} echo $@ @echo $@
depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post
${RUN} echo $@ @echo $@
remove-archive: pre post remove-archive: pre post
rm -f ${ARCHIVE} rm -f ${ARCHIVE}

View file

@ -0,0 +1,9 @@
: undefined
make: Unclosed variable "UNCLOSED"
: unclosed-variable
make: Unclosed variable expression (expecting '}') for "UNCLOSED"
: unclosed-modifier
make: Unknown modifier 'Z'
: unknown-modifier
: end
exit status 2

View file

@ -0,0 +1,32 @@
# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $
#
# Demonstrate how errors in variable expansions affect whether the commands
# are actually executed.
.MAKEFLAGS: -dL
all: undefined unclosed-variable unclosed-modifier unknown-modifier end
# Undefined variables are not an error. They expand to empty strings.
undefined:
: $@ ${UNDEFINED}
# XXX: As of 2020-11-01, this obvious syntax error is not detected.
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unclosed-variable:
: $@ ${UNCLOSED
# XXX: As of 2020-11-01, this obvious syntax error is not detected.
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unclosed-modifier:
: $@ ${UNCLOSED:
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unknown-modifier:
: $@ ${UNKNOWN:Z}
end:
: $@

View file

@ -0,0 +1,9 @@
: undefined eol
make: Unclosed variable "UNCLOSED"
: unclosed-variable
make: Unclosed variable expression (expecting '}') for "UNCLOSED"
: unclosed-modifier
make: Unknown modifier 'Z'
: unknown-modifier eol
: end eol
exit status 0

30
unit-tests/cmd-errors.mk Normal file
View file

@ -0,0 +1,30 @@
# $NetBSD: cmd-errors.mk,v 1.3 2020/11/09 23:36:34 rillig Exp $
#
# Demonstrate how errors in variable expansions affect whether the commands
# are actually executed.
all: undefined unclosed-variable unclosed-modifier unknown-modifier end
# Undefined variables are not an error. They expand to empty strings.
undefined:
: $@ ${UNDEFINED} eol
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unclosed-variable:
: $@ ${UNCLOSED
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unclosed-modifier:
: $@ ${UNCLOSED:
# XXX: As of 2020-11-01, this command is executed even though it contains
# parse errors.
unknown-modifier:
: $@ ${UNKNOWN:Z} eol
end:
: $@ eol
# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0.

View file

@ -1,4 +1,4 @@
# $NetBSD: cmd-interrupt.mk,v 1.2 2020/08/28 18:16:22 rillig Exp $ # $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for interrupting a command. # Tests for interrupting a command.
# #
@ -22,7 +22,7 @@ all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-af
clean-before clean-after: .PHONY clean-before clean-after: .PHONY
@rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious
interrupt-ordinary: .PHONY interrupt-ordinary:
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true
# The ././ is necessary to work around the file cache. # The ././ is necessary to work around the file cache.
@echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok } @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok }

View file

@ -0,0 +1,17 @@
The = assignment operator
make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
The := assignment operator
make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
exit status 0

View file

@ -0,0 +1,40 @@
# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $
#
# Tests for undefined variable expressions in the command line.
all:
# When the command line is parsed, variable assignments using the
# '=' assignment operator do get their variable name expanded
# (which probably occurs rarely in practice, if at all), but their
# variable value is not expanded, as usual.
#
@echo 'The = assignment operator'
@${.MAKE} -f ${MAKEFILE} print-undefined \
CMDLINE='Undefined is $${UNDEFINED}.'
@echo
# The interesting case is using the ':=' assignment operator, which
# expands its right-hand side. But only those variables that are
# defined.
@echo 'The := assignment operator'
@${.MAKE} -f ${MAKEFILE} print-undefined \
CMDLINE:='Undefined is $${UNDEFINED}.'
@echo
.if make(print-undefined)
.MAKEFLAGS: MAKEFLAGS_ASSIGN='Undefined is $${UNDEFINED}.'
.MAKEFLAGS: MAKEFLAGS_SUBST:='Undefined is $${UNDEFINED}.'
.info From the command line: ${CMDLINE}
.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN}
.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST}
UNDEFINED?= now defined
.info From the command line: ${CMDLINE}
.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN}
.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST}
print-undefined:
.endif

View file

@ -1,8 +1,7 @@
# $NetBSD: cmdline.mk,v 1.1 2020/07/28 22:44:44 rillig Exp $ # $NetBSD: cmdline.mk,v 1.2 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for command line parsing and related special variables. # Tests for command line parsing and related special variables.
RUN?= @set -eu;
TMPBASE?= /tmp TMPBASE?= /tmp
SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID
SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID
@ -14,14 +13,14 @@ all: prepare-dirs
all: makeobjdir-direct makeobjdir-indirect all: makeobjdir-direct makeobjdir-indirect
prepare-dirs: prepare-dirs:
${RUN} rm -rf ${DIR2} ${DIR12} @rm -rf ${DIR2} ${DIR12}
${RUN} mkdir -p ${DIR2} ${DIR12} @mkdir -p ${DIR2} ${DIR12}
# The .OBJDIR can be set via the MAKEOBJDIR command line variable. # The .OBJDIR can be set via the MAKEOBJDIR command line variable.
# It must be a command line variable; an environment variable would not work. # It must be a command line variable; an environment variable would not work.
makeobjdir-direct: makeobjdir-direct:
@echo $@: @echo $@:
${RUN} ${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir
# The .OBJDIR can be set via the MAKEOBJDIR command line variable, # The .OBJDIR can be set via the MAKEOBJDIR command line variable,
# and that variable could even contain the usual modifiers. # and that variable could even contain the usual modifiers.
@ -31,7 +30,7 @@ makeobjdir-direct:
# see MAKE_CMD. # see MAKE_CMD.
makeobjdir-indirect: makeobjdir-indirect:
@echo $@: @echo $@:
${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
show-objdir: show-objdir:
@echo $@: ${.OBJDIR:Q} @echo $@: ${.OBJDIR:Q}

View file

@ -1,4 +1,4 @@
# $NetBSD: comment.mk,v 1.2 2020/09/07 19:17:36 rillig Exp $ # $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
# #
# Demonstrate how comments are written in makefiles. # Demonstrate how comments are written in makefiles.
@ -12,7 +12,7 @@ that \
goes \ goes \
on and on. on and on.
# Comments can be indented, but that is rather unusual. # Comments can be indented with spaces, but that is rather unusual.
# Comments can be indented with a tab. # Comments can be indented with a tab.
# These are not shell commands, they are just makefile comments. # These are not shell commands, they are just makefile comments.
@ -21,6 +21,8 @@ on and on.
.endif # And after the closing directive. .endif # And after the closing directive.
VAR= # This comment makes the variable value empty. VAR= # This comment makes the variable value empty.
# ParseGetLine removes any whitespace before the
# comment.
.if ${VAR} != "" .if ${VAR} != ""
. error . error
.endif .endif
@ -35,7 +37,9 @@ VAR= value
. error . error
.endif .endif
# This is NOT an escaped comment due to the double backslashes \\ # This comment ends with 2 backslashes. An even number of backslashes does
# not count as a line continuation, therefore the variable assignment that
# follows is actively interpreted. \\
VAR= not part of the comment VAR= not part of the comment
.if ${VAR} != "not part of the comment" .if ${VAR} != "not part of the comment"
. error . error
@ -55,7 +59,7 @@ WORDS= ${VAR:[#]} [#
. error . error
.endif .endif
# An odd number of comment signs makes a line continuation, \\\ # An odd number of backslashes makes a line continuation, \\\
no matter if it is 3 or 5 \\\\\ no matter if it is 3 or 5 \\\\\
or 9 backslashes. \\\\\\\\\ or 9 backslashes. \\\\\\\\\
This is the last line of the comment. This is the last line of the comment.

View file

@ -1,6 +1,6 @@
make: "cond-cmp-numeric-eq.mk" line 54: warning: Unknown operator make: "cond-cmp-numeric-eq.mk" line 67: warning: Unknown operator
make: "cond-cmp-numeric-eq.mk" line 54: Malformed conditional (!(12345 = 12345)) make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345))
make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345)) make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345))
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-cmp-numeric-eq.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $
# #
# Tests for numeric comparisons with the == operator in .if conditions. # Tests for numeric comparisons with the == operator in .if conditions.
@ -49,6 +49,19 @@
. error . error
.endif .endif
# Because an IEEE 754 double can only hold integers with a mantissa of 53
# bits, these two numbers are considered the same. The 993 is rounded down
# to the 992.
.if 9007199254740993 == 9007199254740992
.else
. error
.endif
# The 995 is rounded up, the 997 is rounded down.
.if 9007199254740995 == 9007199254740997
.else
. error Probably a misconfiguration in the floating point environment, \
or maybe a machine without IEEE 754 floating point support.
.endif
# There is no = operator for numbers. # There is no = operator for numbers.
.if !(12345 = 12345) .if !(12345 = 12345)

View file

@ -6,6 +6,10 @@ make: "cond-cmp-numeric.mk" line 16: warning: String comparison operator must be
make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN) make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN)
CondParser_Eval: !(${:UNaN} == NaN) CondParser_Eval: !(${:UNaN} == NaN)
lhs = "NaN", rhs = "NaN", op = == lhs = "NaN", rhs = "NaN", op = ==
CondParser_Eval: 123 ! 123
lhs = 123.000000, rhs = 123.000000, op = !
make: "cond-cmp-numeric.mk" line 34: warning: Unknown operator
make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123)
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-cmp-numeric.mk,v 1.3 2020/09/12 18:01:51 rillig Exp $ # $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $
# #
# Tests for numeric comparisons in .if conditions. # Tests for numeric comparisons in .if conditions.
@ -25,5 +25,17 @@
. error . error
.endif .endif
# The parsing code in CondParser_Comparison only performs a light check on
# whether the operator is valid, leaving the rest of the work to the
# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this
# parse error is properly reported.
#
# XXX: The warning message does not mention the actual operator.
.if 123 ! 123
. error
.else
. error
.endif
all: all:
@:; @:;

View file

@ -1,8 +1,8 @@
make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str) make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str)
make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing") make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing")
make: "cond-cmp-string.mk" line 42: warning: String comparison operator must be either == or != make: "cond-cmp-string.mk" line 49: warning: String comparison operator must be either == or !=
make: "cond-cmp-string.mk" line 42: Malformed conditional (!("value" = "value")) make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value"))
make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" === "value")) make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value"))
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-cmp-string.mk,v 1.11 2020/10/30 14:53:31 rillig Exp $ # $NetBSD: cond-cmp-string.mk,v 1.13 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for string comparisons in .if conditions. # Tests for string comparisons in .if conditions.
@ -19,9 +19,14 @@
. error . error
.endif .endif
# The left-hand side of the comparison requires a defined variable. # The left-hand side of the comparison requires that any variable expression
# The variable named "" is not defined, but applying the :U modifier to it # is defined.
# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here. #
# The variable named "" is never defined, nevertheless it can be used as a
# starting point for variable expressions. Applying the :U modifier to such
# an undefined expression turns it into a defined expression.
#
# See ApplyModifier_Defined and VEF_DEF.
.if ${:Ustr} != "str" .if ${:Ustr} != "str"
. error . error
.endif .endif
@ -33,9 +38,11 @@
.endif .endif
# It is not possible to concatenate two string literals to form a single # It is not possible to concatenate two string literals to form a single
# string. # string. In C, Python and the shell this is possible, but not in make.
.if "string" != "str""ing" .if "string" != "str""ing"
. error . error
.else
. error
.endif .endif
# There is no = operator for strings. # There is no = operator for strings.
@ -88,3 +95,16 @@
.if ${:U word } != " ${:Uword} " .if ${:U word } != " ${:Uword} "
. error . error
.endif .endif
# If at least one side of the comparison is a string literal, the string
# comparison is performed.
.if 12345 != "12345"
. error
.endif
# If at least one side of the comparison is a string literal, the string
# comparison is performed. The ".0" in the left-hand side makes the two
# sides of the equation unequal.
.if 12345.0 == "12345"
. error
.endif

View file

@ -1 +1,2 @@
make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty.
exit status 0 exit status 0

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-cmp-unary.mk,v 1.1 2020/09/14 06:22:59 rillig Exp $ # $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 rillig Exp $
# #
# Tests for unary comparisons in .if conditions, that is, comparisons with # Tests for unary comparisons in .if conditions, that is, comparisons with
# a single operand. If the operand is a number, it is compared to zero, # a single operand. If the operand is a number, it is compared to zero,
@ -25,6 +25,9 @@
.endif .endif
# The empty string may come from a variable expression. # The empty string may come from a variable expression.
#
# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in
# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber.
.if ${:U} .if ${:U}
. error . error
.endif .endif
@ -40,4 +43,16 @@
. error . error
.endif .endif
# A string of whitespace should evaluate to false.
#
# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip
# whitespace before testing for the end. This was probably an oversight in
# a commit from 1992-04-15 saying "A variable is empty when it just contains
# spaces".
.if ${:U }
. info This is only reached because of a bug in EvalNotEmpty.
.else
. error
.endif
all: # nothing all: # nothing

View file

@ -1,10 +1,11 @@
# $NetBSD: cond-func-commands.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for the commands() function in .if conditions. # Tests for the commands() function in .if conditions.
.MAIN: all .MAIN: all
# The target "target" does not exist yet, therefore it cannot have commands. # At this point, the target 'target' does not exist yet, therefore it cannot
# have commands. Sounds obvious, but good to know that it is really so.
.if commands(target) .if commands(target)
. error . error
.endif .endif

View file

@ -1,5 +1,10 @@
make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined() make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined()
make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B)) make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B))
make: "cond-func-defined.mk" line 33: warning: Missing closing parenthesis for defined()
make: "cond-func-defined.mk" line 33: Malformed conditional (defined(DEF)
make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are
make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable
make: "cond-func-defined.mk" line 47: involved, even if it feels like it.
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-func-defined.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for the defined() function in .if conditions. # Tests for the defined() function in .if conditions.
@ -29,5 +29,24 @@ ${:UA B}= variable name with spaces
. error . error
.endif .endif
# Parse error: missing closing parenthesis; see ParseFuncArg.
.if defined(DEF
. error
.else
. error
.endif
# Variables from .for loops are not defined.
# See directive-for.mk for more details.
.for var in value
. if defined(var)
. error
. else
. info In .for loops, variable expressions for the loop variables are
. info substituted at evaluation time. There is no actual variable
. info involved, even if it feels like it.
. endif
.endfor
all: all:
@:; @:;

View file

@ -1 +1,5 @@
exit status 0 make: "cond-func-empty.mk" line 152: Unclosed variable "WORD"
make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View file

@ -1,11 +1,10 @@
# $NetBSD: cond-func-empty.mk,v 1.8 2020/09/23 08:11:28 rillig Exp $ # $NetBSD: cond-func-empty.mk,v 1.10 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for the empty() function in .if conditions, which tests a variable # Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness. # expression for emptiness.
# #
# Note that the argument in the parentheses is indeed a variable name, # Note that the argument in the parentheses is indeed a variable name,
# optionally followed by variable modifiers. This is like the defined() # optionally followed by variable modifiers.
# function.
# #
.undef UNDEF .undef UNDEF
@ -25,13 +24,15 @@ WORD= word
. error . error
.endif .endif
# The :S modifier replaces the empty value with an actual word, and # The :S modifier replaces the empty value with an actual word. The
# after that the expression is no longer empty. Because the variable # expression is now no longer empty, but it is still possible to see whether
# was undefined in the first place, the expression has the flag VAR_JUNK # the expression was based on an undefined variable. The expression has the
# but not VAR_KEEP, therefore it is still considered undefined. # flag VEF_UNDEF.
# Only very few variable modifiers turn an undefined variable expression #
# into a defined variable expression. The :U and :D modifiers belong to # The expression does not have the flag VEF_DEF though, therefore it is still
# that group, but :S doesn't (see VAR_KEEP). # considered undefined. Yes, indeed, undefined but not empty. There are a
# few variable modifiers that turn an undefined expression into a defined
# expression, among them :U and :D, but not :S.
# #
# XXX: This is hard to explain to someone who doesn't know these # XXX: This is hard to explain to someone who doesn't know these
# implementation details. # implementation details.
@ -49,13 +50,14 @@ WORD= word
.endif .endif
# And now to the surprising part. Applying the following :S modifier to the # And now to the surprising part. Applying the following :S modifier to the
# undefined variable makes it non-empty, but the marker VAR_JUNK is preserved # undefined expression makes it non-empty, but the marker VEF_UNDEF is
# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide # preserved nevertheless. The :U modifier that follows only looks at the
# whether the variable is defined or not. This kind of makes sense since the # VEF_UNDEF flag to decide whether the variable is defined or not. This kind
# :U modifier tests the _variable_, not the _expression_. # of makes sense since the :U modifier tests the _variable_, not the
# _expression_.
# #
# But since the variable was undefined to begin with, the fallback value is # But since the variable was undefined to begin with, the fallback value from
# used in this expression. # the :U modifier is used in this expression.
# #
.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" .if ${UNDEF:S,^$,value,W:Ufallback} != "fallback"
. error . error
@ -128,7 +130,7 @@ ${:U }= space
# If everything goes well, the argument expands to "WORD", and that variable # If everything goes well, the argument expands to "WORD", and that variable
# is defined at the beginning of this file. The surrounding 'W' and 'D' # is defined at the beginning of this file. The surrounding 'W' and 'D'
# ensure that the parser in ParseEmptyArg has the correct position, both # ensure that the parser in ParseEmptyArg has the correct position, both
# before and after the call to Var_ParsePP. # before and after the call to Var_Parse.
.if empty(W${:UOR}D) .if empty(W${:UOR}D)
. error . error
.endif .endif
@ -146,5 +148,12 @@ ${:U WORD }= variable name with spaces
. error . error
.endif .endif
# Parse error: missing closing parenthesis.
.if empty(WORD
. error
.else
. error
.endif
all: all:
@:; @:;

View file

@ -1,9 +1,15 @@
make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined() make: "cond-func.mk" line 36: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 29: Malformed conditional (!defined(A B)) make: "cond-func.mk" line 36: Malformed conditional (!defined(A B))
make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined() make: "cond-func.mk" line 51: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B)) make: "cond-func.mk" line 51: Malformed conditional (!defined(A&B))
make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined() make: "cond-func.mk" line 54: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B)) make: "cond-func.mk" line 54: Malformed conditional (!defined(A|B))
make: "cond-func.mk" line 94: The empty variable is never defined.
make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...).
make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...).
make: "cond-func.mk" line 119: Symbols may start with a function name.
make: "cond-func.mk" line 124: Symbols may start with a function name.
make: "cond-func.mk" line 130: Malformed conditional (defined()
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,15 +1,22 @@
# $NetBSD: cond-func.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $
# #
# Tests for those parts of the functions in .if conditions that are common # Tests for those parts of the functions in .if conditions that are common
# among several functions. # among several functions.
# #
# The below test uses the function defined(...) since it has no side-effects, # The below test uses the function defined(...) since it has no side-effects,
# the other functions (except empty(...)) would work equally well. # the other functions (except empty(...)) would work equally well. The
# function empty is special because it uses a different parsing algorithm for
# its argument.
DEF= defined DEF= defined
${:UA B}= variable name with spaces ${:UA B}= variable name with spaces
${:UVAR(value)}= variable name with parentheses ${:UVAR(value)}= variable name with parentheses
${:UVAR{value}}= variable name with braces ${:UVAR{value}}= variable name with balanced braces
# Really strange variable names must be given indirectly via another variable,
# so that no unbalanced braces appear in the top-level expression.
VARNAME_UNBALANCED_BRACES= VAR{{{value
${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
.if !defined(DEF) .if !defined(DEF)
. error . error
@ -59,6 +66,12 @@ ${:UVAR{value}}= variable name with braces
. error . error
.endif .endif
# Braces do not have any special meaning when parsing arguments.
# They don't need to be balanced.
.if !defined(VAR{{{value)
. error
.endif
# There may be spaces around the operators and parentheses, and even # There may be spaces around the operators and parentheses, and even
# inside the parentheses. The spaces inside the parentheses are not # inside the parentheses. The spaces inside the parentheses are not
# allowed for the empty() function (see cond-func-empty.mk), therefore # allowed for the empty() function (see cond-func-empty.mk), therefore
@ -67,5 +80,58 @@ ${:UVAR{value}}= variable name with braces
. error . error
.endif .endif
# The following condition is interpreted as defined(A) && defined(B).
# In lack of a function call expression, each kind of .if directive has a
# default function that is called when a bare word is parsed. For the plain
# .if directive, this function is defined(); see "struct If ifs" in cond.c.
.if A&B
. error
.endif
.if defined()
. error
.else
. info The empty variable is never defined.
.endif
# The plain word 'defined' is interpreted as '!empty(defined)'.
# That variable is not defined (yet).
.if defined
. error
.else
. info A plain function name is parsed as !empty(...).
.endif
# If a variable named 'defined' is actually defined and not empty, the plain
# symbol 'defined' evaluates to true.
defined= non-empty
.if defined
. info A plain function name is parsed as !empty(...).
.else
. error
.endif
# A plain symbol name may start with one of the function names, in this case
# 'defined'.
.if defined-var
. error
.else
. info Symbols may start with a function name.
.endif
defined-var= non-empty
.if defined-var
. info Symbols may start with a function name.
.else
. error
.endif
# Missing closing parenthesis when parsing the function argument.
.if defined(
. error
.else
. error
.endif
all: all:
@:; @:;

View file

@ -1,7 +1,9 @@
# $NetBSD: cond-late.mk,v 1.2 2020/07/25 20:37:46 rillig Exp $ # $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
# #
# Using the :? modifier, variable expressions can contain conditional # Using the :? modifier, variable expressions can contain conditional
# expressions that are evaluated late. Any variables appearing in these # expressions that are evaluated late, at expansion time.
#
# Any variables appearing in these
# conditions are expanded before parsing the condition. This is # conditions are expanded before parsing the condition. This is
# different from many other places. # different from many other places.
# #
@ -11,15 +13,15 @@
# They should also not contain operators like == or <, since these are # They should also not contain operators like == or <, since these are
# actually interpreted as these operators. This is demonstrated below. # actually interpreted as these operators. This is demonstrated below.
# #
# If the order of evaluation were to change to first parse the condition
# and then expand the variables, the output would change from the
# current "yes no" to "yes yes", since both variables are non-empty.
all: cond-literal all: cond-literal
COND.true= "yes" == "yes" COND.true= "yes" == "yes"
COND.false= "yes" != "yes" COND.false= "yes" != "yes"
# If the order of evaluation were to change to first parse the condition
# and then expand the variables, the output would change from the
# current "yes no" to "yes yes", since both variables are non-empty.
cond-literal: cond-literal:
@echo ${ ${COND.true} :?yes:no} @echo ${ ${COND.true} :?yes:no}
@echo ${ ${COND.false} :?yes:no} @echo ${ ${COND.false} :?yes:no}

View file

@ -0,0 +1,4 @@
make: "cond-op-and-lint.mk" line 9: Unknown operator '&'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View file

@ -0,0 +1,13 @@
# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
#
# Tests for the && operator in .if conditions, in lint mode.
.MAKEFLAGS: -dL
# The '&' operator is not allowed in lint mode.
# It is not used in practice anyway.
.if 0 & 0
. error
.else
. error
.endif

View file

@ -1 +1,6 @@
make: "cond-op-not.mk" line 29: Not empty evaluates to true.
make: "cond-op-not.mk" line 37: Not space evaluates to false.
make: "cond-op-not.mk" line 41: Not 0 evaluates to true.
make: "cond-op-not.mk" line 49: Not 1 evaluates to false.
make: "cond-op-not.mk" line 55: Not word evaluates to false.
exit status 0 exit status 0

View file

@ -1,6 +1,6 @@
# $NetBSD: cond-op-not.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-op-not.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for the ! operator in .if conditions. # Tests for the ! operator in .if conditions, which negates its argument.
# The exclamation mark negates its operand. # The exclamation mark negates its operand.
.if !1 .if !1
@ -18,5 +18,42 @@
. error . error
.endif .endif
# The operator '==' binds more tightly than '!'.
# This is unusual since most other programming languages define the precedence
# to be the other way round.
.if !${:Uexpression} == "expression"
. error
.endif
.if !${:U}
. info Not empty evaluates to true.
.else
. info Not empty evaluates to false.
.endif
.if !${:U }
. info Not space evaluates to true.
.else
. info Not space evaluates to false.
.endif
.if !${:U0}
. info Not 0 evaluates to true.
.else
. info Not 0 evaluates to false.
.endif
.if !${:U1}
. info Not 1 evaluates to true.
.else
. info Not 1 evaluates to false.
.endif
.if !${:Uword}
. info Not word evaluates to true.
.else
. info Not word evaluates to false.
.endif
all: all:
@:; @:;

View file

@ -0,0 +1,4 @@
make: "cond-op-or-lint.mk" line 9: Unknown operator '|'
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View file

@ -0,0 +1,13 @@
# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
#
# Tests for the || operator in .if conditions, in lint mode.
.MAKEFLAGS: -dL
# The '|' operator is not allowed in lint mode.
# It is not used in practice anyway.
.if 0 | 0
. error
.else
. error
.endif

View file

@ -1 +1,2 @@
make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112.
exit status 0 exit status 0

View file

@ -1,8 +1,19 @@
# $NetBSD: cond-op-parentheses.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ # $NetBSD: cond-op-parentheses.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for parentheses in .if conditions. # Tests for parentheses in .if conditions.
# TODO: Implementation # TODO: Implementation
# Test for deeply nested conditions.
.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
(((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
1 \
)))))))))))))))))))))))))))))))))))))))))))))))))))))))) \
))))))))))))))))))))))))))))))))))))))))))))))))))))))))
. info Parentheses can be nested at least to depth 112.
.else
. error
.endif
all: all:
@:; @:;

View file

@ -1,7 +1,16 @@
make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word) make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word)
make: "cond-op.mk" line 70: Malformed conditional (0 ${ERR::=evaluated}) make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated})
make: "cond-op.mk" line 74: warning: After detecting a parse error, the rest is evaluated. make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated.
make: "cond-op.mk" line 78: Parsing continues until here. make: "cond-op.mk" line 83: Parsing continues until here.
make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C)
make: "cond-op.mk" line 93: 0 0 0 => 0 0 0
make: "cond-op.mk" line 93: 0 0 1 => 0 0 0
make: "cond-op.mk" line 93: 0 1 0 => 0 0 0
make: "cond-op.mk" line 93: 0 1 1 => 1 1 1
make: "cond-op.mk" line 93: 1 0 0 => 0 1 1
make: "cond-op.mk" line 93: 1 0 1 => 1 1 1
make: "cond-op.mk" line 93: 1 1 0 => 0 1 1
make: "cond-op.mk" line 93: 1 1 1 => 1 1 1
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-op.mk,v 1.8 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-op.mk,v 1.10 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for operators like &&, ||, ! in .if conditions. # Tests for operators like &&, ||, ! in .if conditions.
# #
@ -9,8 +9,8 @@
# cond-op-parentheses.mk # cond-op-parentheses.mk
# In make, && binds more tightly than ||, like in C. # In make, && binds more tightly than ||, like in C.
# If make had the same precedence for both && and ||, the result would be # If make had the same precedence for both && and ||, like in the shell,
# different. # the result would be different.
# If || were to bind more tightly than &&, the result would be different # If || were to bind more tightly than &&, the result would be different
# as well. # as well.
.if !(1 || 1 && 0) .if !(1 || 1 && 0)
@ -18,13 +18,17 @@
.endif .endif
# If make were to interpret the && and || operators like the shell, the # If make were to interpret the && and || operators like the shell, the
# implicit binding would be this: # previous condition would be interpreted as:
.if (1 || 1) && 0 .if (1 || 1) && 0
. error . error
.endif .endif
# The precedence of the ! operator is different from C though. It has a # The precedence of the ! operator is different from C though. It has a
# lower precedence than the comparison operators. # lower precedence than the comparison operators. Negating a condition
# does not need parentheses.
#
# This kind of condition looks so unfamiliar that it doesn't occur in
# practice.
.if !"word" == "word" .if !"word" == "word"
. error . error
.endif .endif
@ -36,7 +40,8 @@
# TODO: Demonstrate that the precedence of the ! and == operators actually # TODO: Demonstrate that the precedence of the ! and == operators actually
# makes a difference. There is a simple example for sure, I just cannot # makes a difference. There is a simple example for sure, I just cannot
# wrap my head around it. # wrap my head around it right now. See the truth table generator below
# for an example that doesn't require much thought.
# This condition is malformed because the '!' on the right-hand side must not # This condition is malformed because the '!' on the right-hand side must not
# appear unquoted. If any, it must be enclosed in quotes. # appear unquoted. If any, it must be enclosed in quotes.
@ -71,11 +76,27 @@
. error . error
.endif .endif
.if ${ERR:Uundefined} == evaluated .if ${ERR:Uundefined} == evaluated
. warning After detecting a parse error, the rest is evaluated. . info After detecting a parse error, the rest is evaluated.
.endif .endif
# Just in case that parsing should ever stop on the first error. # Just in case that parsing should ever stop on the first error.
.info Parsing continues until here. .info Parsing continues until here.
# Demonstration that '&&' has higher precedence than '||'.
.info A B C => (A || B) && C A || B && C A || (B && C)
.for a in 0 1
. for b in 0 1
. for c in 0 1
. for r1 in ${ ($a || $b) && $c :?1:0}
. for r2 in ${ $a || $b && $c :?1:0}
. for r3 in ${ $a || ($b && $c) :?1:0}
. info $a $b $c => ${r1} ${r2} ${r3}
. endfor
. endfor
. endfor
. endfor
. endfor
.endfor
all: all:
@:; @:;

View file

@ -1,11 +1,14 @@
# $NetBSD: cond-short.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $ # $NetBSD: cond-short.mk,v 1.12 2020/11/15 14:58:14 rillig Exp $
# #
# Demonstrates that in conditions, the right-hand side of an && or || # Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result. # is only evaluated if it can actually influence the result.
# This is called 'short-circuit evaluation' and is the usual evaluation
# mode in most programming languages. A notable exception is Ada, which
# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'.
# #
# Between 2015-10-11 and 2020-06-28, the right-hand side of an && or || # Between 2015-10-11 and 2020-06-28, the right-hand side of an && or ||
# operator was always evaluated, which was wrong. # operator was always evaluated, which was wrong.
# # TODO: Had the evaluation been correct at some time before 2015-11-12?
# The && operator. # The && operator.
@ -113,6 +116,9 @@ VAR= # empty again, for the following tests
# make sure these do not cause complaint # make sure these do not cause complaint
#.MAKEFLAGS: -dc #.MAKEFLAGS: -dc
# TODO: Rewrite this whole section and check all the conditions and variables.
# Several of the assumptions are probably wrong here.
# TODO: replace 'x=' with '.info' or '.error'.
V42= 42 V42= 42
iV1= ${V42} iV1= ${V42}
iV2= ${V66} iV2= ${V66}
@ -167,5 +173,16 @@ x= Fail
.endif .endif
x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo
# TODO: Has this always worked? There may have been a time, maybe around
# 2000, when make would complain about the "Malformed conditional" because
# UNDEF is not defined.
.if defined(UNDEF) && ${UNDEF} != "undefined"
. error
.endif
# TODO: Test each modifier to make sure it is skipped when it is irrelevant
# for the result. Since this test is already quite long, do that in another
# test.
all: all:
@:;: @:;:

View file

@ -1,8 +1,8 @@
make: "cond-token-number.mk" line 13: Malformed conditional (-0) make: "cond-token-number.mk" line 15: Malformed conditional (-0)
make: "cond-token-number.mk" line 21: Malformed conditional (+0) make: "cond-token-number.mk" line 25: Malformed conditional (+0)
make: "cond-token-number.mk" line 29: Malformed conditional (!-1) make: "cond-token-number.mk" line 35: Malformed conditional (!-1)
make: "cond-token-number.mk" line 37: Malformed conditional (!+1) make: "cond-token-number.mk" line 45: Malformed conditional (!+1)
make: "cond-token-number.mk" line 54: End of the tests. make: "cond-token-number.mk" line 80: End of the tests.
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,6 +1,8 @@
# $NetBSD: cond-token-number.mk,v 1.3 2020/09/14 06:22:59 rillig Exp $ # $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for number tokens in .if conditions. # Tests for number tokens in .if conditions.
#
# TODO: Add introduction.
.if 0 .if 0
. error . error
@ -12,6 +14,8 @@
# See the ch_isdigit call in CondParser_String. # See the ch_isdigit call in CondParser_String.
.if -0 .if -0
. error . error
.else
. error
.endif .endif
# Even though +0 is a number and would be accepted by strtod, it is not # Even though +0 is a number and would be accepted by strtod, it is not
@ -20,6 +24,8 @@
# See the ch_isdigit call in CondParser_String. # See the ch_isdigit call in CondParser_String.
.if +0 .if +0
. error . error
.else
. error
.endif .endif
# Even though -1 is a number and would be accepted by strtod, it is not # Even though -1 is a number and would be accepted by strtod, it is not
@ -28,6 +34,8 @@
# See the ch_isdigit call in CondParser_String. # See the ch_isdigit call in CondParser_String.
.if !-1 .if !-1
. error . error
.else
. error
.endif .endif
# Even though +1 is a number and would be accepted by strtod, it is not # Even though +1 is a number and would be accepted by strtod, it is not
@ -36,6 +44,8 @@
# See the ch_isdigit call in CondParser_String. # See the ch_isdigit call in CondParser_String.
.if !+1 .if !+1
. error . error
.else
. error
.endif .endif
# When the number comes from a variable expression though, it may be signed. # When the number comes from a variable expression though, it may be signed.
@ -50,6 +60,22 @@
. error . error
.endif .endif
# Hexadecimal numbers are accepted.
.if 0x0
. error
.endif
.if 0x1
.else
. error
.endif
# This is not a hexadecimal number, even though it has an x.
# It is interpreted as a string instead, effectively meaning defined(3x4).
.if 3x4
.else
. error
.endif
# Ensure that parsing continues until here. # Ensure that parsing continues until here.
.info End of the tests. .info End of the tests.

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-token-plain.mk,v 1.4 2020/09/12 17:47:24 rillig Exp $ # $NetBSD: cond-token-plain.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for plain tokens (that is, string literals without quotes) # Tests for plain tokens (that is, string literals without quotes)
# in .if conditions. # in .if conditions.
@ -14,7 +14,7 @@
# parser gets to see it. # parser gets to see it.
# #
# XXX: The error message is missing for this malformed condition. # XXX: The error message is missing for this malformed condition.
# The right-hand side of the comparison is just a '"'. # The right-hand side of the comparison is just a '"', before unescaping.
.if ${:U} != "#hash" .if ${:U} != "#hash"
. error . error
.endif .endif
@ -31,16 +31,19 @@
# comment handling anymore. The comments are supposed to be stripped off # comment handling anymore. The comments are supposed to be stripped off
# in a very early parsing phase. # in a very early parsing phase.
# #
# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the
# original problems. This workaround is probably not needed anymore.
#
# XXX: Missing error message for the malformed condition. The right-hand # XXX: Missing error message for the malformed condition. The right-hand
# side is double-quotes, backslash, backslash. # side before unescaping is double-quotes, backslash, backslash.
# XXX: It is unexpected that the right-hand side evaluates to a single
# backslash.
.if ${:U\\} != "\\#hash" .if ${:U\\} != "\\#hash"
. error . error
.endif .endif
# The right-hand side of a comparison is not parsed as a token, therefore # The right-hand side of a comparison is not parsed as a token, therefore
# the code from CondParser_Token does not apply to it. # the code from CondParser_Token does not apply to it.
# TODO: Explain the consequences.
# TODO: Does this mean that more syntactic variants are allowed here?
.if ${:U\#hash} != \#hash .if ${:U\#hash} != \#hash
. error . error
.endif .endif

View file

@ -1 +1,8 @@
exit status 0 make: Unknown modifier 'Z'
make: "cond-token-string.mk" line 9: Malformed conditional ("" != "${:Uvalue:Z}")
make: "cond-token-string.mk" line 18: xvalue is not defined.
make: "cond-token-string.mk" line 24: Malformed conditional (x${:Uvalue} == "")
make: "cond-token-string.mk" line 33: Expected.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View file

@ -1,8 +1,39 @@
# $NetBSD: cond-token-string.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ # $NetBSD: cond-token-string.mk,v 1.3 2020/11/10 22:23:37 rillig Exp $
# #
# Tests for quoted and unquoted string literals in .if conditions. # Tests for quoted and unquoted string literals in .if conditions.
# TODO: Implementation # TODO: Implementation
# Cover the code in CondParser_String that frees the memory after parsing
# a variable expression based on an undefined variable.
.if "" != "${:Uvalue:Z}"
. error
.else
. error
.endif
.if x${:Uvalue}
. error
.else
. info xvalue is not defined.
.endif
# The 'x' produces a "Malformed conditional" since the left-hand side of a
# comparison in an .if directive must be either a variable expression, a
# quoted string literal or a number that starts with a digit.
.if x${:Uvalue} == ""
. error
.else
. error
.endif
# In plain words, a '\' can be used to escape any character, just as in
# double-quoted string literals. See CondParser_String.
.if \x${:Uvalue} == "xvalue"
. info Expected.
.else
. error
.endif
all: all:
@:; @:;

View file

@ -1,7 +1,7 @@
make: "cond-token-var.mk" line 9: ok make: "cond-token-var.mk" line 20: ok
make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF}) make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF})
make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF}) make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF})
make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF}) make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF})
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,6 +1,17 @@
# $NetBSD: cond-token-var.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ # $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for variables in .if conditions. # Tests for variable expressions in .if conditions.
#
# Note the fine distinction between a variable and a variable expression.
# A variable has a name and a value. To access the value, one writes a
# variable expression of the form ${VAR}. This is a simple variable
# expression. Variable expressions can get more complicated by adding
# variable modifiers such as in ${VAR:Mpattern}.
#
# XXX: Strictly speaking, variable modifiers should be called expression
# modifiers instead since they only modify the expression, not the variable.
# Well, except for the assignment modifiers, these do indeed change the value
# of the variable.
DEF= defined DEF= defined
@ -12,11 +23,13 @@ DEF= defined
.endif .endif
# A variable that appears on the left-hand side must be defined. # A variable that appears on the left-hand side must be defined.
# The following line thus generates a parse error.
.if ${UNDEF} == ${DEF} .if ${UNDEF} == ${DEF}
. error . error
.endif .endif
# A variable that appears on the right-hand side must be defined. # A variable that appears on the right-hand side must be defined.
# The following line thus generates a parse error.
.if ${DEF} == ${UNDEF} .if ${DEF} == ${UNDEF}
. error . error
.endif .endif
@ -25,10 +38,11 @@ DEF= defined
.if ${DEF} .if ${DEF}
.endif .endif
# An undefined variable generates a warning. # An undefined variable on its own generates a parse error.
.if ${UNDEF} .if ${UNDEF}
.endif .endif
# The :U modifier turns an undefined variable into an ordinary expression. # The :U modifier turns an undefined expression into a defined expression.
# Since the expression is defined now, it doesn't generate any parse error.
.if ${UNDEF:U} .if ${UNDEF:U}
.endif .endif

View file

@ -1,7 +1,7 @@
make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined
make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined
make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined
make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined
make: Fatal errors encountered -- cannot continue make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests make: stopped in unit-tests
exit status 1 exit status 1

View file

@ -1,4 +1,4 @@
# $NetBSD: cond-undef-lint.mk,v 1.2 2020/09/14 07:13:29 rillig Exp $ # $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
# #
# Tests for defined and undefined variables in .if conditions, in lint mode. # Tests for defined and undefined variables in .if conditions, in lint mode.
# #
@ -42,6 +42,10 @@ DEF= defined
.endif .endif
# The variable VAR.defined is not defined and thus generates an error message. # The variable VAR.defined is not defined and thus generates an error message.
#
# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least
# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect
# expression, as in the next example?
.if ${VAR.${DEF}} .if ${VAR.${DEF}}
. error . error
.else .else

View file

@ -1,5 +1,5 @@
make: "cond1.mk" line 75: warning: extra else make: "cond1.mk" line 80: warning: extra else
make: "cond1.mk" line 85: warning: extra else make: "cond1.mk" line 90: warning: extra else
2 is prime 2 is prime
A='other' B='unknown' C='clever' o='no,no' A='other' B='unknown' C='clever' o='no,no'
Passed: Passed:

View file

@ -1,4 +1,9 @@
# $NetBSD: cond1.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ # $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
# TODO: Convert these tests into tutorial form.
# TODO: Split these tests by topic.
# TODO: Use better variable names and expression values that actually express
# the intended behavior. uname(1) has nothing to do with conditions.
# hard code these! # hard code these!
TEST_UNAME_S= NetBSD TEST_UNAME_S= NetBSD

View file

@ -1,6 +1,9 @@
# $NetBSD: dep-double-colon.mk,v 1.4 2020/09/26 15:41:53 rillig Exp $ # $NetBSD: dep-double-colon.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
# #
# Tests for the :: operator in dependency declarations. # Tests for the '::' operator in dependency declarations, which allows
# several dependency groups for a single node, each having its own attributes
# and dependencies. In the code, the additional dependency groups are called
# cohorts.
all:: all::
@echo 'command 1a' @echo 'command 1a'

View file

@ -1,6 +1,11 @@
# $NetBSD: dep-exclam.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ # $NetBSD: dep-exclam.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
# #
# Tests for the ! operator in dependency declarations. # Tests for the ! operator in dependency declarations, which always re-creates
# the target, whether or not it is out of date.
#
# TODO: Is this related to OP_PHONY?
# TODO: Is this related to OP_EXEC?
# TODO: Is this related to OP_MAKE?
# TODO: Implementation # TODO: Implementation

View file

@ -1,11 +1,10 @@
# $NetBSD: depsrc-ignore.mk,v 1.4 2020/08/29 16:13:27 rillig Exp $ # $NetBSD: depsrc-ignore.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
# #
# Tests for the special source .IGNORE in dependency declarations, # Tests for the special source .IGNORE in dependency declarations,
# which ignores any command failures for that target. # which ignores any command failures for that target.
# #
# Even though ignore-errors fails, the all target is still made. # Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is
# Since the all target is not marked with .IGNORE, it stops at the # not marked with .IGNORE, it stops at the first failing command.
# first failing command.
# #
# XXX: The ordering of the messages in the output is confusing. # XXX: The ordering of the messages in the output is confusing.
# The "ignored" comes much too late to be related to the "false # The "ignored" comes much too late to be related to the "false
@ -24,8 +23,8 @@
# This is what actually happens, as of 2020-08-29. To verify it, set the # This is what actually happens, as of 2020-08-29. To verify it, set the
# following breakpoints in CompatRunCommand: # following breakpoints in CompatRunCommand:
# #
# * the "!silent" line, to see all commands. # * the "!silent" line, to see all commands
# * the "fflush" line, to see stdout being flushed. # * the "fflush" line, to see stdout being flushed
# * the "status = WEXITSTATUS" line # * the "status = WEXITSTATUS" line
# * the "(continuing)" line # * the "(continuing)" line
# * the "(ignored)" line # * the "(ignored)" line

View file

@ -1,9 +1,11 @@
# $NetBSD: depsrc-make.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # $NetBSD: depsrc-make.mk,v 1.4 2020/11/15 20:20:58 rillig Exp $
# #
# Tests for the special source .MAKE in dependency declarations, which # Tests for the special source .MAKE in dependency declarations, which
# executes the commands of the target even if the -n or -t command line # executes the commands of the target even if the -n or -t command line
# options are given. # options are given.
# TODO: Add a test for the -t command line option.
.MAKEFLAGS: -n .MAKEFLAGS: -n
all: this-is-made all: this-is-made

View file

@ -1,2 +1,20 @@
`all' is up to date. Make_ExpandUse: examine all
ExamineLater: need to examine "important"
Make_ExpandUse: examine important
ExamineLater: need to examine "optional"
ExamineLater: need to examine "optional-cohort"
Make_ExpandUse: examine optional
Make_ExpandUse: examine optional-cohort
Examining optional...non-existent...up-to-date.
Examining optional-cohort...non-existent...:: operator and no sources...out-of-date.
: A leaf node using '::' is considered out-of-date.
recheck(optional-cohort): update time from 0:00:00 Jan 01, 1970 to now
Examining important...non-existent...modified before source "optional-cohort"...out-of-date.
: important is made.
recheck(important): update time from 0:00:00 Jan 01, 1970 to now
Examining all...non-existent...modified before source "important"...out-of-date.
: all is made.
recheck(all): update time from 0:00:00 Jan 01, 1970 to now
Examining .END...non-existent...non-existent and no sources...out-of-date.
recheck(.END): update time from 0:00:00 Jan 01, 1970 to now
exit status 0 exit status 0

View file

@ -1,18 +1,21 @@
# $NetBSD: depsrc-optional.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ # $NetBSD: depsrc-optional.mk,v 1.5 2020/11/08 10:33:47 rillig Exp $
# #
# Tests for the special source .OPTIONAL in dependency declarations, # Tests for the special source .OPTIONAL in dependency declarations,
# which ignores the target if make cannot find out how to create it. # which ignores the target if make cannot find out how to create it.
# #
# TODO: Describe practical use cases for this feature. # TODO: Describe practical use cases for this feature.
# TODO: Explain why the commands for "important" are not executed.
# I had thought that only the "optional" commands were skipped.
all: important all: important
: ${.TARGET} is made. : ${.TARGET} is made.
important: optional important: optional optional-cohort
: ${.TARGET} is made. : ${.TARGET} is made.
optional: .OPTIONAL optional: .OPTIONAL
: This is not executed. : An optional leaf node is not executed.
# See IsOODateRegular.
optional-cohort:: .OPTIONAL
: A leaf node using '::' is considered out-of-date.
.MAKEFLAGS: -dm

Some files were not shown because too many files have changed in this diff Show more