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>
* 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.exp
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.mk
unit-tests/cmdline-undefined.exp
unit-tests/cmdline-undefined.mk
unit-tests/cmdline.exp
unit-tests/cmdline.mk
unit-tests/comment.exp
@ -115,10 +121,14 @@ unit-tests/cond-func.exp
unit-tests/cond-func.mk
unit-tests/cond-late.exp
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.mk
unit-tests/cond-op-not.exp
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.mk
unit-tests/cond-op-parentheses.exp
@ -287,6 +297,8 @@ unit-tests/directive-for.exp
unit-tests/directive-for.mk
unit-tests/directive-hyphen-include.exp
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.mk
unit-tests/directive-ifdef.exp
@ -315,8 +327,6 @@ unit-tests/directive-warning.exp
unit-tests/directive-warning.mk
unit-tests/directive.exp
unit-tests/directive.mk
unit-tests/directives.exp
unit-tests/directives.mk
unit-tests/dollar.exp
unit-tests/dollar.mk
unit-tests/doterror.exp
@ -341,6 +351,8 @@ unit-tests/forloop.exp
unit-tests/forloop.mk
unit-tests/forsubst.exp
unit-tests/forsubst.mk
unit-tests/gnode-submake.exp
unit-tests/gnode-submake.mk
unit-tests/hanoi-include.exp
unit-tests/hanoi-include.mk
unit-tests/impsrc.exp
@ -349,6 +361,8 @@ unit-tests/include-main.exp
unit-tests/include-main.mk
unit-tests/include-sub.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.mk
unit-tests/lint.exp
@ -365,6 +379,8 @@ unit-tests/modts.exp
unit-tests/modts.mk
unit-tests/modword.exp
unit-tests/modword.mk
unit-tests/objdir-writable.exp
unit-tests/objdir-writable.mk
unit-tests/opt-backwards.exp
unit-tests/opt-backwards.mk
unit-tests/opt-chdir.exp
@ -447,6 +463,8 @@ unit-tests/opt-raw.exp
unit-tests/opt-raw.mk
unit-tests/opt-silent.exp
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.mk
unit-tests/opt-tracefile.exp
@ -517,6 +535,8 @@ unit-tests/suff-main.exp
unit-tests/suff-main.mk
unit-tests/suff-rebuild.exp
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.mk
unit-tests/suff-transform-expand.exp
@ -737,14 +757,14 @@ unit-tests/varname.exp
unit-tests/varname.mk
unit-tests/varparse-dynamic.exp
unit-tests/varparse-dynamic.mk
unit-tests/varparse-errors.exp
unit-tests/varparse-errors.mk
unit-tests/varparse-mod.exp
unit-tests/varparse-mod.mk
unit-tests/varparse-undef-partial.exp
unit-tests/varparse-undef-partial.mk
unit-tests/varquote.exp
unit-tests/varquote.mk
unit-tests/varshell.exp
unit-tests/varshell.mk
util.c
var.c
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
@ -49,6 +49,12 @@ CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE
CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}}
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
# should be set by now
USE_FILEMON ?= no

View File

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

View File

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

553
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
@ -68,38 +68,38 @@
* SUCH DAMAGE.
*/
/*-
* arch.c --
* Functions to manipulate libraries, archives and their members.
/* Manipulate libraries, archives and their members.
*
* Once again, cacheing/hashing comes into play in the manipulation
* of archives. The first time an archive is referenced, all of its members'
* headers are read and hashed and the archive closed again. All hashed
* archives are kept on a list which is searched each time an archive member
* is referenced.
* The first time an archive is referenced, all of its members' headers are
* read and cached and the archive closed again. All cached archives are kept
* on a list which is searched each time an archive member is referenced.
*
* The interface to this module is:
*
* Arch_Init Initialize this module.
*
* Arch_End Clean up this module.
*
* Arch_ParseArchive
* Given an archive specification, return a list
* of GNode's, one for each member in the spec.
* FALSE is returned if the specification is
* invalid for some reason.
* Parse an archive specification such as
* "archive.a(member1 member2)".
*
* Arch_Touch Alter the modification time of the archive
* 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
* described by the given node. This is special
* because it also updates the modification time
* of the library's table of contents.
*
* Arch_MTime Find the modification time of a member of
* an archive *in the archive*. The time is also
* placed in the member's GNode. Returns the
* modification time.
* Arch_UpdateMTime
* Find the modification time of a member of
* an archive *in the archive* and place it in the
* 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
* already exist. Looks in the archive for the
* modification time. Returns the modification
@ -109,12 +109,7 @@
* library name in the GNode should be in
* -l<name> format.
*
* Arch_LibOODate Special function to decide if a library node
* is out-of-date.
*
* Arch_Init Initialize this module.
*
* Arch_End Clean up this module.
* Arch_LibOODate Decide if a library node is out-of-date.
*/
#ifdef HAVE_CONFIG_H
@ -151,16 +146,7 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 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
MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
@ -230,38 +216,36 @@ ArchFree(void *ap)
#endif
/*-
*-----------------------------------------------------------------------
* Arch_ParseArchive --
* Parse the archive specification in the given line and find/create
* the nodes for the specified archive members, placing their nodes
* on the given list.
/*
* Parse an archive specification such as "archive.a(member1 member2.${EXT})",
* adding nodes for the expanded members to nodeLst. Nodes are created as
* necessary.
*
* Input:
* linePtr Pointer to start of specification
* nodeLst Lst on which to place the nodes
* ctxt Context in which to expand variables
* pp The start of the specification.
* nodeLst The list on which to place the nodes.
* ctxt The context in which to expand variables.
*
* Results:
* TRUE if it was a valid specification. The linePtr is updated
* to point to the first non-space after the archive spec. The
* nodes for the members are placed on the given list.
*-----------------------------------------------------------------------
* Output:
* return TRUE if it was a valid specification.
* *pp Points to the first non-space after the archive spec.
* *nodeLst Nodes for the members have been added.
*/
Boolean
Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
{
char *cp; /* Pointer into line */
GNode *gn; /* New node */
char *libName; /* Library-part of specification */
char *libName_freeIt = NULL;
char *memName; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */
Boolean subLibName; /* TRUE if libName should have/had
* variable substitution performed on it */
Boolean expandLibName; /* Whether the parsed libName contains
* variable expressions that need to be
* expanded */
libName = *linePtr;
subLibName = FALSE;
libName = *pp;
expandLibName = FALSE;
for (cp = libName; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
@ -274,7 +258,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
const char *result;
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);
/* TODO: handle errors */
isError = result == var_Error;
@ -282,16 +267,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
if (isError)
return FALSE;
subLibName = TRUE;
expandLibName = TRUE;
cp += nested_p - cp;
} else
cp++;
}
*cp++ = '\0';
if (subLibName) {
(void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName);
if (expandLibName) {
(void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName);
/* TODO: handle errors */
libName_freeIt = libName;
}
@ -317,7 +303,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
Boolean isError;
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);
/* TODO: handle errors */
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
*/
if (*cp == '\0') {
printf("No closing parenthesis in archive specification\n");
Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification");
return FALSE;
}
@ -370,7 +356,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
char *sacrifice;
char *oldMemName = memName;
(void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES,
(void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&memName);
/* TODO: handle errors */
@ -381,7 +367,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*/
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.
* Just create an ARCHV node for the thing and let
@ -437,17 +424,12 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*cp = saveChar;
}
/*
* If substituted libName, free it now, since we need it no longer.
*/
if (subLibName) {
free(libName);
}
free(libName_freeIt);
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);
*linePtr = cp;
*pp = cp;
return TRUE;
}
@ -457,15 +439,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
* Input:
* archive Path to the archive
* 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:
* 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 *
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 */
size_t size; /* Size of archive member */
char magic[SARMAG];
@ -484,8 +468,8 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
member = lastSlash + 1;
for (ln = archives->first; ln != NULL; ln = ln->next) {
const Arch *archPtr = ln->datum;
if (strcmp(archPtr->name, archive) == 0)
const Arch *a = ln->datum;
if (strcmp(a->name, archive) == 0)
break;
}
@ -505,17 +489,17 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
if (len > AR_MAX_NAME_LEN) {
len = AR_MAX_NAME_LEN;
snprintf(copy, sizeof copy, "%s", member);
hdr = HashTable_FindValue(&ar->members, copy);
}
hdr = HashTable_FindValue(&ar->members, copy);
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
* 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,
* so just declare it static.
*/
@ -541,98 +525,92 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
* We use the ARMAG string to make sure this is an archive we
* can handle...
*/
if ((fread(magic, SARMAG, 1, arch) != 1) ||
(strncmp(magic, ARMAG, SARMAG) != 0)) {
fclose(arch);
if (fread(magic, SARMAG, 1, arch) != 1 ||
strncmp(magic, ARMAG, SARMAG) != 0) {
(void)fclose(arch);
return NULL;
}
ar = bmake_malloc(sizeof(Arch));
ar = bmake_malloc(sizeof *ar);
ar->name = bmake_strdup(archive);
ar->fnametab = NULL;
ar->fnamesize = 0;
HashTable_Init(&ar->members);
memName[AR_MAX_NAME_LEN] = '\0';
while (fread((char *)&arh, sizeof(struct ar_hdr), 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...
*/
while (fread(&arh, sizeof arh, 1, arch) == 1) {
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;
} else {
char *nameend;
/*
* We need to advance the stream's pointer to the start of the
* next header. Files are padded with newlines to an even-byte
* 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.
*/
arh.AR_SIZE[sizeof(arh.AR_SIZE) - 1] = '\0';
size = (size_t)strtol(arh.ar_size, NULL, 10);
/*
* We need to advance the stream's pointer to the start of the
* next header. Files are padded with newlines to an even-byte
* 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.
*/
arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
memcpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME));
nameend = memName + AR_MAX_NAME_LEN;
while (*nameend == ' ') {
nameend--;
}
nameend[1] = '\0';
memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME);
nameend = memName + AR_MAX_NAME_LEN;
while (nameend > memName && *nameend == ' ')
nameend--;
nameend[1] = '\0';
#ifdef SVR4ARCHIVES
/*
* svr4 names are slash terminated. Also svr4 extended AR format.
*/
if (memName[0] == '/') {
/*
* svr4 names are slash terminated. Also svr4 extended AR format.
* svr4 magic mode; handle it
*/
if (memName[0] == '/') {
/*
* svr4 magic mode; handle it
*/
switch (ArchSVR4Entry(ar, memName, size, arch)) {
case -1: /* Invalid data */
goto badarch;
case 0: /* List of files entry */
continue;
default: /* Got the entry */
break;
}
} else {
if (nameend[0] == '/')
nameend[0] = '\0';
switch (ArchSVR4Entry(ar, memName, size, arch)) {
case -1: /* Invalid data */
goto badarch;
case 0: /* List of files entry */
continue;
default: /* Got the entry */
break;
}
} else {
if (nameend[0] == '/')
nameend[0] = '\0';
}
#endif
#ifdef AR_EFMT1
/*
* BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file
*/
if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) {
/*
* BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file
*/
if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
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)
goto badarch;
if (fread(memName, (size_t)elen, 1, arch) != 1)
goto badarch;
memName[elen] = '\0';
if (fseek(arch, -elen, SEEK_CUR) != 0)
goto badarch;
if (DEBUG(ARCH) || DEBUG(MAKE)) {
debug_printf("ArchStat: Extended format entry for %s\n",
memName);
}
}
if ((unsigned int)elen > MAXPATHLEN)
goto badarch;
if (fread(memName, (size_t)elen, 1, arch) != 1)
goto badarch;
memName[elen] = '\0';
if (fseek(arch, -elen, SEEK_CUR) != 0)
goto badarch;
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchStatMember: Extended format entry for %s\n",
memName);
}
#endif
{
HashEntry *he;
he = HashTable_CreateEntry(&ar->members, memName, NULL);
HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr)));
memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr));
}
{
struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr);
memcpy(cached_hdr, &arh, sizeof arh);
HashTable_Set(&ar->members, memName, cached_hdr);
}
if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0)
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
* 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);
@ -674,15 +652,15 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
*-----------------------------------------------------------------------
*/
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 ARLONGNAMES2 "/ARFILENAMES"
size_t entry;
char *ptr, *eptr;
if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 ||
strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
if (ar->fnametab != NULL) {
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;
}
if (name[1] == ' ' || name[1] == '\0')
if (inout_name[1] == ' ' || inout_name[1] == '\0')
return 2;
entry = (size_t)strtol(&name[1], &eptr, 0);
if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name);
entry = (size_t)strtol(&inout_name[1], &eptr, 0);
if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) {
DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name);
return 2;
}
if (entry >= ar->fnamesize) {
DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
name, (unsigned long)ar->fnamesize);
inout_name, (unsigned long)ar->fnamesize);
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;
}
#endif
/*-
*-----------------------------------------------------------------------
* ArchFindMember --
* 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,
* the mode should be "r+", if not, it should be "r".
* The passed struct ar_hdr structure is filled in.
static Boolean
ArchiveMember_HasName(const struct ar_hdr *hdr,
const char *name, size_t namelen)
{
const size_t ar_name_len = sizeof hdr->AR_NAME;
const char *ar_name = hdr->AR_NAME;
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:
* archive Path to the archive
* member Name of member. If it is a path, only the last
* component is used.
* arhPtr Pointer to header structure to be filled in
* mode The mode for opening the stream
* out_arh Archive header to be filled in
* mode "r" for read-only access, "r+" for read-write access
*
* Results:
* An FILE *, opened for reading and writing, positioned at the
* start of the member's struct ar_hdr, or NULL if the member was
* nonexistent. The current struct ar_hdr for member.
*-----------------------------------------------------------------------
* Output:
* return The archive file, positioned at the start of the
* member's struct ar_hdr, or NULL if the member doesn't
* exist.
* *out_arh The current struct ar_hdr for member.
*
* See ArchStatMember for an almost identical copy of this code.
*/
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)
{
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
* can handle...
*/
if ((fread(magic, SARMAG, 1, arch) != 1) ||
(strncmp(magic, ARMAG, SARMAG) != 0)) {
if (fread(magic, SARMAG, 1, arch) != 1 ||
strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch);
return NULL;
}
@ -787,13 +788,13 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
member = lastSlash + 1;
len = tlen = strlen(member);
if (len > sizeof(arhPtr->AR_NAME)) {
tlen = sizeof(arhPtr->AR_NAME);
if (len > sizeof out_arh->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
* 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;
}
if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) {
/*
* If the member's name doesn't take up the entire 'name' field,
* we have to be careful of matching prefixes. Names are space-
* 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;
DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
archive,
(int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
(int)sizeof out_arh->ar_date, out_arh->ar_date);
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.
* In a more general situation, it might be better to leave
* 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);
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
* first <namelen> bytes of the file
*/
if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]))
if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
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];
if ((unsigned int)elen > MAXPATHLEN) {
@ -847,9 +844,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
return NULL;
}
ename[elen] = '\0';
if (DEBUG(ARCH) || DEBUG(MAKE)) {
debug_printf("ArchFind: Extended format entry for %s\n", ename);
}
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("ArchFindMember: Extended format entry for %s\n",
ename);
if (strncmp(ename, member, len) == 0) {
/* Found as extended name */
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
skip:
/*
* 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
@ -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
* header and round it up during the seek.
*/
arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0';
size = (int)strtol(arhPtr->ar_size, NULL, 10);
out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0';
size = (int)strtol(out_arh->AR_SIZE, NULL, 10);
if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
fclose(arch);
return NULL;
}
}
/*
* We've looked everywhere, but the member is not to be found. Close the
* archive and return NULL -- an error.
*/
fclose(arch);
return NULL;
}
/*-
*-----------------------------------------------------------------------
* Arch_Touch --
* Touch a member of an archive.
* The modification time of the entire archive is also changed.
* For a library, this could necessitate the re-ranlib'ing of the
* whole thing.
/* Touch a member of an archive, on disk.
* The GNode's modification time is left as-is.
*
* The st_mtime of the entire archive is also changed.
* For a library, it may be required to run ranlib after this.
*
* Input:
* gn Node of member to touch
*
* Results:
* The 'time' field of the member's header is updated.
*-----------------------------------------------------------------------
*/
void
Arch_Touch(GNode *gn)
{
FILE *arch; /* Stream open to archive, positioned properly */
struct ar_hdr arh; /* Current header describing member */
FILE *f;
struct ar_hdr arh;
arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn),
&arh, "r+");
f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+");
if (f == NULL)
return;
snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now);
if (arch != NULL) {
(void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
fclose(arch);
}
snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
(void)fwrite(&arh, sizeof arh, 1, f);
fclose(f); /* TODO: handle errors */
}
/* 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
* set to 'now'.
*
* Input:
* gn The node of the library to touch
*/
* set to 'now'. */
void
Arch_TouchLib(GNode *gn)
Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
{
#ifdef RANLIBMAG
FILE * arch; /* Stream open to archive */
struct ar_hdr arh; /* Header describing table of contents */
struct utimbuf times; /* Times for utime() call */
FILE *f;
struct ar_hdr arh; /* Header describing table of contents */
struct utimbuf times;
arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now);
f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
if (f == NULL)
return;
if (arch != NULL) {
(void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
fclose(arch);
snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
(void)fwrite(&arh, sizeof arh, 1, f);
fclose(f); /* TODO: handle errors */
times.actime = times.modtime = now;
utime(gn->path, &times);
}
#else
(void)gn;
times.actime = times.modtime = now;
utime(gn->path, &times); /* TODO: handle errors */
#endif
}
/* Return the modification time of a member of an archive. The mtime field
* of the given node is filled in with the value returned by the function.
*
* Input:
* gn Node describing archive member
*/
time_t
Arch_MTime(GNode *gn)
/* Update the mtime of the GNode with the mtime from the archive member on
* disk (or in the cache). */
void
Arch_UpdateMTime(GNode *gn)
{
struct ar_hdr *arhPtr; /* Header of desired member */
time_t modTime; /* Modification time as an integer */
struct ar_hdr *arh;
arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
if (arhPtr != NULL) {
modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10);
} else {
modTime = 0;
}
gn->mtime = modTime;
return modTime;
arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
if (arh != NULL)
gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10);
else
gn->mtime = 0;
}
/* 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)
/* Given a non-existent archive member's node, update gn->mtime from its
* archived form, if it exists. */
void
Arch_UpdateMemberMTime(GNode *gn)
{
GNodeListNode *ln;
@ -1001,7 +973,8 @@ Arch_MemMTime(GNode *gn)
if ((pgn->flags & REMAKE) &&
strncmp(nameStart, gn->name, nameLen) == 0) {
gn->mtime = Arch_MTime(pgn);
Arch_UpdateMTime(pgn);
gn->mtime = pgn->mtime;
}
} else if (pgn->flags & REMAKE) {
/*
@ -1012,8 +985,6 @@ Arch_MemMTime(GNode *gn)
break;
}
}
return gn->mtime;
}
/* 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
* Make_OODate to make its life easier.
* The library will be hashed if it hasn't been already.
* GNode_IsOODate to make its life easier.
* The library is cached if it hasn't been already.
*
* There are several ways for a library to be out-of-date that are
* not available to ordinary files. In addition, there are ways
* 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
* 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,
@ -1066,16 +1039,10 @@ Arch_FindLib(GNode *gn, SearchPath *path)
*
* 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).
* 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
* since this is used by 'ar' rules that affect the data contents of the
* 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
Arch_LibOODate(GNode *gn)
@ -1093,25 +1060,23 @@ Arch_LibOODate(GNode *gn)
oodate = TRUE;
} else {
#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 */
arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE);
arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
if (arhPtr != NULL) {
modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10);
if (arh != NULL) {
modTimeTOC = (int)strtol(arh->ar_date, NULL, 10);
if (DEBUG(ARCH) || DEBUG(MAKE)) {
debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
}
oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC);
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("%s modified %s...",
RANLIBMAG, Targ_FmtTime(modTimeTOC));
oodate = gn->youngestChild == NULL ||
gn->youngestChild->mtime > modTimeTOC;
} else {
/*
* A library w/o a table of contents is out-of-date
*/
if (DEBUG(ARCH) || DEBUG(MAKE)) {
debug_printf("No t.o.c....");
}
/* A library without a table of contents is out-of-date. */
if (DEBUG(ARCH) || DEBUG(MAKE))
debug_printf("no toc...");
oodate = TRUE;
}
#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
.\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
.Dd November 1, 2020
.Dd November 14, 2020
.Dt BMAKE 1
.Os
.Sh NAME
@ -37,7 +37,7 @@
.Nd maintain program dependencies
.Sh SYNOPSIS
.Nm
.Op Fl BeikNnqrstWwX
.Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory
.Op Fl D Ar variable
.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.
.It Fl r
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
Do not echo any commands as they are executed.
Equivalent to specifying
@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and
.Ql Ev PWD
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
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
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]
[-m directory] [-T file] [-V variable] [-v variable]
[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.
-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
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'
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
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.
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
# machine.sh
#
# --with-force_machine_arch="machine_arch"
# force "machine_arch" to override that determined by
# machine.sh
#
# --with-default-sys-path="syspath"
# set an explicit default "syspath" which is where bmake
# will look for sys.mk and friends.
@ -115,7 +119,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# 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
#

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.
@ -75,7 +75,7 @@
#include "make.h"
/* "@(#)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. */
void
@ -155,19 +155,22 @@ Buf_Empty(Buffer *buf)
buf->data[0] = '\0';
}
/* Initialize a buffer.
* If the given initial capacity is 0, a reasonable default is used. */
/* Initialize a buffer. */
void
Buf_Init(Buffer *buf, size_t cap)
Buf_InitSize(Buffer *buf, size_t cap)
{
if (cap <= 0)
cap = 256;
buf->cap = cap;
buf->len = 0;
buf->data = bmake_malloc(cap);
buf->data[0] = '\0';
}
void
Buf_Init(Buffer *buf)
{
Buf_InitSize(buf, 256);
}
/* Reset the buffer.
* If freeData is TRUE, the data from the buffer is freed as well.
* 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.
@ -94,7 +94,7 @@ typedef struct Buffer {
void Buf_Expand_1(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)
{
size_t old_len = buf->len++;
@ -106,13 +106,13 @@ Buf_AddByte(Buffer *buf, char byte)
end[1] = '\0';
}
static inline MAKE_ATTR_UNUSED size_t
MAKE_INLINE size_t
Buf_Len(const Buffer *buf)
{
return buf->len;
}
static inline MAKE_ATTR_UNUSED Boolean
MAKE_INLINE Boolean
Buf_EndsWith(const Buffer *buf, char 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);
char *Buf_GetAll(Buffer *, size_t *);
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_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.
@ -99,15 +99,15 @@
#include "pathnames.h"
/* "@(#)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 pid_t compatChild;
static int compatSigno;
/*
* CompatDeleteTarget -- delete a failed, interrupted, or otherwise
* duffed target if not inhibited by .PRECIOUS.
* CompatDeleteTarget -- delete the file of a failed, interrupted, or
* otherwise duffed target if not inhibited by .PRECIOUS.
*/
static void
CompatDeleteTarget(GNode *gn)
@ -132,8 +132,6 @@ CompatDeleteTarget(GNode *gn)
static void
CompatInterrupt(int signo)
{
GNode *gn;
CompatDeleteTarget(curTarg);
if (curTarg != NULL && !Targ_Precious(curTarg)) {
@ -141,7 +139,7 @@ CompatInterrupt(int signo)
* Run .INTERRUPT only if hit with interrupt signal
*/
if (signo == SIGINT) {
gn = Targ_FindNode(".INTERRUPT");
GNode *gn = Targ_FindNode(".INTERRUPT");
if (gn != NULL) {
Compat_Make(gn, gn);
}
@ -206,7 +204,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
(void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
/* TODO: handle errors */
if (*cmdStart == '\0') {
if (cmdStart[0] == '\0') {
free(cmdStart);
return 0;
}
@ -225,20 +223,17 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
return 0;
}
while (*cmd == '@' || *cmd == '-' || *cmd == '+') {
switch (*cmd) {
case '@':
for (;;) {
if (*cmd == '@')
silent = !DEBUG(LOUD);
break;
case '-':
else if (*cmd == '-')
errCheck = FALSE;
break;
case '+':
else if (*cmd == '+') {
doIt = TRUE;
if (!shellName) /* we came here from jobs */
if (!shellName) /* we came here from jobs */
Shell_Init();
} else
break;
}
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 (!*cmd)
if (cmd[0] == '\0')
return 0;
#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
* we go...
*/
if (!doIt && !GNode_ShouldExecute(gn)) {
if (!doIt && !GNode_ShouldExecute(gn))
return 0;
}
DEBUG1(JOB, "Execute: '%s'\n", cmd);
if (useShell) {
@ -297,20 +292,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
* because the command contains a "meta" character.
*/
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;
/*
* The following work for any of the builtin shell specs.
*/
if (errCheck && shellErrFlag) {
if (errCheck && shellErrFlag)
shargv[shargc++] = shellErrFlag;
}
if (DEBUG(SHELL))
shargv[shargc++] = "-xc";
else
shargv[shargc++] = "-c";
shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
shargv[shargc++] = cmd;
shargv[shargc] = NULL;
av = shargv;
@ -389,17 +377,19 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
#endif
if (status != 0) {
if (DEBUG(ERROR)) {
const char *cp;
const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
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(" ");
while (ch_isspace(*cp))
cp++;
cpp_skip_whitespace(&p);
} else {
debug_printf("%c", *cp);
cp++;
debug_printf("%c", *p);
p++;
}
}
debug_printf("\n");
@ -480,16 +470,17 @@ MakeNodes(GNodeList *gnodes, GNode *pgn)
void
Compat_Make(GNode *gn, GNode *pgn)
{
if (!shellName) /* we came here from jobs */
if (shellName == NULL) /* we came here from jobs */
Shell_Init();
if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
/*
* First mark ourselves to be made, then apply whatever transformations
* 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
* but the -k flag was given, our 'make' field will be set FALSE again.
* This is our signal to not attempt to do anything but abort our
* parent as well.
* but the -k flag was given, our 'make' field will be set to FALSE
* again. This is our signal to not attempt to do anything but abort
* our parent as well.
*/
gn->flags |= REMAKE;
gn->made = BEINGMADE;
@ -509,10 +500,10 @@ Compat_Make(GNode *gn, GNode *pgn)
* All the children were made ok. Now youngestChild->mtime contains the
* modification time of the newest child, we need to find out if we
* 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);
if (!Make_OODate(gn)) {
if (!GNode_IsOODate(gn)) {
gn->made = UPTODATE;
DEBUG0(MAKE, "up-to-date.\n");
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
* to tell him/her "yes".
*/
if (opts.queryFlag) {
if (opts.queryFlag)
exit(1);
}
/*
* 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 the node was made successfully, mark it so, update
* its modification time and timestamp all its parents. Note
* that for .ZEROTIME targets, the timestamping isn't done.
* its modification time and timestamp all its parents.
* This is to keep its state from affecting that of its parent.
*/
gn->made = MADE;
pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
if (Make_Recheck(gn) == 0)
pgn->flags |= FORCE;
if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE;
Make_TimeStamp(pgn, gn);
GNode_UpdateYoungestChild(pgn, gn);
}
} else if (opts.keepgoing) {
pgn->flags &= ~(unsigned)REMAKE;
@ -604,15 +594,14 @@ Compat_Make(GNode *gn, GNode *pgn)
pgn->flags &= ~(unsigned)REMAKE;
break;
case MADE:
if ((gn->type & OP_EXEC) == 0) {
if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE;
Make_TimeStamp(pgn, gn);
GNode_UpdateYoungestChild(pgn, gn);
}
break;
case UPTODATE:
if ((gn->type & OP_EXEC) == 0) {
Make_TimeStamp(pgn, gn);
}
if (!(gn->type & OP_EXEC))
GNode_UpdateYoungestChild(pgn, gn);
break;
default:
break;

563
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.
@ -72,7 +72,8 @@
/* Handling of conditionals in a makefile.
*
* Interface:
* Cond_EvalLine Evaluate the conditional.
* Cond_EvalLine Evaluate the conditional directive, such as
* '.if <cond>', '.elifnmake <cond>', '.else', '.endif'.
*
* Cond_EvalCondition
* Evaluate the conditional, which is either the argument
@ -93,7 +94,7 @@
#include "dir.h"
/* "@(#)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:
@ -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)
* 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.
*/
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]);
}
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. */
static void
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
* 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
ParseFuncArg(const char **pp, Boolean doEval, const char *func,
char **out_arg) {
@ -213,26 +220,18 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
p++; /* Skip opening '(' - verified by caller */
if (*p == '\0') {
/*
* No arguments whatsoever. Because 'make' and 'defined' aren't really
* "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;
*out_arg = NULL; /* Missing closing parenthesis: */
return 0; /* .if defined( */
}
while (*p == ' ' || *p == '\t') {
p++;
}
cpp_skip_hspace(&p);
Buf_Init(&argBuf, 16);
Buf_InitSize(&argBuf, 16);
paren_depth = 0;
for (;;) {
char ch = *p;
if (ch == 0 || ch == ' ' || ch == '\t')
if (ch == '\0' || ch == ' ' || ch == '\t')
break;
if ((ch == '&' || ch == '|') && paren_depth == 0)
break;
@ -244,7 +243,8 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
* though perhaps we should...
*/
void *nestedVal_freeIt;
VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0);
VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR
: VARE_NONE;
const char *nestedVal;
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal,
&nestedVal_freeIt);
@ -264,9 +264,7 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
*out_arg = Buf_GetAll(&argBuf, &argLen);
Buf_Destroy(&argBuf, FALSE);
while (*p == ' ' || *p == '\t') {
p++;
}
cpp_skip_hspace(&p);
if (func != NULL && *p++ != ')') {
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;
path = Dir_FindFile(arg, dirSearchPath);
DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : "");
if (path != NULL) {
result = TRUE;
free(path);
} else {
result = FALSE;
}
DEBUG2(COND, "exists(%s) result is \"%s\"\n",
arg, path != NULL ? path : "");
result = path != NULL;
free(path);
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);
}
/*-
/*
* Convert the given number into a double.
* We try a base 10 or 16 integer conversion first, if that fails
* then we try a floating point conversion instead.
*
* Results:
* Sets 'value' to double value of string.
* Returns TRUE if the conversion succeeded.
* Sets 'out_value' to the converted number.
*/
static Boolean
TryParseNumber(const char *str, double *value)
TryParseNumber(const char *str, double *out_value)
{
char *eptr, ech;
unsigned long l_val;
double d_val;
char *end;
unsigned long ul_val;
double dbl_val;
errno = 0;
if (!*str) {
*value = 0.0;
if (str[0] == '\0') { /* XXX: why is an empty string a number? */
*out_value = 0.0;
return TRUE;
}
l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
ech = *eptr;
if (ech == '\0' && errno != ERANGE) {
d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
} else {
if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E')
return FALSE;
d_val = strtod(str, &eptr);
if (*eptr)
return FALSE;
ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
if (*end == '\0' && errno != ERANGE) {
*out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
return TRUE;
}
*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;
}
@ -385,31 +381,31 @@ is_separator(char ch)
*
* Results:
* Returns the string, absent any quotes, or NULL on error.
* Sets quoted if the string was quoted.
* Sets freeIt if needed.
* Sets out_quoted if the string was quoted.
* Sets out_freeIt.
*/
/* coverity:[+alloc : arg-*4] */
static const char *
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
Boolean *quoted, void **freeIt)
Boolean *out_quoted, void **out_freeIt)
{
Buffer buf;
const char *str;
Boolean atStart;
const char *nested_p;
Boolean qt;
Boolean quoted;
const char *start;
VarEvalFlags eflags;
VarParseResult parseResult;
Buf_Init(&buf, 0);
Buf_Init(&buf);
str = NULL;
*freeIt = NULL;
*quoted = qt = par->p[0] == '"' ? 1 : 0;
*out_freeIt = NULL;
*out_quoted = quoted = par->p[0] == '"';
start = par->p;
if (qt)
if (quoted)
par->p++;
while (par->p[0] && str == NULL) {
while (par->p[0] != '\0' && str == NULL) {
switch (par->p[0]) {
case '\\':
par->p++;
@ -419,40 +415,44 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
}
continue;
case '"':
if (qt) {
par->p++; /* we don't want the quotes */
if (quoted) {
par->p++; /* skip the closing quote */
goto got_str;
}
Buf_AddByte(&buf, par->p[0]); /* likely? */
par->p++;
continue;
case ')':
case ')': /* see is_separator */
case '!':
case '=':
case '>':
case '<':
case ' ':
case '\t':
if (!qt)
if (!quoted)
goto got_str;
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
case '$':
/* if we are in quotes, an undefined variable is ok */
eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) |
(doEval ? VARE_WANTRES : 0);
eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
doEval ? VARE_WANTRES :
VARE_NONE;
nested_p = par->p;
atStart = nested_p == start;
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str,
freeIt);
out_freeIt);
/* TODO: handle errors */
if (str == var_Error) {
if (parseResult & VPR_ANY_MSG)
par->printedError = TRUE;
if (*freeIt) {
free(*freeIt);
*freeIt = NULL;
if (*out_freeIt != NULL) {
/* XXX: Can there be any situation in which a returned
* var_Error requires freeIt? */
free(*out_freeIt);
*out_freeIt = NULL;
}
/*
* Even if !doEval, we still report syntax errors, which
@ -473,19 +473,15 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
goto cleanup;
Buf_AddStr(&buf, str);
if (*freeIt) {
free(*freeIt);
*freeIt = NULL;
if (*out_freeIt) {
free(*out_freeIt);
*out_freeIt = NULL;
}
str = NULL; /* not finished yet */
continue;
default:
if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) {
if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) {
/* lhs must be quoted, a variable reference or number */
if (*freeIt) {
free(*freeIt);
*freeIt = NULL;
}
str = NULL;
goto cleanup;
}
@ -495,20 +491,22 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
}
}
got_str:
*freeIt = Buf_GetAll(&buf, NULL);
str = *freeIt;
*out_freeIt = Buf_GetAll(&buf, NULL);
str = *out_freeIt;
cleanup:
Buf_Destroy(&buf, FALSE);
return str;
}
/* The different forms of .if directives. */
static const struct If {
struct If {
const char *form; /* Form of if */
size_t formlen; /* Length of form */
Boolean doNot; /* TRUE if default function should be negated */
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 },
{ "ndef", 4, TRUE, FuncDefined },
{ "make", 4, FALSE, FuncMake },
@ -516,28 +514,39 @@ static const struct If {
{ "", 0, FALSE, FuncDefined },
{ 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
* ".if 0". */
static Token
EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted)
static Boolean
EvalNotEmpty(CondParser *par, const char *value, Boolean quoted)
{
double left;
double num;
/* For .ifxxx "..." check for non-empty string. */
if (lhsQuoted)
return lhs[0] != '\0';
/* For .ifxxx "...", check for non-empty string. */
if (quoted)
return value[0] != '\0';
/* For .ifxxx <number> compare against zero */
if (TryParseNumber(lhs, &left))
return left != 0.0;
/* For .ifxxx <number>, compare against zero */
if (TryParseNumber(value, &num))
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')
return lhs[0] != 0;
return value[0] != '\0';
/* Otherwise action default test ... */
return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot;
/* For the other variants of .ifxxx ${...}, use its default function. */
return If_Eval(par->if_info, value, strlen(value));
}
/* 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. */
return TOK_ERROR;
}
return lhs != rhs;
return ToToken(lhs != rhs);
case '=':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING, "Unknown operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
return TOK_ERROR;
}
return lhs == rhs;
return ToToken(lhs == rhs);
case '<':
return op[1] == '=' ? lhs <= rhs : lhs < rhs;
return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs);
case '>':
return op[1] == '=' ? lhs >= rhs : lhs > rhs;
return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs);
}
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);
return (*op == '=') == (strcmp(lhs, rhs) == 0);
return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0));
}
/* Evaluate a comparison, such as "${VAR} == 12345". */
@ -609,43 +618,34 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
{
Token t = TOK_ERROR;
const char *lhs, *op, *rhs;
void *lhsFree, *rhsFree;
void *lhs_freeIt, *rhs_freeIt;
Boolean lhsQuoted, rhsQuoted;
rhs = NULL;
lhsFree = rhsFree = NULL;
lhsQuoted = rhsQuoted = FALSE;
/*
* Parse the variable spec and skip over it, saving its
* value in lhs.
*/
lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree);
if (!lhs)
goto done;
lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt);
if (lhs == NULL)
goto done_lhs;
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;
switch (par->p[0]) {
case '!':
case '=':
case '<':
case '>':
if (par->p[1] == '=') {
if (par->p[1] == '=')
par->p += 2;
} else {
else
par->p++;
}
break;
default:
t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE;
goto done;
/* Unknown operator, compare against an empty string or 0. */
t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted));
goto done_lhs;
}
CondParser_SkipWhitespace(par);
@ -653,42 +653,45 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
if (par->p[0] == '\0') {
Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator");
/* 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)
goto done;
goto done_rhs;
if (!doEval) {
t = TOK_FALSE;
goto done;
goto done_rhs;
}
t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted);
done:
free(lhsFree);
free(rhsFree);
done_rhs:
free(rhs_freeIt);
done_lhs:
free(lhs_freeIt);
return t;
}
/* The argument to empty() is a variable name, optionally followed by
* variable modifiers. */
static size_t
ParseEmptyArg(const char **linePtr, Boolean doEval,
const char *func MAKE_ATTR_UNUSED, char **argPtr)
ParseEmptyArg(const char **pp, Boolean doEval,
const char *func MAKE_ATTR_UNUSED, char **out_arg)
{
void *val_freeIt;
const char *val;
size_t magic_res;
/* 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 '('. */
(void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0,
(*pp)--; /* Make (*pp)[1] point to the '('. */
(void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
&val, &val_freeIt);
/* TODO: handle errors */
/* If successful, *linePtr points beyond the closing ')' now. */
/* If successful, *pp points beyond the closing ')' now. */
if (val == var_Error) {
free(val_freeIt);
@ -714,54 +717,71 @@ FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED)
return arglen == 1;
}
static Token
CondParser_Func(CondParser *par, Boolean doEval)
static Boolean
CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
{
static const struct fn_def {
const char *fn_name;
size_t fn_name_len;
size_t (*fn_parse)(const char **, Boolean, const char *, char **);
Boolean (*fn_eval)(size_t, const char *);
} fn_defs[] = {
} fns[] = {
{ "defined", 7, ParseFuncArg, FuncDefined },
{ "make", 4, ParseFuncArg, FuncMake },
{ "exists", 6, ParseFuncArg, FuncExists },
{ "empty", 5, ParseEmptyArg, FuncEmpty },
{ "target", 6, ParseFuncArg, FuncTarget },
{ "commands", 8, ParseFuncArg, FuncCommands },
{ NULL, 0, NULL, NULL },
{ "commands", 8, ParseFuncArg, FuncCommands }
};
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;
char *arg = NULL;
size_t arglen;
const char *cp = par->p;
const char *cp1;
for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
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;
if (CondParser_Func(par, doEval, &t))
return t;
}
/* Push anything numeric through the compare expression */
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);
/*
@ -785,7 +805,7 @@ CondParser_Func(CondParser *par, Boolean doEval)
* after .if must have been taken literally, so the argument cannot
* 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);
return t;
}
@ -802,9 +822,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return t;
}
while (par->p[0] == ' ' || par->p[0] == '\t') {
par->p++;
}
cpp_skip_hspace(&par->p);
switch (par->p[0]) {
@ -818,15 +836,23 @@ CondParser_Token(CondParser *par, Boolean doEval)
case '|':
par->p++;
if (par->p[0] == '|') {
if (par->p[0] == '|')
par->p++;
else if (opts.lint) {
Parse_Error(PARSE_FATAL, "Unknown operator '|'");
par->printedError = TRUE;
return TOK_ERROR;
}
return TOK_OR;
case '&':
par->p++;
if (par->p[0] == '&') {
if (par->p[0] == '&')
par->p++;
else if (opts.lint) {
Parse_Error(PARSE_FATAL, "Unknown operator '&'");
par->printedError = TRUE;
return TOK_ERROR;
}
return TOK_AND;
@ -834,8 +860,9 @@ CondParser_Token(CondParser *par, Boolean doEval)
par->p++;
return TOK_NOT;
case '#':
case '\n':
case '#': /* XXX: see unit-tests/cond-token-plain.mk */
case '\n': /* XXX: why should this end the condition? */
/* Probably obsolete now, from 1993-03-21. */
case '\0':
return TOK_EOF;
@ -844,7 +871,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return CondParser_Comparison(par, doEval);
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,
Boolean eprint, Boolean strictLHS)
{
static const struct If *dflt_info;
CondParser par;
int rval;
CondEvalResult rval;
lhsStrict = strictLHS;
while (*cond == ' ' || *cond == '\t')
cond++;
cpp_skip_hspace(&cond);
if (info == NULL && (info = dflt_info) == NULL) {
/* 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.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX;
par.p = cond;
par.curr = TOK_NONE;
par.printedError = FALSE;
@ -1034,123 +1050,154 @@ CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
return rval;
}
/* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}. */
CondEvalResult
Cond_EvalCondition(const char *cond, Boolean *out_value)
{
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE);
}
/* Evaluate the conditional in the passed line. The line looks like this:
* .<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.
/* Evaluate the conditional directive in the line, which is one of:
*
* Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
* to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF),
* otherwise .else could be treated as '.elif 1'.
* .if <cond>
* .ifmake <cond>
* .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:
* COND_PARSE to continue parsing the lines after the conditional
* (when .if or .else returns TRUE)
* COND_PARSE to continue parsing the lines that follow the
* conditional (when <cond> evaluates to TRUE)
* 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)
* COND_INVALID if the conditional was not valid, either because of
* a syntax error or because some variable was undefined
* or because the condition could not be evaluated
*/
CondEvalResult
Cond_EvalLine(const char *line)
Cond_EvalLine(const char *const line)
{
enum { MAXIF = 128 }; /* maximum depth of .if'ing */
enum { MAXIF_BUMP = 32 }; /* how much to grow by */
enum if_states {
IF_ACTIVE, /* .if or .elif part active */
ELSE_ACTIVE, /* .else part active */
SEARCH_FOR_ELIF, /* searching for .elif/else to execute */
SKIP_TO_ELSE, /* has been true, but not seen '.else' */
SKIP_TO_ENDIF /* nothing else to execute */
};
static enum if_states *cond_state = NULL;
static unsigned int max_if_depth = MAXIF;
typedef enum IfState {
/* None of the previous <cond> evaluated to TRUE. */
IFS_INITIAL = 0,
/* The previous <cond> evaluated to TRUE.
* The lines following this condition are interpreted. */
IFS_ACTIVE = 1 << 0,
/* The previous directive was an '.else'. */
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;
Boolean isElif;
Boolean value;
enum if_states state;
IfState state;
const char *p = line;
if (!cond_state) {
cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state));
cond_state[0] = IF_ACTIVE;
if (cond_states == NULL) {
cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states);
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. */
if (line[0] == 'e') {
if (line[1] != 'l') {
if (!is_token(line + 1, "ndif", 4))
p++; /* skip the leading '.' */
cpp_skip_hspace(&p);
/* 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;
/* End of conditional section */
}
/* It is an '.endif'. */
/* TODO: check for extraneous <cond> */
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif");
return COND_PARSE;
}
/* Return state for previous conditional */
cond_depth--;
return cond_state[cond_depth] <= ELSE_ACTIVE
return cond_states[cond_depth] & IFS_ACTIVE
? COND_PARSE : COND_SKIP;
}
/* Quite likely this is 'else' or 'elif' */
line += 2;
if (is_token(line, "se", 2)) {
/* It is else... */
p += 2;
if (is_token(p, "se", 2)) { /* It is an 'else'. */
if (opts.lint && p[2] != '\0')
Parse_Error(PARSE_FATAL,
"The .else directive does not take arguments.");
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else");
return COND_PARSE;
}
state = cond_state[cond_depth];
switch (state) {
case SEARCH_FOR_ELIF:
state = ELSE_ACTIVE;
break;
case ELSE_ACTIVE:
case SKIP_TO_ENDIF:
Parse_Error(PARSE_WARNING, "extra else");
/* FALLTHROUGH */
default:
case IF_ACTIVE:
case SKIP_TO_ELSE:
state = SKIP_TO_ENDIF;
break;
state = cond_states[cond_depth];
if (state == IFS_INITIAL) {
state = IFS_ACTIVE | IFS_SEEN_ELSE;
} else {
if (state & IFS_SEEN_ELSE)
Parse_Error(PARSE_WARNING, "extra else");
state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
}
cond_state[cond_depth] = state;
return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
cond_states[cond_depth] = state;
return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
}
/* Assume for now it is an elif */
isElif = TRUE;
} else
isElif = FALSE;
if (line[0] != 'i' || line[1] != 'f')
/* Not an ifxxx or elifxxx line */
return COND_INVALID;
if (p[0] != 'i' || p[1] != 'f') {
/* Unknown directive. It might still be a transformation rule like
* '.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
* function is, etc. -- by looking in the table of valid "ifs"
*/
line += 2;
p += 2;
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;
if (is_token(ifp->form, line, ifp->formlen)) {
line += ifp->formlen;
}
if (is_token(p, ifp->form, ifp->formlen)) {
p += ifp->formlen;
break;
}
}
@ -1162,51 +1209,51 @@ Cond_EvalLine(const char *line)
Parse_Error(PARSE_FATAL, "if-less elif");
return COND_PARSE;
}
state = cond_state[cond_depth];
if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
state = cond_states[cond_depth];
if (state & IFS_SEEN_ELSE) {
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;
}
if (state != SEARCH_FOR_ELIF) {
/* Either just finished the 'true' block, or already SKIP_TO_ELSE */
cond_state[cond_depth] = SKIP_TO_ELSE;
if (state != IFS_INITIAL) {
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
}
} else {
/* Normal .if */
if (cond_depth + 1 >= max_if_depth) {
if (cond_depth + 1 >= cond_states_cap) {
/*
* This is rare, but not impossible.
* In meta mode, dirdeps.mk (only runs at level 0)
* can need more than the default.
*/
max_if_depth += MAXIF_BUMP;
cond_state = bmake_realloc(cond_state,
max_if_depth * sizeof(*cond_state));
cond_states_cap += 32;
cond_states = bmake_realloc(cond_states,
cond_states_cap * sizeof *cond_states);
}
state = cond_state[cond_depth];
state = cond_states[cond_depth];
cond_depth++;
if (state > ELSE_ACTIVE) {
if (!(state & IFS_ACTIVE)) {
/* 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;
}
}
/* 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. */
/* 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;
}
if (!value) {
cond_state[cond_depth] = SEARCH_FOR_ELIF;
cond_states[cond_depth] = IFS_INITIAL;
return COND_SKIP;
}
cond_state[cond_depth] = IF_ACTIVE;
cond_states[cond_depth] = IFS_ACTIVE;
return COND_PARSE;
}

30
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# 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>.
#
@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='bmake'
PACKAGE_TARNAME='bmake'
PACKAGE_VERSION='20201018'
PACKAGE_STRING='bmake 20201018'
PACKAGE_VERSION='20201112'
PACKAGE_STRING='bmake 20201112'
PACKAGE_BUGREPORT='sjg@NetBSD.org'
PACKAGE_URL=''
@ -631,6 +631,7 @@ GCC
INSTALL
default_sys_path
mksrc
force_machine_arch
machine_arch
force_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.
# This message is too long to be a string in the A/UX 3.1 sh.
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]...
@ -1315,7 +1316,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of bmake 20201018:";;
short | recursive ) echo "Configuration of bmake 20201112:";;
esac
cat <<\_ACEOF
@ -1331,9 +1332,9 @@ Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-defshell=SHELL use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions
--without-makefile disable use of generated makefile
--without-meta disable use of meta-mode
--with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev
--without-makefile disable use of generated makefile
--without-meta disable use of meta-mode
--with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev
--with-machine=MACHINE explicitly set MACHINE
--with-force-machine=MACHINE set FORCE_MACHINE
--with-force-machine-arch=MACHINE set FORCE_MACHINE_ARCH
@ -1421,7 +1422,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
bmake configure 20201018
bmake configure 20201112
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@ -2001,7 +2002,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
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
$ $0 $@
@ -6045,7 +6046,7 @@ if test "${with_force_machine_arch+set}" = set; then :
withval=$with_force_machine_arch; case "${withval}" in
yes) force_machine_arch=FORCE_;;
no) ;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac
fi
@ -6059,7 +6060,7 @@ no) ;;
esac
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
# Check whether --with-default-sys-path was given.
@ -6151,6 +6152,7 @@ fi
bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh"
if test $use_makefile = yes; then
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
# values after options handling.
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
CONFIG_FILES = $CONFIG_FILES
@ -6726,7 +6728,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
bmake config.status 20201018
bmake config.status 20201112
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"

View File

@ -1,11 +1,11 @@
dnl
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 Process this file with autoconf to produce a configure script
dnl
AC_PREREQ(2.50)
AC_INIT([bmake], [20201018], [sjg@NetBSD.org])
AC_INIT([bmake], [20201112], [sjg@NetBSD.org])
AC_CONFIG_HEADERS(config.h)
dnl make srcdir absolute
@ -38,7 +38,7 @@ CYGWIN*|MINGW*) use_makefile=no;;
*) use_makefile=yes;;
esac
AC_ARG_WITH(makefile,
[ --without-makefile disable use of generated makefile],
[ --without-makefile disable use of generated makefile],
[case "${withval}" in
yes|no) use_makefile=${withval};;
*) AC_MSG_ERROR(bad value ${withval} given for makefile) ;;
@ -46,14 +46,14 @@ esac])
dnl
use_meta=yes
AC_ARG_WITH(meta,
[ --without-meta disable use of meta-mode],
[ --without-meta disable use of meta-mode],
[case "${withval}" in
yes|no) use_meta=${withval};;
*) AC_MSG_ERROR(bad value ${withval} given for meta) ;;
esac])
dnl
AC_ARG_WITH(filemon,
[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev],
[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev],
[ case "/${withval}" in
/no) use_filemon=no;;
/*trace) filemon_h=no use_filemon="${withval}";;
@ -308,7 +308,7 @@ AC_ARG_WITH(force_machine_arch,
[case "${withval}" in
yes) force_machine_arch=FORCE_;;
no) ;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;;
*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac])
dnl
AC_ARG_WITH(machine_arch,
@ -321,7 +321,7 @@ esac])
dnl
dnl Tell them what we ended up with
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 Allow folk to control _PATH_DEFSYSPATH
dnl
@ -407,6 +407,7 @@ dnl
AC_SUBST(machine)
AC_SUBST(force_machine)
AC_SUBST(machine_arch)
AC_SUBST(force_machine_arch)
AC_SUBST(mksrc)
AC_SUBST(default_sys_path)
AC_SUBST(INSTALL)

244
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.
@ -100,9 +100,9 @@
* then all the directories above it in turn until
* the path is found or we reach the root ("/").
*
* Dir_MTime Return the modification time of a node. The file
* is searched for along the default search path.
* The path and mtime fields of the node are filled in.
* Dir_UpdateMTime
* Update the modification time and path of a node with
* data from the file corresponding to the node.
*
* Dir_AddDir Add a directory to a search path.
*
@ -134,7 +134,7 @@
#include "job.h"
/* "@(#)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_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
* parallelism available as each directory open would take another file
* 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
* 20 or 32 file descriptors for a process, this doesn't seem acceptable
* to me.
* it is only recently (as of 1993 or earlier) that UNIX OS's have taken
* to allowing more than 20 or 32 file descriptors for a process, this
* doesn't seem acceptable to me.
*
* 3) record the mtime of the directory in the CachedDir structure and
* 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.
*
* 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
* specify where the resulting object file should be placed. This forced all
* objects to be created in the current directory. This isn't meant as a full
* excuse, just an explanation of some of the reasons for the caching used
* here.
* C programs and until recently (as of 1993 or earlier) pcc-based compilers
* refused to allow you to specify where the resulting object file should be
* placed. This forced all objects to be created in the current directory.
* This isn't meant as a full excuse, just an explanation of some of the
* reasons for the caching used here.
*
* 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
@ -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,
* 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
* 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;
@ -253,12 +253,10 @@ OpenDirs_Find(OpenDirs *odirs, const char *name)
static void
OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir)
{
HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name);
if (he != NULL)
if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL)
return;
he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL);
Lst_Append(odirs->list, cdir);
HashEntry_Set(he, odirs->list->last);
HashTable_Set(&odirs->table, cdir->name, odirs->list->last);
}
static void
@ -273,10 +271,10 @@ OpenDirs_Remove(OpenDirs *odirs, const char *name)
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.
*/
static int hits; /* Found in directory cache */
@ -300,74 +298,50 @@ static HashTable mtimes;
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 {
CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */
CST_UPDATE = 0x02 /* ignore existing cached entry */
CST_NONE = 0,
CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */
CST_UPDATE = 1 << 1 /* ignore existing cached entry */
} 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
cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst,
cached_stats(const char *pathname, struct cached_stat *out_cst,
CachedStatsFlags flags)
{
HashEntry *entry;
HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes;
struct stat sys_st;
struct cache_st *cst;
struct cached_stat *cst;
int rc;
if (!pathname || !pathname[0])
return -1;
if (pathname == NULL || pathname[0] == '\0')
return -1; /* This can happen in meta mode. */
entry = HashTable_FindEntry(htp, pathname);
if (entry && !(flags & CST_UPDATE)) {
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",
Targ_FmtTime(mst->mst_mtime), pathname);
return 0;
}
cst = HashTable_FindValue(tbl, pathname);
if (cst != NULL && !(flags & CST_UPDATE)) {
*out_cst = *cst;
DIR_DEBUG2("Using cached time %s for %s\n",
Targ_FmtTime(cst->cst_mtime), pathname);
return 0;
}
rc = (flags & CST_LSTAT)
? lstat(pathname, &sys_st)
: stat(pathname, &sys_st);
rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st);
if (rc == -1)
return -1;
return -1; /* don't cache negative lookups */
if (sys_st.st_mtime == 0)
sys_st.st_mtime = 1; /* avoid confusion with missing file */
mst->mst_mode = sys_st.st_mode;
mst->mst_mtime = sys_st.st_mtime;
if (cst == NULL) {
cst = bmake_malloc(sizeof *cst);
HashTable_Set(tbl, pathname, cst);
}
if (entry == NULL)
entry = HashTable_CreateEntry(htp, pathname, NULL);
if (HashEntry_Get(entry) == NULL) {
HashEntry_Set(entry, bmake_malloc(sizeof(*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;
cst->cst_mtime = sys_st.st_mtime;
cst->cst_mode = sys_st.st_mode;
*out_cst = *cst;
DIR_DEBUG2(" Caching %s for %s\n",
Targ_FmtTime(sys_st.st_mtime), pathname);
@ -375,15 +349,15 @@ cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst,
}
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
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. */
@ -401,7 +375,7 @@ Dir_InitDir(const char *cdname)
{
Dir_InitCur(cdname);
dotLast = bmake_malloc(sizeof(CachedDir));
dotLast = bmake_malloc(sizeof *dotLast);
dotLast->refCount = 1;
dotLast->hits = 0;
dotLast->name = bmake_strdup(".DOTLAST");
@ -416,23 +390,31 @@ Dir_InitCur(const char *cdname)
{
CachedDir *dir;
if (cdname != NULL) {
if (cdname == NULL)
return;
/*
* Our build directory is not the same as our source directory.
* Keep this one around too.
*/
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++;
if (cur != NULL && cur != dir) {
/*
* Our build directory is not the same as our source directory.
* Keep this one around too.
* We've been here before, clean up.
*/
if ((dir = Dir_AddDir(NULL, cdname))) {
dir->refCount++;
if (cur && cur != dir) {
/*
* We've been here before, clean up.
*/
cur->refCount--;
Dir_Destroy(cur);
}
cur = dir;
}
cur->refCount--;
Dir_Destroy(cur);
}
cur = dir;
}
/* (Re)initialize "dot" (current/object directory) path hash.
@ -588,6 +570,9 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
Boolean isDot = dirName[0] == '.' && dirName[1] == '\0';
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);
while (HashIter_Next(&hi) != NULL) {
const char *base = hi.entry->key;
@ -879,13 +864,13 @@ DirLookup(CachedDir *dir, const char *base)
static char *
DirLookupSubdir(CachedDir *dir, const char *name)
{
struct make_stat mst;
struct cached_stat cst;
char *file = dir == dot ? bmake_strdup(name)
: str_concat3(dir->name, "/", name);
DIR_DEBUG1("checking %s ...\n", file);
if (cached_stat(file, &mst) == 0) {
if (cached_stat(file, &cst) == 0) {
nearmisses++;
return file;
}
@ -974,7 +959,7 @@ Dir_FindFile(const char *name, SearchPath *path)
const char *base; /* Terminal name of file */
Boolean hasLastDot = FALSE; /* true if we should search dot last */
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 = ".";
/*
@ -1176,7 +1161,7 @@ Dir_FindFile(const char *name, SearchPath *path)
* When searching for $(FILE), we will find it in $(INSTALLDIR)
* b/c we added it here. This is not good...
*/
#ifdef notdef
#if 0
if (base == trailing_dot) {
base = strrchr(name, '/');
base++;
@ -1198,17 +1183,17 @@ Dir_FindFile(const char *name, SearchPath *path)
} else {
return NULL;
}
#else /* !notdef */
#else
DIR_DEBUG1(" Looking for \"%s\" ...\n", name);
bigmisses++;
if (cached_stat(name, &mst) == 0) {
if (cached_stat(name, &cst) == 0) {
return bmake_strdup(name);
}
DIR_DEBUG0(" failed. Returning NULL\n");
return NULL;
#endif /* notdef */
#endif
}
@ -1225,7 +1210,7 @@ Dir_FindFile(const char *name, SearchPath *path)
char *
Dir_FindHereOrAbove(const char *here, const char *search_path)
{
struct make_stat mst;
struct cached_stat cst;
char *dirbase, *dirbase_end;
char *try, *try_end;
@ -1238,12 +1223,12 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
/* try and stat(2) it ... */
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
* 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);
while (try_end > try && *try_end != '/')
try_end--;
@ -1275,36 +1260,27 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
return NULL;
}
/*-
*-----------------------------------------------------------------------
* Dir_MTime --
* Find the modification time of the file described by gn along the
* search path dirSearchPath.
/* Search gn along dirSearchPath and store its modification time in gn->mtime.
* If no file is found, store 0 instead.
*
* Input:
* gn the file whose modification time is desired
*
* 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)
* The found file is stored in gn->path, unless the node already had a path. */
void
Dir_UpdateMTime(GNode *gn, Boolean recheck)
{
char *fullName; /* the full pathname of name */
struct make_stat mst; /* buffer for finding the mod time */
char *fullName;
struct cached_stat cst;
if (gn->type & OP_ARCHV) {
return Arch_MTime(gn);
} else if (gn->type & OP_PHONY) {
Arch_UpdateMTime(gn);
return;
}
if (gn->type & OP_PHONY) {
gn->mtime = 0;
return 0;
} else if (gn->path == NULL) {
return;
}
if (gn->path == NULL) {
if (gn->type & OP_NOPATH)
fullName = NULL;
else {
@ -1344,25 +1320,24 @@ Dir_MTime(GNode *gn, Boolean recheck)
fullName = gn->path;
}
if (fullName == NULL) {
if (fullName == NULL)
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 (fullName != gn->path)
free(fullName);
return Arch_MemMTime(gn);
} else {
mst.mst_mtime = 0;
Arch_UpdateMemberMTime(gn);
return;
}
cst.cst_mtime = 0;
}
if (fullName != NULL && gn->path == NULL)
gn->path = fullName;
gn->mtime = mst.mst_mtime;
return gn->mtime;
gn->mtime = cst.cst_mtime;
}
/* 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) {
SearchPathNode *ln;
/* XXX: Linear search gets slow with thousands of entries. */
for (ln = path->first; ln != NULL; ln = ln->next) {
CachedDir *pathDir = ln->datum;
if (strcmp(pathDir->name, name) == 0)
@ -1410,7 +1386,7 @@ Dir_AddDir(SearchPath *path, const char *name)
DIR_DEBUG1("Caching %s ...", name);
if ((d = opendir(name)) != NULL) {
dir = bmake_malloc(sizeof(CachedDir));
dir = bmake_malloc(sizeof *dir);
dir->name = bmake_strdup(name);
dir->hits = 0;
dir->refCount = 1;
@ -1480,7 +1456,7 @@ Dir_MakeFlags(const char *flag, SearchPath *path)
Buffer buf;
SearchPathNode *ln;
Buf_Init(&buf, 0);
Buf_Init(&buf);
if (path != NULL) {
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.
@ -101,7 +101,7 @@ Boolean Dir_HasWildcards(const char *);
void Dir_Expand(const char *, SearchPath *, StringList *);
char *Dir_FindFile(const char *, SearchPath *);
char *Dir_FindHereOrAbove(const char *, const char *);
time_t Dir_MTime(GNode *, Boolean);
void Dir_UpdateMTime(GNode *, Boolean);
CachedDir *Dir_AddDir(SearchPath *, const char *);
char *Dir_MakeFlags(const char *, SearchPath *);
void Dir_ClearPath(SearchPath *);
@ -112,12 +112,12 @@ void Dir_Destroy(void *);
SearchPath *Dir_CopyDirSearchPath(void);
/* Stripped-down variant of struct stat. */
struct make_stat {
time_t mst_mtime;
mode_t mst_mode;
struct cached_stat {
time_t cst_mtime;
mode_t cst_mode;
};
int cached_lstat(const char *, struct make_stat *);
int cached_stat(const char *, struct make_stat *);
int cached_lstat(const char *, struct cached_stat *);
int cached_stat(const char *, struct cached_stat *);
#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.
@ -65,7 +65,7 @@ filemon_open(void)
int error;
/* Allocate and zero a struct filemon object. */
F = calloc(1, sizeof(*F));
F = calloc(1, sizeof *F);
if (F == 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.
@ -198,7 +198,7 @@ filemon_open(void)
int error;
/* Allocate and zero a struct filemon object. */
F = calloc(1, sizeof(*F));
F = calloc(1, sizeof *F);
if (F == 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.
@ -60,15 +60,7 @@
#include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 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;
MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $");
static int forLevel = 0; /* Nesting level */
@ -120,30 +112,6 @@ For_Free(For *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
IsFor(const char *p)
{
@ -191,11 +159,11 @@ For_Eval(const char *line)
*/
f = bmake_malloc(sizeof *f);
Buf_Init(&f->body, 0);
Buf_Init(&f->body);
Vector_Init(&f->vars, sizeof(ForVar));
f->items.words = NULL;
f->items.freeIt = NULL;
Buf_Init(&f->curBody, 0);
Buf_Init(&f->curBody);
f->short_var = FALSE;
f->sub_next = 0;
@ -302,7 +270,7 @@ for_var_len(const char *var)
size_t len;
var_start = *var;
if (var_start == 0)
if (var_start == '\0')
/* just escape the $ */
return 0;
@ -315,7 +283,7 @@ for_var_len(const char *var)
return 1;
depth = 1;
for (len = 1; (ch = var[len++]) != 0;) {
for (len = 1; (ch = var[len++]) != '\0';) {
if (ch == var_start)
depth++;
else if (ch == var_end && --depth == 0)
@ -326,18 +294,30 @@ for_var_len(const char *var)
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...}
* expression, escaping characters as needed. See ApplyModifier_Defined. */
* expression, escaping characters as needed.
*
* The result is later unescaped by ApplyModifier_Defined. */
static void
Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
{
ForEscapes escapes = GetEscapes(item);
char ch;
/* If there were no escapes, or the only escape is the other variable
* terminator, then just substitute the full string */
if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE
: ~(unsigned)FOR_SUB_ESCAPE_PAREN))) {
if (!NeedsEscapes(item, ech)) {
Buf_AddStr(cmds, item);
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.
@ -74,7 +74,7 @@
#include "make.h"
/* "@(#)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
@ -128,7 +128,7 @@ void
HashTable_Init(HashTable *t)
{
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++)
buckets[i] = NULL;
@ -195,7 +195,7 @@ HashTable_Enlarge(HashTable *t)
HashEntry **oldBuckets = t->buckets;
unsigned int newSize = 2 * oldSize;
unsigned int newMask = newSize - 1;
HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize);
HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize);
size_t 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)
HashTable_Enlarge(t);
he = bmake_malloc(sizeof(*he) + keylen);
he = bmake_malloc(sizeof *he + keylen);
he->value = NULL;
he->key_hash = h;
memcpy(he->key, key, keylen + 1);
@ -253,6 +253,14 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
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. */
void
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.
@ -103,13 +103,13 @@ typedef struct HashIter {
HashEntry *entry; /* Next entry to check in current bucket. */
} HashIter;
static inline MAKE_ATTR_UNUSED void *
MAKE_INLINE void *
HashEntry_Get(HashEntry *h)
{
return h->value;
}
static inline MAKE_ATTR_UNUSED void
MAKE_INLINE void
HashEntry_Set(HashEntry *h, void *datum)
{
h->value = datum;
@ -122,6 +122,7 @@ void *HashTable_FindValue(HashTable *, const char *);
unsigned int Hash_Hash(const char *);
void *HashTable_FindValueHash(HashTable *, const char *, unsigned int);
HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *);
HashEntry *HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *);

542
job.c

File diff suppressed because it is too large Load Diff

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.
@ -117,25 +117,25 @@ struct pollfd;
# include "meta.h"
#endif
typedef enum JobState {
typedef enum JobStatus {
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_FINISHED = 4 /* Job is done (ie after SIGCHILD) */
} JobState;
} JobStatus;
typedef enum JobFlags {
JOB_NONE = 0,
/* Ignore non-zero exits */
JOB_IGNERR = 0x001,
JOB_IGNERR = 1 << 0,
/* no output */
JOB_SILENT = 0x002,
JOB_SILENT = 1 << 1,
/* Target is a special one. i.e. run it locally
* if we can't export it and maxLocal is 0 */
JOB_SPECIAL = 0x004,
/* Ignore "..." lines when processing commands */
JOB_IGNDOTS = 0x008,
JOB_SPECIAL = 1 << 2,
/* we've sent 'set -x' */
JOB_TRACED = 0x400
JOB_TRACED = 1 << 10
} JobFlags;
/* 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 */
JobState job_state; /* status of the job entry */
JobStatus status;
char job_suspended;
Boolean suspended;
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
@ -34,7 +34,7 @@
#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
#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 *
LstNodeNew(ListNode *prev, ListNode *next, void *datum)
{
ListNode *node = bmake_malloc(sizeof *node);
node->prev = prev;
node->next = next;
node->datum = datum;
return node;
ListNode *ln = bmake_malloc(sizeof *ln);
ln->prev = prev;
ln->next = next;
ln->datum = datum;
return ln;
}
/* Create and initialize a new, empty list. */
@ -68,12 +68,11 @@ Lst_New(void)
void
Lst_Free(List *list)
{
ListNode *node;
ListNode *next;
ListNode *ln, *next;
for (node = list->first; node != NULL; node = next) {
next = node->next;
free(node);
for (ln = list->first; ln != NULL; ln = next) {
next = ln->next;
free(ln);
}
free(list);
@ -84,37 +83,32 @@ Lst_Free(List *list)
void
Lst_Destroy(List *list, LstFreeProc freeProc)
{
ListNode *node;
ListNode *next;
ListNode *ln, *next;
for (node = list->first; node != NULL; node = next) {
next = node->next;
freeProc(node->datum);
free(node);
for (ln = list->first; ln != NULL; ln = next) {
next = ln->next;
freeProc(ln->datum);
free(ln);
}
free(list);
}
/*
* Functions to modify a list
*/
/* Insert a new node with the datum before the given node. */
void
Lst_InsertBefore(List *list, ListNode *node, void *datum)
Lst_InsertBefore(List *list, ListNode *ln, void *datum)
{
ListNode *newNode;
assert(datum != NULL);
newNode = LstNodeNew(node->prev, node, datum);
newNode = LstNodeNew(ln->prev, ln, datum);
if (node->prev != NULL)
node->prev->next = newNode;
node->prev = newNode;
if (ln->prev != NULL)
ln->prev->next = newNode;
ln->prev = newNode;
if (node == list->first)
if (ln == list->first)
list->first = newNode;
}
@ -122,18 +116,18 @@ Lst_InsertBefore(List *list, ListNode *node, void *datum)
void
Lst_Prepend(List *list, void *datum)
{
ListNode *node;
ListNode *ln;
assert(datum != NULL);
node = LstNodeNew(NULL, list->first, datum);
ln = LstNodeNew(NULL, list->first, datum);
if (list->first == NULL) {
list->first = node;
list->last = node;
list->first = ln;
list->last = ln;
} else {
list->first->prev = node;
list->first = node;
list->first->prev = ln;
list->first = ln;
}
}
@ -141,71 +135,69 @@ Lst_Prepend(List *list, void *datum)
void
Lst_Append(List *list, void *datum)
{
ListNode *node;
ListNode *ln;
assert(datum != NULL);
node = LstNodeNew(list->last, NULL, datum);
ln = LstNodeNew(list->last, NULL, datum);
if (list->last == NULL) {
list->first = node;
list->last = node;
list->first = ln;
list->last = ln;
} else {
list->last->next = node;
list->last = node;
list->last->next = ln;
list->last = ln;
}
}
/* Remove the given node from the given list.
* The datum stored in the node must be freed by the caller, if necessary. */
void
Lst_Remove(List *list, ListNode *node)
Lst_Remove(List *list, ListNode *ln)
{
/* unlink it from its neighbors */
if (node->next != NULL)
node->next->prev = node->prev;
if (node->prev != NULL)
node->prev->next = node->next;
if (ln->next != NULL)
ln->next->prev = ln->prev;
if (ln->prev != NULL)
ln->prev->next = ln->next;
/* unlink it from the list */
if (list->first == node)
list->first = node->next;
if (list->last == node)
list->last = node->prev;
if (list->first == ln)
list->first = ln->next;
if (list->last == ln)
list->last = ln->prev;
}
/* Replace the datum in the given node with the new datum. */
void
LstNode_Set(ListNode *node, void *datum)
LstNode_Set(ListNode *ln, void *datum)
{
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. */
void
LstNode_SetNull(ListNode *node)
LstNode_SetNull(ListNode *ln)
{
node->datum = NULL;
ln->datum = NULL;
}
/*
* Functions for entire lists
*/
/* Return the first node that contains the given datum, or NULL. */
/* Return the first node that contains the given datum, or NULL.
*
* Time complexity: O(length(list)) */
ListNode *
Lst_FindDatum(List *list, const void *datum)
{
ListNode *node;
ListNode *ln;
assert(datum != NULL);
for (node = list->first; node != NULL; node = node->next)
if (node->datum == datum)
return node;
for (ln = list->first; ln != NULL; ln = ln->next)
if (ln->datum == datum)
return ln;
return NULL;
}
@ -213,32 +205,32 @@ Lst_FindDatum(List *list, const void *datum)
int
Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData)
{
ListNode *node;
ListNode *ln;
int result = 0;
for (node = list->first; node != NULL; node = node->next) {
result = proc(node->datum, procData);
for (ln = list->first; ln != NULL; ln = ln->next) {
result = proc(ln->datum, procData);
if (result != 0)
break;
}
return result;
}
/* Move all nodes from list2 to the end of list1.
* List2 is destroyed and freed. */
/* Move all nodes from src to the end of dst.
* The source list is destroyed and freed. */
void
Lst_MoveAll(List *list1, List *list2)
Lst_MoveAll(List *dst, List *src)
{
if (list2->first != NULL) {
list2->first->prev = list1->last;
if (list1->last != NULL)
list1->last->next = list2->first;
if (src->first != NULL) {
src->first->prev = dst->last;
if (dst->last != NULL)
dst->last->next = src->first;
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. */

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.
@ -118,7 +118,7 @@ void Lst_Destroy(List *, LstFreeProc);
/* Get information about a list */
static inline MAKE_ATTR_UNUSED Boolean
MAKE_INLINE Boolean
Lst_IsEmpty(List *list) { return list->first == 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.
* 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)
{
unsigned char *items = v->items;

1392
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@
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}\""
@ -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 \
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"
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
.\" The Regents of the University of California. All rights reserved.
@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
.Dd November 1, 2020
.Dd November 14, 2020
.Dt MAKE 1
.Os
.Sh NAME
@ -37,7 +37,7 @@
.Nd maintain program dependencies
.Sh SYNOPSIS
.Nm
.Op Fl BeikNnqrstWwX
.Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory
.Op Fl D Ar variable
.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.
.It Fl r
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
Do not echo any commands as they are executed.
Equivalent to specifying
@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and
.Ql Ev PWD
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
A path to the directory of the current

345
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
@ -68,33 +68,28 @@
* SUCH DAMAGE.
*/
/*-
* make.c --
* The functions which perform the examination of targets and
* their suitability for creation
/* Examination of targets and their suitability for creation.
*
* Interface:
* Make_Run Initialize things for the module and recreate
* whatever needs recreating. Returns TRUE if
* work was (or would have been) done and FALSE
* otherwise.
* Make_Run Initialize things for the module. Returns TRUE if
* work was (or would have been) done.
*
* Make_Update Update all parents of a given child. Performs
* various bookkeeping chores like the updating
* Make_Update After a target is made, update all its parents.
* Perform various bookkeeping chores like the updating
* of the youngestChild field of the parent, filling
* of the IMPSRC context variable, etc. It will
* place the parent on the toBeMade queue if it
* should be.
* of the IMPSRC context variable, etc. Place the parent
* on the toBeMade queue if it should be.
*
* Make_TimeStamp Function to set the parent's youngestChild field
* based on a child's modification time.
* GNode_UpdateYoungestChild
* Update the node's youngestChild field based on the
* child's modification time.
*
* Make_DoAllVar Set up the various local variables for a
* target, including the .ALLSRC variable, making
* sure that any variable that needs to exist
* 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
* and perform the .USE actions if so.
@ -107,17 +102,16 @@
#include "job.h"
/* "@(#)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. */
static unsigned int checked = 1;
static unsigned int checked_seqno = 1;
/* The current fringe of the graph.
* These are nodes which await examination by MakeOODate.
* It is added to by Make_Update and subtracted from by MakeStartJobs */
static GNodeList *toBeMade;
static int MakeCheckOrder(void *, void *);
static int MakeBuildParent(void *, void *);
void
@ -185,11 +179,37 @@ GNode_ShouldExecute(GNode *gn)
/* Update the youngest child of the node, according to the given child. */
void
Make_TimeStamp(GNode *pgn, GNode *cgn)
GNode_UpdateYoungestChild(GNode *gn, GNode *cgn)
{
if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) {
pgn->youngestChild = cgn;
if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime)
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.
@ -204,7 +224,7 @@ Make_TimeStamp(GNode *pgn, GNode *cgn)
* may be changed.
*/
Boolean
Make_OODate(GNode *gn)
GNode_IsOODate(GNode *gn)
{
Boolean oodate;
@ -212,14 +232,13 @@ Make_OODate(GNode *gn)
* Certain types of targets needn't even be sought as their datedness
* doesn't depend on their modification time...
*/
if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) {
(void)Dir_MTime(gn, 1);
if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
Dir_UpdateMTime(gn, TRUE);
if (DEBUG(MAKE)) {
if (gn->mtime != 0) {
if (gn->mtime != 0)
debug_printf("modified %s...", Targ_FmtTime(gn->mtime));
} else {
else
debug_printf("non-existent...");
}
}
}
@ -244,8 +263,7 @@ Make_OODate(GNode *gn)
*/
DEBUG0(MAKE, ".USE node...");
oodate = FALSE;
} else if ((gn->type & OP_LIB) &&
((gn->mtime==0) || Arch_IsLib(gn))) {
} else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) {
DEBUG0(MAKE, "library...");
/*
@ -261,7 +279,7 @@ Make_OODate(GNode *gn)
*/
DEBUG0(MAKE, ".JOIN node...");
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)) {
/*
* A node which is the object of the force (!) operator or which has
@ -277,30 +295,7 @@ Make_OODate(GNode *gn)
}
}
oodate = TRUE;
} else if ((gn->youngestChild != NULL &&
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...");
}
}
} else if (IsOODateRegular(gn)) {
oodate = TRUE;
} else {
/*
@ -314,7 +309,7 @@ Make_OODate(GNode *gn)
if (gn->flags & FORCE)
debug_printf("non existing child...");
}
oodate = (gn->flags & FORCE) ? TRUE : FALSE;
oodate = (gn->flags & FORCE) != 0;
}
#ifdef USE_META
@ -333,46 +328,24 @@ Make_OODate(GNode *gn)
if (!oodate) {
GNodeListNode *ln;
for (ln = gn->parents->first; ln != NULL; ln = ln->next)
Make_TimeStamp(ln->datum, gn);
GNode_UpdateYoungestChild(ln->datum, gn);
}
return oodate;
}
/* Add the node to the list if it needs to be examined. */
static int
MakeAddChild(void *gnp, void *lp)
static void
PretendAllChildrenAreMade(GNode *pgn)
{
GNode *gn = gnp;
GNodeList *l = lp;
GNodeListNode *ln;
if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n",
gn->name, gn->cohort_num);
Lst_Enqueue(l, gn);
for (ln = pgn->children->first; ln != NULL; ln = ln->next) {
GNode *cgn = ln->datum;
Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */
GNode_UpdateYoungestChild(pgn, cgn);
pgn->unmade--;
}
return 0;
}
/* Find the pathname of a child that was already made.
*
* 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--;
return 0;
}
/* 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 */
#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);
return;
return; /* XXX: debug mode should not affect control flow */
}
#endif
@ -457,10 +430,10 @@ MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln)
{
Boolean unmarked;
unmarked = ((cgn->type & OP_MARK) == 0);
unmarked = !(cgn->type & OP_MARK);
cgn->type |= OP_MARK;
if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
if (!(cgn->type & (OP_USE|OP_USEBEFORE)))
return;
if (unmarked)
@ -493,7 +466,10 @@ HandleUseNodes(GNode *gn)
time_t
Make_Recheck(GNode *gn)
{
time_t mtime = Dir_MTime(gn, 1);
time_t mtime;
Dir_UpdateMTime(gn, TRUE);
mtime = gn->mtime;
#ifndef RECHECK
/*
@ -512,13 +488,11 @@ Make_Recheck(GNode *gn)
* 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
* reflect the current modification time for parse.h. This is
* something of a kludge, I admit, but it's a useful one..
* XXX: People like to use a rule like
* something of a kludge, I admit, but it's a useful one.
*
* FRC:
*
* To force things that depend on FRC to be made, so we have to
* check for gn->children being empty as well...
* 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
* being empty as well.
*/
if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
gn->mtime = now;
@ -535,7 +509,7 @@ Make_Recheck(GNode *gn)
* using the same file from a common server), there are times
* when the modification time of a file created on a remote
* 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.
*
* 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
*/
/*
* Christos, 4/9/92: If we are saving commands pretend that
* the target is made now. Otherwise archives with ... rules
* Christos, 4/9/92: If we are saving commands, pretend that
* the target is made now. Otherwise archives with '...' rules
* don't work!
*/
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",
gn->name, Targ_FmtTime(gn->mtime));
gn->mtime = now;
}
else {
} else {
DEBUG2(MAKE, " recheck(%s): current update time: %s\n",
gn->name, Targ_FmtTime(gn->mtime));
}
#endif
/* XXX: The returned mtime may differ from gn->mtime.
* Intentionally? */
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
* a node has been dealt with and by MakeStartJobs if it finds an
* up-to-date node.
@ -610,7 +605,7 @@ Make_Update(GNode *cgn)
GNode *centurion;
/* It is save to re-examine any nodes again */
checked++;
checked_seqno++;
cname = GNode_VarTarget(cgn);
@ -647,11 +642,11 @@ Make_Update(GNode *cgn)
for (ln = parents->first; ln != NULL; ln = ln->next) {
GNode *pgn = ln->datum;
if (DEBUG(MAKE))
debug_printf("inspect parent %s%s: flags %x, "
"type %x, made %d, unmade %d ",
pgn->name, pgn->cohort_num, pgn->flags,
pgn->type, pgn->made, pgn->unmade - 1);
if (DEBUG(MAKE)) {
debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num);
GNode_FprintDetails(opts.debug_file, "", pgn, "");
debug_printf(", unmade %d ", pgn->unmade - 1);
}
if (!(pgn->flags & REMAKE)) {
/* This parent isn't needed */
@ -674,10 +669,10 @@ Make_Update(GNode *cgn)
continue;
}
if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) {
if (cgn->made == MADE)
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");
continue;
}
assert(pgn->order_pred != NULL);
if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) {
/* A .ORDER rule stops us building this */
if (IsWaitingForOrder(pgn))
continue;
}
if (DEBUG(MAKE)) {
debug_printf("- %s%s made, schedule %s%s (made %d)\n",
cgn->name, cgn->cohort_num,
@ -771,7 +765,7 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
return;
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;
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
* confused if something else updates the parent after the
* 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
* the kid and anything that relies on the OODATE variable will
* the child and anything that relies on the OODATE variable will
* be hosed.
*
* 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)
MakeAddAllSrc(ln->datum, gn);
if (!Var_Exists(OODATE, gn)) {
if (!Var_Exists(OODATE, gn))
Var_Set(OODATE, "", gn);
}
if (!Var_Exists(ALLSRC, gn)) {
if (!Var_Exists(ALLSRC, gn))
Var_Set(ALLSRC, "", gn);
}
if (gn->type & OP_JOIN)
Var_Set(TARGET, GNode_VarAllsrc(gn), gn);
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
MakeBuildChild(void *v_cn, void *toBeMade_next)
{
GNode *cn = v_cn;
DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
cn->name, cn->cohort_num, cn->made, cn->type);
if (DEBUG(MAKE)) {
debug_printf("MakeBuildChild: inspect %s%s, ",
cn->name, cn->cohort_num);
GNode_FprintDetails(opts.debug_file, "", cn, "\n");
}
if (cn->made > DEFERRED)
return 0;
/* If this node is on the RHS of a .ORDER, check LHSs. */
assert(cn->order_pred);
if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) {
if (IsWaitingForOrder(cn)) {
/* Can't build this (or anything else in this child list) yet */
cn->made = DEFERRED;
return 0; /* but keep looking */
@ -899,7 +880,7 @@ MakeBuildChild(void *v_cn, void *toBeMade_next)
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
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.
*
* 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
* returns TRUE. At all other times, this function returns FALSE.
* returns TRUE. In all other cases, this function returns FALSE.
*/
static Boolean
MakeStartJobs(void)
{
GNode *gn;
int have_token = 0;
GNode *gn;
Boolean have_token = FALSE;
while (!Lst_IsEmpty(toBeMade)) {
/* Get token now to avoid cycling job-list when we only have 1 token */
if (!have_token && !Job_TokenWithdraw())
break;
have_token = 1;
have_token = TRUE;
gn = Lst_Dequeue(toBeMade);
DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num);
@ -943,13 +924,13 @@ MakeStartJobs(void)
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... */
DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num);
gn->made = DEFERRED;
continue;
}
gn->checked_seqno = checked;
gn->checked_seqno = checked_seqno;
if (gn->unmade != 0) {
/*
@ -964,14 +945,13 @@ MakeStartJobs(void)
}
gn->made = BEINGMADE;
if (Make_OODate(gn)) {
if (GNode_IsOODate(gn)) {
DEBUG0(MAKE, "out-of-date\n");
if (opts.queryFlag) {
if (opts.queryFlag)
return TRUE;
}
Make_DoAllVar(gn);
Job_Make(gn);
have_token = 0;
have_token = FALSE;
} else {
DEBUG0(MAKE, "up-to-date\n");
gn->made = UPTODATE;
@ -994,6 +974,7 @@ MakeStartJobs(void)
return FALSE;
}
/* Print the status of a .ORDER node. */
static void
MakePrintStatusOrderNode(GNode *ogn, GNode *gn)
{
@ -1074,7 +1055,7 @@ MakePrintStatus(GNode *gn, int *errors)
* print out the cycle by recursing on its children.
*/
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;
MakePrintStatusList(gn->children, errors);
/* Mark that this node needn't be processed again */
@ -1103,6 +1084,25 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
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.
*
* Input:
@ -1111,17 +1111,8 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
void
Make_ExpandUse(GNodeList *targs)
{
GNodeList *examine; /* List of targets to examine */
{
/* 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);
}
GNodeList *examine = Lst_New(); /* Queue of targets to examine */
Lst_AppendAll(examine, targs);
/*
* Make an initial downward pass over the graph, marking nodes to be made
@ -1151,9 +1142,8 @@ Make_ExpandUse(GNodeList *targs)
* expansions.
*/
if (gn->type & OP_ARCHV) {
char *eoa, *eon;
eoa = strchr(gn->name, '(');
eon = strchr(gn->name, ')');
char *eoa = strchr(gn->name, '(');
char *eon = strchr(gn->name, ')');
if (eoa == NULL || eon == NULL)
continue;
*eoa = '\0';
@ -1164,23 +1154,22 @@ Make_ExpandUse(GNodeList *targs)
*eon = ')';
}
(void)Dir_MTime(gn, 0);
Dir_UpdateMTime(gn, FALSE);
Var_Set(TARGET, GNode_Path(gn), gn);
UnmarkChildren(gn);
HandleUseNodes(gn);
if ((gn->type & OP_MADE) == 0)
if (!(gn->type & OP_MADE))
Suff_FindDeps(gn);
else {
/* Pretend we made all this node's children */
Lst_ForEachUntil(gn->children, MakeFindChild, gn);
PretendAllChildrenAreMade(gn);
if (gn->unmade != 0)
printf("Warning: %s%s still has %d unmade children\n",
gn->name, gn->cohort_num, gn->unmade);
printf("Warning: %s%s still has %d unmade children\n",
gn->name, gn->cohort_num, gn->unmade);
}
if (gn->unmade != 0)
Lst_ForEachUntil(gn->children, MakeAddChild, examine);
ExamineLater(examine, gn->children);
}
Lst_Free(examine);
@ -1218,7 +1207,7 @@ Make_ProcessWait(GNodeList *targs)
* Perhaps this should be done earlier...
*/
pgn = Targ_NewGN(".MAIN");
pgn = GNode_New(".MAIN");
pgn->flags = REMAKE;
pgn->type = OP_PHONY | OP_DEPENDS;
/* Get it displayed in the diag dumps */
@ -1353,9 +1342,9 @@ Make_Run(GNodeList *targs)
MakePrintStatusList(targs, &errors);
if (DEBUG(MAKE)) {
debug_printf("done: errors %d\n", errors);
if (errors)
if (errors > 0)
Targ_PrintGraph(4);
}
}
return errors != 0;
return errors > 0;
}

272
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
@ -135,6 +135,8 @@
#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
#endif
#define MAKE_INLINE static inline MAKE_ATTR_UNUSED
/*
* 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).
@ -187,7 +189,7 @@ typedef int Boolean;
#define POSIX_SIGNALS
#endif
typedef enum {
typedef enum GNodeMade {
UNMADE, /* Not examined yet */
DEFERRED, /* Examined once (building child) */
REQUESTED, /* on toBeMade list */
@ -207,6 +209,8 @@ typedef enum {
*
* Some of the OP_ constants can be combined, others cannot. */
typedef enum GNodeType {
OP_NONE = 0,
/* The dependency operator ':' is the most common one. The commands of
* this node are executed if any child is out-of-date. */
OP_DEPENDS = 1 << 0,
@ -215,7 +219,8 @@ typedef enum GNodeType {
OP_FORCE = 1 << 1,
/* The dependency operator '::' behaves like ':', except that it allows
* 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,
/* Matches the dependency operators ':', '!' and '::'. */
@ -246,7 +251,7 @@ typedef enum GNodeType {
/* Like .USE, only prepend commands */
OP_USEBEFORE = 1 << 13,
/* 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,
/* The node is exempt from normal 'main target' processing in parse.c */
OP_NOTMAIN = 1 << 15,
@ -254,7 +259,10 @@ typedef enum GNodeType {
OP_PHONY = 1 << 16,
/* Don't search for file in the path */
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,
/* .NOMETA do not create a .meta file */
OP_NOMETA = 1 << 19,
@ -267,7 +275,7 @@ typedef enum GNodeType {
/* Attributes applied by PMake */
/* The node is a transformation rule */
/* The node is a transformation rule, such as ".c.o". */
OP_TRANSFORM = 1 << 31,
/* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */
@ -337,7 +345,7 @@ typedef struct GNode {
int unmade; /* The number of unmade children */
/* The modification time; 0 means the node does not have a corresponding
* file; see Make_OODate. */
* file; see GNode_IsOODate. */
time_t mtime;
struct GNode *youngestChild;
@ -346,9 +354,6 @@ typedef struct GNode {
* file.c has the node for file.o in this list. */
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
* which this is a source. */
GNodeList *parents;
@ -364,6 +369,8 @@ typedef struct GNode {
* in the normal sense. */
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 */
char cohort_num[8];
/* The number of unmade instances on the cohorts list */
@ -389,20 +396,20 @@ typedef struct GNode {
* but the Suff module) */
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;
/* line number where the GNode got defined */
/* Line number where the GNode got defined */
int lineno;
} GNode;
/*
* 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.
*/
/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
/* Exit when the current top-level makefile has been parsed completely. */
PARSE_FATAL = 1,
/* Print "warning"; may be upgraded to fatal by the -w option. */
PARSE_WARNING,
/* Informational, mainly used during development of makefiles. */
PARSE_INFO
} ParseErrorLevel;
@ -415,9 +422,7 @@ typedef enum CondEvalResult {
COND_INVALID /* Not a conditional statement */
} CondEvalResult;
/*
* Definitions for the "local" variables. Used only for clarity.
*/
/* Names of the variables that are "local" to a specific target. */
#define TARGET "@" /* Target of dependency */
#define OODATE "?" /* All out-of-date sources */
#define ALLSRC ">" /* All sources */
@ -426,58 +431,77 @@ typedef enum CondEvalResult {
#define ARCHIVE "!" /* Archive 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
*/
/* 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;
/* Variables defined internally by make which should not override those set
* by 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 Boolean preserveUndefined;
/* The list of directories to search when looking for targets (set by the
* special target .PATH). */
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 */
/* 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 GNode *DEFAULT; /* .DEFAULT rule */
/* Startup directory */
extern char curdir[];
/* The basename of the program name, suffixed with [n] for sub-makes. */
extern char *progname;
/* Name of the .depend makefile */
extern char *makeDependfile;
/* If we replaced environ, this will be non-NULL. */
extern char **savedEnv;
extern GNode *VAR_INTERNAL; /* Variables defined internally by make
* which should not override those set by
* makefiles.
*/
extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g
* 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
* process */
extern Boolean oldVars; /* Do old-style variable substitution */
extern SearchPath *sysIncPath; /* The system include path. */
extern SearchPath *defSysIncPath; /* The default system include path. */
extern char curdir[]; /* Startup directory */
extern char *progname; /* The program name */
extern char *makeDependfile; /* .depend */
extern char **savedEnv; /* if we replaced environ this will be non-NULL */
extern int makelevel;
extern int makelevel;
/*
* We cannot vfork() in a child of vfork().
* Most systems do not enforce this but some do.
*/
#define vFork() ((getpid() == myPid) ? vfork() : fork())
extern pid_t myPid;
extern pid_t myPid;
#define MAKEFLAGS ".MAKEFLAGS"
#define MAKEOVERRIDES ".MAKEOVERRIDES"
@ -485,7 +509,7 @@ extern pid_t myPid;
#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */
#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_MODE ".MAKE.MODE"
#ifndef MAKE_LEVEL_ENV
@ -493,29 +517,28 @@ extern pid_t myPid;
#endif
typedef enum DebugFlags {
DEBUG_NONE = 0,
DEBUG_ARCH = 1 << 0,
DEBUG_COND = 1 << 1,
DEBUG_DIR = 1 << 2,
DEBUG_GRAPH1 = 1 << 3,
DEBUG_GRAPH2 = 1 << 4,
DEBUG_JOB = 1 << 5,
DEBUG_MAKE = 1 << 6,
DEBUG_SUFF = 1 << 7,
DEBUG_TARG = 1 << 8,
DEBUG_VAR = 1 << 9,
DEBUG_FOR = 1 << 10,
DEBUG_SHELL = 1 << 11,
DEBUG_ERROR = 1 << 12,
DEBUG_LOUD = 1 << 13,
DEBUG_META = 1 << 14,
DEBUG_HASH = 1 << 15,
DEBUG_GRAPH3 = 1 << 16,
DEBUG_SCRIPT = 1 << 17,
DEBUG_PARSE = 1 << 18,
DEBUG_CWD = 1 << 19,
DEBUG_LINT = 1 << 20
DEBUG_CWD = 1 << 2,
DEBUG_DIR = 1 << 3,
DEBUG_ERROR = 1 << 4,
DEBUG_FOR = 1 << 5,
DEBUG_GRAPH1 = 1 << 6,
DEBUG_GRAPH2 = 1 << 7,
DEBUG_GRAPH3 = 1 << 8,
DEBUG_HASH = 1 << 9,
DEBUG_JOB = 1 << 10,
DEBUG_LOUD = 1 << 11,
DEBUG_MAKE = 1 << 12,
DEBUG_META = 1 << 13,
DEBUG_PARSE = 1 << 14,
DEBUG_SCRIPT = 1 << 15,
DEBUG_SHELL = 1 << 16,
DEBUG_SUFF = 1 << 17,
DEBUG_TARG = 1 << 18,
DEBUG_VAR = 1 << 19,
DEBUG_ALL = (1 << 20) - 1
} DebugFlags;
#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)
typedef enum PrintVarsMode {
COMPAT_VARS = 1,
EXPAND_VARS
PVM_NONE,
PVM_UNEXPANDED,
PVM_EXPANDED
} PrintVarsMode;
/* Command line options */
@ -565,6 +589,12 @@ typedef struct CmdOpts {
/* -df: debug output is written here - default stderr */
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 */
Boolean debugVflag;
@ -630,49 +660,49 @@ extern CmdOpts opts;
#include "nonints.h"
void Make_TimeStamp(GNode *, GNode *);
Boolean Make_OODate(GNode *);
void GNode_UpdateYoungestChild(GNode *, GNode *);
Boolean GNode_IsOODate(GNode *);
void Make_ExpandUse(GNodeList *);
time_t Make_Recheck(GNode *);
void Make_HandleUse(GNode *, GNode *);
void Make_Update(GNode *);
void Make_DoAllVar(GNode *);
Boolean Make_Run(GNodeList *);
int dieQuietly(GNode *, int);
Boolean shouldDieQuietly(GNode *, int);
void PrintOnError(GNode *, const char *);
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 str2Lst_Append(StringList *, char *, const char *);
int str2Lst_Append(StringList *, char *);
void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
Boolean GNode_ShouldExecute(GNode *gn);
/* 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)
{
return (gn->type & OP_OPMASK) != 0;
}
static MAKE_ATTR_UNUSED const char *
MAKE_INLINE const char *
GNode_Path(const GNode *gn)
{
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); }
static MAKE_ATTR_UNUSED const char *
MAKE_INLINE const char *
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); }
static MAKE_ATTR_UNUSED const char *
MAKE_INLINE const char *
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); }
static MAKE_ATTR_UNUSED const char *
MAKE_INLINE const char *
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); }
#ifdef __GNUC__
@ -703,35 +733,49 @@ GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#define KILLPG(pid, sig) killpg((pid), (sig))
#endif
static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch)
{ return isalnum((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch)
{ return isalpha((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch)
{ return isdigit((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch)
{ return isspace((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch)
{ return isupper((unsigned char)ch) != 0; }
static inline MAKE_ATTR_UNUSED char ch_tolower(char ch)
{ return (char)tolower((unsigned char)ch); }
static inline MAKE_ATTR_UNUSED char ch_toupper(char ch)
{ return (char)toupper((unsigned char)ch); }
MAKE_INLINE Boolean
ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; }
MAKE_INLINE Boolean
ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; }
MAKE_INLINE Boolean
ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; }
MAKE_INLINE Boolean
ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; }
MAKE_INLINE Boolean
ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; }
MAKE_INLINE char
ch_tolower(char ch) { return (char)tolower((unsigned char)ch); }
MAKE_INLINE char
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)
{
while (ch_isspace(**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)
{
while (ch_isspace(**pp))
(*pp)++;
}
MAKE_INLINE void
pp_skip_hspace(char **pp)
{
while (**pp == ' ' || **pp == '\t')
(*pp)++;
}
#ifdef MAKE_NATIVE
# include <sys/cdefs.h>
# 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.
@ -46,7 +46,7 @@ char *bmake_strsedup(const char *, const char *);
*
* The case of a NULL pointer happens especially often after Var_Value,
* since only environment variables need to be freed, but not others. */
static inline MAKE_ATTR_UNUSED void
MAKE_INLINE void
bmake_free(void *p)
{
if (p != NULL)

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

View File

@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# 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
#
@ -70,7 +70,7 @@
# sjg@crufty.net
#
MK_VERSION=20201101
MK_VERSION=20201106
OWNER=
GROUP=
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
@ -178,7 +178,8 @@ DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh
@case "${.MAKE.META.FILES:T:M*.po.*}" in \
*.po.*) mv $@.${.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};; \
esac
.else

View File

@ -77,7 +77,7 @@
# 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.
# All rights reserved.
@ -259,7 +259,7 @@ meta2deps() {
0) error "no filemon data";;
*) ;;
esac
version=0
version=0
continue
;;
$pid,$pid) ;;

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
@ -79,8 +79,8 @@ void Arch_End(void);
Boolean Arch_ParseArchive(char **, GNodeList *, GNode *);
void Arch_Touch(GNode *);
void Arch_TouchLib(GNode *);
time_t Arch_MTime(GNode *);
time_t Arch_MemMTime(GNode *);
void Arch_UpdateMTime(GNode *gn);
void Arch_UpdateMemberMTime(GNode *gn);
void Arch_FindLib(GNode *, SearchPath *);
Boolean Arch_LibOODate(GNode *);
Boolean Arch_IsLib(GNode *);
@ -107,6 +107,7 @@ void JobReapChild(pid_t, WAIT_T, Boolean);
#endif
/* main.c */
Boolean GetBooleanVar(const char *, Boolean);
void Main_ParseArgLine(const char *);
void MakeMode(const char *);
char *Cmd_Exec(const char *, const char **);
@ -118,8 +119,7 @@ void Finish(int) MAKE_ATTR_DEAD;
int eunlink(const char *);
void execDie(const char *, const char *);
char *getTmpdir(void);
Boolean s2Boolean(const char *, Boolean);
Boolean getBoolean(const char *, Boolean);
Boolean ParseBoolean(const char *, Boolean);
char *cached_realpath(const char *, char *);
/* parse.c */
@ -159,7 +159,7 @@ typedef struct Words {
} Words;
Words Str_Words(const char *, Boolean);
static inline MAKE_ATTR_UNUSED void
MAKE_INLINE void
Words_Free(Words w) {
free(w.words);
free(w.freeIt);
@ -199,15 +199,15 @@ void Targ_End(void);
void Targ_Stats(void);
GNodeList *Targ_List(void);
GNode *Targ_NewGN(const char *);
GNode *GNode_New(const char *);
GNode *Targ_FindNode(const char *);
GNode *Targ_GetNode(const char *);
GNode *Targ_NewInternalNode(const char *);
GNode *Targ_GetEndNode(void);
GNodeList *Targ_FindList(StringList *);
Boolean Targ_Ignore(GNode *);
Boolean Targ_Silent(GNode *);
Boolean Targ_Precious(GNode *);
Boolean Targ_Ignore(const GNode *);
Boolean Targ_Silent(const GNode *);
Boolean Targ_Precious(const GNode *);
void Targ_SetMain(GNode *);
void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int);
@ -223,21 +223,40 @@ void Var_End(void);
typedef enum VarEvalFlags {
VARE_NONE = 0,
/* Treat undefined variables as errors. */
VARE_UNDEFERR = 0x01,
/* Expand and evaluate variables during parsing. */
VARE_WANTRES = 0x02,
/* In an assignment using the ':=' operator, keep '$$' as '$$' instead
* of reducing it to a single '$'. */
VARE_ASSIGN = 0x04
/* Expand and evaluate variables during parsing.
*
* TODO: Document what Var_Parse and Var_Subst return when this flag
* is not set. */
VARE_WANTRES = 1 << 0,
/* 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;
typedef enum VarSet_Flags {
VAR_NO_EXPORT = 0x01, /* do not export */
typedef enum VarSetFlags {
VAR_SET_NONE = 0,
/* do not export */
VAR_SET_NO_EXPORT = 1 << 0,
/* Make the variable read-only. No further modification is possible,
* except for another call to Var_Set with the same flag. */
VAR_SET_READONLY = 0x02
} VarSet_Flags;
VAR_SET_READONLY = 1 << 1
} VarSetFlags;
/* The state of error handling returned by Var_Parse.
*
@ -296,7 +315,7 @@ typedef enum VarParseResult {
void Var_Delete(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 *);
Boolean Var_Exists(const char *, GNode *);
const char *Var_Value(const char *, GNode *, void **);

706
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
@ -71,7 +71,7 @@
#include "make.h"
/* "@(#)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. */
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)
* 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,
* etc... are expanded. In this case, the return value is NULL on parse
* errors.
* etc... are expanded. In this case, return NULL on parse errors.
*
* 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
* case, nothing needs to be freed.
* Returns the fractured words, which must be freed later using Words_Free,
* unless the returned Words.words was NULL.
*/
Words
Str_Words(const char *str, Boolean expand)
@ -139,9 +137,8 @@ Str_Words(const char *str, Boolean expand)
char *word_end;
const char *str_p;
/* skip leading space chars. */
for (; *str == ' ' || *str == '\t'; ++str)
continue;
/* XXX: why only hspace, not whitespace? */
cpp_skip_hspace(&str); /* skip leading space chars. */
/* words_buf holds the words, separated by '\0'. */
str_len = strlen(str);
@ -239,7 +236,7 @@ Str_Words(const char *str, Boolean expand)
case '\n':
/* hmmm; fix it up as best we can */
ch = '\\';
--str_p;
str_p--;
break;
case 'b':
ch = '\b';
@ -264,21 +261,15 @@ Str_Words(const char *str, Boolean expand)
*word_end++ = ch;
}
done:
words[words_len] = NULL;
words[words_len] = NULL; /* useful for argv */
return (Words){ words, words_len, words_buf };
}
/*
* 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.
*
* 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.
* XXX: this function does not detect or report malformed patterns.
*/
Boolean
Str_Match(const char *str, const char *pat)
@ -286,12 +277,12 @@ Str_Match(const char *str, const char *pat)
for (;;) {
/*
* 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.
*/
if (*pat == 0)
return *str == 0;
if (*str == 0 && *pat != '*')
if (*pat == '\0')
return *str == '\0';
if (*str == '\0' && *pat != '*')
return FALSE;
/*
@ -302,9 +293,9 @@ Str_Match(const char *str, const char *pat)
pat++;
while (*pat == '*')
pat++;
if (*pat == 0)
if (*pat == '\0')
return TRUE;
while (*str != 0) {
while (*str != '\0') {
if (Str_Match(str, pat))
return TRUE;
str++;
@ -327,15 +318,18 @@ Str_Match(const char *str, const char *pat)
pat += neg ? 2 : 1;
for (;;) {
if (*pat == ']' || *pat == 0) {
if (*pat == ']' || *pat == '\0') {
if (neg)
break;
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)
break;
if (pat[1] == '-') {
if (pat[2] == 0)
if (pat[2] == '\0')
return neg;
if (*pat <= *str && pat[2] >= *str)
break;
@ -345,11 +339,11 @@ Str_Match(const char *str, const char *pat)
}
pat++;
}
if (neg && *pat != ']' && *pat != 0)
if (neg && *pat != ']' && *pat != '\0')
return FALSE;
while (*pat != ']' && *pat != 0)
while (*pat != ']' && *pat != '\0')
pat++;
if (*pat == 0)
if (*pat == '\0')
pat--;
goto thisCharOK;
}
@ -360,7 +354,7 @@ Str_Match(const char *str, const char *pat)
*/
if (*pat == '\\') {
pat++;
if (*pat == 0)
if (*pat == '\0')
return FALSE;
}

545
suff.c

File diff suppressed because it is too large Load Diff

166
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
@ -68,19 +68,17 @@
* SUCH DAMAGE.
*/
/*-
* targ.c --
* Functions for maintaining the Lst allTargets. Target nodes are
* kept in two structures: a Lst and a hash table.
/*
* Maintaining the targets and sources, which are both implemented as GNode.
*
* 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_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
* hash table, though all its fields are
* initialized.
@ -121,23 +119,26 @@
#include "dir.h"
/* "@(#)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 */
#ifdef CLEANUP
static GNodeList *allGNs; /* List of all the GNodes */
#endif
static HashTable targets; /* a hash table of same */
/* All target nodes found so far, but not the source nodes. */
static GNodeList *allTargets;
static HashTable allTargetsByName;
#ifdef CLEANUP
static void TargFreeGN(void *);
static GNodeList *allNodes;
static void GNode_Free(void *);
#endif
void
Targ_Init(void)
{
allTargets = Lst_New();
HashTable_Init(&targets);
HashTable_Init(&allTargetsByName);
#ifdef CLEANUP
allNodes = Lst_New();
#endif
}
void
@ -146,56 +147,67 @@ Targ_End(void)
Targ_Stats();
#ifdef CLEANUP
Lst_Free(allTargets);
if (allGNs != NULL)
Lst_Destroy(allGNs, TargFreeGN);
HashTable_Done(&targets);
HashTable_Done(&allTargetsByName);
Lst_Destroy(allNodes, GNode_Free);
#endif
}
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 *
Targ_List(void)
{
return allTargets;
}
/* Create and initialize a new graph node. The gnode is added to the list of
* all gnodes.
/* Create a new graph node, but don't register it anywhere.
*
* Input:
* name the name of the node, such as "clean", "src.c", ".END"
* Graph nodes that appear on the left-hand side of a dependency line such
* 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 *
Targ_NewGN(const char *name)
GNode_New(const char *name)
{
GNode *gn;
gn = bmake_malloc(sizeof(GNode));
gn = bmake_malloc(sizeof *gn);
gn->name = bmake_strdup(name);
gn->uname = NULL;
gn->path = NULL;
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->checked_seqno = 0;
gn->made = UNMADE;
gn->unmade = 0;
gn->mtime = 0;
gn->youngestChild = NULL;
gn->implicitParents = Lst_New();
gn->cohorts = Lst_New();
gn->parents = Lst_New();
gn->children = Lst_New();
gn->order_pred = 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);
gn->commands = Lst_New();
gn->suffix = NULL;
@ -203,9 +215,7 @@ Targ_NewGN(const char *name)
gn->lineno = 0;
#ifdef CLEANUP
if (allGNs == NULL)
allGNs = Lst_New();
Lst_Append(allGNs, gn);
Lst_Append(allNodes, gn);
#endif
return gn;
@ -213,24 +223,29 @@ Targ_NewGN(const char *name)
#ifdef CLEANUP
static void
TargFreeGN(void *gnp)
GNode_Free(void *gnp)
{
GNode *gn = gnp;
free(gn->name);
free(gn->uname);
free(gn->path);
Lst_Free(gn->implicitParents);
Lst_Free(gn->cohorts);
Lst_Free(gn->parents);
Lst_Free(gn->children);
Lst_Free(gn->order_succ);
Lst_Free(gn->order_pred);
HashTable_Done(&gn->context);
Lst_Free(gn->commands);
/* XXX: does gn->suffix need to be freed? It is reference-counted. */
/* gn->youngestChild is not owned by this node. */
Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */
Lst_Free(gn->parents); /* as they are not owned by this node. */
Lst_Free(gn->children); /* likewise */
Lst_Free(gn->order_pred); /* likewise */
Lst_Free(gn->order_succ); /* likewise */
Lst_Free(gn->cohorts); /* likewise */
HashTable_Done(&gn->context); /* ... but not the variables themselves,
* even though they are owned by this node.
* XXX: they should probably be freed. */
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);
}
@ -240,7 +255,7 @@ TargFreeGN(void *gnp)
GNode *
Targ_FindNode(const char *name)
{
return HashTable_FindValue(&targets, name);
return HashTable_FindValue(&allTargetsByName, name);
}
/* Get the existing global node, or create it. */
@ -248,7 +263,7 @@ GNode *
Targ_GetNode(const char *name)
{
Boolean isNew;
HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew);
HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew);
if (!isNew)
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.
*
* 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 *
Targ_NewInternalNode(const char *name)
{
GNode *gn = Targ_NewGN(name);
GNode *gn = GNode_New(name);
Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
Lst_Append(allTargets, gn);
if (doing_depend)
@ -274,8 +291,10 @@ Targ_NewInternalNode(const char *name)
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)
{
/* 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. */
Boolean
Targ_Ignore(GNode *gn)
Targ_Ignore(const GNode *gn)
{
return opts.ignoreErrors || gn->type & OP_IGNORE;
}
/* Return true if be silent when creating gn. */
Boolean
Targ_Silent(GNode *gn)
Targ_Silent(const GNode *gn)
{
return opts.beSilent || gn->type & OP_SILENT;
}
/* See if the given target is precious. */
Boolean
Targ_Precious(GNode *gn)
Targ_Precious(const GNode *gn)
{
/* XXX: Why are '::' targets precious? */
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 */
/* Set our idea of the main target we'll be creating. Used for debugging
* output. */
/* Remember the main target to make; only used for debugging. */
void
Targ_SetMain(GNode *gn)
{
@ -459,12 +480,12 @@ Targ_PrintNode(GNode *gn, int pass)
debug_printf("# *** MAIN TARGET ***\n");
}
if (pass >= 2) {
if (gn->unmade) {
if (gn->unmade > 0) {
debug_printf("# %d unmade children\n", gn->unmade);
} else {
debug_printf("# No unmade children\n");
}
if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
if (gn->mtime != 0) {
debug_printf("# last modified %s: %s\n",
Targ_FmtTime(gn->mtime),
@ -532,20 +553,27 @@ Targ_PrintGraph(int pass)
{
debug_printf("#*** Input graph:\n");
Targ_PrintNodes(allTargets, pass);
debug_printf("\n\n");
debug_printf("#\n# Files that are only sources:\n");
debug_printf("\n");
debug_printf("\n");
debug_printf("#\n");
debug_printf("# Files that are only sources:\n");
PrintOnlySources();
debug_printf("#*** Global Variables:\n");
Var_Dump(VAR_GLOBAL);
debug_printf("#*** Command-line Variables:\n");
Var_Dump(VAR_CMDLINE);
debug_printf("\n");
Dir_PrintDirectories();
debug_printf("\n");
Suff_PrintAll();
}
/* Propagate some type information to cohort nodes (those from the ::
/* Propagate some type information to cohort nodes (those from the '::'
* dependency operator).
*
* 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)
#
@ -36,8 +36,11 @@
# src/tests/usr.bin/make/t_make.sh as well.
#TESTS+= archive
TESTS+= archive-suffix
TESTS+= cmd-errors
TESTS+= cmd-errors-lint
TESTS+= cmd-interrupt
TESTS+= cmdline
TESTS+= cmdline-undefined
TESTS+= comment
TESTS+= cond-cmp-numeric
TESTS+= cond-cmp-numeric-eq
@ -58,8 +61,10 @@ TESTS+= cond-func-target
TESTS+= cond-late
TESTS+= cond-op
TESTS+= cond-op-and
TESTS+= cond-op-and-lint
TESTS+= cond-op-not
TESTS+= cond-op-or
TESTS+= cond-op-or-lint
TESTS+= cond-op-parentheses
TESTS+= cond-short
TESTS+= cond-token-number
@ -144,6 +149,7 @@ TESTS+= directive-for
TESTS+= directive-for-generating-endif
TESTS+= directive-hyphen-include
TESTS+= directive-if
TESTS+= directive-if-nested
TESTS+= directive-ifdef
TESTS+= directive-ifmake
TESTS+= directive-ifndef
@ -156,7 +162,6 @@ TESTS+= directive-undef
TESTS+= directive-unexport
TESTS+= directive-unexport-env
TESTS+= directive-warning
TESTS+= directives
TESTS+= dollar
TESTS+= doterror
TESTS+= dotwait
@ -169,9 +174,11 @@ TESTS+= export-env
TESTS+= export-variants
TESTS+= forloop
TESTS+= forsubst
TESTS+= gnode-submake
TESTS+= hanoi-include
TESTS+= impsrc
TESTS+= include-main
TESTS+= job-flags
#TESTS+= job-output-long-lines
TESTS+= lint
TESTS+= make-exported
@ -180,6 +187,7 @@ TESTS+= modmatch
TESTS+= modmisc
TESTS+= modts
TESTS+= modword
TESTS+= objdir-writable
TESTS+= opt
TESTS+= opt-backwards
TESTS+= opt-chdir
@ -223,6 +231,7 @@ TESTS+= opt-query
TESTS+= opt-raw
TESTS+= opt-silent
TESTS+= opt-touch
TESTS+= opt-touch-jobs
TESTS+= opt-tracefile
TESTS+= opt-var-expanded
TESTS+= opt-var-literal
@ -248,7 +257,9 @@ TESTS+= sh-multi-line
TESTS+= sh-single-line
TESTS+= shell-csh
TESTS+= shell-custom
.if exists(/bin/ksh)
TESTS+= shell-ksh
.endif
TESTS+= shell-sh
TESTS+= suff-add-later
TESTS+= suff-clear-regular
@ -256,6 +267,7 @@ TESTS+= suff-clear-single
TESTS+= suff-lookup
TESTS+= suff-main
TESTS+= suff-rebuild
TESTS+= suff-self
TESTS+= suff-transform-endless
TESTS+= suff-transform-expand
TESTS+= suff-transform-select
@ -366,38 +378,54 @@ TESTS+= varname-makeflags
TESTS+= varname-pwd
TESTS+= varname-vpath
TESTS+= varparse-dynamic
TESTS+= varparse-errors
TESTS+= varparse-mod
TESTS+= varparse-undef-partial
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.
# The base environment is -i PATH="$PATH".
ENV.depsrc-optional+= TZ=UTC
ENV.envfirst= FROM_ENV=value-from-env
ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR}
ENV.varmisc= FROM_ENV=env
ENV.varmisc+= FROM_ENV_BEFORE=env
ENV.varmisc+= FROM_ENV_AFTER=env
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.
# 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.
FLAGS.cond-func-make= via-cmdline
FLAGS.directive-ifmake= first second
FLAGS.doterror= # none
FLAGS.envfirst= -e
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'
FLAGS.doterror= # none, especially not -k
FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
# Some tests need extra postprocessing.
SED_CMDS.export= \
@ -406,6 +434,9 @@ SED_CMDS.export= \
.for t in export-all export-env
SED_CMDS.$t= ${SED_CMDS.export}
.endfor
SED_CMDS.directive-export-gmake= \
${:D dash is a pain } \
-e /non-zero/d
SED_CMDS.job-output-long-lines= \
${:D Job separators on their own line are ok. } \
-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. } \
-e '/^aa*--- job-b ---$$/d' \
-e '/^bb*--- job-a ---$$/d'
SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g'
SED_CMDS.opt-debug-graph1= \
-e 's,${.CURDIR},CURDIR,'
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>,'
# 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.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+= \
-e 's,\(Regex compilation error:\).*,\1 (details 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-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
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.
POSTPROC.deptgt-suffixes= \
${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'
# 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'
_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: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
_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
# 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
# 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.
#
# 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.
# 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
# in PSD.doc/tutorial.ms.
#
# 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
# strictly necessary.
# archive handling. That's why it deviates from the tutorial style of
# several other tests.
ARCHIVE= libprog.a
FILES= archive.mk modmisc.mk varmisc.mk
MAKE_CMD= ${.MAKE} -f ${MAKEFILE}
RUN?= @set -eu;
all:
.if ${.PARSEDIR:tA} != ${.CURDIR:tA}
@cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR}
@ -20,13 +17,13 @@ all:
# The following targets create and remove files. The filesystem cache in
# dir.c would probably not handle this correctly, therefore each of the
# targets is run in its separate sub-make.
${RUN} ${MAKE_CMD} remove-archive
${RUN} ${MAKE_CMD} create-archive
${RUN} ${MAKE_CMD} list-archive
${RUN} ${MAKE_CMD} list-archive-wildcard
${RUN} ${MAKE_CMD} depend-on-existing-member
${RUN} ${MAKE_CMD} depend-on-nonexistent-member
${RUN} ${MAKE_CMD} remove-archive
@${MAKE} -f ${MAKEFILE} remove-archive
@${MAKE} -f ${MAKEFILE} create-archive
@${MAKE} -f ${MAKEFILE} list-archive
@${MAKE} -f ${MAKEFILE} list-archive-wildcard
@${MAKE} -f ${MAKEFILE} depend-on-existing-member
@${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member
@${MAKE} -f ${MAKEFILE} remove-archive
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
# 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.
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
${RUN} echo $@
@echo $@
depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post
${RUN} echo $@
@echo $@
remove-archive: pre post
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.
#
@ -22,7 +22,7 @@ all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-af
clean-before clean-after: .PHONY
@rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious
interrupt-ordinary: .PHONY
interrupt-ordinary:
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true
# The ././ is necessary to work around the file cache.
@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.
RUN?= @set -eu;
TMPBASE?= /tmp
SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # 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
prepare-dirs:
${RUN} rm -rf ${DIR2} ${DIR12}
${RUN} mkdir -p ${DIR2} ${DIR12}
@rm -rf ${DIR2} ${DIR12}
@mkdir -p ${DIR2} ${DIR12}
# The .OBJDIR can be set via the MAKEOBJDIR command line variable.
# It must be a command line variable; an environment variable would not work.
makeobjdir-direct:
@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,
# and that variable could even contain the usual modifiers.
@ -31,7 +30,7 @@ makeobjdir-direct:
# see MAKE_CMD.
makeobjdir-indirect:
@echo $@:
${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
@${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
show-objdir:
@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.
@ -12,7 +12,7 @@ that \
goes \
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.
# These are not shell commands, they are just makefile comments.
@ -21,6 +21,8 @@ on and on.
.endif # And after the closing directive.
VAR= # This comment makes the variable value empty.
# ParseGetLine removes any whitespace before the
# comment.
.if ${VAR} != ""
. error
.endif
@ -35,7 +37,9 @@ VAR= value
. error
.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
.if ${VAR} != "not part of the comment"
. error
@ -55,7 +59,7 @@ WORDS= ${VAR:[#]} [#
. error
.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 \\\\\
or 9 backslashes. \\\\\\\\\
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 54: Malformed conditional (!(12345 = 12345))
make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345))
make: "cond-cmp-numeric-eq.mk" line 67: warning: Unknown operator
make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345))
make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345))
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
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.
@ -49,6 +49,19 @@
. error
.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.
.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)
CondParser_Eval: !(${:UNaN} == NaN)
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: stopped in unit-tests
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.
@ -25,5 +25,17 @@
. error
.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:
@:;

View File

@ -1,8 +1,8 @@
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: 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 42: Malformed conditional ("string" != "str""ing")
make: "cond-cmp-string.mk" line 49: warning: String comparison operator must be either == or !=
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: stopped in unit-tests
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.
@ -19,9 +19,14 @@
. error
.endif
# The left-hand side of the comparison requires a defined variable.
# The variable named "" is not defined, but applying the :U modifier to it
# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here.
# The left-hand side of the comparison requires that any variable expression
# is defined.
#
# 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"
. error
.endif
@ -33,9 +38,11 @@
.endif
# 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"
. error
.else
. error
.endif
# There is no = operator for strings.
@ -88,3 +95,16 @@
.if ${:U word } != " ${:Uword} "
. error
.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

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
# a single operand. If the operand is a number, it is compared to zero,
@ -25,6 +25,9 @@
.endif
# 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}
. error
.endif
@ -40,4 +43,16 @@
. error
.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

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.
.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)
. error
.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: 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: stopped in unit-tests
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.
@ -29,5 +29,24 @@ ${:UA B}= variable name with spaces
. error
.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:
@:;

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
# expression for emptiness.
#
# Note that the argument in the parentheses is indeed a variable name,
# optionally followed by variable modifiers. This is like the defined()
# function.
# optionally followed by variable modifiers.
#
.undef UNDEF
@ -25,13 +24,15 @@ WORD= word
. error
.endif
# The :S modifier replaces the empty value with an actual word, and
# after that the expression is no longer empty. Because the variable
# was undefined in the first place, the expression has the flag VAR_JUNK
# but not VAR_KEEP, therefore it is still considered undefined.
# Only very few variable modifiers turn an undefined variable expression
# into a defined variable expression. The :U and :D modifiers belong to
# that group, but :S doesn't (see VAR_KEEP).
# The :S modifier replaces the empty value with an actual word. The
# expression is now no longer empty, but it is still possible to see whether
# the expression was based on an undefined variable. The expression has the
# flag VEF_UNDEF.
#
# The expression does not have the flag VEF_DEF though, therefore it is still
# 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
# implementation details.
@ -49,13 +50,14 @@ WORD= word
.endif
# 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
# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide
# whether the variable is defined or not. This kind of makes sense since the
# :U modifier tests the _variable_, not the _expression_.
# undefined expression makes it non-empty, but the marker VEF_UNDEF is
# preserved nevertheless. The :U modifier that follows only looks at the
# VEF_UNDEF flag to decide whether the variable is defined or not. This kind
# 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
# used in this expression.
# But since the variable was undefined to begin with, the fallback value from
# the :U modifier is used in this expression.
#
.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback"
. error
@ -128,7 +130,7 @@ ${:U }= space
# 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'
# 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)
. error
.endif
@ -146,5 +148,12 @@ ${:U WORD }= variable name with spaces
. error
.endif
# Parse error: missing closing parenthesis.
.if empty(WORD
. error
.else
. error
.endif
all:
@:;

View File

@ -1,9 +1,15 @@
make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 29: Malformed conditional (!defined(A B))
make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B))
make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B))
make: "cond-func.mk" line 36: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 36: Malformed conditional (!defined(A B))
make: "cond-func.mk" line 51: warning: Missing closing parenthesis for defined()
make: "cond-func.mk" line 51: Malformed conditional (!defined(A&B))
make: "cond-func.mk" line 54: warning: Missing closing parenthesis for defined()
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: stopped in unit-tests
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
# among several functions.
#
# 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
${:UA B}= variable name with spaces
${: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)
. error
@ -59,6 +66,12 @@ ${:UVAR{value}}= variable name with braces
. error
.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
# inside the parentheses. The spaces inside the parentheses are not
# allowed for the empty() function (see cond-func-empty.mk), therefore
@ -67,5 +80,58 @@ ${:UVAR{value}}= variable name with braces
. error
.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:
@:;

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
# 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
# different from many other places.
#
@ -11,15 +13,15 @@
# They should also not contain operators like == or <, since these are
# 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
COND.true= "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:
@echo ${ ${COND.true} :?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

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.
.if !1
@ -18,5 +18,42 @@
. error
.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:
@:;

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

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.
# TODO: Implementation
# Test for deeply nested conditions.
.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
(((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
1 \
)))))))))))))))))))))))))))))))))))))))))))))))))))))))) \
))))))))))))))))))))))))))))))))))))))))))))))))))))))))
. info Parentheses can be nested at least to depth 112.
.else
. error
.endif
all:
@:;

View File

@ -1,7 +1,16 @@
make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word)
make: "cond-op.mk" line 70: 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 78: Parsing continues until here.
make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word)
make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated})
make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated.
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: stopped in unit-tests
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.
#
@ -9,8 +9,8 @@
# cond-op-parentheses.mk
# In make, && binds more tightly than ||, like in C.
# If make had the same precedence for both && and ||, the result would be
# different.
# If make had the same precedence for both && and ||, like in the shell,
# the result would be different.
# If || were to bind more tightly than &&, the result would be different
# as well.
.if !(1 || 1 && 0)
@ -18,13 +18,17 @@
.endif
# 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
. error
.endif
# 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"
. error
.endif
@ -36,7 +40,8 @@
# TODO: Demonstrate that the precedence of the ! and == operators actually
# 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
# appear unquoted. If any, it must be enclosed in quotes.
@ -71,11 +76,27 @@
. error
.endif
.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
# Just in case that parsing should ever stop on the first error.
.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:
@:;

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 ||
# 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 ||
# operator was always evaluated, which was wrong.
#
# TODO: Had the evaluation been correct at some time before 2015-11-12?
# The && operator.
@ -113,6 +116,9 @@ VAR= # empty again, for the following tests
# make sure these do not cause complaint
#.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
iV1= ${V42}
iV2= ${V66}
@ -167,5 +173,16 @@ x= Fail
.endif
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:
@:;:

View File

@ -1,8 +1,8 @@
make: "cond-token-number.mk" line 13: Malformed conditional (-0)
make: "cond-token-number.mk" line 21: Malformed conditional (+0)
make: "cond-token-number.mk" line 29: Malformed conditional (!-1)
make: "cond-token-number.mk" line 37: Malformed conditional (!+1)
make: "cond-token-number.mk" line 54: End of the tests.
make: "cond-token-number.mk" line 15: Malformed conditional (-0)
make: "cond-token-number.mk" line 25: Malformed conditional (+0)
make: "cond-token-number.mk" line 35: Malformed conditional (!-1)
make: "cond-token-number.mk" line 45: Malformed conditional (!+1)
make: "cond-token-number.mk" line 80: End of the tests.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
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.
#
# TODO: Add introduction.
.if 0
. error
@ -12,6 +14,8 @@
# See the ch_isdigit call in CondParser_String.
.if -0
. error
.else
. error
.endif
# 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.
.if +0
. error
.else
. error
.endif
# 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.
.if !-1
. error
.else
. error
.endif
# 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.
.if !+1
. error
.else
. error
.endif
# When the number comes from a variable expression though, it may be signed.
@ -50,6 +60,22 @@
. error
.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.
.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)
# in .if conditions.
@ -14,7 +14,7 @@
# parser gets to see it.
#
# 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"
. error
.endif
@ -31,16 +31,19 @@
# comment handling anymore. The comments are supposed to be stripped off
# 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
# side is double-quotes, backslash, backslash.
# XXX: It is unexpected that the right-hand side evaluates to a single
# backslash.
# side before unescaping is double-quotes, backslash, backslash.
.if ${:U\\} != "\\#hash"
. error
.endif
# The right-hand side of a comparison is not parsed as a token, therefore
# 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
. error
.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.
# 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:
@:;

View File

@ -1,7 +1,7 @@
make: "cond-token-var.mk" line 9: ok
make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF})
make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF})
make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF})
make: "cond-token-var.mk" line 20: ok
make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF})
make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF})
make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
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
@ -12,11 +23,13 @@ DEF= defined
.endif
# A variable that appears on the left-hand side must be defined.
# The following line thus generates a parse error.
.if ${UNDEF} == ${DEF}
. error
.endif
# A variable that appears on the right-hand side must be defined.
# The following line thus generates a parse error.
.if ${DEF} == ${UNDEF}
. error
.endif
@ -25,10 +38,11 @@ DEF= defined
.if ${DEF}
.endif
# An undefined variable generates a warning.
# An undefined variable on its own generates a parse error.
.if ${UNDEF}
.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}
.endif

View File

@ -1,7 +1,7 @@
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 "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: stopped in unit-tests
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.
#
@ -42,6 +42,10 @@ DEF= defined
.endif
# 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}}
. error
.else

View File

@ -1,5 +1,5 @@
make: "cond1.mk" line 75: warning: extra else
make: "cond1.mk" line 85: warning: extra else
make: "cond1.mk" line 80: warning: extra else
make: "cond1.mk" line 90: warning: extra else
2 is prime
A='other' B='unknown' C='clever' o='no,no'
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!
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::
@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

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,
# which ignores any command failures for that target.
#
# Even though ignore-errors fails, the all target is still made.
# Since the all target is not marked with .IGNORE, it stops at the
# first failing command.
# Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is
# not marked with .IGNORE, it stops at the first failing command.
#
# XXX: The ordering of the messages in the output is confusing.
# 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
# following breakpoints in CompatRunCommand:
#
# * the "!silent" line, to see all commands.
# * the "fflush" line, to see stdout being flushed.
# * the "!silent" line, to see all commands
# * the "fflush" line, to see stdout being flushed
# * the "status = WEXITSTATUS" line
# * the "(continuing)" 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
# executes the commands of the target even if the -n or -t command line
# options are given.
# TODO: Add a test for the -t command line option.
.MAKEFLAGS: -n
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

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,
# which ignores the target if make cannot find out how to create it.
#
# 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
: ${.TARGET} is made.
important: optional
important: optional optional-cohort
: ${.TARGET} is made.
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