Import bmake-20220204

Features of interest:

Allow setting target local variables (similar to gmake)

In META_MODE .MAKE.META.CMP_FILTER can be used for filtering commands
before comparion.

contrib/bmake/mk/cc-wrap.mk is an example of using these

See ChangeLog for the gory details.
This commit is contained in:
Simon J. Gerraty 2022-02-05 12:03:50 -08:00
parent 2935fe8237
commit cdde9e894d
218 changed files with 5446 additions and 4172 deletions

155
ChangeLog
View File

@ -1,3 +1,158 @@
2022-02-04 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220204
Merge with NetBSD make, pick up
o use unsigned consistently for line numbers, avoid the need for %z
o parse.c: do not step off end of input in Parse_IsVar
when checking for target local variable assignments
2022-02-02 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220202
Merge with NetBSD make, pick up
o remove redundant declaration of HashIter_Init
o make DEBUG0 simpler
2022-01-30 Simon J Gerraty <sjg@beast.crufty.net>
* cast gn->lineno to avoid %z
* VERSION (_MAKE_VERSION): 20220130
Merge with NetBSD make, pick up
o more unit tests
o make GNode lineno unsigned to please lint
o print location of recursive variable references in commands
o print "stack trace" (makefile includes) on fatal errors
o make.1: refine documentation for target local assignments
2022-01-28 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220128
Merge with NetBSD make, pick up
o inline functions called only once
o for.c: clean up AddEscape for building the body of a .for loop
o hash.c: merge duplicate code for finding an entry in a hash table
replace HashEntry_KeyEquals with strncmp
o make.1: document quirks of target local variable assignments.
o parse.c: cleanup white-space
2022-01-26 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220126
Merge with NetBSD make, pick up
o allow setting target local variables
o more unit tests
o add missing newline after "cannot continue" message
o meta.c: clean up eat_dots
o parse.c: fix filename in warning about duplicate script
o var.c: when expanding nested variables, check simple things first
2022-01-16 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220116
Merge with NetBSD make, pick up
o fix for unit-tests/varname-makeflags on non-BSD systems
o use Var_Exists rather than Var_Value where appropriate
o remove unnecessary functions for expanding variable names
o cond.c: inline EvalBare
o main.c: lint cleanup
o parse.c: condense code in Parse_IsVar
use islower for parsing directives (none have upper case)
2022-01-12 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220112
Merge with NetBSD make, pick up
o meta.c: add .MAKE.META.CMP_FILTER for filtering commands before
comparion, rarely needed but useful when it is.
2022-01-10 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220110
Merge with NetBSD make, pick up
o inline Buf_Clear
o remove redundant braces
o rename and inline Targ_Precious
o cond.c: remove redundant initializer in CondParser_ComparisonOrLeaf
o for.c: clean up handling of .for loops
fix reported line numbers of continuation lines
add details about .for loop variables to stack traces
o job.c: reduce code for initializing error handling in shell
o main.c: in Cmd_Exec, return error message instead of format string
have as few statements as possible between va_start and va_end
add debug logging for capturing the output of external commands
o make.c: use consistent variable names for varargs
o make_malloc.c: remove duplicate code from bmake_strdup
o parse.c: add missing printflike annotations
remove redundant lines from stack traces
fix stack traces in -dp mode
reduce confusing code in ParseForLoop
fix line number in debug log after returning from a file
rename IFile and its fields to match their actual content
clean up ParseDependencySources
o var.c: shorten ApplyModifier_Assign
rename is_shell_metachar, fix character conversion warning
merge calls to ApplyModifier_Time
merge duplicate code for modifiers 'gmtime' and 'localtime'
2022-01-04 Simon J Gerraty <sjg@beast.crufty.net>
* parse.c: loadfile restore extra byte in buffer.
2022-01-01 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20220101
Merge with NetBSD make, pick up
o more unit-tests
o remove unnecessary words from command line options in CmdOpts
o rename eunlink to unlink_file
o cond.c: make ParseWord in condition parser simpler
internally return false for irrelevant leaves in conditions
replace table for function lookup in conditions with simple code
merge duplicate types CondEvalResult and CondResult
o for.c: clean up handling of .for loops and .include directives
o main.c: constify cached_realpath
clean up Cmd_Exec
o parse.c: sync API documentation
fix error message when reading more than 1 GB from stdin
clean up parsing of makefiles
fix line number in error message about open conditionals
unexport types VarAssignOp and VarAssign
clean up function names
remove redundant parameters in dependency parsing functions
reduce scope of the list of wildcard target names
extract OP_NOTARGET into separate function
clean up variable names for parsing dependency lines
make debug logging a bit more human-friendly
o var.c: condense code in ApplyModifier_Assign
2021-12-21 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20211221
Merge with NetBSD make, pick up
o more unit-tests
o style cleanup
o in CLEANUP mode, free interned strings at the very end
o fix memory leak for filenames in .for loops
o buf.c: avoid memory leak
o cond.c: condense CondParser_ComparisonOp
o hash.c: change return type of HashTable_Set to void
o job.c: change return type of Compat_RunCommand from int to bool
o main.c: remove bmake_free
o parse.c: condense repetetive code in ParseDirective
remove dead code for handling traditional include directives
clean up parsing of variable assignments
remove unreachable code for parsing the dependency operator
clean up loading of files
fix memory leak in IncludeFile
o var.c: fix memory leak when parsing a variable name
fix memory leak from ${.SUFFIXES}
reduce memory allocation in modifier ':?' and ':C'
condense RegexReplace for the modifier ':C' and avoid strlen
merge duplicate code for memory handling in Var_Parse
distinguish between short-lived and environment variables
rename VarFreeEnv to VarFreeShortLived
2021-12-15 Simon J Gerraty <sjg@beast.crufty.net>
* cond.c: fix mem leak in CondParser_Leaf

41
FILES
View File

@ -53,7 +53,6 @@ metachar.c
metachar.h
missing/sys/cdefs.h
mkdeps.sh
nonints.h
os.sh
parse.c
pathnames.h
@ -173,10 +172,14 @@ unit-tests/dep-double-colon-indep.exp
unit-tests/dep-double-colon-indep.mk
unit-tests/dep-double-colon.exp
unit-tests/dep-double-colon.mk
unit-tests/dep-duplicate.exp
unit-tests/dep-duplicate.mk
unit-tests/dep-exclam.exp
unit-tests/dep-exclam.mk
unit-tests/dep-none.exp
unit-tests/dep-none.mk
unit-tests/dep-op-missing.exp
unit-tests/dep-op-missing.mk
unit-tests/dep-percent.exp
unit-tests/dep-percent.mk
unit-tests/dep-var.exp
@ -371,8 +374,6 @@ unit-tests/doterror.exp
unit-tests/doterror.mk
unit-tests/dotwait.exp
unit-tests/dotwait.mk
unit-tests/envfirst.exp
unit-tests/envfirst.mk
unit-tests/error.exp
unit-tests/error.mk
unit-tests/escape.exp
@ -427,10 +428,6 @@ unit-tests/modmatch.exp
unit-tests/modmatch.mk
unit-tests/modmisc.exp
unit-tests/modmisc.mk
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
@ -535,6 +532,8 @@ unit-tests/opt-var-expanded.exp
unit-tests/opt-var-expanded.mk
unit-tests/opt-var-literal.exp
unit-tests/opt-var-literal.mk
unit-tests/opt-version.exp
unit-tests/opt-version.mk
unit-tests/opt-warnings-as-errors.exp
unit-tests/opt-warnings-as-errors.mk
unit-tests/opt-where-am-i.exp
@ -547,6 +546,8 @@ unit-tests/order.exp
unit-tests/order.mk
unit-tests/parse-var.exp
unit-tests/parse-var.mk
unit-tests/parse.exp
unit-tests/parse.mk
unit-tests/phony-end.exp
unit-tests/phony-end.mk
unit-tests/posix.exp
@ -625,18 +626,6 @@ unit-tests/unexport.exp
unit-tests/unexport.mk
unit-tests/use-inference.exp
unit-tests/use-inference.mk
unit-tests/var-class-cmdline.exp
unit-tests/var-class-cmdline.mk
unit-tests/var-class-env.exp
unit-tests/var-class-env.mk
unit-tests/var-class-global.exp
unit-tests/var-class-global.mk
unit-tests/var-class-local-legacy.exp
unit-tests/var-class-local-legacy.mk
unit-tests/var-class-local.exp
unit-tests/var-class-local.mk
unit-tests/var-class.exp
unit-tests/var-class.mk
unit-tests/var-eval-short.exp
unit-tests/var-eval-short.mk
unit-tests/var-op-append.exp
@ -655,6 +644,18 @@ unit-tests/var-op.exp
unit-tests/var-op.mk
unit-tests/var-recursive.exp
unit-tests/var-recursive.mk
unit-tests/var-scope-cmdline.exp
unit-tests/var-scope-cmdline.mk
unit-tests/var-scope-env.exp
unit-tests/var-scope-env.mk
unit-tests/var-scope-global.exp
unit-tests/var-scope-global.mk
unit-tests/var-scope-local-legacy.exp
unit-tests/var-scope-local-legacy.mk
unit-tests/var-scope-local.exp
unit-tests/var-scope-local.mk
unit-tests/var-scope.exp
unit-tests/var-scope.mk
unit-tests/varcmd.exp
unit-tests/varcmd.mk
unit-tests/vardebug.exp
@ -663,6 +664,8 @@ unit-tests/varfind.exp
unit-tests/varfind.mk
unit-tests/varmisc.exp
unit-tests/varmisc.mk
unit-tests/varmod-assign-shell.exp
unit-tests/varmod-assign-shell.mk
unit-tests/varmod-assign.exp
unit-tests/varmod-assign.mk
unit-tests/varmod-defined.exp

View File

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

90
arch.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $ */
/* $NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -147,7 +147,7 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $");
MAKE_RCSID("$NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
@ -247,19 +247,20 @@ FullName(const char *archive, const char *member)
bool
Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
{
char *cp; /* Pointer into line */
char *spec; /* For modifying some bytes of *pp */
const char *cp; /* Pointer into line */
GNode *gn; /* New node */
MFStr libName; /* Library-part of specification */
FStr lib; /* Library-part of specification */
FStr mem; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */
bool expandLibName; /* Whether the parsed libName contains
* variable expressions that need to be
* expanded */
bool expandLib; /* Whether the parsed lib contains variable
* expressions that need to be expanded */
libName = MFStr_InitRefer(*pp);
expandLibName = false;
spec = *pp;
lib = FStr_InitRefer(spec);
expandLib = false;
for (cp = libName.str; *cp != '(' && *cp != '\0';) {
for (cp = lib.str; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
/* Expand nested variable expressions. */
/* XXX: This code can probably be shortened. */
@ -276,20 +277,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
if (isError)
return false;
expandLibName = true;
expandLib = true;
cp += nested_p - cp;
} else
cp++;
}
*cp++ = '\0';
if (expandLibName) {
char *expanded;
(void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded);
/* TODO: handle errors */
libName = MFStr_InitOwn(expanded);
}
spec[cp++ - spec] = '\0';
if (expandLib)
Var_Expand(&lib, scope, VARE_UNDEFERR);
for (;;) {
/*
@ -299,13 +295,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
bool doSubst = false;
pp_skip_whitespace(&cp);
cpp_skip_whitespace(&cp);
mem = FStr_InitRefer(cp);
while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) {
if (*cp == '$') {
/* Expand nested variable expressions. */
/* XXX: This code can probably be shortened. */
/*
* XXX: This code can probably be shortened.
*/
FStr result;
bool isError;
const char *nested_p = cp;
@ -334,8 +332,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
if (*cp == '\0') {
Parse_Error(PARSE_FATAL,
"No closing parenthesis "
"in archive specification");
"No closing parenthesis "
"in archive specification");
return false;
}
@ -346,7 +344,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
break;
saveChar = *cp;
*cp = '\0';
spec[cp - spec] = '\0';
/*
* XXX: This should be taken care of intelligently by
@ -363,19 +361,16 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
if (doSubst) {
char *fullName;
char *p, *expandedMem;
char *p;
const char *unexpandedMem = mem.str;
(void)Var_Subst(mem.str, scope, VARE_UNDEFERR,
&expandedMem);
/* TODO: handle errors */
mem = FStr_InitOwn(expandedMem);
Var_Expand(&mem, scope, VARE_UNDEFERR);
/*
* Now form an archive spec and recurse to deal with
* nested variables and multi-word variable values.
*/
fullName = FullName(libName.str, mem.str);
fullName = FullName(lib.str, mem.str);
p = fullName;
if (strcmp(mem.str, unexpandedMem) == 0) {
@ -404,7 +399,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
while (!Lst_IsEmpty(&members)) {
char *member = Lst_Dequeue(&members);
char *fullname = FullName(libName.str, member);
char *fullname = FullName(lib.str, member);
free(member);
gn = Targ_GetNode(fullname);
@ -416,7 +411,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
Lst_Done(&members);
} else {
char *fullname = FullName(libName.str, mem.str);
char *fullname = FullName(lib.str, mem.str);
gn = Targ_GetNode(fullname);
free(fullname);
@ -432,15 +427,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
}
FStr_Done(&mem);
*cp = saveChar;
spec[cp - spec] = saveChar;
}
MFStr_Done(&libName);
FStr_Done(&lib);
cp++; /* skip the ')' */
/* We promised that pp would be set up at the next non-space. */
pp_skip_whitespace(&cp);
*pp = cp;
cpp_skip_whitespace(&cp);
*pp += cp - *pp;
return true;
}
@ -672,7 +667,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
if (ar->fnametab != NULL) {
DEBUG0(ARCH,
"Attempted to redefine an SVR4 name table\n");
"Attempted to redefine an SVR4 name table\n");
return -1;
}
@ -693,8 +688,9 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
entry++;
*ptr = '\0';
}
DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n",
(unsigned long)entry);
DEBUG1(ARCH,
"Found svr4 archive name table with %lu entries\n",
(unsigned long)entry);
return 0;
}
@ -708,7 +704,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
}
if (entry >= ar->fnamesize) {
DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
inout_name, (unsigned long)ar->fnamesize);
inout_name, (unsigned long)ar->fnamesize);
return 2;
}
@ -737,8 +733,10 @@ ArchiveMember_HasName(const struct ar_hdr *hdr,
if (ar_name[namelen] == ' ')
return true;
/* In archives created by GNU binutils 2.27, the member names end with
* a slash. */
/*
* 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;
@ -809,9 +807,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
}
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);
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)) {
/*
@ -912,7 +910,7 @@ Arch_Touch(GNode *gn)
struct ar_hdr arh;
f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh,
"r+");
"r+");
if (f == NULL)
return;

51
bmake.1
View File

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $
.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg 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 December 12, 2021
.Dd January 28, 2022
.Dt BMAKE 1
.Os
.Sh NAME
@ -691,10 +691,38 @@ Variables defined as part of the command line.
Variables that are defined specific to a certain target.
.El
.Pp
Local variables are all built in and their values vary magically from
target to target.
It is not currently possible to define new local variables.
The seven local variables are as follows:
Local variables can be set on a dependency line, if
.Va .MAKE.TARGET_LOCAL_VARIABLES ,
is not set to
.Ql false .
The rest of the line
(which will already have had Global variables expanded),
is the variable value.
For example:
.Bd -literal -offset indent
COMPILER_WRAPPERS+= ccache distcc icecc
${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
.Ed
.Pp
Only the targets
.Ql ${OBJS}
will be impacted by that filter (in "meta" mode) and
simply enabling/disabling any of the wrappers will not render all
of those targets out-of-date.
.Pp
.Em NOTE :
target local variable assignments behave differently in that;
.Bl -tag -width Ds -offset indent
.It Ic \&+=
Only appends to a previous local assignment
for the same target and variable.
.It Ic \&:=
Is redundant with respect to Global variables,
which have already been expanded.
.El
.Pp
The seven built-in local variables are as follows:
.Bl -tag -width ".ARCHIVE" -offset indent
.It Va .ALLSRC
The list of all sources for this target; also known as
@ -846,6 +874,11 @@ For example:
would produce tokens like
.Ql ---make[1234] target ---
making it easier to track the degree of parallelism being achieved.
.It .MAKE.TARGET_LOCAL_VARIABLES
If set to
.Ql false ,
apparent variable assignments in dependency lines are
treated as normal sources.
.It Ev MAKEFLAGS
The environment variable
.Ql Ev MAKEFLAGS
@ -954,6 +987,12 @@ If a file that was generated outside of
.Va .OBJDIR
but within said bailiwick is missing,
the current target is considered out-of-date.
.It Va .MAKE.META.CMP_FILTER
In "meta" mode, it can (very rarely!) be useful to filter command
lines before comparison.
This variable can be set to a set of modifiers that will be applied to
each line of the old and new command that differ, if the filtered
commands still differ, the target is considered out-of-date.
.It Va .MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the meta files
updated.

View File

@ -443,9 +443,28 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Local variables
Variables that are defined specific to a certain target.
Local variables are all built in and their values vary magically from
target to target. It is not currently possible to define new local vari-
ables. The seven local variables are as follows:
Local variables can be set on a dependency line, if
.MAKE.TARGET_LOCAL_VARIABLES, is not set to `false'. The rest of the
line (which will already have had Global variables expanded), is the
variable value. For example:
COMPILER_WRAPPERS+= ccache distcc icecc
${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
Only the targets `${OBJS}' will be impacted by that filter (in "meta"
mode) and simply enabling/disabling any of the wrappers will not render
all of those targets out-of-date.
NOTE: target local variable assignments behave differently in that;
+= Only appends to a previous local assignment for the same
target and variable.
:= Is redundant with respect to Global variables, which have
already been expanded.
The seven built-in local variables are as follows:
.ALLSRC The list of all sources for this target; also known as
`>'.
@ -538,6 +557,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
ing it easier to track the degree of parallelism being
achieved.
.MAKE.TARGET_LOCAL_VARIABLES
If set to `false', apparent variable assignments in de-
pendency lines are treated as normal sources.
MAKEFLAGS The environment variable `MAKEFLAGS' may contain anything
that may be specified on bmake's command line. Anything
specified on bmake's command line is appended to the
@ -616,6 +639,14 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
generated outside of .OBJDIR but within said bailiwick is
missing, the current target is considered out-of-date.
.MAKE.META.CMP_FILTER
In "meta" mode, it can (very rarely!) be useful to filter
command lines before comparison. This variable can be
set to a set of modifiers that will be applied to each
line of the old and new command that differ, if the fil-
tered commands still differ, the target is considered
out-of-date.
.MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the
meta files updated. If not empty, it can be used to
@ -1591,4 +1622,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
There is no way of escaping a space character in a filename.
FreeBSD 13.0 December 12, 2021 FreeBSD 13.0
FreeBSD 13.0 January 28, 2022 FreeBSD 13.0

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 82 KiB

15
buf.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: buf.c,v 1.53 2021/11/28 22:48:06 rillig Exp $ */
/* $NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 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.53 2021/11/28 22:48:06 rillig Exp $");
MAKE_RCSID("$NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 rillig Exp $");
/* Make space in the buffer for adding at least 16 more bytes. */
void
@ -138,14 +138,6 @@ Buf_AddFlag(Buffer *buf, bool flag, const char *name)
}
}
/* Mark the buffer as empty, so it can be filled with data again. */
void
Buf_Empty(Buffer *buf)
{
buf->len = 0;
buf->data[0] = '\0';
}
/* Initialize a buffer. */
void
Buf_InitSize(Buffer *buf, size_t cap)
@ -214,8 +206,9 @@ Buf_DoneDataCompact(Buffer *buf)
if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) {
/* We trust realloc to be smart */
char *data = bmake_realloc(buf->data, buf->len + 1);
buf->data = NULL;
data[buf->len] = '\0'; /* XXX: unnecessary */
Buf_DoneData(buf);
Buf_Done(buf);
return data;
}
#endif

19
buf.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: buf.h,v 1.44 2021/11/28 22:48:06 rillig Exp $ */
/* $NetBSD: buf.h,v 1.47 2022/01/08 17:25:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -88,6 +88,14 @@ typedef struct Buffer {
void Buf_Expand(Buffer *);
/* Mark the buffer as empty, so it can be filled with data again. */
MAKE_INLINE void
Buf_Clear(Buffer *buf)
{
buf->len = 0;
buf->data[0] = '\0';
}
/* Buf_AddByte adds a single byte to a buffer. */
MAKE_INLINE void
Buf_AddByte(Buffer *buf, char byte)
@ -101,7 +109,7 @@ Buf_AddByte(Buffer *buf, char byte)
end[1] = '\0';
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
Buf_EndsWith(const Buffer *buf, char ch)
{
return buf->len > 0 && buf->data[buf->len - 1] == ch;
@ -112,11 +120,10 @@ void Buf_AddBytesBetween(Buffer *, const char *, const char *);
void Buf_AddStr(Buffer *, const char *);
void Buf_AddInt(Buffer *, int);
void Buf_AddFlag(Buffer *, bool, const char *);
void Buf_Empty(Buffer *);
void Buf_Init(Buffer *);
void Buf_InitSize(Buffer *, size_t);
void Buf_Done(Buffer *);
char *Buf_DoneData(Buffer *);
char *Buf_DoneDataCompact(Buffer *);
char *Buf_DoneData(Buffer *) MAKE_ATTR_USE;
char *Buf_DoneDataCompact(Buffer *) MAKE_ATTR_USE;
#endif /* MAKE_BUF_H */
#endif

View File

@ -1,4 +1,4 @@
/* $NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $ */
/* $NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -99,7 +99,7 @@
#include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $");
MAKE_RCSID("$NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
@ -112,10 +112,10 @@ static int compatSigno;
static void
CompatDeleteTarget(GNode *gn)
{
if (gn != NULL && !Targ_Precious(gn)) {
if (gn != NULL && !GNode_IsPrecious(gn)) {
const char *file = GNode_VarTarget(gn);
if (!opts.noExecute && eunlink(file) != -1) {
if (!opts.noExecute && unlink_file(file)) {
Error("*** %s removed", file);
}
}
@ -135,7 +135,7 @@ CompatInterrupt(int signo)
{
CompatDeleteTarget(curTarg);
if (curTarg != NULL && !Targ_Precious(curTarg)) {
if (curTarg != NULL && !GNode_IsPrecious(curTarg)) {
/*
* Run .INTERRUPT only if hit with interrupt signal
*/
@ -168,10 +168,12 @@ DebugFailedTarget(const char *cmd, const GNode *gn)
{
const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
gn->name);
gn->name);
/* Replace runs of whitespace with a single space, to reduce
* the amount of whitespace for multi-line command lines. */
/*
* 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(" ");
@ -220,24 +222,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED)
* ln List node that contains the command
*
* Results:
* 0 if the command succeeded, 1 if an error occurred.
* true if the command succeeded.
*/
int
bool
Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
{
char *cmdStart; /* Start of expanded command */
char *bp;
bool silent; /* Don't print command */
bool doIt; /* Execute even if -n */
volatile bool errCheck; /* Check errors */
volatile bool errCheck; /* Check errors */
WAIT_T reason; /* Reason for child's death */
WAIT_T status; /* Description of child's death */
pid_t cpid; /* Child actually found */
pid_t retstat; /* Result of wait */
const char **volatile av; /* Argument vector for thing to exec */
char **volatile mav; /* Copy of the argument vector for freeing */
bool useShell; /* True if command should be executed
* using a shell */
bool useShell; /* True if command should be executed using a
* shell */
const char *volatile cmd = cmdp;
silent = (gn->type & OP_SILENT) != OP_NONE;
@ -249,7 +251,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
if (cmdStart[0] == '\0') {
free(cmdStart);
return 0;
return true;
}
cmd = cmdStart;
LstNode_Set(ln, cmdStart);
@ -269,12 +271,12 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* usual '$$'.
*/
Lst_Append(&endNode->commands, cmdStart);
return 0;
return true;
}
}
if (strcmp(cmdStart, "...") == 0) {
gn->type |= OP_SAVE_CMDS;
return 0;
return true;
}
for (;;) {
@ -298,7 +300,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* If we did not end up with a command, just skip it.
*/
if (cmd[0] == '\0')
return 0;
return true;
useShell = UseShell(cmd);
/*
@ -315,7 +317,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* we go...
*/
if (!doIt && !GNode_ShouldExecute(gn))
return 0;
return true;
DEBUG1(JOB, "Execute: '%s'\n", cmd);
@ -350,25 +352,20 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
}
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_compat_start();
}
#endif
Var_ReexportVars();
/*
* Fork and execute the single command. If the fork fails, we abort.
*/
compatChild = cpid = vfork();
if (cpid < 0) {
if (cpid < 0)
Fatal("Could not fork");
}
if (cpid == 0) {
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_compat_child();
}
#endif
(void)execvp(av[0], (char *const *)UNCONST(av));
execDie("exec", av[0]);
@ -382,9 +379,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
LstNode_SetNull(ln);
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_compat_parent(cpid);
}
#endif
/*
@ -406,9 +402,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
} else if (WIFEXITED(reason)) {
status = WEXITSTATUS(reason); /* exited */
#if defined(USE_META) && defined(USE_FILEMON_ONCE)
if (useMeta) {
meta_cmd_finish(NULL);
}
if (useMeta)
meta_cmd_finish(NULL);
#endif
if (status != 0) {
if (DEBUG(ERROR))
@ -424,9 +419,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
if (!WIFEXITED(reason) || status != 0) {
if (errCheck) {
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_job_error(NULL, gn, false, status);
}
#endif
gn->made = ERROR;
if (opts.keepgoing) {
@ -457,7 +451,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
kill(myPid, compatSigno);
}
return status;
return status == 0;
}
static void
@ -467,7 +461,7 @@ RunCommands(GNode *gn)
for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
const char *cmd = ln->datum;
if (Compat_RunCommand(cmd, gn, ln) != 0)
if (!Compat_RunCommand(cmd, gn, ln))
break;
}
}
@ -532,7 +526,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
* to tell him/her "yes".
*/
DEBUG0(MAKE, "out-of-date.\n");
if (opts.queryFlag)
if (opts.query)
exit(1);
/*
@ -547,7 +541,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
*/
if (opts.ignoreErrors)
gn->type |= OP_IGNORE;
if (opts.beSilent)
if (opts.silent)
gn->type |= OP_SILENT;
if (Job_CheckCommands(gn, Fatal)) {
@ -555,12 +549,11 @@ MakeUnmade(GNode *gn, GNode *pgn)
* Our commands are ok, but we still have to worry about
* the -t flag.
*/
if (!opts.touchFlag || (gn->type & OP_MAKE)) {
if (!opts.touch || (gn->type & OP_MAKE)) {
curTarg = gn;
#ifdef USE_META
if (useMeta && GNode_ShouldExecute(gn)) {
if (useMeta && GNode_ShouldExecute(gn))
meta_job_start(NULL, gn);
}
#endif
RunCommands(gn);
curTarg = NULL;
@ -593,7 +586,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
} else if (opts.keepgoing) {
pgn->flags.remake = false;
} else {
PrintOnError(gn, "\nStop.");
PrintOnError(gn, "\nStop.\n");
exit(1);
}
return true;
@ -681,7 +674,7 @@ MakeBeginNode(void)
Compat_Make(gn, gn);
if (GNode_IsError(gn)) {
PrintOnError(gn, "\nStop.");
PrintOnError(gn, "\nStop.\n");
exit(1);
}
}
@ -715,12 +708,14 @@ Compat_Run(GNodeList *targs)
InitSignals();
/* Create the .END node now, to keep the (debug) output of the
* counter.mk test the same as before 2020-09-23. This implementation
* detail probably doesn't matter though. */
/*
* Create the .END node now, to keep the (debug) output of the
* counter.mk test the same as before 2020-09-23. This
* implementation detail probably doesn't matter though.
*/
(void)Targ_GetEndNode();
if (!opts.queryFlag)
if (!opts.query)
MakeBeginNode();
/*
@ -737,7 +732,7 @@ Compat_Run(GNodeList *targs)
printf("`%s' is up to date.\n", gn->name);
} else if (gn->made == ABORTED) {
printf("`%s' not remade because of errors.\n",
gn->name);
gn->name);
}
if (GNode_IsError(gn) && errorNode == NULL)
errorNode = gn;
@ -756,7 +751,7 @@ Compat_Run(GNodeList *targs)
Targ_PrintGraph(2);
else if (DEBUG(GRAPH3))
Targ_PrintGraph(3);
PrintOnError(errorNode, "\nStop.");
PrintOnError(errorNode, "\nStop.\n");
exit(1);
}
}

427
cond.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $ */
/* $NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -95,10 +95,10 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
MAKE_RCSID("$NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $");
/*
* The parsing of conditional expressions is based on this grammar:
* Conditional expressions conform to this grammar:
* Or -> And ('||' And)*
* And -> Term ('&&' Term)*
* Term -> Function '(' Argument ')'
@ -109,13 +109,13 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
* Leaf -> "string"
* Leaf -> Number
* Leaf -> VariableExpression
* Leaf -> Symbol
* Leaf -> BareWord
* Operator -> '==' | '!=' | '>' | '<' | '>=' | '<='
*
* 'Symbol' is an unquoted string literal to which the default function is
* applied.
* BareWord is an unquoted string literal, its evaluation depends on the kind
* of '.if' directive.
*
* The tokens are scanned by CondToken, which returns:
* The tokens are scanned by CondParser_Token, which returns:
* TOK_AND for '&&'
* TOK_OR for '||'
* TOK_NOT for '!'
@ -123,18 +123,14 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
* TOK_RPAREN for ')'
*
* Other terminal symbols are evaluated using either the default function or
* the function given in the terminal, they return either TOK_TRUE or
* TOK_FALSE.
* the function given in the terminal, they return either TOK_TRUE, TOK_FALSE
* or TOK_ERROR.
*/
typedef enum Token {
TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT,
TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
} Token;
typedef enum CondResult {
CR_FALSE, CR_TRUE, CR_ERROR
} CondResult;
typedef enum ComparisonOp {
LT, LE, GT, GE, EQ, NE
} ComparisonOp;
@ -186,10 +182,14 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
/* Names for ComparisonOp. */
static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" };
static bool
is_token(const char *str, const char *tok, unsigned char len)
MAKE_INLINE bool
skip_string(const char **pp, const char *str)
{
return strncmp(str, tok, (size_t)len) == 0 && !ch_isalpha(str[len]);
size_t len = strlen(str);
bool ok = strncmp(*pp, str, len) == 0;
if (ok)
*pp += len;
return ok;
}
static Token
@ -208,31 +208,13 @@ CondParser_SkipWhitespace(CondParser *par)
* Parse a single word, taking into account balanced parentheses as well as
* embedded expressions. Used for the argument of a built-in function as
* well as for bare words, which are then passed to the default function.
*
* Arguments:
* *pp initially points at the '(',
* upon successful return it points right after the ')'.
*
* *out_arg receives the argument as string.
*
* func says whether the argument belongs to an actual function, or
* NULL when parsing a bare word.
*
* Return the length of the argument, or an ambiguous 0 on error.
*/
static size_t
ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
char **out_arg)
static char *
ParseWord(const char **pp, bool doEval)
{
const char *p = *pp;
Buffer argBuf;
int paren_depth;
size_t argLen;
if (func != NULL)
p++; /* Skip opening '(' - verified by caller */
cpp_skip_hspace(&p);
Buf_InitSize(&argBuf, 16);
@ -243,7 +225,7 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
break;
if ((ch == '&' || ch == '|') && paren_depth == 0)
break;
if (*p == '$') {
if (ch == '$') {
/*
* Parse the variable expression and install it as
* part of the argument if it's valid. We tell
@ -266,58 +248,73 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
paren_depth++;
else if (ch == ')' && --paren_depth < 0)
break;
Buf_AddByte(&argBuf, *p);
Buf_AddByte(&argBuf, ch);
p++;
}
argLen = argBuf.len;
*out_arg = Buf_DoneData(&argBuf);
cpp_skip_hspace(&p);
*pp = p;
return Buf_DoneData(&argBuf);
}
/* Parse the function argument, including the surrounding parentheses. */
static char *
ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func)
{
const char *p = *pp;
char *res;
p++; /* Skip opening '(' - verified by caller */
cpp_skip_hspace(&p);
res = ParseWord(&p, doEval);
cpp_skip_hspace(&p);
if (func != NULL && *p++ != ')') {
if (*p++ != ')') {
int len = 0;
while (ch_isalpha(func[len]))
len++;
Parse_Error(PARSE_FATAL,
"Missing closing parenthesis for %s()", func);
"Missing closing parenthesis for %.*s()", len, func);
par->printedError = true;
return 0;
free(res);
return NULL;
}
*pp = p;
return argLen;
return res;
}
/* Test whether the given variable is defined. */
static bool
FuncDefined(const char *arg)
FuncDefined(const char *var)
{
FStr value = Var_Value(SCOPE_CMDLINE, arg);
bool result = value.str != NULL;
FStr_Done(&value);
return result;
return Var_Exists(SCOPE_CMDLINE, var);
}
/* See if the given target is requested to be made. */
static bool
FuncMake(const char *arg)
FuncMake(const char *target)
{
StringListNode *ln;
for (ln = opts.create.first; ln != NULL; ln = ln->next)
if (Str_Match(ln->datum, arg))
if (Str_Match(ln->datum, target))
return true;
return false;
}
/* See if the given file exists. */
static bool
FuncExists(const char *arg)
FuncExists(const char *file)
{
bool result;
char *path;
path = Dir_FindFile(arg, &dirSearchPath);
path = Dir_FindFile(file, &dirSearchPath);
DEBUG2(COND, "exists(%s) result is \"%s\"\n",
arg, path != NULL ? path : "");
file, path != NULL ? path : "");
result = path != NULL;
free(path);
return result;
@ -325,9 +322,9 @@ FuncExists(const char *arg)
/* See if the given node exists and is an actual target. */
static bool
FuncTarget(const char *arg)
FuncTarget(const char *node)
{
GNode *gn = Targ_FindNode(arg);
GNode *gn = Targ_FindNode(node);
return gn != NULL && GNode_IsTarget(gn);
}
@ -336,20 +333,16 @@ FuncTarget(const char *arg)
* associated with it.
*/
static bool
FuncCommands(const char *arg)
FuncCommands(const char *node)
{
GNode *gn = Targ_FindNode(arg);
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands);
GNode *gn = Targ_FindNode(node);
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:
* Returns true if the conversion succeeded.
* Sets 'out_value' to the converted number.
* Convert the string into a floating-point number. Accepted formats are
* base-10 integer, base-16 integer and finite floating point numbers.
*/
static bool
TryParseNumber(const char *str, double *out_value)
@ -399,7 +392,7 @@ CondParser_StringExpr(CondParser *par, const char *start,
Buffer *buf, FStr *inout_str)
{
VarEvalMode emode;
const char *nested_p;
const char *p;
bool atStart;
VarParseResult parseResult;
@ -407,9 +400,9 @@ CondParser_StringExpr(CondParser *par, const char *start,
: doEval ? VARE_UNDEFERR
: VARE_PARSE_ONLY;
nested_p = par->p;
atStart = nested_p == start;
parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str);
p = par->p;
atStart = p == start;
parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str);
/* TODO: handle errors */
if (inout_str->str == var_Error) {
if (parseResult == VPR_ERR) {
@ -433,7 +426,7 @@ CondParser_StringExpr(CondParser *par, const char *start,
*inout_str = FStr_InitRefer(NULL);
return false;
}
par->p = nested_p;
par->p = p;
/*
* If the '$' started the string literal (which means no quotes), and
@ -445,17 +438,16 @@ CondParser_StringExpr(CondParser *par, const char *start,
Buf_AddStr(buf, inout_str->str);
FStr_Done(inout_str);
*inout_str = FStr_InitRefer(NULL); /* not finished yet */
*inout_str = FStr_InitRefer(NULL); /* not finished yet */
return true;
}
/*
* Parse a string from a variable expression or an optionally quoted
* string. This is called for the left-hand and right-hand sides of
* comparisons.
* Parse a string from a variable expression or an optionally quoted string,
* on the left-hand and right-hand sides of comparisons.
*
* Results:
* Returns the string, absent any quotes, or NULL on error.
* Returns the string without any enclosing quotes, or NULL on error.
* Sets out_quoted if the leaf was a quoted string literal.
*/
static void
@ -486,7 +478,7 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
case '"':
par->p++;
if (quoted)
goto got_str; /* skip the closing quote */
goto return_buf; /* skip the closing quote */
Buf_AddByte(&buf, '"');
continue;
case ')': /* see is_separator */
@ -497,14 +489,14 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
case ' ':
case '\t':
if (!quoted)
goto got_str;
goto return_buf;
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
case '$':
if (!CondParser_StringExpr(par,
start, doEval, quoted, &buf, &str))
goto cleanup;
goto return_str;
continue;
default:
if (!unquotedOK && !quoted && *start != '$' &&
@ -514,28 +506,21 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
* a variable expression or a number.
*/
str = FStr_InitRefer(NULL);
goto cleanup;
goto return_str;
}
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
}
}
got_str:
return_buf:
str = FStr_InitOwn(buf.data);
buf.data = NULL;
cleanup:
return_str:
Buf_Done(&buf);
*out_str = str;
}
static bool
EvalBare(const CondParser *par, const char *arg)
{
bool res = par->evalBare(arg);
return par->negateEvalBare ? !res : res;
}
/*
* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0".
@ -553,9 +538,11 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted)
if (TryParseNumber(value, &num))
return num != 0.0;
/* 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. */
/*
* 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
* CondParser_FuncCallEmpty.
@ -563,7 +550,7 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted)
if (par->plain)
return value[0] != '\0';
return EvalBare(par, value);
return par->evalBare(value) != par->negateEvalBare;
}
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
@ -623,33 +610,19 @@ CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op)
{
const char *p = par->p;
if (p[0] == '<' && p[1] == '=') {
*out_op = LE;
goto length_2;
} else if (p[0] == '<') {
*out_op = LT;
goto length_1;
} else if (p[0] == '>' && p[1] == '=') {
*out_op = GE;
goto length_2;
} else if (p[0] == '>') {
*out_op = GT;
goto length_1;
} else if (p[0] == '=' && p[1] == '=') {
*out_op = EQ;
goto length_2;
} else if (p[0] == '!' && p[1] == '=') {
*out_op = NE;
goto length_2;
}
if (p[0] == '<' && p[1] == '=')
return par->p += 2, *out_op = LE, true;
if (p[0] == '<')
return par->p += 1, *out_op = LT, true;
if (p[0] == '>' && p[1] == '=')
return par->p += 2, *out_op = GE, true;
if (p[0] == '>')
return par->p += 1, *out_op = GT, true;
if (p[0] == '=' && p[1] == '=')
return par->p += 2, *out_op = EQ, true;
if (p[0] == '!' && p[1] == '=')
return par->p += 2, *out_op = NE, true;
return false;
length_2:
par->p = p + 2;
return true;
length_1:
par->p = p + 1;
return true;
}
/*
@ -718,9 +691,8 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
Token tok;
FStr val;
if (!is_token(cp, "empty", 5))
if (!skip_string(&cp, "empty"))
return false;
cp += 5;
cpp_skip_whitespace(&cp);
if (*cp != '(')
@ -735,7 +707,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
tok = TOK_ERROR;
else {
cpp_skip_whitespace(&val.str);
tok = val.str[0] != '\0' && doEval ? TOK_FALSE : TOK_TRUE;
tok = ToToken(doEval && val.str[0] == '\0');
}
FStr_Done(&val);
@ -748,37 +720,34 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
static bool
CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token)
{
static const struct fn_def {
const char fn_name[9];
unsigned char fn_name_len;
bool (*fn_eval)(const char *);
} fns[] = {
{ "defined", 7, FuncDefined },
{ "make", 4, FuncMake },
{ "exists", 6, FuncExists },
{ "target", 6, FuncTarget },
{ "commands", 8, FuncCommands }
};
const struct fn_def *fn;
char *arg = NULL;
size_t arglen;
const char *cp = par->p;
const struct fn_def *last_fn = fns + sizeof fns / sizeof fns[0] - 1;
char *arg;
const char *p = par->p;
bool (*fn)(const char *);
const char *fn_name = p;
for (fn = fns; !is_token(cp, fn->fn_name, fn->fn_name_len); fn++)
if (fn == last_fn)
return false;
cp += fn->fn_name_len;
cpp_skip_whitespace(&cp);
if (*cp != '(')
if (skip_string(&p, "defined"))
fn = FuncDefined;
else if (skip_string(&p, "make"))
fn = FuncMake;
else if (skip_string(&p, "exists"))
fn = FuncExists;
else if (skip_string(&p, "target"))
fn = FuncTarget;
else if (skip_string(&p, "commands"))
fn = FuncCommands;
else
return false;
arglen = ParseWord(par, &cp, doEval, fn->fn_name, &arg);
*out_token = ToToken(arglen != 0 && (!doEval || fn->fn_eval(arg)));
cpp_skip_whitespace(&p);
if (*p != '(')
return false;
arg = ParseFuncArg(par, &p, doEval, fn_name);
*out_token = ToToken(doEval &&
arg != NULL && arg[0] != '\0' && fn(arg));
free(arg);
par->p = cp;
par->p = p;
return true;
}
@ -793,9 +762,8 @@ static Token
CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
{
Token t;
char *arg = NULL;
char *arg;
const char *cp;
const char *cp1;
/* Push anything numeric through the compare expression */
cp = par->p;
@ -811,13 +779,13 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
* as an expression.
*/
/*
* XXX: Is it possible to have a variable expression evaluated twice
* at this point?
* XXX: In edge cases, a variable expression may be evaluated twice,
* see cond-token-plain.mk, keyword 'twice'.
*/
(void)ParseWord(par, &cp, doEval, NULL, &arg);
cp1 = cp;
cpp_skip_whitespace(&cp1);
if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>')
arg = ParseWord(&cp, doEval);
assert(arg[0] != '\0');
if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>')
return CondParser_Comparison(par, doEval);
par->p = cp;
@ -827,7 +795,7 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
* after .if must have been taken literally, so the argument cannot
* be empty - even if it contained a variable expansion.
*/
t = ToToken(!doEval || EvalBare(par, arg));
t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare);
free(arg);
return t;
}
@ -998,42 +966,32 @@ CondParser_Or(CondParser *par, bool doEval)
return res;
}
static CondEvalResult
CondParser_Eval(CondParser *par, bool *out_value)
static CondResult
CondParser_Eval(CondParser *par)
{
CondResult res;
DEBUG1(COND, "CondParser_Eval: %s\n", par->p);
res = CondParser_Or(par, true);
if (res == CR_ERROR)
return COND_INVALID;
if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF)
return CR_ERROR;
if (CondParser_Token(par, false) != TOK_EOF)
return COND_INVALID;
*out_value = res == CR_TRUE;
return COND_PARSE;
return res;
}
/*
* Evaluate the condition, including any side effects from the variable
* expressions in the condition. The condition consists of &&, ||, !,
* function(arg), comparisons and parenthetical groupings thereof.
*
* Results:
* COND_PARSE if the condition was valid grammatically
* COND_INVALID if not a valid conditional.
*
* *out_value is set to the boolean value of the condition
*/
static CondEvalResult
CondEvalExpression(const char *cond, bool *out_value, bool plain,
static CondResult
CondEvalExpression(const char *cond, bool plain,
bool (*evalBare)(const char *), bool negate,
bool eprint, bool leftUnquotedOK)
{
CondParser par;
CondEvalResult rval;
CondResult rval;
cpp_skip_hspace(&cond);
@ -1045,9 +1003,9 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain,
par.curr = TOK_NONE;
par.printedError = false;
rval = CondParser_Eval(&par, out_value);
rval = CondParser_Eval(&par);
if (rval == COND_INVALID && eprint && !par.printedError)
if (rval == CR_ERROR && eprint && !par.printedError)
Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
return rval;
@ -1057,10 +1015,10 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain,
* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}.
*/
CondEvalResult
Cond_EvalCondition(const char *cond, bool *out_value)
CondResult
Cond_EvalCondition(const char *cond)
{
return CondEvalExpression(cond, out_value, true,
return CondEvalExpression(cond, true,
FuncDefined, false, false, true);
}
@ -1076,36 +1034,33 @@ DetermineKindOfConditional(const char **pp, bool *out_plain,
bool (**out_evalBare)(const char *),
bool *out_negate)
{
const char *p = *pp;
const char *p = *pp + 2;
p += 2;
*out_plain = false;
*out_evalBare = FuncDefined;
*out_negate = false;
if (*p == 'n') {
p++;
*out_negate = true;
}
if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */
p += 3;
} else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */
p += 4;
*out_negate = skip_string(&p, "n");
if (skip_string(&p, "def")) { /* .ifdef and .ifndef */
} else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */
*out_evalBare = FuncMake;
} else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */
else if (!*out_negate) /* plain .if */
*out_plain = true;
} else {
/*
* TODO: Add error message about unknown directive,
* since there is no other known directive that starts
* with 'el' or 'if'.
*
* Example: .elifx 123
*/
return false;
}
else
goto unknown_directive;
if (ch_isalpha(*p))
goto unknown_directive;
*pp = p;
return true;
unknown_directive:
/*
* TODO: Add error message about unknown directive, since there is no
* other known directive that starts with 'el' or 'if'.
*
* Example: .elifx 123
*/
return false;
}
/*
@ -1129,16 +1084,16 @@ DetermineKindOfConditional(const char **pp, bool *out_plain,
* parenthetical groupings thereof.
*
* Results:
* COND_PARSE to continue parsing the lines that follow the
* CR_TRUE to continue parsing the lines that follow the
* conditional (when <cond> evaluates to true)
* COND_SKIP to skip the lines after the conditional
* CR_FALSE to skip the lines after the conditional
* (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
* CR_ERROR 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
CondResult
Cond_EvalLine(const char *line)
{
typedef enum IfState {
@ -1146,8 +1101,10 @@ Cond_EvalLine(const char *line)
/* None of the previous <cond> evaluated to true. */
IFS_INITIAL = 0,
/* The previous <cond> evaluated to true.
* The lines following this condition are interpreted. */
/*
* The previous <cond> evaluated to true. The lines following
* this condition are interpreted.
*/
IFS_ACTIVE = 1 << 0,
/* The previous directive was an '.else'. */
@ -1165,7 +1122,7 @@ Cond_EvalLine(const char *line)
bool (*evalBare)(const char *);
bool negate;
bool isElif;
bool value;
CondResult res;
IfState state;
const char *p = line;
@ -1186,13 +1143,13 @@ Cond_EvalLine(const char *line)
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif");
return COND_PARSE;
return CR_TRUE;
}
/* Return state for previous conditional */
cond_depth--;
return cond_states[cond_depth] & IFS_ACTIVE
? COND_PARSE : COND_SKIP;
? CR_TRUE : CR_FALSE;
}
/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
@ -1203,13 +1160,12 @@ Cond_EvalLine(const char *line)
* transformation rule like '.err.txt',
* therefore no error message here.
*/
return COND_INVALID;
return CR_ERROR;
}
/* Quite likely this is 'else' or 'elif' */
p += 2;
if (is_token(p, "se", 2)) { /* It is an 'else'. */
if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) {
if (p[2] != '\0')
Parse_Error(PARSE_FATAL,
"The .else directive "
@ -1217,7 +1173,7 @@ Cond_EvalLine(const char *line)
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else");
return COND_PARSE;
return CR_TRUE;
}
state = cond_states[cond_depth];
@ -1226,12 +1182,12 @@ Cond_EvalLine(const char *line)
} else {
if (state & IFS_SEEN_ELSE)
Parse_Error(PARSE_WARNING,
"extra else");
"extra else");
state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
}
cond_states[cond_depth] = state;
return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE;
}
/* Assume for now it is an elif */
isElif = true;
@ -1243,27 +1199,27 @@ Cond_EvalLine(const char *line)
* 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 */
return CR_ERROR; /* Not an ifxxx or elifxxx line */
}
if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate))
return COND_INVALID;
return CR_ERROR;
if (isElif) {
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less elif");
return COND_PARSE;
return CR_TRUE;
}
state = cond_states[cond_depth];
if (state & IFS_SEEN_ELSE) {
Parse_Error(PARSE_WARNING, "extra elif");
cond_states[cond_depth] =
IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
return COND_SKIP;
return CR_FALSE;
}
if (state != IFS_INITIAL) {
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
return CR_FALSE;
}
} else {
/* Normal .if */
@ -1275,8 +1231,7 @@ Cond_EvalLine(const char *line)
*/
cond_states_cap += 32;
cond_states = bmake_realloc(cond_states,
cond_states_cap *
sizeof *cond_states);
cond_states_cap * sizeof *cond_states);
}
state = cond_states[cond_depth];
cond_depth++;
@ -1286,26 +1241,22 @@ Cond_EvalLine(const char *line)
* treat as always false.
*/
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
return CR_FALSE;
}
}
/* And evaluate the conditional expression */
if (CondEvalExpression(p, &value, plain, evalBare, negate,
true, false) == COND_INVALID) {
/* Syntax error in conditional, error message already output. */
/* Skip everything to matching .endif */
/* XXX: An extra '.else' is not detected in this case. */
res = CondEvalExpression(p, plain, evalBare, negate, true, false);
if (res == CR_ERROR) {
/* Syntax error, error message already output. */
/* Skip everything to the matching '.endif'. */
/* An extra '.else' is not detected in this case. */
cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
return CR_FALSE;
}
if (!value) {
cond_states[cond_depth] = IFS_INITIAL;
return COND_SKIP;
}
cond_states[cond_depth] = IFS_ACTIVE;
return COND_PARSE;
cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL;
return res;
}
void
@ -1315,7 +1266,7 @@ Cond_restore_depth(unsigned int saved_depth)
if (open_conds != 0 || saved_depth > cond_depth) {
Parse_Error(PARSE_FATAL, "%u open conditional%s",
open_conds, open_conds == 1 ? "" : "s");
open_conds, open_conds == 1 ? "" : "s");
cond_depth = cond_min_depth;
}

21
dir.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $ */
/* $NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -138,7 +138,7 @@
#include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
MAKE_RCSID("$NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $");
MAKE_RCSID("$NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $");
/*
* A search path is a list of CachedDir structures. A CachedDir has in it the
@ -672,8 +672,8 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
{
char *fullName = isDot
? bmake_strdup(base)
: str_concat3(dirName, "/", base);
? bmake_strdup(base)
: str_concat3(dirName, "/", base);
Lst_Append(expansions, fullName);
}
}
@ -792,7 +792,7 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path,
size_t piece_len = (size_t)(piece_end - piece);
char *file = concat3(prefix, prefix_len, piece, piece_len,
suffix, suffix_len);
suffix, suffix_len);
if (contains_wildcard(file)) {
SearchPath_Expand(path, file, expansions);
@ -984,8 +984,9 @@ static char *
DirLookupSubdir(CachedDir *dir, const char *name)
{
struct cached_stat cst;
char *file = dir == dot ? bmake_strdup(name)
: str_concat3(dir->name, "/", name);
char *file = dir == dot
? bmake_strdup(name)
: str_concat3(dir->name, "/", name);
DEBUG1(DIR, "checking %s ...\n", file);
@ -1424,9 +1425,9 @@ ResolveMovedDepends(GNode *gn)
gn->path = bmake_strdup(fullName);
if (!Job_RunTarget(".STALE", gn->fname))
fprintf(stdout, /* XXX: Why stdout? */
"%s: %s, %d: ignoring stale %s for %s, found %s\n",
progname, gn->fname, gn->lineno,
makeDependfile, gn->name, fullName);
"%s: %s, %u: ignoring stale %s for %s, found %s\n",
progname, gn->fname, gn->lineno,
makeDependfile, gn->name, fullName);
return fullName;
}

14
dir.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: dir.h,v 1.44 2021/04/03 11:08:40 rillig Exp $ */
/* $NetBSD: dir.h,v 1.46 2021/12/15 12:08:25 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -82,18 +82,18 @@ void Dir_InitCur(const char *);
void Dir_InitDot(void);
void Dir_End(void);
void Dir_SetPATH(void);
bool Dir_HasWildcards(const char *);
bool Dir_HasWildcards(const char *) MAKE_ATTR_USE;
void SearchPath_Expand(SearchPath *, const char *, StringList *);
char *Dir_FindFile(const char *, SearchPath *);
char *Dir_FindHereOrAbove(const char *, const char *);
char *Dir_FindFile(const char *, SearchPath *) MAKE_ATTR_USE;
char *Dir_FindHereOrAbove(const char *, const char *) MAKE_ATTR_USE;
void Dir_UpdateMTime(GNode *, bool);
CachedDir *SearchPath_Add(SearchPath *, const char *);
char *SearchPath_ToFlags(SearchPath *, const char *);
char *SearchPath_ToFlags(SearchPath *, const char *) MAKE_ATTR_USE;
void SearchPath_Clear(SearchPath *);
void SearchPath_AddAll(SearchPath *, SearchPath *);
void Dir_PrintDirectories(void);
void SearchPath_Print(const SearchPath *);
SearchPath *Dir_CopyDirSearchPath(void);
SearchPath *Dir_CopyDirSearchPath(void) MAKE_ATTR_USE;
/* Stripped-down variant of struct stat. */
struct cached_stat {
@ -104,4 +104,4 @@ struct cached_stat {
int cached_lstat(const char *, struct cached_stat *);
int cached_stat(const char *, struct cached_stat *);
#endif /* MAKE_DIR_H */
#endif

View File

@ -1,4 +1,4 @@
/* $NetBSD: filemon.h,v 1.5 2021/01/19 20:51:46 rillig Exp $ */
/* $NetBSD: filemon.h,v 1.6 2021/12/15 12:08:25 rillig Exp $ */
/*
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@ -50,4 +50,4 @@ int filemon_setpid_child(const struct filemon *, pid_t);
int filemon_readfd(const struct filemon *);
int filemon_process(struct filemon *);
#endif /* MAKE_FILEMON_H */
#endif

228
for.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $ */
/* $NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1992, The Regents of the University of California.
@ -58,26 +58,18 @@
#include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $");
MAKE_RCSID("$NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $");
/* One of the variables to the left of the "in" in a .for loop. */
typedef struct ForVar {
char *name;
size_t nameLen;
} ForVar;
typedef struct ForLoop {
Buffer body; /* Unexpanded body of the loop */
Vector /* of ForVar */ vars; /* Iteration variables */
Vector /* of 'char *' */ vars; /* Iteration variables */
SubstringWords items; /* Substitution items */
Buffer curBody; /* Expanded body of the current iteration */
Buffer body; /* Unexpanded body of the loop */
unsigned int nextItem; /* Where to continue iterating */
} ForLoop;
static ForLoop *accumFor; /* Loop being accumulated */
static int forLevel = 0; /* Nesting level */
static ForLoop *
@ -85,38 +77,49 @@ ForLoop_New(void)
{
ForLoop *f = bmake_malloc(sizeof *f);
Buf_Init(&f->body);
Vector_Init(&f->vars, sizeof(ForVar));
Vector_Init(&f->vars, sizeof(char *));
SubstringWords_Init(&f->items);
Buf_Init(&f->curBody);
Buf_Init(&f->body);
f->nextItem = 0;
return f;
}
static void
void
ForLoop_Free(ForLoop *f)
{
Buf_Done(&f->body);
while (f->vars.len > 0) {
ForVar *var = Vector_Pop(&f->vars);
free(var->name);
}
while (f->vars.len > 0)
free(*(char **)Vector_Pop(&f->vars));
Vector_Done(&f->vars);
SubstringWords_Free(f->items);
Buf_Done(&f->curBody);
Buf_Done(&f->body);
free(f);
}
static void
ForLoop_AddVar(ForLoop *f, const char *name, size_t len)
char *
ForLoop_Details(ForLoop *f)
{
ForVar *var = Vector_Push(&f->vars);
var->name = bmake_strldup(name, len);
var->nameLen = len;
size_t i, n;
const char **vars;
const Substring *items;
Buffer buf;
n = f->vars.len;
vars = f->vars.items;
assert(f->nextItem >= n);
items = f->items.words + f->nextItem - n;
Buf_Init(&buf);
for (i = 0; i < n; i++) {
if (i > 0)
Buf_AddStr(&buf, ", ");
Buf_AddStr(&buf, vars[i]);
Buf_AddStr(&buf, " = ");
Buf_AddBytesBetween(&buf, items[i].start, items[i].end);
}
return Buf_DoneData(&buf);
}
static bool
@ -145,7 +148,7 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
break;
}
ForLoop_AddVar(f, p, len);
*(char **)Vector_Push(&f->vars) = bmake_strldup(p, len);
p += len;
}
@ -174,9 +177,9 @@ ForLoop_ParseItems(ForLoop *f, const char *p)
free(items);
if (f->items.len == 1 && Substring_IsEmpty(f->items.words[0]))
f->items.len = 0; /* .for var in ${:U} */
f->items.len = 0; /* .for var in ${:U} */
if (f->items.len != 0 && f->items.len % f->vars.len != 0) {
if (f->items.len % f->vars.len != 0) {
Parse_Error(PARSE_FATAL,
"Wrong number of words (%u) in .for "
"substitution list with %u variables",
@ -204,47 +207,38 @@ IsEndfor(const char *p)
* Evaluate the for loop in the passed line. The line looks like this:
* .for <varname...> in <value...>
*
* Input:
* line Line to parse
*
* Results:
* 0: Not a .for statement, parse the line
* 1: We found a for loop
* -1: A .for statement with a bad syntax error, discard.
* 0 not a .for directive
* 1 found a .for directive
* -1 erroneous .for directive
*/
int
For_Eval(const char *line)
{
ForLoop *f;
const char *p;
ForLoop *f;
p = line + 1; /* skip the '.' */
cpp_skip_whitespace(&p);
if (!IsFor(p)) {
if (IsEndfor(p)) {
Parse_Error(PARSE_FATAL, "for-less endfor");
if (IsFor(p)) {
p += 3;
f = ForLoop_New();
if (!ForLoop_ParseVarnames(f, &p)) {
ForLoop_Free(f);
return -1;
}
return 0;
}
p += 3;
if (!ForLoop_ParseItems(f, p))
f->items.len = 0; /* don't iterate */
f = ForLoop_New();
if (!ForLoop_ParseVarnames(f, &p)) {
ForLoop_Free(f);
accumFor = f;
return 1;
} else if (IsEndfor(p)) {
Parse_Error(PARSE_FATAL, "for-less endfor");
return -1;
}
if (!ForLoop_ParseItems(f, p)) {
/* Continue parsing the .for loop, but don't iterate. */
f->items.len = 0;
}
accumFor = f;
forLevel = 1;
return 1;
} else
return 0;
}
/*
@ -252,7 +246,7 @@ For_Eval(const char *line)
* Returns false when the matching .endfor is reached.
*/
bool
For_Accum(const char *line)
For_Accum(const char *line, int *forLevel)
{
const char *p = line;
@ -261,12 +255,12 @@ For_Accum(const char *line)
cpp_skip_whitespace(&p);
if (IsEndfor(p)) {
DEBUG1(FOR, "For: end for %d\n", forLevel);
if (--forLevel <= 0)
DEBUG1(FOR, "For: end for %d\n", *forLevel);
if (--*forLevel == 0)
return false;
} else if (IsFor(p)) {
forLevel++;
DEBUG1(FOR, "For: new loop %d\n", forLevel);
(*forLevel)++;
DEBUG1(FOR, "For: new loop %d\n", *forLevel);
}
}
@ -325,12 +319,11 @@ NeedsEscapes(Substring value, char endc)
/*
* While expanding the body of a .for loop, write the item in the ${:U...}
* expression, escaping characters as needed.
*
* The result is later unescaped by ApplyModifier_Defined.
* expression, escaping characters as needed. The result is later unescaped
* by ApplyModifier_Defined.
*/
static void
Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
AddEscaped(Buffer *cmds, Substring item, char endc)
{
const char *p;
char ch;
@ -340,9 +333,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
return;
}
/* Escape ':', '$', '\\' and 'endc' - these will be removed later by
* :U processing, see ApplyModifier_Defined. */
for (p = item.start; p != item.end; p++) {
for (p = item.start; p != item.end;) {
ch = *p;
if (ch == '$') {
size_t len = ExprLen(p + 1, item.end);
@ -352,7 +343,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
* See directive-for-escape.mk, ExprLen.
*/
Buf_AddBytes(cmds, p, 1 + len);
p += len;
p += 1 + len;
continue;
}
Buf_AddByte(cmds, '\\');
@ -363,6 +354,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
ch = ' '; /* prevent newline injection */
}
Buf_AddByte(cmds, ch);
p++;
}
}
@ -371,36 +363,30 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
* expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue".
*/
static void
ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd,
char endc, const char **inout_mark)
ForLoop_SubstVarLong(ForLoop *f, unsigned int firstItem, Buffer *body,
const char **pp, char endc, const char **inout_mark)
{
size_t i;
const char *p = *pp;
const char *start = *pp;
const char **vars = Vector_Get(&f->vars, 0);
for (i = 0; i < f->vars.len; i++) {
const ForVar *forVar = Vector_Get(&f->vars, i);
const char *varname = forVar->name;
size_t varnameLen = forVar->nameLen;
const char *p = start;
if (varnameLen >= (size_t)(bodyEnd - p))
continue;
if (memcmp(p, varname, varnameLen) != 0)
if (!cpp_skip_string(&p, vars[i]))
continue;
/* XXX: why test for backslash here? */
if (p[varnameLen] != ':' && p[varnameLen] != endc &&
p[varnameLen] != '\\')
if (*p != ':' && *p != endc && *p != '\\')
continue;
/*
* Found a variable match. Skip over the variable name and
* instead add ':U<value>' to the current body.
*/
Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
Buf_AddStr(&f->curBody, ":U");
Buf_AddEscaped(&f->curBody,
f->items.words[f->nextItem + i], endc);
Buf_AddBytesBetween(body, *inout_mark, start);
Buf_AddStr(body, ":U");
AddEscaped(body, f->items.words[firstItem + i], endc);
p += varnameLen;
*inout_mark = p;
*pp = p;
return;
@ -412,10 +398,11 @@ ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd,
* variable expressions like $i with their ${:U...} expansion.
*/
static void
ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark)
ForLoop_SubstVarShort(ForLoop *f, unsigned int firstItem, Buffer *body,
const char *p, const char **inout_mark)
{
const char ch = *p;
const ForVar *vars;
const char **vars;
size_t i;
/* Skip $$ and stupid ones. */
@ -424,20 +411,20 @@ ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark)
vars = Vector_Get(&f->vars, 0);
for (i = 0; i < f->vars.len; i++) {
const char *varname = vars[i].name;
const char *varname = vars[i];
if (varname[0] == ch && varname[1] == '\0')
goto found;
}
return;
found:
Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
Buf_AddBytesBetween(body, *inout_mark, p);
*inout_mark = p + 1;
/* Replace $<ch> with ${:U<value>} */
Buf_AddStr(&f->curBody, "{:U");
Buf_AddEscaped(&f->curBody, f->items.words[f->nextItem + i], '}');
Buf_AddByte(&f->curBody, '}');
Buf_AddStr(body, "{:U");
AddEscaped(body, f->items.words[firstItem + i], '}');
Buf_AddByte(body, '}');
}
/*
@ -454,68 +441,59 @@ ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark)
* possible to contrive a makefile where an unwanted substitution happens.
*/
static void
ForLoop_SubstBody(ForLoop *f)
ForLoop_SubstBody(ForLoop *f, unsigned int firstItem, Buffer *body)
{
const char *p, *bodyEnd;
const char *p, *end;
const char *mark; /* where the last substitution left off */
Buf_Empty(&f->curBody);
Buf_Clear(body);
mark = f->body.data;
bodyEnd = f->body.data + f->body.len;
end = f->body.data + f->body.len;
for (p = mark; (p = strchr(p, '$')) != NULL;) {
if (p[1] == '{' || p[1] == '(') {
char endc = p[1] == '{' ? '}' : ')';
p += 2;
ForLoop_SubstVarLong(f, &p, bodyEnd, endc, &mark);
ForLoop_SubstVarLong(f, firstItem, body,
&p, endc, &mark);
} else if (p[1] != '\0') {
ForLoop_SubstVarShort(f, p + 1, &mark);
ForLoop_SubstVarShort(f, firstItem, body,
p + 1, &mark);
p += 2;
} else
break;
}
Buf_AddBytesBetween(&f->curBody, mark, bodyEnd);
Buf_AddBytesBetween(body, mark, end);
}
/*
* Compute the body for the current iteration by copying the unexpanded body,
* replacing the expressions for the iteration variables on the way.
*/
static char *
ForReadMore(void *v_arg, size_t *out_len)
bool
For_NextIteration(ForLoop *f, Buffer *body)
{
ForLoop *f = v_arg;
if (f->nextItem == f->items.len)
return false;
if (f->nextItem == f->items.len) {
/* No more iterations */
ForLoop_Free(f);
return NULL;
}
ForLoop_SubstBody(f);
DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data);
f->nextItem += (unsigned int)f->vars.len;
*out_len = f->curBody.len;
return f->curBody.data;
ForLoop_SubstBody(f, f->nextItem - (unsigned int)f->vars.len, body);
DEBUG1(FOR, "For: loop body:\n%s", body->data);
return true;
}
/* Run the .for loop, imitating the actions of an include file. */
void
For_Run(int lineno)
For_Run(unsigned headLineno, unsigned bodyReadLines)
{
Buffer buf;
ForLoop *f = accumFor;
accumFor = NULL;
if (f->items.len == 0) {
/*
* Nothing to expand - possibly due to an earlier syntax
* error.
*/
if (f->items.len > 0) {
Buf_Init(&buf);
Parse_PushInput(NULL, headLineno, bodyReadLines, buf, f);
} else
ForLoop_Free(f);
return;
}
Parse_PushInput(NULL, lineno, -1, ForReadMore, f);
}

80
hash.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $ */
/* $NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 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.66 2021/12/07 21:58:01 rillig Exp $");
MAKE_RCSID("$NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 rillig Exp $");
/*
* The ratio of # entries to # buckets at which we rebuild the table to
@ -84,7 +84,7 @@ MAKE_RCSID("$NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $");
/* This hash function matches Gosling's Emacs and java.lang.String. */
static unsigned int
Hash_String(const char *key, size_t *out_keylen)
Hash_String(const char *key, const char **out_keyEnd)
{
unsigned int h;
const char *p;
@ -93,8 +93,7 @@ Hash_String(const char *key, size_t *out_keylen)
for (p = key; *p != '\0'; p++)
h = 31 * h + (unsigned char)*p;
if (out_keylen != NULL)
*out_keylen = (size_t)(p - key);
*out_keyEnd = p;
return h;
}
@ -112,53 +111,22 @@ Hash_Substring(Substring key)
}
static HashEntry *
HashTable_Find(HashTable *t, unsigned int h, const char *key)
HashTable_Find(HashTable *t, Substring key, unsigned int h)
{
HashEntry *e;
unsigned int chainlen = 0;
size_t keyLen = Substring_Length(key);
#ifdef DEBUG_HASH_LOOKUP
DEBUG4(HASH, "%s: %p h=%08x key=%s\n", __func__, t, h, key);
DEBUG4(HASH, "HashTable_Find: %p h=%08x key=%.*s\n",
t, h, (int)keyLen, key.start);
#endif
for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
chainlen++;
if (e->key_hash == h && strcmp(e->key, key) == 0)
break;
}
if (chainlen > t->maxchain)
t->maxchain = chainlen;
return e;
}
static bool
HashEntry_KeyEquals(const HashEntry *he, Substring key)
{
const char *heKey, *p;
heKey = he->key;
for (p = key.start; p != key.end; p++, heKey++)
if (*p != *heKey || *heKey == '\0')
return false;
return *heKey == '\0';
}
static HashEntry *
HashTable_FindEntryBySubstring(HashTable *t, Substring key, unsigned int h)
{
HashEntry *e;
unsigned int chainlen = 0;
#ifdef DEBUG_HASH_LOOKUP
DEBUG5(HASH, "%s: %p h=%08x key=%.*s\n", __func__, t, h,
(int)Substring_Length(key), key.start);
#endif
for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
chainlen++;
if (e->key_hash == h && HashEntry_KeyEquals(e, key))
if (e->key_hash == h &&
strncmp(e->key, key.start, keyLen) == 0 &&
e->key[keyLen] == '\0')
break;
}
@ -213,8 +181,9 @@ HashTable_Done(HashTable *t)
HashEntry *
HashTable_FindEntry(HashTable *t, const char *key)
{
unsigned int h = Hash_String(key, NULL);
return HashTable_Find(t, h, key);
const char *keyEnd;
unsigned int h = Hash_String(key, &keyEnd);
return HashTable_Find(t, Substring_Init(key, keyEnd), h);
}
/* Find the value corresponding to the key, or return NULL. */
@ -232,7 +201,7 @@ HashTable_FindValue(HashTable *t, const char *key)
void *
HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h)
{
HashEntry *he = HashTable_FindEntryBySubstring(t, key, h);
HashEntry *he = HashTable_Find(t, key, h);
return he != NULL ? he->value : NULL;
}
@ -268,8 +237,8 @@ HashTable_Enlarge(HashTable *t)
t->bucketsSize = newSize;
t->bucketsMask = newMask;
t->buckets = newBuckets;
DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n",
__func__, (void *)t, t->bucketsSize, t->numEntries, t->maxchain);
DEBUG4(HASH, "HashTable_Enlarge: %p size=%d entries=%d maxchain=%d\n",
(void *)t, t->bucketsSize, t->numEntries, t->maxchain);
t->maxchain = 0;
}
@ -280,9 +249,9 @@ HashTable_Enlarge(HashTable *t)
HashEntry *
HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
{
size_t keylen;
unsigned int h = Hash_String(key, &keylen);
HashEntry *he = HashTable_Find(t, h, key);
const char *keyEnd;
unsigned int h = Hash_String(key, &keyEnd);
HashEntry *he = HashTable_Find(t, Substring_Init(key, keyEnd), h);
if (he != NULL) {
if (out_isNew != NULL)
@ -293,10 +262,10 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
if (t->numEntries >= rebuildLimit * t->bucketsSize)
HashTable_Enlarge(t);
he = bmake_malloc(sizeof *he + keylen);
he = bmake_malloc(sizeof *he + (size_t)(keyEnd - key));
he->value = NULL;
he->key_hash = h;
memcpy(he->key, key, keylen + 1);
memcpy(he->key, key, (size_t)(keyEnd - key) + 1);
he->next = t->buckets[h & t->bucketsMask];
t->buckets[h & t->bucketsMask] = he;
@ -307,12 +276,11 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
return he;
}
HashEntry *
void
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. */
@ -361,5 +329,5 @@ void
HashTable_DebugStats(HashTable *t, const char *name)
{
DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n",
name, t->bucketsSize, t->numEntries, t->maxchain);
name, t->bucketsSize, t->numEntries, t->maxchain);
}

24
hash.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: hash.h,v 1.41 2021/12/07 21:58:01 rillig Exp $ */
/* $NetBSD: hash.h,v 1.46 2022/01/31 22:58:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -88,8 +88,8 @@ typedef struct HashEntry {
/* The hash table containing the entries. */
typedef struct HashTable {
HashEntry **buckets; /* Pointers to HashEntry, one
* for each bucket in the table. */
HashEntry **buckets; /* Pointers to HashEntry, one for each bucket
* in the table. */
unsigned int bucketsSize;
unsigned int numEntries; /* Number of entries in the table. */
unsigned int bucketsMask; /* Used to select the bucket for a hash. */
@ -108,7 +108,7 @@ typedef struct HashSet {
HashTable tbl;
} HashSet;
MAKE_INLINE void *
MAKE_INLINE void * MAKE_ATTR_USE
HashEntry_Get(HashEntry *h)
{
return h->value;
@ -131,16 +131,16 @@ HashIter_Init(HashIter *hi, HashTable *t)
void HashTable_Init(HashTable *);
void HashTable_Done(HashTable *);
HashEntry *HashTable_FindEntry(HashTable *, const char *);
void *HashTable_FindValue(HashTable *, const char *);
unsigned int Hash_Substring(Substring);
void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int);
HashEntry *HashTable_FindEntry(HashTable *, const char *) MAKE_ATTR_USE;
void *HashTable_FindValue(HashTable *, const char *) MAKE_ATTR_USE;
unsigned int Hash_Substring(Substring) MAKE_ATTR_USE;
void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int)
MAKE_ATTR_USE;
HashEntry *HashTable_CreateEntry(HashTable *, const char *, bool *);
HashEntry *HashTable_Set(HashTable *, const char *, void *);
void HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *);
void HashIter_Init(HashIter *, HashTable *);
HashEntry *HashIter_Next(HashIter *);
MAKE_INLINE void
@ -164,7 +164,7 @@ HashSet_Add(HashSet *set, const char *key)
return isNew;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
HashSet_Contains(HashSet *set, const char *key)
{
return HashTable_FindEntry(&set->tbl, key) != NULL;
@ -176,4 +176,4 @@ HashIter_InitSet(HashIter *hi, HashSet *set)
HashIter_Init(hi, &set->tbl);
}
#endif /* MAKE_HASH_H */
#endif

99
job.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $ */
/* $NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -155,7 +155,7 @@
#include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $");
MAKE_RCSID("$NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $");
/*
* A shell defines how the commands are run. All commands for a target are
@ -214,13 +214,15 @@ typedef struct Shell {
const char *errOff; /* command to turn off error checking */
const char *echoTmpl; /* template to echo a command */
const char *runIgnTmpl; /* template to run a command
* without error checking */
const char *runChkTmpl; /* template to run a command
* with error checking */
const char *runIgnTmpl; /* template to run a command without error
* checking */
const char *runChkTmpl; /* template to run a command with error
* checking */
/* string literal that results in a newline character when it appears
* outside of any 'quote' or "quote" characters */
/*
* A string literal that results in a newline character when it
* occurs outside of any 'quote' or "quote" characters.
*/
const char *newline;
char commentChar; /* character used by shell for comment lines */
@ -438,7 +440,7 @@ static void watchfd(Job *);
static void clearfd(Job *);
static bool readyfd(Job *);
static char *targPrefix = NULL; /* To identify a job change in the output. */
static char *targPrefix = NULL; /* To identify a job change in the output. */
static Job tokenWaitJob; /* token wait pseudo-job */
static Job childExitJob; /* child exit pseudo-job */
@ -517,13 +519,13 @@ JobDeleteTarget(GNode *gn)
return;
if (gn->type & OP_PHONY)
return;
if (Targ_Precious(gn))
if (GNode_IsPrecious(gn))
return;
if (opts.noExecute)
return;
file = GNode_Path(gn);
if (eunlink(file) != -1)
if (unlink_file(file))
Error("*** %s removed", file);
}
@ -924,7 +926,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
run = GNode_ShouldExecute(job->node);
Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd);
(void)Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd);
/* TODO: handle errors */
xcmdStart = xcmd;
@ -938,7 +940,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
* We're not actually executing anything...
* but this one needs to be - use compat mode just for it.
*/
Compat_RunCommand(ucmd, job->node, ln);
(void)Compat_RunCommand(ucmd, job->node, ln);
free(xcmdStart);
return;
}
@ -1119,7 +1121,7 @@ JobFinishDoneExitedError(Job *job, WAIT_T *inout_status)
else {
if (deleteOnError)
JobDeleteTarget(job->node);
PrintOnError(job->node, NULL);
PrintOnError(job->node, "\n");
}
}
@ -1279,9 +1281,11 @@ TouchRegular(GNode *gn)
return; /* XXX: What about propagating the error? */
}
/* Last resort: update the file's time stamps in the traditional way.
/*
* Last resort: update the file's time stamps in the traditional way.
* XXX: This doesn't work for empty files, which are sometimes used
* as marker files. */
* as marker files.
*/
if (read(fd, &c, 1) == 1) {
(void)lseek(fd, 0, SEEK_SET);
while (write(fd, &c, 1) == -1 && errno == EAGAIN)
@ -1383,7 +1387,7 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
if (gn->flags.fromDepend) {
if (!Job_RunTarget(".STALE", gn->fname))
fprintf(stdout,
"%s: %s, %d: ignoring stale %s for %s\n",
"%s: %s, %u: ignoring stale %s for %s\n",
progname, gn->fname, gn->lineno, makeDependfile,
gn->name);
return true;
@ -1455,9 +1459,8 @@ JobExec(Job *job, char **argv)
sigset_t tmask;
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_job_child(job);
}
#endif
/*
* Reset all signal handlers; this is necessary because we
@ -1539,9 +1542,8 @@ JobExec(Job *job, char **argv)
Trace_Log(JOBSTART, job);
#ifdef USE_META
if (useMeta) {
if (useMeta)
meta_job_parent(job, cpid);
}
#endif
/*
@ -1635,7 +1637,7 @@ JobWriteShellCommands(Job *job, GNode *gn, bool *out_run)
#ifdef USE_META
if (useMeta) {
meta_job_start(job, gn);
if (gn->type & OP_SILENT) /* might have changed */
if (gn->type & OP_SILENT) /* might have changed */
job->echo = false;
}
#endif
@ -1679,7 +1681,7 @@ JobStart(GNode *gn, bool special)
job->special = special || gn->type & OP_SPECIAL;
job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE;
job->echo = !(opts.beSilent || gn->type & OP_SILENT);
job->echo = !(opts.silent || gn->type & OP_SILENT);
/*
* Check the commands now so any attributes from .DEFAULT have a
@ -1698,11 +1700,11 @@ JobStart(GNode *gn, bool special)
* also dead...
*/
if (!cmdsOK) {
PrintOnError(gn, NULL); /* provide some clue */
PrintOnError(gn, "\n"); /* provide some clue */
DieHorribly();
}
} else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) ||
(!opts.noExecute && !opts.touchFlag)) {
(!opts.noExecute && !opts.touch)) {
/*
* The above condition looks very similar to
* GNode_ShouldExecute but is subtly different. It prevents
@ -1715,7 +1717,7 @@ JobStart(GNode *gn, bool special)
* also dead...
*/
if (!cmdsOK) {
PrintOnError(gn, NULL); /* provide some clue */
PrintOnError(gn, "\n"); /* provide some clue */
DieHorribly();
}
@ -1935,7 +1937,7 @@ CollectOutput(Job *job, bool finish)
* we add one of our own free will.
*/
if (*cp != '\0') {
if (!opts.beSilent)
if (!opts.silent)
SwitchOutputTo(job->node);
#ifdef USE_META
if (useMeta) {
@ -1999,7 +2001,7 @@ JobRun(GNode *targ)
Compat_Make(targ, targ);
/* XXX: Replace with GNode_IsError(gn) */
if (targ->made == ERROR) {
PrintOnError(targ, "\n\nStop.");
PrintOnError(targ, "\n\nStop.\n");
exit(1);
}
#endif
@ -2147,9 +2149,8 @@ Job_CatchOutput(void)
* than job->inPollfd.
*/
if (useMeta && job->inPollfd != &fds[i]) {
if (meta_job_event(job) <= 0) {
fds[i].events = 0; /* never mind */
}
if (meta_job_event(job) <= 0)
fds[i].events = 0; /* never mind */
}
#endif
if (--nready == 0)
@ -2203,14 +2204,8 @@ Shell_Init(void)
free(shellErrFlag);
shellErrFlag = NULL;
}
if (shellErrFlag == NULL) {
size_t n = strlen(shell->errFlag) + 2;
shellErrFlag = bmake_malloc(n);
if (shellErrFlag != NULL)
snprintf(shellErrFlag, n, "-%s",
shell->errFlag);
}
if (shellErrFlag == NULL)
shellErrFlag = str_concat2("-", shell->errFlag);
} else if (shellErrFlag != NULL) {
free(shellErrFlag);
shellErrFlag = NULL;
@ -2329,8 +2324,10 @@ Job_Init(void)
AddSig(SIGCONT, JobContinueSig);
(void)Job_RunTarget(".BEGIN", NULL);
/* Create the .END node now, even though no code in the unit tests
* depends on it. See also Targ_GetEndNode in Compat_Run. */
/*
* Create the .END node now, even though no code in the unit tests
* depends on it. See also Targ_GetEndNode in Compat_Run.
*/
(void)Targ_GetEndNode();
}
@ -2470,13 +2467,17 @@ Job_ParseShell(char *line)
} else if (strncmp(arg, "newline=", 8) == 0) {
newShell.newline = arg + 8;
} else if (strncmp(arg, "check=", 6) == 0) {
/* Before 2020-12-10, these two variables
* had been a single variable. */
/*
* Before 2020-12-10, these two variables had
* been a single variable.
*/
newShell.errOn = arg + 6;
newShell.echoTmpl = arg + 6;
} else if (strncmp(arg, "ignore=", 7) == 0) {
/* Before 2020-12-10, these two variables
* had been a single variable. */
/*
* Before 2020-12-10, these two variables had
* been a single variable.
*/
newShell.errOff = arg + 7;
newShell.runIgnTmpl = arg + 7;
} else if (strncmp(arg, "errout=", 7) == 0) {
@ -2618,7 +2619,7 @@ JobInterrupt(bool runINTERRUPT, int signo)
JobSigUnlock(&mask);
if (runINTERRUPT && !opts.touchFlag) {
if (runINTERRUPT && !opts.touch) {
interrupt = Targ_FindNode(".INTERRUPT");
if (interrupt != NULL) {
opts.ignoreErrors = false;
@ -2838,7 +2839,7 @@ Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz)
JobSigLock(&mask);
fd = mkTempFile(pattern, tfile, tfile_sz);
if (tfile != NULL && !DEBUG(SCRIPT))
unlink(tfile);
unlink(tfile);
JobSigUnlock(&mask);
return fd;
@ -2970,7 +2971,7 @@ Job_RunTarget(const char *target, const char *fname)
JobRun(gn);
/* XXX: Replace with GNode_IsError(gn) */
if (gn->made == ERROR) {
PrintOnError(gn, "\n\nStop.");
PrintOnError(gn, "\n\nStop.\n");
exit(1);
}
return true;
@ -3035,4 +3036,4 @@ emul_poll(struct pollfd *fd, int nfd, int timeout)
return npoll;
}
#endif /* USE_SELECT */
#endif /* USE_SELECT */

34
job.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: job.h,v 1.73 2021/04/03 11:08:40 rillig Exp $ */
/* $NetBSD: job.h,v 1.77 2021/12/15 12:58:01 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -80,7 +80,7 @@
#ifndef MAKE_JOB_H
#define MAKE_JOB_H
#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
#ifdef USE_SELECT
/*
@ -92,16 +92,15 @@
#define pollfd emul_pollfd
struct emul_pollfd {
int fd;
short events;
short revents;
int fd;
short events;
short revents;
};
#define POLLIN 0x0001
#define POLLOUT 0x0004
int
emul_poll(struct pollfd *fd, int nfd, int timeout);
int emul_poll(struct pollfd *, int, int);
#endif
/*
@ -145,9 +144,11 @@ typedef struct Job {
/* The target the child is making */
GNode *node;
/* If one of the shell commands is "...", all following commands are
* delayed until the .END node is made. This list node points to the
* first of these commands, if any. */
/*
* If one of the shell commands is "...", all following commands are
* delayed until the .END node is made. This list node points to the
* first of these commands, if any.
*/
StringListNode *tailCmds;
/* This is where the shell commands go. */
@ -187,24 +188,25 @@ extern char *shellErrFlag;
extern int jobTokensRunning; /* tokens currently "out" */
void Shell_Init(void);
const char *Shell_GetNewline(void);
const char *Shell_GetNewline(void) MAKE_ATTR_USE;
void Job_Touch(GNode *, bool);
bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...));
bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...))
MAKE_ATTR_USE;
void Job_CatchChildren(void);
void Job_CatchOutput(void);
void Job_Make(GNode *);
void Job_Init(void);
bool Job_ParseShell(char *);
bool Job_ParseShell(char *) MAKE_ATTR_USE;
int Job_Finish(void);
void Job_End(void);
void Job_Wait(void);
void Job_AbortAll(void);
void Job_TokenReturn(void);
bool Job_TokenWithdraw(void);
bool Job_TokenWithdraw(void) MAKE_ATTR_USE;
void Job_ServerStart(int, int, int);
void Job_SetPrefix(void);
bool Job_RunTarget(const char *, const char *);
void Job_FlagsToString(const Job *, char *, size_t);
int Job_TempFile(const char *, char *, size_t);
int Job_TempFile(const char *, char *, size_t) MAKE_ATTR_USE;
#endif /* MAKE_JOB_H */
#endif

20
lst.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: lst.h,v 1.99 2021/12/05 10:11:31 rillig Exp $ */
/* $NetBSD: lst.h,v 1.102 2021/12/15 12:24:13 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@ -109,7 +109,7 @@ typedef void LstFreeProc(void *);
/* Create or destroy a list */
/* Create a new list. */
List *Lst_New(void);
List *Lst_New(void) MAKE_ATTR_USE;
/* Free the list nodes, but not the list itself. */
void Lst_Done(List *);
/* Free the list nodes, freeing the node data using the given function. */
@ -129,14 +129,14 @@ Lst_Init(List *list)
/* Get information about a list */
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
Lst_IsEmpty(List *list)
{
return list->first == NULL;
}
/* Find the first node that contains the given datum, or NULL. */
ListNode *Lst_FindDatum(List *, const void *);
ListNode *Lst_FindDatum(List *, const void *) MAKE_ATTR_USE;
/* Modify a list */
@ -163,12 +163,13 @@ void LstNode_SetNull(ListNode *);
/* Add a datum at the tail of the queue. */
MAKE_INLINE void
Lst_Enqueue(List *list, void *datum) {
Lst_Enqueue(List *list, void *datum)
{
Lst_Append(list, datum);
}
/* Remove the head node of the queue and return its datum. */
void *Lst_Dequeue(List *);
void *Lst_Dequeue(List *) MAKE_ATTR_USE;
/*
* A vector is an ordered collection of items, allowing for fast indexed
@ -187,7 +188,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.
*/
MAKE_INLINE void *
MAKE_INLINE void * MAKE_ATTR_USE
Vector_Get(Vector *v, size_t i)
{
unsigned char *items = v->items;
@ -198,8 +199,9 @@ void *Vector_Push(Vector *);
void *Vector_Pop(Vector *);
MAKE_INLINE void
Vector_Done(Vector *v) {
Vector_Done(Vector *v)
{
free(v->items);
}
#endif /* MAKE_LST_H */
#endif

548
main.c

File diff suppressed because it is too large Load Diff

51
make.1
View File

@ -1,4 +1,4 @@
.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $
.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg 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 December 12, 2021
.Dd January 28, 2022
.Dt MAKE 1
.Os
.Sh NAME
@ -691,10 +691,38 @@ Variables defined as part of the command line.
Variables that are defined specific to a certain target.
.El
.Pp
Local variables are all built in and their values vary magically from
target to target.
It is not currently possible to define new local variables.
The seven local variables are as follows:
Local variables can be set on a dependency line, if
.Va .MAKE.TARGET_LOCAL_VARIABLES ,
is not set to
.Ql false .
The rest of the line
(which will already have had Global variables expanded),
is the variable value.
For example:
.Bd -literal -offset indent
COMPILER_WRAPPERS+= ccache distcc icecc
${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
.Ed
.Pp
Only the targets
.Ql ${OBJS}
will be impacted by that filter (in "meta" mode) and
simply enabling/disabling any of the wrappers will not render all
of those targets out-of-date.
.Pp
.Em NOTE :
target local variable assignments behave differently in that;
.Bl -tag -width Ds -offset indent
.It Ic \&+=
Only appends to a previous local assignment
for the same target and variable.
.It Ic \&:=
Is redundant with respect to Global variables,
which have already been expanded.
.El
.Pp
The seven built-in local variables are as follows:
.Bl -tag -width ".ARCHIVE" -offset indent
.It Va .ALLSRC
The list of all sources for this target; also known as
@ -846,6 +874,11 @@ For example:
would produce tokens like
.Ql ---make[1234] target ---
making it easier to track the degree of parallelism being achieved.
.It .MAKE.TARGET_LOCAL_VARIABLES
If set to
.Ql false ,
apparent variable assignments in dependency lines are
treated as normal sources.
.It Ev MAKEFLAGS
The environment variable
.Ql Ev MAKEFLAGS
@ -954,6 +987,12 @@ If a file that was generated outside of
.Va .OBJDIR
but within said bailiwick is missing,
the current target is considered out-of-date.
.It Va .MAKE.META.CMP_FILTER
In "meta" mode, it can (very rarely!) be useful to filter command
lines before comparison.
This variable can be set to a set of modifiers that will be applied to
each line of the old and new command that differ, if the filtered
commands still differ, the target is considered out-of-date.
.It Va .MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the meta files
updated.

32
make.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $ */
/* $NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -104,7 +104,7 @@
#include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
MAKE_RCSID("$NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $");
MAKE_RCSID("$NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $");
/* Sequence # to detect recursion. */
static unsigned int checked_seqno = 1;
@ -120,11 +120,11 @@ static GNodeList toBeMade = LST_INIT;
void
debug_printf(const char *fmt, ...)
{
va_list args;
va_list ap;
va_start(args, fmt);
vfprintf(opts.debug_file, fmt, args);
va_end(args);
va_start(ap, fmt);
vfprintf(opts.debug_file, fmt, ap);
va_end(ap);
}
MAKE_ATTR_DEAD static void
@ -370,9 +370,8 @@ GNode_IsOODate(GNode *gn)
}
#ifdef USE_META
if (useMeta) {
if (useMeta)
oodate = meta_oodate(gn, oodate);
}
#endif
/*
@ -599,8 +598,9 @@ Make_Recheck(GNode *gn)
}
#endif
/* XXX: The returned mtime may differ from gn->mtime.
* Intentionally? */
/*
* XXX: The returned mtime may differ from gn->mtime. Intentionally?
*/
return mtime;
}
@ -952,7 +952,9 @@ MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext)
/* If this node is on the RHS of a .ORDER, check LHSs. */
if (IsWaitingForOrder(cn)) {
/* Can't build this (or anything else in this child list) yet */
/*
* Can't build this (or anything else in this child list) yet
*/
cn->made = DEFERRED;
return false; /* but keep looking */
}
@ -1070,7 +1072,7 @@ MakeStartJobs(void)
gn->made = BEINGMADE;
if (GNode_IsOODate(gn)) {
DEBUG0(MAKE, "out-of-date\n");
if (opts.queryFlag)
if (opts.query)
return true;
GNode_SetLocalVars(gn);
Job_Make(gn);
@ -1327,7 +1329,9 @@ add_wait_dependency(GNodeListNode *owln, GNode *wn)
DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n",
cn->name, cn->cohort_num, wn->name);
/* XXX: This pattern should be factored out, it repeats often */
/*
* XXX: This pattern should be factored out, it repeats often
*/
Lst_Append(&wn->children, cn);
wn->unmade++;
Lst_Append(&cn->parents, wn);
@ -1436,7 +1440,7 @@ Make_Run(GNodeList *targs)
Targ_PrintGraph(1);
}
if (opts.queryFlag) {
if (opts.query) {
/*
* We wouldn't do any work unless we could start some jobs
* in the next loop... (we won't actually start any, of

672
make.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: make.h,v 1.270 2021/11/28 23:12:51 rillig Exp $ */
/* $NetBSD: make.h,v 1.298 2022/02/05 00:26:21 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -74,7 +74,7 @@
/*
* make.h --
* The global definitions for pmake
* The global definitions for make
*/
#ifndef MAKE_MAKE_H
@ -110,9 +110,9 @@
#define MAKE_GNUC_PREREQ(x, y) \
((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
(__GNUC__ > (x)))
#else /* defined(__GNUC__) */
#else
#define MAKE_GNUC_PREREQ(x, y) 0
#endif /* defined(__GNUC__) */
#endif
#if MAKE_GNUC_PREREQ(2, 7)
#define MAKE_ATTR_UNUSED __attribute__((__unused__))
@ -135,7 +135,17 @@
#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
#endif
#if MAKE_GNUC_PREREQ(4, 0)
#define MAKE_ATTR_USE __attribute__((__warn_unused_result__))
#else
#define MAKE_ATTR_USE /* delete */
#endif
#if __STDC__ >= 199901L || defined(lint)
#define MAKE_INLINE static inline MAKE_ATTR_UNUSED
#else
#define MAKE_INLINE static MAKE_ATTR_UNUSED
#endif
/* MAKE_STATIC marks a function that may or may not be inlined. */
#if defined(lint)
@ -206,26 +216,34 @@ typedef unsigned char bool;
typedef enum GNodeMade {
/* Not examined yet. */
UNMADE,
/* The node has been examined but is not yet ready since its
* dependencies have to be made first. */
/*
* The node has been examined but is not yet ready since its
* dependencies have to be made first.
*/
DEFERRED,
/* The node is on the toBeMade list. */
REQUESTED,
/* The node is already being made. Trying to build a node in this
* state indicates a cycle in the graph. */
/*
* The node is already being made. Trying to build a node in this
* state indicates a cycle in the graph.
*/
BEINGMADE,
/* Was out-of-date and has been made. */
MADE,
/* Was already up-to-date, does not need to be made. */
UPTODATE,
/* An error occurred while it was being made.
* Used only in compat mode. */
/*
* An error occurred while it was being made. Used only in compat
* mode.
*/
ERROR,
/* The target was aborted due to an error making a dependency.
* Used only in compat mode. */
/*
* The target was aborted due to an error making a dependency. Used
* only in compat mode.
*/
ABORTED
} GNodeMade;
@ -241,16 +259,22 @@ typedef enum GNodeMade {
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. */
/*
* 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,
/* The dependency operator '!' always executes its commands, even if
* its children are up-to-date. */
/*
* The dependency operator '!' always executes its commands, even if
* its children are up-to-date.
*/
OP_FORCE = 1 << 1,
/* The dependency operator '::' behaves like ':', except that it
/*
* 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.
* Each individual dependency group is called a cohort. */
* groups is 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 '::'. */
@ -260,21 +284,29 @@ typedef enum GNodeType {
OP_OPTIONAL = 1 << 3,
/* Use associated commands for parents. */
OP_USE = 1 << 4,
/* Target is never out of date, but always execute commands anyway.
* Its time doesn't matter, so it has none...sort of. */
/*
* Target is never out of date, but always execute commands anyway.
* Its time doesn't matter, so it has none...sort of.
*/
OP_EXEC = 1 << 5,
/* Ignore non-zero exit status from shell commands when creating the
* node. */
/*
* Ignore non-zero exit status from shell commands when creating the
* node.
*/
OP_IGNORE = 1 << 6,
/* Don't remove the target when interrupted. */
OP_PRECIOUS = 1 << 7,
/* Don't echo commands when executed. */
OP_SILENT = 1 << 8,
/* Target is a recursive make so its commands should always be
* executed when it is out of date, regardless of the state of the
* -n or -t flags. */
/*
* Target is a recursive make so its commands should always be
* executed when it is out of date, regardless of the state of the -n
* or -t flags.
*/
OP_MAKE = 1 << 9,
/* Target is out-of-date only if any of its children was out-of-date. */
/*
* Target is out-of-date only if any of its children was out-of-date.
*/
OP_JOIN = 1 << 10,
/* Assume the children of the node have been already made. */
OP_MADE = 1 << 11,
@ -282,20 +314,26 @@ typedef enum GNodeType {
OP_SPECIAL = 1 << 12,
/* 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 (.IMPSRC, .ALLSRC). */
/*
* The node is invisible to its parents. I.e. it doesn't show up in
* the parents' local variables (.IMPSRC, .ALLSRC).
*/
OP_INVISIBLE = 1 << 14,
/* The node does not become the main target, even if it is the first
* target in the first makefile. */
/*
* The node does not become the main target, even if it is the first
* target in the first makefile.
*/
OP_NOTMAIN = 1 << 15,
/* Not a file target; run always. */
OP_PHONY = 1 << 16,
/* Don't search for the file in the path. */
OP_NOPATH = 1 << 17,
/* In a dependency line "target: source1 .WAIT source2", source1 is
/*
* 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. */
* appear more than once in a single dependency declaration.
*/
OP_WAIT = 1 << 18,
/* .NOMETA do not create a .meta file */
OP_NOMETA = 1 << 19,
@ -313,28 +351,35 @@ typedef enum GNodeType {
/* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */
OP_MEMBER = 1 << 29,
/* The node is a library,
* its name has the form "-l<libname>" */
/*
* The node is a library, its name has the form "-l<libname>".
*/
OP_LIB = 1 << 28,
/* The node is an archive member,
* its name has the form "archive(member)" */
/*
* The node is an archive member, its name has the form
* "archive(member)".
*/
/* XXX: How does this differ from OP_MEMBER? */
OP_ARCHV = 1 << 27,
/* Target has all the commands it should. Used when parsing to catch
/*
* Target has all the commands it should. Used when parsing to catch
* multiple command groups for a target. Only applies to the
* dependency operators ':' and '!', but not to '::'. */
* dependency operators ':' and '!', but not to '::'.
*/
OP_HAS_COMMANDS = 1 << 26,
/* The special command "..." has been seen. All further commands from
* this node will be saved on the .END node instead, to be executed at
* the very end. */
/*
* The special command "..." has been seen. All further commands from
* this node will be saved on the .END node instead, to be executed
* at the very end.
*/
OP_SAVE_CMDS = 1 << 25,
/* Already processed by Suff_FindDeps, to find dependencies from
* suffix transformation rules. */
/*
* Already processed by Suff_FindDeps, to find dependencies from
* suffix transformation rules.
*/
OP_DEPS_FOUND = 1 << 24,
/* Node found while expanding .ALLSRC */
OP_MARK = 1 << 23,
OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
OP_MARK = 1 << 23
} GNodeType;
typedef struct GNodeFlags {
@ -377,14 +422,20 @@ typedef struct GNode {
char *name;
/* The unexpanded name of a .USE node */
char *uname;
/* The full pathname of the file belonging to the target.
/*
* The full pathname of the file belonging to the target.
*
* XXX: What about .PHONY targets? These don't have an associated
* path. */
* path.
*/
char *path;
/* The type of operator used to define the sources (see the OP flags
/*
* The type of operator used to define the sources (see the OP flags
* below).
* XXX: This looks like a wild mixture of type and flags. */
*
* XXX: This looks like a wild mixture of type and flags.
*/
GNodeType type;
GNodeFlags flags;
@ -393,29 +444,39 @@ typedef struct GNode {
/* The number of unmade children */
int unmade;
/* The modification time; 0 means the node does not have a
* corresponding file; see GNode_IsOODate. */
/*
* The modification time; 0 means the node does not have a
* corresponding file; see GNode_IsOODate.
*/
time_t mtime;
struct GNode *youngestChild;
/* The GNodes for which this node is an implied source. May be empty.
* For example, when there is an inference rule for .c.o, the node for
* file.c has the node for file.o in this list. */
/*
* The GNodes for which this node is an implied source. May be empty.
* For example, when there is an inference rule for .c.o, the node
* for file.c has the node for file.o in this list.
*/
GNodeList implicitParents;
/* The nodes that depend on this one, or in other words, the nodes for
* which this is a source. */
/*
* The nodes that depend on this one, or in other words, the nodes
* for which this is a source.
*/
GNodeList parents;
/* The nodes on which this one depends. */
GNodeList children;
/* .ORDER nodes we need made. The nodes that must be made (if they're
/*
* .ORDER nodes we need made. The nodes that must be made (if they're
* made) before this node can be made, but that do not enter into the
* datedness of this node. */
* datedness of this node.
*/
GNodeList order_pred;
/* .ORDER nodes who need us. The nodes that must be made (if they're
/*
* .ORDER nodes who need us. The nodes that must be made (if they're
* made at all) after this node is made, but that do not depend on
* this node, in the normal sense. */
* this node, in the normal sense.
*/
GNodeList order_succ;
/*
@ -427,8 +488,10 @@ typedef struct GNode {
char cohort_num[8];
/* The number of unmade instances on the cohorts list */
int unmade_cohorts;
/* Pointer to the first instance of a '::' node; only set when on a
* cohorts list */
/*
* Pointer to the first instance of a '::' node; only set when on a
* cohorts list
*/
struct GNode *centurion;
/* Last time (sequence number) we tried to make this node */
@ -447,21 +510,24 @@ typedef struct GNode {
/* The commands to be given to a shell to create this target. */
StringList commands;
/* Suffix for the node (determined by Suff_FindDeps and opaque to
* everyone but the Suff module) */
/*
* Suffix for the node (determined by Suff_FindDeps and opaque to
* everyone but the Suff module)
*/
struct Suffix *suffix;
/* Filename where the GNode got defined */
/* XXX: What is the lifetime of this string? */
/* Filename where the GNode got defined, unlimited lifetime */
const char *fname;
/* Line number where the GNode got defined */
int lineno;
/* Line number where the GNode got defined, 1-based */
unsigned lineno;
} GNode;
/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
/* Exit when the current top-level makefile has been parsed
* completely. */
/*
* 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,
@ -472,20 +538,20 @@ typedef enum ParseErrorLevel {
/*
* Values returned by Cond_EvalLine and Cond_EvalCondition.
*/
typedef enum CondEvalResult {
COND_PARSE, /* Parse the next lines */
COND_SKIP, /* Skip the next lines */
COND_INVALID /* Not a conditional statement */
} CondEvalResult;
typedef enum CondResult {
CR_TRUE, /* Parse the next lines */
CR_FALSE, /* Skip the next lines */
CR_ERROR /* Unknown directive or parse error */
} CondResult;
/* 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 */
#define IMPSRC "<" /* Source implied by transformation */
#define PREFIX "*" /* Common prefix */
#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
#define MEMBER "%" /* Member in "archive(member)" syntax */
#define TARGET "@" /* Target of dependency */
#define OODATE "?" /* All out-of-date sources */
#define ALLSRC ">" /* All sources */
#define IMPSRC "<" /* Source implied by transformation */
#define PREFIX "*" /* Common prefix */
#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
#define MEMBER "%" /* Member in "archive(member)" syntax */
/*
* Global Variables
@ -543,6 +609,7 @@ extern int makelevel;
extern char *makeDependfile;
/* If we replaced environ, this will be non-NULL. */
extern char **savedEnv;
extern GNode *mainNode;
extern pid_t myPid;
@ -560,34 +627,32 @@ extern pid_t myPid;
# define MAKE_LEVEL_ENV "MAKELEVEL"
#endif
typedef enum DebugFlags {
DEBUG_NONE = 0,
DEBUG_ARCH = 1 << 0,
DEBUG_COND = 1 << 1,
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
typedef struct DebugFlags {
bool DEBUG_ARCH:1;
bool DEBUG_COND:1;
bool DEBUG_CWD:1;
bool DEBUG_DIR:1;
bool DEBUG_ERROR:1;
bool DEBUG_FOR:1;
bool DEBUG_GRAPH1:1;
bool DEBUG_GRAPH2:1;
bool DEBUG_GRAPH3:1;
bool DEBUG_HASH:1;
bool DEBUG_JOB:1;
bool DEBUG_LOUD:1;
bool DEBUG_MAKE:1;
bool DEBUG_META:1;
bool DEBUG_PARSE:1;
bool DEBUG_SCRIPT:1;
bool DEBUG_SHELL:1;
bool DEBUG_SUFF:1;
bool DEBUG_TARG:1;
bool DEBUG_VAR:1;
} DebugFlags;
#define CONCAT(a, b) a##b
#define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0)
#define DEBUG(module) (opts.debug.CONCAT(DEBUG_, module))
void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
@ -597,8 +662,8 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
debug_printf args; \
} while (false)
#define DEBUG0(module, text) \
DEBUG_IMPL(module, ("%s", text))
#define DEBUG0(module, fmt) \
DEBUG_IMPL(module, (fmt))
#define DEBUG1(module, fmt, arg1) \
DEBUG_IMPL(module, (fmt, arg1))
#define DEBUG2(module, fmt, arg1, arg2) \
@ -621,17 +686,21 @@ typedef struct CmdOpts {
/* -B: whether we are make compatible */
bool compatMake;
/* -d: debug control: There is one bit per module. It is up to the
* module what debug information to print. */
/*
* -d: debug control: There is one bit per module. It is up to the
* module what debug information to print.
*/
DebugFlags debug;
/* -df: debug output is written here - default stderr */
FILE *debug_file;
/* -dL: lint mode
/*
* -dL: lint mode
*
* Runs make in strict mode, with additional checks and better error
* handling. */
* handling.
*/
bool strict;
/* -dV: for the -V option, print unexpanded variable values */
@ -646,12 +715,16 @@ typedef struct CmdOpts {
/* -i: if true, ignore all errors from shell commands */
bool ignoreErrors;
/* -j: the maximum number of jobs that can run in parallel;
* this is coordinated with the submakes */
/*
* -j: the maximum number of jobs that can run in parallel; this is
* coordinated with the submakes
*/
int maxJobs;
/* -k: if true and an error occurs while making a node, continue
* making nodes that do not depend on the erroneous node */
/*
* -k: if true and an error occurs while making a node, continue
* making nodes that do not depend on the erroneous node
*/
bool keepgoing;
/* -N: execute no commands from the targets */
@ -664,17 +737,19 @@ typedef struct CmdOpts {
* -q: if true, do not really make anything, just see if the targets
* are out-of-date
*/
bool queryFlag;
bool query;
/* -r: raw mode, do not load the builtin rules. */
bool noBuiltins;
/* -s: don't echo the shell commands before executing them */
bool beSilent;
bool silent;
/* -t: touch the targets if they are out-of-date, but don't actually
* make them */
bool touchFlag;
/*
* -t: touch the targets if they are out-of-date, but don't actually
* make them
*/
bool touch;
/* -[Vv]: print expanded or unexpanded selected variables */
PrintVarsMode printVars;
@ -687,90 +762,364 @@ typedef struct CmdOpts {
/* -w: print 'Entering' and 'Leaving' for submakes */
bool enterFlag;
/* -X: if true, do not export variables set on the command line to the
* environment. */
/*
* -X: if true, do not export variables set on the command line to
* the environment.
*/
bool varNoExportEnv;
/* The target names specified on the command line.
* Used to resolve .if make(...) statements. */
/*
* The target names specified on the command line. Used to resolve
* .if make(...) statements.
*/
StringList create;
} CmdOpts;
extern CmdOpts opts;
#include "nonints.h"
/* arch.c */
void Arch_Init(void);
void Arch_End(void);
bool Arch_ParseArchive(char **, GNodeList *, GNode *);
void Arch_Touch(GNode *);
void Arch_TouchLib(GNode *);
void Arch_UpdateMTime(GNode *gn);
void Arch_UpdateMemberMTime(GNode *gn);
void Arch_FindLib(GNode *, SearchPath *);
bool Arch_LibOODate(GNode *) MAKE_ATTR_USE;
bool Arch_IsLib(GNode *) MAKE_ATTR_USE;
/* compat.c */
bool Compat_RunCommand(const char *, GNode *, StringListNode *);
void Compat_Run(GNodeList *);
void Compat_Make(GNode *, GNode *);
/* cond.c */
CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE;
CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE;
void Cond_restore_depth(unsigned int);
unsigned int Cond_save_depth(void) MAKE_ATTR_USE;
/* dir.c; see also dir.h */
MAKE_INLINE const char * MAKE_ATTR_USE
str_basename(const char *pathname)
{
const char *lastSlash = strrchr(pathname, '/');
return lastSlash != NULL ? lastSlash + 1 : pathname;
}
MAKE_INLINE SearchPath * MAKE_ATTR_USE
SearchPath_New(void)
{
SearchPath *path = bmake_malloc(sizeof *path);
Lst_Init(&path->dirs);
return path;
}
void SearchPath_Free(SearchPath *);
/* for.c */
struct ForLoop;
int For_Eval(const char *) MAKE_ATTR_USE;
bool For_Accum(const char *, int *) MAKE_ATTR_USE;
void For_Run(unsigned, unsigned);
bool For_NextIteration(struct ForLoop *, Buffer *);
char *ForLoop_Details(struct ForLoop *);
void ForLoop_Free(struct ForLoop *);
/* job.c */
void JobReapChild(pid_t, int, bool);
/* main.c */
void Main_ParseArgLine(const char *);
char *Cmd_Exec(const char *, char **) MAKE_ATTR_USE;
void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
void DieHorribly(void) MAKE_ATTR_DEAD;
void Finish(int) MAKE_ATTR_DEAD;
bool unlink_file(const char *) MAKE_ATTR_USE;
void execDie(const char *, const char *);
char *getTmpdir(void) MAKE_ATTR_USE;
bool ParseBoolean(const char *, bool) MAKE_ATTR_USE;
const char *cached_realpath(const char *, char *);
bool GetBooleanExpr(const char *, bool);
/* parse.c */
void Parse_Init(void);
void Parse_End(void);
void PrintLocation(FILE *, bool, const char *, unsigned);
void PrintStackTrace(bool);
void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
bool Parse_VarAssign(const char *, bool, GNode *) MAKE_ATTR_USE;
void Parse_AddIncludeDir(const char *);
void Parse_File(const char *, int);
void Parse_PushInput(const char *, unsigned, unsigned, Buffer,
struct ForLoop *);
void Parse_MainName(GNodeList *);
int Parse_NumErrors(void) MAKE_ATTR_USE;
/* suff.c */
void Suff_Init(void);
void Suff_End(void);
void Suff_ClearSuffixes(void);
bool Suff_IsTransform(const char *) MAKE_ATTR_USE;
GNode *Suff_AddTransform(const char *);
void Suff_EndTransform(GNode *);
void Suff_AddSuffix(const char *);
SearchPath *Suff_GetPath(const char *) MAKE_ATTR_USE;
void Suff_ExtendPaths(void);
void Suff_AddInclude(const char *);
void Suff_AddLib(const char *);
void Suff_FindDeps(GNode *);
SearchPath *Suff_FindPath(GNode *) MAKE_ATTR_USE;
void Suff_SetNull(const char *);
void Suff_PrintAll(void);
char *Suff_NamesStr(void) MAKE_ATTR_USE;
/* targ.c */
void Targ_Init(void);
void Targ_End(void);
void Targ_Stats(void);
GNodeList *Targ_List(void) MAKE_ATTR_USE;
GNode *GNode_New(const char *) MAKE_ATTR_USE;
GNode *Targ_FindNode(const char *) MAKE_ATTR_USE;
GNode *Targ_GetNode(const char *) MAKE_ATTR_USE;
GNode *Targ_NewInternalNode(const char *) MAKE_ATTR_USE;
GNode *Targ_GetEndNode(void);
void Targ_FindList(GNodeList *, StringList *);
void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int);
void Targ_PrintNodes(GNodeList *, int);
const char *Targ_FmtTime(time_t) MAKE_ATTR_USE;
void Targ_PrintType(GNodeType);
void Targ_PrintGraph(int);
void Targ_Propagate(void);
const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE;
/* var.c */
void Var_Init(void);
void Var_End(void);
typedef enum VarEvalMode {
/*
* Only parse the expression but don't evaluate any part of it.
*
* TODO: Document what Var_Parse and Var_Subst return in this mode.
* As of 2021-03-15, they return unspecified, inconsistent results.
*/
VARE_PARSE_ONLY,
/* Parse and evaluate the expression. */
VARE_WANTRES,
/*
* Parse and evaluate the expression. It is an error if a
* subexpression evaluates to undefined.
*/
VARE_UNDEFERR,
/*
* Parse and evaluate the expression. Keep '$$' as '$$' instead of
* reducing it to a single '$'. Subexpressions that evaluate to
* undefined expand to an empty string.
*
* 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.
*/
VARE_EVAL_KEEP_DOLLAR,
/*
* Parse and evaluate the expression. Keep undefined variables as-is
* instead of expanding them to an empty string.
*
* 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)".
*/
VARE_EVAL_KEEP_UNDEF,
/*
* Parse and evaluate the expression. Keep '$$' as '$$' and preserve
* undefined subexpressions.
*/
VARE_KEEP_DOLLAR_UNDEF
} VarEvalMode;
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 = 1 << 1
} VarSetFlags;
/* The state of error handling returned by Var_Parse. */
typedef enum VarParseResult {
/* Both parsing and evaluation succeeded. */
VPR_OK,
/* Parsing or evaluating failed, with an error message. */
VPR_ERR,
/*
* Parsing succeeded, undefined expressions are allowed and the
* expression was still undefined after applying all modifiers.
* No error message is printed in this case.
*
* Some callers handle this case differently, so return this
* information to them, for now.
*
* TODO: Instead of having this special return value, rather ensure
* that VARE_EVAL_KEEP_UNDEF is processed properly.
*/
VPR_UNDEF
} VarParseResult;
typedef enum VarExportMode {
/* .export-env */
VEM_ENV,
/* .export: Initial export or update an already exported variable. */
VEM_PLAIN,
/* .export-literal: Do not expand the variable value. */
VEM_LITERAL
} VarExportMode;
void Var_Delete(GNode *, const char *);
void Var_Undef(const char *);
void Var_Set(GNode *, const char *, const char *);
void Var_SetExpand(GNode *, const char *, const char *);
void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags);
void Var_Append(GNode *, const char *, const char *);
void Var_AppendExpand(GNode *, const char *, const char *);
bool Var_Exists(GNode *, const char *) MAKE_ATTR_USE;
bool Var_ExistsExpand(GNode *, const char *) MAKE_ATTR_USE;
FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE;
const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE;
VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *);
VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **);
void Var_Expand(FStr *, GNode *, VarEvalMode);
void Var_Stats(void);
void Var_Dump(GNode *);
void Var_ReexportVars(void);
void Var_Export(VarExportMode, const char *);
void Var_ExportVars(const char *);
void Var_UnExport(bool, const char *);
void Global_Set(const char *, const char *);
void Global_Append(const char *, const char *);
void Global_Delete(const char *);
/* util.c */
typedef void (*SignalProc)(int);
SignalProc bmake_signal(int, SignalProc);
/* make.c */
void GNode_UpdateYoungestChild(GNode *, GNode *);
bool GNode_IsOODate(GNode *);
bool GNode_IsOODate(GNode *) MAKE_ATTR_USE;
void Make_ExpandUse(GNodeList *);
time_t Make_Recheck(GNode *);
time_t Make_Recheck(GNode *) MAKE_ATTR_USE;
void Make_HandleUse(GNode *, GNode *);
void Make_Update(GNode *);
void GNode_SetLocalVars(GNode *);
bool Make_Run(GNodeList *);
bool shouldDieQuietly(GNode *, int);
bool shouldDieQuietly(GNode *, int) MAKE_ATTR_USE;
void PrintOnError(GNode *, const char *);
void Main_ExportMAKEFLAGS(bool);
bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
int mkTempFile(const char *, char *, size_t);
int mkTempFile(const char *, char *, size_t) MAKE_ATTR_USE;
int str2Lst_Append(StringList *, char *);
void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
bool GNode_ShouldExecute(GNode *gn);
bool GNode_ShouldExecute(GNode *gn) MAKE_ATTR_USE;
/* See if the node was seen on the left-hand side of a dependency operator. */
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsTarget(const GNode *gn)
{
return (gn->type & OP_OPMASK) != OP_NONE;
}
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_Path(const GNode *gn)
{
return gn->path != NULL ? gn->path : gn->name;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsWaitingFor(const GNode *gn)
{
return gn->flags.remake && gn->made <= REQUESTED;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsReady(const GNode *gn)
{
return gn->made > DEFERRED;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsDone(const GNode *gn)
{
return gn->made >= MADE;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsError(const GNode *gn)
{
return gn->made == ERROR || gn->made == ABORTED;
}
MAKE_INLINE const char *
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsMainCandidate(const GNode *gn)
{
return (gn->type & (OP_NOTMAIN | OP_USE | OP_USEBEFORE |
OP_EXEC | OP_TRANSFORM)) == 0;
}
/* Return whether the target file should be preserved on interrupt. */
MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsPrecious(const GNode *gn)
{
/* XXX: Why are '::' targets precious? */
return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
}
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); }
MAKE_INLINE const char *
MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); }
MAKE_INLINE void *
MAKE_INLINE void * MAKE_ATTR_USE
UNCONST(const void *ptr)
{
void *ret;
@ -795,19 +1144,21 @@ UNCONST(const void *ptr)
#define KILLPG(pid, sig) killpg((pid), (sig))
#endif
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; }
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; }
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; }
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
ch_islower(char ch) { return islower((unsigned char)ch) != 0; }
MAKE_INLINE bool MAKE_ATTR_USE
ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; }
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; }
MAKE_INLINE char
MAKE_INLINE char MAKE_ATTR_USE
ch_tolower(char ch) { return (char)tolower((unsigned char)ch); }
MAKE_INLINE char
MAKE_INLINE char MAKE_ATTR_USE
ch_toupper(char ch) { return (char)toupper((unsigned char)ch); }
MAKE_INLINE void
@ -824,6 +1175,17 @@ cpp_skip_hspace(const char **pp)
(*pp)++;
}
MAKE_INLINE bool
cpp_skip_string(const char **pp, const char *s)
{
const char *p = *pp;
while (*p == *s && *s != '\0')
p++, s++;
if (*s == '\0')
*pp = p;
return *s == '\0';
}
MAKE_INLINE void
pp_skip_whitespace(char **pp)
{
@ -863,4 +1225,4 @@ pp_skip_hspace(char **pp)
# define MAKE_RCSID(id) static volatile char rcsid[] = id
#endif
#endif /* MAKE_MAKE_H */
#endif

View File

@ -1,4 +1,4 @@
/* $NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $ */
/* $NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $ */
/*
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@ -30,7 +30,7 @@
#include "make.h"
MAKE_RCSID("$NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $");
MAKE_RCSID("$NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $");
#ifndef USE_EMALLOC
@ -57,12 +57,12 @@ bmake_malloc(size_t len)
char *
bmake_strdup(const char *str)
{
size_t len;
size_t size;
char *p;
len = strlen(str) + 1;
p = bmake_malloc(len);
return memcpy(p, str, len);
size = strlen(str) + 1;
p = bmake_malloc(size);
return memcpy(p, str, size);
}
/* Allocate a string starting from str with exactly len characters. */

View File

@ -1,4 +1,4 @@
/* $NetBSD: make_malloc.h,v 1.16 2021/01/19 20:51:46 rillig Exp $ */
/* $NetBSD: make_malloc.h,v 1.18 2021/12/15 11:01:39 rillig Exp $ */
/*
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@ -27,10 +27,10 @@
*/
#ifndef USE_EMALLOC
void *bmake_malloc(size_t);
void *bmake_realloc(void *, size_t);
char *bmake_strdup(const char *);
char *bmake_strldup(const char *, size_t);
void *bmake_malloc(size_t) MAKE_ATTR_USE;
void *bmake_realloc(void *, size_t) MAKE_ATTR_USE;
char *bmake_strdup(const char *) MAKE_ATTR_USE;
char *bmake_strldup(const char *, size_t) MAKE_ATTR_USE;
#else
#include <util.h>
#define bmake_malloc(n) emalloc(n)
@ -39,18 +39,4 @@ char *bmake_strldup(const char *, size_t);
#define bmake_strldup(s, n) estrndup(s, n)
#endif
char *bmake_strsedup(const char *, const char *);
/*
* Thin wrapper around free(3) to avoid the extra function call in case
* p is NULL, to save a few machine instructions.
*
* The case of a NULL pointer happens especially often after Var_Value,
* since only environment variables need to be freed, but not others.
*/
MAKE_INLINE void
bmake_free(void *p)
{
if (p != NULL)
free(p);
}
char *bmake_strsedup(const char *, const char *) MAKE_ATTR_USE;

170
meta.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: meta.c,v 1.185 2021/11/27 22:04:02 rillig Exp $ */
/* $NetBSD: meta.c,v 1.196 2022/02/04 23:22:19 rillig Exp $ */
/*
* Implement 'meta' mode.
@ -69,6 +69,9 @@ static char *metaIgnorePathsStr; /* string storage for the list */
#ifndef MAKE_META_IGNORE_FILTER
#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER"
#endif
#ifndef MAKE_META_CMP_FILTER
#define MAKE_META_CMP_FILTER ".MAKE.META.CMP_FILTER"
#endif
bool useMeta = false;
static bool useFilemon = false;
@ -80,6 +83,7 @@ static bool metaVerbose = false;
static bool metaIgnoreCMDs = false; /* ignore CMDs in .meta files */
static bool metaIgnorePatterns = false; /* do we need to do pattern matches */
static bool metaIgnoreFilter = false; /* do we have more complex filtering? */
static bool metaCmpFilter = false; /* do we have CMP_FILTER ? */
static bool metaCurdirOk = false; /* write .meta in .CURDIR Ok? */
static bool metaSilent = false; /* if we have a .meta be SILENT */
@ -208,42 +212,25 @@ filemon_read(FILE *mfp, int fd)
* we use this, to clean up ./ and ../
*/
static void
eat_dots(char *buf, size_t bufsz, int dots)
eat_dots(char *buf)
{
char *cp;
char *cp2;
const char *eat;
size_t eatlen;
char *p;
switch (dots) {
case 1:
eat = "/./";
eatlen = 2;
break;
case 2:
eat = "/../";
eatlen = 3;
break;
default:
return;
}
while ((p = strstr(buf, "/./")) != NULL)
memmove(p, p + 2, strlen(p + 2) + 1);
do {
cp = strstr(buf, eat);
if (cp != NULL) {
cp2 = cp + eatlen;
if (dots == 2 && cp > buf) {
do {
cp--;
} while (cp > buf && *cp != '/');
}
if (*cp == '/') {
strlcpy(cp, cp2, bufsz - (size_t)(cp - buf));
} else {
return; /* can't happen? */
}
while ((p = strstr(buf, "/../")) != NULL) {
char *p2 = p + 3;
if (p > buf) {
do {
p--;
} while (p > buf && *p != '/');
}
} while (cp != NULL);
if (*p == '/')
memmove(p, p2, strlen(p2) + 1);
else
return; /* can't happen? */
}
}
static char *
@ -287,8 +274,7 @@ meta_name(char *mname, size_t mnamelen,
} else {
snprintf(buf, sizeof buf, "%s/%s", cwd, tname);
}
eat_dots(buf, sizeof buf, 1); /* ./ */
eat_dots(buf, sizeof buf, 2); /* ../ */
eat_dots(buf);
tname = buf;
}
}
@ -330,15 +316,14 @@ is_submake(const char *cmd, GNode *gn)
static const char *p_make = NULL;
static size_t p_len;
char *mp = NULL;
const char *cp, *cp2;
const char *cp2;
bool rc = false;
if (p_make == NULL) {
p_make = Var_Value(gn, ".MAKE").str;
p_len = strlen(p_make);
}
cp = strchr(cmd, '$');
if (cp != NULL) {
if (strchr(cmd, '$') != NULL) {
(void)Var_Subst(cmd, gn, VARE_WANTRES, &mp);
/* TODO: handle errors */
cmd = mp;
@ -385,13 +370,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn)
{
FStr xcmd = FStr_InitRefer(ucmd);
if (strchr(ucmd, '$') != NULL) {
char *expanded;
(void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded);
/* TODO: handle errors */
xcmd = FStr_InitOwn(expanded);
}
Var_Expand(&xcmd, gn, VARE_WANTRES);
fprintf(fp, "CMD %s\n", xcmd.str);
FStr_Done(&xcmd);
}
@ -601,7 +580,6 @@ meta_mode_init(const char *make_mode)
{
static bool once = false;
const char *cp;
FStr value;
useMeta = true;
useFilemon = true;
@ -658,16 +636,9 @@ meta_mode_init(const char *make_mode)
/*
* We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
*/
value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS);
if (value.str != NULL) {
metaIgnorePatterns = true;
FStr_Done(&value);
}
value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER);
if (value.str != NULL) {
metaIgnoreFilter = true;
FStr_Done(&value);
}
metaIgnorePatterns = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS);
metaIgnoreFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER);
metaCmpFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_CMP_FILTER);
}
/*
@ -1061,7 +1032,7 @@ meta_ignore(GNode *gn, const char *p)
* Setting oodate true will have that effect.
*/
#define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \
warnx("%s: %d: malformed", fname, lineno); \
warnx("%s: %u: malformed", fname, lineno); \
oodate = true; \
continue; \
}
@ -1084,6 +1055,39 @@ append_if_new(StringList *list, const char *str)
Lst_Append(list, bmake_strdup(str));
}
static char *
meta_filter_cmd(Buffer *buf, GNode *gn, char *s)
{
Buf_Clear(buf);
Buf_AddStr(buf, "${");
Buf_AddStr(buf, s);
Buf_AddStr(buf, ":L:${" MAKE_META_CMP_FILTER ":ts:}}");
Var_Subst(buf->data, gn, VARE_WANTRES, &s);
return s;
}
static int
meta_cmd_cmp(GNode *gn, char *a, char *b, bool filter)
{
static bool once = false;
static Buffer buf;
int rc;
rc = strcmp(a, b);
if (rc == 0 || !filter)
return rc;
if (!once) {
once = true;
Buf_Init(&buf);
}
a = meta_filter_cmd(&buf, gn, a);
b = meta_filter_cmd(&buf, gn, b);
rc = strcmp(a, b);
free(a);
free(b);
return rc;
}
bool
meta_oodate(GNode *gn, bool oodate)
{
@ -1108,6 +1112,7 @@ meta_oodate(GNode *gn, bool oodate)
bool needOODATE = false;
StringList missingFiles;
bool have_filemon = false;
bool cmp_filter;
if (oodate)
return oodate; /* we're done */
@ -1139,7 +1144,7 @@ meta_oodate(GNode *gn, bool oodate)
if ((fp = fopen(fname, "r")) != NULL) {
static char *buf = NULL;
static size_t bufsz;
int lineno = 0;
unsigned lineno = 0;
int lastpid = 0;
int pid;
int x;
@ -1167,13 +1172,16 @@ meta_oodate(GNode *gn, bool oodate)
/* we want to track all the .meta we read */
Global_Append(".MAKE.META.FILES", fname);
cmp_filter = metaCmpFilter ? metaCmpFilter :
Var_Exists(gn, MAKE_META_CMP_FILTER);
cmdNode = gn->commands.first;
while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
lineno++;
if (buf[x - 1] == '\n')
buf[x - 1] = '\0';
else {
warnx("%s: %d: line truncated at %u", fname, lineno, x);
warnx("%s: %u: line truncated at %u", fname, lineno, x);
oodate = true;
break;
}
@ -1194,7 +1202,7 @@ meta_oodate(GNode *gn, bool oodate)
/* Delimit the record type. */
p = buf;
#ifdef DEBUG_META_MODE
DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf);
DEBUG3(META, "%s: %u: %s\n", fname, lineno, buf);
#endif
strsep(&p, " ");
if (have_filemon) {
@ -1240,8 +1248,8 @@ meta_oodate(GNode *gn, bool oodate)
if (lastpid > 0) {
/* We need to remember these. */
Global_SetExpand(lcwd_vname, lcwd);
Global_SetExpand(ldir_vname, latestdir);
Global_Set(lcwd_vname, lcwd);
Global_Set(ldir_vname, latestdir);
}
snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid);
snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid);
@ -1262,7 +1270,7 @@ meta_oodate(GNode *gn, bool oodate)
continue;
#ifdef DEBUG_META_MODE
if (DEBUG(META))
debug_printf("%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
debug_printf("%s: %u: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
fname, lineno,
pid, buf[0], cwd, lcwd, latestdir);
#endif
@ -1274,8 +1282,8 @@ meta_oodate(GNode *gn, bool oodate)
/* Process according to record type. */
switch (buf[0]) {
case 'X': /* eXit */
Var_DeleteExpand(SCOPE_GLOBAL, lcwd_vname);
Var_DeleteExpand(SCOPE_GLOBAL, ldir_vname);
Var_Delete(SCOPE_GLOBAL, lcwd_vname);
Var_Delete(SCOPE_GLOBAL, ldir_vname);
lastpid = 0; /* no need to save ldir_vname */
break;
@ -1287,13 +1295,13 @@ meta_oodate(GNode *gn, bool oodate)
child = atoi(p);
if (child > 0) {
snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child);
Global_SetExpand(cldir, lcwd);
Global_Set(cldir, lcwd);
snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child);
Global_SetExpand(cldir, latestdir);
Global_Set(cldir, latestdir);
#ifdef DEBUG_META_MODE
if (DEBUG(META))
debug_printf(
"%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n",
"%s: %u: %d: cwd=%s lcwd=%s ldir=%s\n",
fname, lineno,
child, cwd, lcwd, latestdir);
#endif
@ -1305,10 +1313,10 @@ meta_oodate(GNode *gn, bool oodate)
/* Update lcwd and latest directory. */
strlcpy(latestdir, p, sizeof latestdir);
strlcpy(lcwd, p, sizeof lcwd);
Global_SetExpand(lcwd_vname, lcwd);
Global_SetExpand(ldir_vname, lcwd);
Global_Set(lcwd_vname, lcwd);
Global_Set(ldir_vname, lcwd);
#ifdef DEBUG_META_MODE
DEBUG4(META, "%s: %d: cwd=%s ldir=%s\n",
DEBUG4(META, "%s: %u: cwd=%s ldir=%s\n",
fname, lineno, cwd, lcwd);
#endif
break;
@ -1461,7 +1469,7 @@ meta_oodate(GNode *gn, bool oodate)
for (sdp = sdirs; *sdp != NULL && !found; sdp++) {
#ifdef DEBUG_META_MODE
DEBUG3(META, "%s: %d: looking for: %s\n",
DEBUG3(META, "%s: %u: looking for: %s\n",
fname, lineno, *sdp);
#endif
if (cached_stat(*sdp, &cst) == 0) {
@ -1471,12 +1479,12 @@ meta_oodate(GNode *gn, bool oodate)
}
if (found) {
#ifdef DEBUG_META_MODE
DEBUG3(META, "%s: %d: found: %s\n",
DEBUG3(META, "%s: %u: found: %s\n",
fname, lineno, p);
#endif
if (!S_ISDIR(cst.cst_mode) &&
cst.cst_mtime > gn->mtime) {
DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n",
DEBUG3(META, "%s: %u: file '%s' is newer than the target...\n",
fname, lineno, p);
oodate = true;
} else if (S_ISDIR(cst.cst_mode)) {
@ -1508,7 +1516,7 @@ meta_oodate(GNode *gn, bool oodate)
* meta data file.
*/
if (cmdNode == NULL) {
DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n",
DEBUG2(META, "%s: %u: there were more build commands in the meta data file than there are now...\n",
fname, lineno);
oodate = true;
} else {
@ -1525,7 +1533,7 @@ meta_oodate(GNode *gn, bool oodate)
}
if (hasOODATE) {
needOODATE = true;
DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n",
DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n",
fname, lineno);
}
(void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd);
@ -1548,7 +1556,7 @@ meta_oodate(GNode *gn, bool oodate)
x = n;
lineno++;
if (buf[x - 1] != '\n') {
warnx("%s: %d: line truncated at %u", fname, lineno, x);
warnx("%s: %u: line truncated at %u", fname, lineno, x);
break;
}
cp = strchr(cp + 1, '\n');
@ -1559,8 +1567,8 @@ meta_oodate(GNode *gn, bool oodate)
if (p != NULL &&
!hasOODATE &&
!(gn->type & OP_NOMETA_CMP) &&
(strcmp(p, cmd) != 0)) {
DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n",
(meta_cmd_cmp(gn, p, cmd, cmp_filter) != 0)) {
DEBUG4(META, "%s: %u: a build command has changed\n%s\nvs\n%s\n",
fname, lineno, p, cmd);
if (!metaIgnoreCMDs)
oodate = true;
@ -1574,13 +1582,13 @@ meta_oodate(GNode *gn, bool oodate)
* that weren't in the meta data file.
*/
if (!oodate && cmdNode != NULL) {
DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n",
DEBUG2(META, "%s: %u: there are extra build commands now that weren't in the meta data file\n",
fname, lineno);
oodate = true;
}
CHECK_VALID_META(p);
if (strcmp(p, cwd) != 0) {
DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n",
DEBUG4(META, "%s: %u: the current working directory has changed from '%s' to '%s'\n",
fname, lineno, p, curdir);
oodate = true;
}

8
meta.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: meta.h,v 1.10 2021/04/03 11:08:40 rillig Exp $ */
/* $NetBSD: meta.h,v 1.11 2021/12/15 09:53:41 rillig Exp $ */
/*
* Things needed for 'meta' mode.
@ -46,13 +46,13 @@ void meta_mode_init(const char *);
void meta_job_start(struct Job *, GNode *);
void meta_job_child(struct Job *);
void meta_job_parent(struct Job *, pid_t);
int meta_job_fd(struct Job *);
int meta_job_event(struct Job *);
int meta_job_fd(struct Job *) MAKE_ATTR_USE;
int meta_job_event(struct Job *) MAKE_ATTR_USE;
void meta_job_error(struct Job *, GNode *, bool, int);
void meta_job_output(struct Job *, char *, const char *);
int meta_cmd_finish(void *);
int meta_job_finish(struct Job *);
bool meta_oodate(GNode *, bool);
bool meta_oodate(GNode *, bool) MAKE_ATTR_USE;
void meta_compat_start(void);
void meta_compat_child(void);
void meta_compat_parent(pid_t);

View File

@ -1,4 +1,4 @@
/* $NetBSD: metachar.h,v 1.17 2021/06/21 18:54:41 rillig Exp $ */
/* $NetBSD: metachar.h,v 1.20 2022/01/08 11:04:13 rillig Exp $ */
/*
* Copyright (c) 2015 The NetBSD Foundation, Inc.
@ -35,18 +35,18 @@
extern const unsigned char _metachar[];
MAKE_INLINE bool
is_shell_metachar(char c)
MAKE_INLINE bool MAKE_ATTR_USE
ch_is_shell_meta(char c)
{
return _metachar[c & 0x7f] != 0;
}
MAKE_INLINE bool
MAKE_INLINE bool MAKE_ATTR_USE
needshell(const char *cmd)
{
while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=')
while (!ch_is_shell_meta(*cmd) && *cmd != ':' && *cmd != '=')
cmd++;
return *cmd != '\0';
}
#endif /* MAKE_METACHAR_H */
#endif

View File

@ -1,3 +1,60 @@
2022-02-04 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20220204
* host-target.mk: use .MAKE.OS if available
2022-02-02 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20220202
* cc-wrap.mk: allow other entries in CC_WRAP_FILTER
We add our filter on extensions last, so prior filters
can apply to the whole value of .IMPSRC
2022-02-01 Simon J Gerraty <sjg@beast.crufty.net>
* cc-wrap.mk: take advantage of target local variables to
wrap compilers like CC CXX with wrappers like ccache distcc etc
2022-01-28 Simon J Gerraty <sjg@beast.crufty.net>
* meta2deps: we do not expect any trace data for setid apps
2022-01-26 Simon J Gerraty <sjg@beast.crufty.net>
* dirdeps.mk: ensure TARGET_SPEC and TARGET_SPEC_VARS are passed
to sub-make using DIRDEPS_CACHE
2022-01-07 Simon J Gerraty <sjg@beast.crufty.net>
* dirdeps.mk: use _cache_script to minimize the number of shells
forked when generating dirdeps.cache
2022-01-02 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20220101
* dirdeps.mk: initialize DEP_* and _debug_reldir earlier.
If initial DIRDEPS are from command line, create the target
_dirdeps_cmdline as an indication.
2022-01-01 Simon J Gerraty <sjg@beast.crufty.net>
* init.mk (_SKIP_BUILD): when doing DIRDEPS_BUILD
at top-level only some targets are allowed at level 0,
for leaf makefiles only the default (all) target is restricted
2021-12-28 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20211228
* meta2deps.py: filemon on Linux is not as reliable as we might
like, we do not want to update DIRDEPS if filemon output is
incomplete. Track pids that we 'E'xec and make sure we see an
e'X'it for each one. Throw an error if we are missing any 'X'
records.
2021-12-12 Simon J Gerraty <sjg@beast.crufty.net>
* sys.mk: simplify; include meta.sys.mk if MK_META_MODE is yes.

View File

@ -6,6 +6,7 @@ auto.obj.mk
autoconf.mk
autodep.mk
auto.dep.mk
cc-wrap.mk
compiler.mk
cython.mk
dep.mk

61
mk/cc-wrap.mk Normal file
View File

@ -0,0 +1,61 @@
# $Id: cc-wrap.mk,v 1.4 2022/02/02 17:41:56 sjg Exp $
#
# @(#) Copyright (c) 2022, Simon J. Gerraty
#
# This file is provided in the hope that it will
# be of use. There is absolutely NO WARRANTY.
# Permission to copy, redistribute or otherwise
# use this file is hereby granted provided that
# the above copyright notice and this notice are
# left intact.
#
# Please send copies of changes and bug-fixes to:
# sjg@crufty.net
#
.if ${MAKE_VERSION} >= 20220126
# which targets are we interested in?
CC_WRAP_TARGETS ?= ${OBJS:U} ${POBJS:U} ${SOBJS:U}
.if !empty(CC_WRAP_TARGETS)
# cleanup
# all the target assignments below are effectively := anyway
# so we might as well do this once
CC_WRAP_TARGETS := ${CC_WRAP_TARGETS:O:u}
# what do we wrap?
CC_WRAP_LIST += CC CXX
CC_WRAP_LIST := ${CC_WRAP_LIST:O:u}
# what might we wrap them with?
CC_WRAPPERS += ccache distcc icecc
CC_WRAPPERS := ${CC_WRAPPERS:O:u}
.for w in ${CC_WRAPPERS}
${w:tu} ?= $w
.endfor
# we do not want to make all these targets out-of-date
# just because one of the above wrappers are enabled/disabled
${CC_WRAP_TARGETS}: .MAKE.META.CMP_FILTER = ${CC_WRAPPERS:tu@W@${$W}@:S,^,N,}
# some object src types we should not wrap
CC_WRAP_SKIP_EXTS += s
# We add the sequence we care about - excluding CC_WRAP_SKIP_EXTS
# but prior filters can apply to full value of .IMPSRC
CC_WRAP_FILTER += E:tl:${CC_WRAP_SKIP_EXTS:${M_ListToSkip}}
CC_WRAP_FILTER := ${CC_WRAP_FILTER:ts:}
# last one enabled wins!
.for W in ${CC_WRAPPERS:tu}
.if ${MK_$W:U} == "yes"
.for C in ${CC_WRAP_LIST}
# we have to protect the check of .IMPSRC from Global expansion
${CC_WRAP_TARGETS}: $C = $${"$${.IMPSRC:${CC_WRAP_FILTER}}":?${$W}:} ${$C}
.endfor
.endif
.endfor
.endif
.endif

View File

@ -1,6 +1,6 @@
# $Id: dirdeps.mk,v 1.147 2021/12/14 02:09:53 sjg Exp $
# $Id: dirdeps.mk,v 1.151 2022/01/28 01:13:14 sjg Exp $
# Copyright (c) 2010-2021, Simon J. Gerraty
# Copyright (c) 2010-2022, Simon J. Gerraty
# Copyright (c) 2010-2018, Juniper Networks, Inc.
# All rights reserved.
#
@ -134,6 +134,16 @@
# A list of MACHINEs the current directory should not be
# built for.
#
# DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS)
# It is discouraged, but sometimes necessary for a
# Makefile.depend file to influence the environment.
# Doing this is correctly (especially if using DIRDEPS_CACHE) is
# tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS
# and dirdeps.mk will do the deed:
#
# MK_UEFI = yes
# DIRDEPS_EXPORT_VARS = MK_UEFI
#
# _build_xtra_dirs
# local.dirdeps.mk can add targets to this variable.
# They will be hooked into the build, but independent of
@ -161,6 +171,8 @@ _DIRDEP_USE_LEVEL?= 0
.if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL}
# only the first instance is interested in all this
# the first time we are included the _DIRDEP_USE target will not be defined
# we can use this as a clue to do initialization and other one time things.
.if !target(_DIRDEP_USE)
# do some setup we only need once
@ -184,6 +196,8 @@ TARGET_MACHINE := ${MACHINE}
.endif
# disable DIRDEPS_CACHE as it does not like this trick
MK_DIRDEPS_CACHE = no
# incase anyone needs to know
_dirdeps_cmdline:
.endif
# make sure we get the behavior we expect
@ -257,12 +271,36 @@ _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*}
.endif
.endif
# this is how we identify non-machine specific dependfiles
N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}}
# this gets reset for each dirdep we check
DEP_RELDIR ?= ${RELDIR}
# remember the initial value of DEP_RELDIR - we test for it below.
_DEP_RELDIR := ${DEP_RELDIR}
# this can cause lots of output!
# set to a set of glob expressions that might match RELDIR
DEBUG_DIRDEPS ?= no
# make sure this target exists
dirdeps: beforedirdeps .WAIT
beforedirdeps:
.endif # !target(_DIRDEP_USE)
.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != ""
_debug_reldir = 1
.else
_debug_reldir = 0
.endif
.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend depend:L:M$x}@} != ""
_debug_search = 1
.else
_debug_search = 0
.endif
# First off, we want to know what ${MACHINE} to build for.
# This can be complicated if we are using a mixture of ${MACHINE} specific
# and non-specific Makefile.depend*
@ -297,26 +335,6 @@ DEP_MACHINE := ${_DEP_TARGET_SPEC}
_build_all_dirs =
_build_xtra_dirs =
# the first time we are included the _DIRDEP_USE target will not be defined
# we can use this as a clue to do initialization and other one time things.
.if !target(_DIRDEP_USE)
# make sure this target exists
dirdeps: beforedirdeps .WAIT
beforedirdeps:
# We normally expect to be included by Makefile.depend.*
# which sets the DEP_* macros below.
DEP_RELDIR ?= ${RELDIR}
# this can cause lots of output!
# set to a set of glob expressions that might match RELDIR
DEBUG_DIRDEPS ?= no
# remember the initial value of DEP_RELDIR - we test for it below.
_DEP_RELDIR := ${DEP_RELDIR}
.endif
# DIRDEPS_CACHE can be very handy for debugging.
# Also if repeatedly building the same target,
# we can avoid the overhead of re-computing the tree dependencies.
@ -329,16 +347,6 @@ BUILD_DIRDEPS ?= yes
DIRDEPS_CACHE ?= ${_OBJDIR:tA}/dirdeps.cache${_TARGETS:U${.TARGETS}:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.}
.endif
.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != ""
_debug_reldir = 1
.else
_debug_reldir = 0
.endif
.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != ""
_debug_search = 1
.else
_debug_search = 0
.endif
# pickup customizations
# as below you can use !target(_DIRDEP_USE) to protect things
@ -532,7 +540,10 @@ BUILD_DIRDEPS = no
dirdeps: dirdeps-cached
dirdeps-cached: ${DIRDEPS_CACHE} .MAKE
@echo "${TRACER}Using ${DIRDEPS_CACHE}"
@MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \
@MAKELEVEL=${.MAKE.LEVEL} \
TARGET_SPEC=${TARGET_SPEC} \
${TARGET_SPEC_VARS:@v@$v=${$v}@} \
${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \
dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no
# leaf makefiles rarely work for building DIRDEPS_CACHE
@ -701,15 +712,18 @@ _build_all_dirs := ${_build_all_dirs:O:u}
.if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS:U}} == ""
.if !empty(_build_all_dirs)
.if ${BUILD_DIRDEPS_CACHE} == "yes"
x!= echo; { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; } >&3
# we use _cache_script to minimize the number of times we fork the shell
_cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}';
# guard against _new_dirdeps being too big for a single command line
_new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,}
_cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,}
.export _cache_xtra_deps _new_dirdeps
.if !empty(DEP_EXPORT_VARS)
.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
# Discouraged, but there are always exceptions.
# Handle it here rather than explain how.
x!= echo; { echo; ${DEP_EXPORT_VARS:@v@echo '$v=${$v}';@} echo '.export ${DEP_EXPORT_VARS}'; echo; } >&3
DIRDEPS_EXPORT_VARS ?= ${DEP_EXPORT_VARS}
_cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo;
_cache_script += ${_cache_xvars}
.endif
.else
# this makes it all happen
@ -721,16 +735,17 @@ ${_build_all_dirs}: _DIRDEP_USE
.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,}
.endif
.if !empty(DEP_EXPORT_VARS)
.export ${DEP_EXPORT_VARS}
DEP_EXPORT_VARS=
.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
.export ${DIRDEPS_EXPORT_VARS} ${DEP_EXPORT_VARS}
DIRDEPS_EXPORT_VARS =
DEP_EXPORT_VARS =
.endif
# this builds the dependency graph
.for m in ${_machines}
.if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs)
_cache_deps =
x!= echo; { echo; echo 'DIRDEPS.${_this_dir}.$m = \'; } >&3
_cache_script += echo; echo 'DIRDEPS.${_this_dir}.$m = \';
.endif
# it would be nice to do :N${.TARGET}
.if !empty(__qual_depdirs)
@ -753,10 +768,10 @@ ${_this_dir}.$m: ${_build_dirs:M*.$q}
_cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m:S,^${SRCTOP}/,,}
.if !empty(_cache_deps)
.export _cache_deps
x!= echo; for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done >&3
_cache_script += for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done;
.endif
# anything in _{build,env}_xtra_dirs is hooked to dirdeps: only
x!= echo; { echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \
x!= echo; { echo; ${_cache_script} echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \
echo; echo 'dirdeps: ${_this_dir}.$m \'; \
for x in $$_cache_xtra_deps; do echo " _{SRCTOP}/$$x \\"; done; \
echo; for x in $$_new_dirdeps; do echo "_{SRCTOP}/$$x: _DIRDEP_USE"; done; } >&3

View File

@ -1,9 +1,10 @@
# RCSid:
# $Id: host-target.mk,v 1.13 2020/08/05 23:32:08 sjg Exp $
# $Id: host-target.mk,v 1.14 2022/02/04 18:05:22 sjg Exp $
# Host platform information; may be overridden
.if !defined(_HOST_OSNAME)
_HOST_OSNAME != uname -s
# use .MAKE.OS if available
_HOST_OSNAME := ${.MAKE.OS:U${uname -s:L:sh}}
.export _HOST_OSNAME
.endif
.if !defined(_HOST_OSREL)
@ -15,7 +16,7 @@ _HOST_MACHINE != uname -m
.export _HOST_MACHINE
.endif
.if !defined(_HOST_ARCH)
# for NetBSD prefer $MACHINE (amd64 rather than x86_64)
# for Darwin and NetBSD prefer $MACHINE (amd64 rather than x86_64)
.if ${_HOST_OSNAME:NDarwin:NNetBSD} == ""
_HOST_ARCH := ${_HOST_MACHINE}
.else

View File

@ -1,4 +1,4 @@
# $Id: init.mk,v 1.26 2021/12/08 05:56:50 sjg Exp $
# $Id: init.mk,v 1.27 2022/01/01 17:32:18 sjg Exp $
#
# @(#) Copyright (c) 2002, Simon J. Gerraty
#
@ -66,13 +66,17 @@ CXX_PIC?= ${CC_PIC}
PROFFLAGS?= -DGPROF -DPROF
.if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes"
# targets that are ok at level 0
.if ${RELDIR} == "."
# top-level targets that are ok at level 0
DIRDEPS_BUILD_LEVEL0_TARGETS += clean* destroy*
M_ListToSkip?= O:u:S,^,N,:ts:
.if ${.TARGETS:Uall:${DIRDEPS_BUILD_LEVEL0_TARGETS:${M_ListToSkip}}} != ""
# this tells lib.mk and prog.mk to not actually build anything
_SKIP_BUILD = not building at level 0
.endif
.elif ${.TARGETS:U:Nall} == ""
_SKIP_BUILD = not building at level 0
.endif
.endif
.if !defined(.PARSEDIR)

View File

@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
# $Id: install-mk,v 1.206 2021/12/11 18:57:41 sjg Exp $
# $Id: install-mk,v 1.213 2022/02/05 01:39:12 sjg Exp $
#
# @(#) Copyright (c) 1994 Simon J. Gerraty
#
@ -70,7 +70,7 @@
# sjg@crufty.net
#
MK_VERSION=20211212
MK_VERSION=20220204
OWNER=
GROUP=
MODE=444

View File

@ -37,7 +37,7 @@
"""
RCSid:
$Id: meta2deps.py,v 1.40 2021/12/13 19:32:46 sjg Exp $
$Id: meta2deps.py,v 1.44 2022/01/29 02:42:01 sjg Exp $
Copyright (c) 2011-2020, Simon J. Gerraty
Copyright (c) 2011-2017, Juniper Networks, Inc.
@ -66,7 +66,10 @@
"""
import os, re, sys
import os
import re
import sys
import stat
def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr):
"""
@ -244,6 +247,7 @@ def __init__(self, name, conf={}):
self.curdir = conf.get('CURDIR')
self.reldir = conf.get('RELDIR')
self.dpdeps = conf.get('DPDEPS')
self.pids = {}
self.line = 0
if not self.conf:
@ -449,7 +453,7 @@ def parse(self, name=None, file=None):
if self.curdir:
self.seenit(self.curdir) # we ignore this
interesting = 'CEFLRV'
interesting = 'CEFLRVX'
for line in f:
self.line += 1
# ignore anything we don't care about
@ -505,6 +509,13 @@ def parse(self, name=None, file=None):
print("cwd=", cwd, file=self.debug_out)
continue
if w[0] == 'X':
try:
del self.pids[pid]
except KeyError:
pass
continue
if w[2] in self.seen:
if self.debug > 2:
print("seen:", w[2], file=self.debug_out)
@ -518,11 +529,30 @@ def parse(self, name=None, file=None):
continue
elif w[0] in 'ERWS':
path = w[2]
if path == '.':
if w[0] == 'E':
self.pids[pid] = path
elif path == '.':
continue
self.parse_path(path, cwd, w[0], w)
assert(version > 0)
setid_pids = []
# self.pids should be empty!
for pid,path in self.pids.items():
try:
# no guarantee that path is still valid
if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID):
# we do not expect anything after Exec
setid_pids.append(pid)
continue
except:
# we do not care why the above fails,
# we do not want to miss the ERROR below.
pass
print("ERROR: missing eXit for {} pid {}".format(path, pid))
for pid in setid_pids:
del self.pids[pid]
assert(len(self.pids) == 0)
if not file:
f.close()

View File

@ -77,7 +77,7 @@
# RCSid:
# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $
# $Id: meta2deps.sh,v 1.18 2022/01/28 21:17:43 sjg Exp $
# Copyright (c) 2010-2013, Juniper Networks, Inc.
# All rights reserved.
@ -241,8 +241,8 @@ meta2deps() {
;;
*) cat /dev/null "$@";;
esac 2> /dev/null |
sed -e 's,^CWD,C C,;/^[CREFLMV] /!d' -e "s,',,g" |
$_excludes | ( version=no
sed -e 's,^CWD,C C,;/^[CREFLMVX] /!d' -e "s,',,g" |
$_excludes | ( version=no epids= xpids=
while read op pid path junk
do
: op=$op pid=$pid path=$path
@ -273,8 +273,9 @@ meta2deps() {
;;
esac
: op=$op path=$path
case "$op,$path" in
V,*) version=$path; continue;;
V,*) version=$pid; continue;;
W,*srcrel|*.dirdep) continue;;
C,*)
case "$path" in
@ -291,6 +292,15 @@ meta2deps() {
continue
;;
*) dir=${path%/*}
case "$op" in
E) # setid apps get no tracing so we won't see eXit
case `'ls' -l $path 2> /dev/null | sed 's, .*,,'` in
*s*) ;;
*) epids="$epids $pid";;
esac
;;
X) xpids="$xpids $pid"; continue;;
esac
case "$path" in
$src_re|$obj_re) ;;
/*/stage/*) ;;
@ -380,9 +390,18 @@ meta2deps() {
echo $dir;;
esac
done > $tf.dirdep
: version=$version
case "$version" in
0) error "no filemon data";;
esac ) || exit 1
esac
for p in $epids
do
: p=$p
case " $xpids " in
*" $p "*) ;;
*) error "missing eXit for pid $p";;
esac
done ) || exit 1
_nl=echo
for f in $tf.dirdep $tf.qual $tf.srcdep
do

View File

@ -143,6 +143,8 @@ examples/sys.clean-env.mk
host-target.mk
Is used to set macros like ``HOST_TARGET``, ``HOST_OS`` and
``host_os`` which are used to find the next step.
Note: since 20130303 bmake provides ``.MAKE.OS`` set to
the equivalent of ``HOST_OS``.
sys/\*.mk
Platform specific additions, such as ``Darwin.mk`` or ``SunOS.mk``
@ -159,27 +161,40 @@ local.sys.mk
The above arrangement makes it easy for the mk files to be part of a
src tree on an NFS volume and to allow building on multiple platforms.
options.mk
----------
Inspired by FreeBSD's ``bsd.own.mk`` but more flexible.
FreeBSD now have similar functionality in ``bsd.mkopt.mk``.
It allows users to express their intent with respect to options
``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``.
Note: ``WITHOUT_*`` wins if both are set, and makefiles can set
``NO_*`` to say they cannot handle that option, or even ``MK_*`` if
they really need to.
lib.mk
------
This file is used to build a number of different libraries from the
same SRCS.
lib${LIB}.a
``lib${LIB}.a``
An archive lib of ``.o`` files, this is the default
lib${LIB}_p.a
``lib${LIB}_p.a``
A profiled lib of ``.po`` files.
Still an archive lib, but all the objects are built with
profiling in mind - hence the different extension.
It is skipped if ``MKPROFILE`` is "no".
It is skipped if ``MK_PROFILE`` is "no".
lib${LIB}_pic.a
``lib${LIB}_pic.a``
An archive of ``.so`` objects compiled for relocation.
On NetBSD this is the input to ``lib${LIB}.${LD_so}``, it is
skipped if ``MKPICLIB`` is "no".
skipped if ``MK_PIC`` or ``MK_PICLIB`` are "no".
lib${LIB}.${LD_so}
``lib${LIB}.${LD_so}``
A shared library. The value of ``LD_so`` is very platform
specific. For example::
@ -190,7 +205,7 @@ lib${LIB}.${LD_so}
libsslfd.1.dylib
This library will only be built if ``SHLIB_MAJOR`` has
a value, and ``MKPIC`` is not set to "no".
a value, and ``MK_PIC`` is not set to "no".
There is a lot of platform specific tweaking in ``lib.mk``, largely the
result of the original distributions trying to avoid interfering with
@ -202,7 +217,7 @@ libnames.mk
This is included by both ``prog.mk`` and ``lib.mk`` and tries to
include ``*.libnames.mk`` of which:
local.libnames.mk
``local.libnames.mk``
does not exist unless you create it. It is a handy way for you
to customize without touching the distributed files.
For example, on a test machine I needed to build openssl but
@ -217,7 +232,7 @@ local.libnames.mk
The makefile created an openssl dir in ``${OBJ_libcrypto}`` to
gather all the headers. dpadd.mk_ did the rest.
host.libnames.mk
``host.libnames.mk``
contains logic to find any libs named in ``HOST_LIBS`` in
``HOST_LIBDIRS``.
@ -256,15 +271,15 @@ else in various ways::
Any library (referenced by its full path) in any of the above, is
added to ``DPMAGIC_LIBS`` with the following results, for each lib *foo*.
SRC_libfoo
``SRC_libfoo``
Is set to indicate where the src for libfoo is.
By default it is derived from ``LIBFOO`` by replacing
``${OBJTOP}`` with ``${SRCTOP}``.
OBJ_libfoo
``OBJ_libfoo``
Not very exciting, is just the dir where libfoo lives.
INCLUDES_libfoo
``INCLUDES_libfoo``
What to add to ``CFLAGS`` to find the public headers.
The default varies. If ``${SRC_libfoo}/h`` exists, it is assumed
to be the home of all public headers and thus the default is
@ -273,7 +288,7 @@ INCLUDES_libfoo
Otherwise we make no assumptions and the default is
``-I${SRC_libfoo} -I${OBJ_libfoo}``
LDADD_libfoo
``LDADD_libfoo``
This only applies to libs reference via ``DPLIBS``.
The default is ``-lfoo``, ``LDADD_*`` provides a hook to
instantiate other linker flags at the appropriate point
@ -303,13 +318,16 @@ object files from the src tree. This is also the source of much
confusion to some.
Traditionally one had to do a separate ``make obj`` pass through the
tree. If ``MKOBJDIRS`` is "auto", we include auto.obj.mk_.
tree. If ``MK_AUTO_OBJ`` is set we include auto.obj.mk_.
In fact if ``MKOBJDIRS`` is set to "auto", `sys.mk`_ will set
``MK_AUTO_OBJ=yes`` and include auto.obj.mk_ since it is best done early.
auto.obj.mk
-----------
This leverages the ``.OBJDIR`` target introduced some years ago to
NetBSD make, to automatically create the desired object dir.
NetBSD make, to automatically create and use the desired object dir.
subdir.mk
---------
@ -334,6 +352,8 @@ you can suppress that - or enhance it by setting ``ECHO_DIR``::
# print time stamps
ECHO_DIR=echo @ `date "+%s [%Y-%m-%d %T] "`
I prefer to use `dirdeps.mk`_ which makes ``subdir.mk`` irrelevant.
links.mk
--------
@ -367,9 +387,12 @@ dep.mk
Deals with collecting dependencies. Another useful feature of BSD
make is the separation of this sort of information into a ``.depend``
file. ``MKDEP`` needs to point to a suitable tool (like mkdeps.sh_)
file. ``MKDEP_CMD`` needs to point to a suitable tool (like mkdeps.sh_)
If ``USE_AUTODEP_MK`` is "yes" includes autodep.mk_
If ``MK_AUTODEP`` is "yes" it sets ``MKDEP_MK`` to autodep.mk_ by default.
``MKDEP_MK`` can also be set to `auto.dep.mk`_ which is more efficient
but does not support an explicit ``depend`` target.
autodep.mk
----------
@ -397,19 +420,9 @@ to avoid possible conflicts during parallel builds.
This precludes the use of suffix rules to drive ``make depend``, so
dep.mk_ handles that if specifically requested.
options.mk
----------
Inspired by FreeBSD's ``bsd.own.mk`` more flexible.
FreeBSD now have similar functionality in ``bsd.mkopt.mk``.
It allows users to express their intent with respect to options
``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``.
Note: ``WITHOUT_*`` wins if both are set, and makefiles can set
``NO_*`` to say they cannot handle that option, or even ``MK_*`` if
they really need to.
If ``bmake`` is 20160218 or newer, ``auto.dep.mk`` uses ``.dinclude``
to includes the ``*.d`` files directly thus avoiding the need to
create a ``.depend`` file from them.
own.mk
------
@ -424,7 +437,7 @@ ldorder.mk
Leverages ``bmake`` to compute optimal link order for libraries.
This works nicely and makes refactoring a breeze - so long as you
have not (or few) cicular dependencies between libraries.
have no (or few) cicular dependencies between libraries.
man.mk
------
@ -463,6 +476,22 @@ Logic to build Python C interface modules using Cython_
.. _Cython: http://www.cython.org/
cc-wrap.mk
----------
This makefile leverages two new features in bmake 20220126 and later.
First is the ablity to set target local variables (GNU make has done
this for ages).
The second (only intersting if using `meta mode`_)
allows filtering commands before comparison with previous run to
decide if a target is out-of-date.
In the past, making use of compiler wrappers like ``ccache``,
``distcc`` or the newer ``icecc`` could get quite ugly.
Using ``cc-wrap.mk`` it could not be simpler.
Meta mode
=========
@ -496,5 +525,5 @@ where you unpacked the tar file, you can::
.. _mk.tar.gz: http://www.crufty.net/ftp/pub/sjg/mk.tar.gz
:Author: sjg@crufty.net
:Revision: $Id: mk-files.txt,v 1.20 2020/08/19 17:51:53 sjg Exp $
:Revision: $Id: mk-files.txt,v 1.21 2022/02/04 19:01:05 sjg Exp $
:Copyright: Crufty.NET

View File

@ -1,4 +1,4 @@
# $Id: sys.clean-env.mk,v 1.23 2020/08/19 17:51:53 sjg Exp $
# $Id: sys.clean-env.mk,v 1.24 2022/01/15 17:34:42 sjg Exp $
#
# @(#) Copyright (c) 2009, Simon J. Gerraty
#
@ -52,7 +52,7 @@ MAKE_ENV_SAVE_PREFIX_LIST += \
# This could be a list of vars or patterns to explicitly exclude.
MAKE_ENV_SAVE_EXCLUDE_LIST ?= _
MAKE_ENV_SAVE_EXCLUDE_LIST += _
# This is the actual list that we will save
# HOME is probably something worth clobbering eg.
@ -115,7 +115,7 @@ MAKEOBJDIR = $${.CURDIR:S,${_srctop},$${OBJTOP},}
$v := ${$v}
.endfor
.else
# we cannot use the '$$' trick, anymore
# we cannot rely on the '$$' trick (depending on .MAKE.SAVE_DOLLARS)
# but we can export a literal (unexpanded) value
SRCTOP := ${_srctop}
OBJROOT := ${_objroot}

348
nonints.h
View File

@ -1,348 +0,0 @@
/* $NetBSD: nonints.h,v 1.217 2021/12/12 20:45:48 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Adam de Boor.
*
* 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. 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.
*
* from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
*/
/*
* Copyright (c) 1989 by Berkeley Softworks
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Adam de Boor.
*
* 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.
*
* from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
*/
/* arch.c */
void Arch_Init(void);
void Arch_End(void);
bool Arch_ParseArchive(char **, GNodeList *, GNode *);
void Arch_Touch(GNode *);
void Arch_TouchLib(GNode *);
void Arch_UpdateMTime(GNode *gn);
void Arch_UpdateMemberMTime(GNode *gn);
void Arch_FindLib(GNode *, SearchPath *);
bool Arch_LibOODate(GNode *);
bool Arch_IsLib(GNode *);
/* compat.c */
int Compat_RunCommand(const char *, GNode *, StringListNode *);
void Compat_Run(GNodeList *);
void Compat_Make(GNode *, GNode *);
/* cond.c */
CondEvalResult Cond_EvalCondition(const char *, bool *);
CondEvalResult Cond_EvalLine(const char *);
void Cond_restore_depth(unsigned int);
unsigned int Cond_save_depth(void);
/* dir.c; see also dir.h */
MAKE_INLINE const char *
str_basename(const char *pathname)
{
const char *lastSlash = strrchr(pathname, '/');
return lastSlash != NULL ? lastSlash + 1 : pathname;
}
MAKE_INLINE SearchPath *
SearchPath_New(void)
{
SearchPath *path = bmake_malloc(sizeof *path);
Lst_Init(&path->dirs);
return path;
}
void SearchPath_Free(SearchPath *);
/* for.c */
int For_Eval(const char *);
bool For_Accum(const char *);
void For_Run(int);
/* job.c */
#ifdef WAIT_T
void JobReapChild(pid_t, WAIT_T, bool);
#endif
/* main.c */
bool GetBooleanExpr(const char *, bool);
void Main_ParseArgLine(const char *);
char *Cmd_Exec(const char *, const char **);
void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
void DieHorribly(void) MAKE_ATTR_DEAD;
void Finish(int) MAKE_ATTR_DEAD;
int eunlink(const char *);
void execDie(const char *, const char *);
char *getTmpdir(void);
bool ParseBoolean(const char *, bool);
char *cached_realpath(const char *, char *);
/* parse.c */
void Parse_Init(void);
void Parse_End(void);
typedef enum VarAssignOp {
VAR_NORMAL, /* = */
VAR_SUBST, /* := */
VAR_SHELL, /* != or :sh= */
VAR_APPEND, /* += */
VAR_DEFAULT /* ?= */
} VarAssignOp;
typedef struct VarAssign {
char *varname; /* unexpanded */
VarAssignOp op;
const char *value; /* unexpanded */
} VarAssign;
typedef char *(*ReadMoreProc)(void *, size_t *);
void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
bool Parse_IsVar(const char *, VarAssign *out_var);
void Parse_Var(VarAssign *, GNode *);
void Parse_AddIncludeDir(const char *);
void Parse_File(const char *, int);
void Parse_PushInput(const char *, int, int, ReadMoreProc, void *);
void Parse_MainName(GNodeList *);
int Parse_NumErrors(void);
#ifndef HAVE_STRLCPY
/* strlcpy.c */
size_t strlcpy(char *, const char *, size_t);
#endif
/* suff.c */
void Suff_Init(void);
void Suff_End(void);
void Suff_ClearSuffixes(void);
bool Suff_IsTransform(const char *);
GNode *Suff_AddTransform(const char *);
void Suff_EndTransform(GNode *);
void Suff_AddSuffix(const char *, GNode **);
SearchPath *Suff_GetPath(const char *);
void Suff_ExtendPaths(void);
void Suff_AddInclude(const char *);
void Suff_AddLib(const char *);
void Suff_FindDeps(GNode *);
SearchPath *Suff_FindPath(GNode *);
void Suff_SetNull(const char *);
void Suff_PrintAll(void);
const char *Suff_NamesStr(void);
/* targ.c */
void Targ_Init(void);
void Targ_End(void);
void Targ_Stats(void);
GNodeList *Targ_List(void);
GNode *GNode_New(const char *);
GNode *Targ_FindNode(const char *);
GNode *Targ_GetNode(const char *);
GNode *Targ_NewInternalNode(const char *);
GNode *Targ_GetEndNode(void);
void Targ_FindList(GNodeList *, StringList *);
bool Targ_Precious(const GNode *);
void Targ_SetMain(GNode *);
void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int);
void Targ_PrintNodes(GNodeList *, int);
const char *Targ_FmtTime(time_t);
void Targ_PrintType(GNodeType);
void Targ_PrintGraph(int);
void Targ_Propagate(void);
const char *GNodeMade_Name(GNodeMade);
/* var.c */
void Var_Init(void);
void Var_End(void);
typedef enum VarEvalMode {
/*
* Only parse the expression but don't evaluate any part of it.
*
* TODO: Document what Var_Parse and Var_Subst return in this mode.
* As of 2021-03-15, they return unspecified, inconsistent results.
*/
VARE_PARSE_ONLY,
/* Parse and evaluate the expression. */
VARE_WANTRES,
/*
* Parse and evaluate the expression. It is an error if a
* subexpression evaluates to undefined.
*/
VARE_UNDEFERR,
/*
* Parse and evaluate the expression. Keep '$$' as '$$' instead of
* reducing it to a single '$'. Subexpressions that evaluate to
* undefined expand to an empty string.
*
* 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.
*/
VARE_EVAL_KEEP_DOLLAR,
/*
* Parse and evaluate the expression. Keep undefined variables as-is
* instead of expanding them to an empty string.
*
* 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)".
*/
VARE_EVAL_KEEP_UNDEF,
/*
* Parse and evaluate the expression. Keep '$$' as '$$' and preserve
* undefined subexpressions.
*/
VARE_KEEP_DOLLAR_UNDEF
} VarEvalMode;
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 = 1 << 1
} VarSetFlags;
/* The state of error handling returned by Var_Parse. */
typedef enum VarParseResult {
/* Both parsing and evaluation succeeded. */
VPR_OK,
/* Parsing or evaluating failed, with an error message. */
VPR_ERR,
/*
* Parsing succeeded, undefined expressions are allowed and the
* expression was still undefined after applying all modifiers.
* No error message is printed in this case.
*
* Some callers handle this case differently, so return this
* information to them, for now.
*
* TODO: Instead of having this special return value, rather ensure
* that VARE_EVAL_KEEP_UNDEF is processed properly.
*/
VPR_UNDEF
} VarParseResult;
typedef enum VarExportMode {
/* .export-env */
VEM_ENV,
/* .export: Initial export or update an already exported variable. */
VEM_PLAIN,
/* .export-literal: Do not expand the variable value. */
VEM_LITERAL
} VarExportMode;
void Var_Delete(GNode *, const char *);
void Var_DeleteExpand(GNode *, const char *);
void Var_Undef(const char *);
void Var_Set(GNode *, const char *, const char *);
void Var_SetExpand(GNode *, const char *, const char *);
void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags);
void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags);
void Var_Append(GNode *, const char *, const char *);
void Var_AppendExpand(GNode *, const char *, const char *);
bool Var_Exists(GNode *, const char *);
bool Var_ExistsExpand(GNode *, const char *);
FStr Var_Value(GNode *, const char *);
const char *GNode_ValueDirect(GNode *, const char *);
VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *);
VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **);
void Var_Stats(void);
void Var_Dump(GNode *);
void Var_ReexportVars(void);
void Var_Export(VarExportMode, const char *);
void Var_ExportVars(const char *);
void Var_UnExport(bool, const char *);
void Global_Set(const char *, const char *);
void Global_SetExpand(const char *, const char *);
void Global_Append(const char *, const char *);
void Global_Delete(const char *);
/* util.c */
typedef void (*SignalProc)(int);
SignalProc bmake_signal(int, SignalProc);

1614
parse.c

File diff suppressed because it is too large Load Diff

29
str.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $ */
/* $NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -71,7 +71,11 @@
#include "make.h"
/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */
MAKE_RCSID("$NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $");
MAKE_RCSID("$NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $");
static HashTable interned_strings;
/* Return the concatenation of s1 and s2, freshly allocated. */
char *
@ -395,3 +399,24 @@ Str_Match(const char *str, const char *pat)
str++;
}
}
void
Str_Intern_Init(void)
{
HashTable_Init(&interned_strings);
}
void
Str_Intern_End(void)
{
#ifdef CLEANUP
HashTable_Done(&interned_strings);
#endif
}
/* Return a canonical instance of str, with unlimited lifetime. */
const char *
Str_Intern(const char *str)
{
return HashTable_CreateEntry(&interned_strings, str, NULL)->key;
}

46
str.h
View File

@ -1,4 +1,4 @@
/* $NetBSD: str.h,v 1.12 2021/12/12 13:43:47 rillig Exp $ */
/* $NetBSD: str.h,v 1.15 2021/12/15 10:57:01 rillig Exp $ */
/*
Copyright (c) 2021 Roland Illig <rillig@NetBSD.org>
@ -39,12 +39,6 @@ typedef struct FStr {
void *freeIt;
} FStr;
/* A modifiable string that may need to be freed after use. */
typedef struct MFStr {
char *str;
void *freeIt;
} MFStr;
/* A read-only range of a character array, NOT null-terminated. */
typedef struct Substring {
const char *start;
@ -111,40 +105,6 @@ FStr_Done(FStr *fstr)
}
MAKE_INLINE MFStr
MFStr_Init(char *str, void *freeIt)
{
MFStr mfstr;
mfstr.str = str;
mfstr.freeIt = freeIt;
return mfstr;
}
/* Return a string that is the sole owner of str. */
MAKE_INLINE MFStr
MFStr_InitOwn(char *str)
{
return MFStr_Init(str, str);
}
/* Return a string that refers to the shared str. */
MAKE_INLINE MFStr
MFStr_InitRefer(char *str)
{
return MFStr_Init(str, NULL);
}
MAKE_INLINE void
MFStr_Done(MFStr *mfstr)
{
free(mfstr->freeIt);
#ifdef CLEANUP
mfstr->str = NULL;
mfstr->freeIt = NULL;
#endif
}
MAKE_STATIC Substring
Substring_Init(const char *start, const char *end)
{
@ -383,3 +343,7 @@ char *str_concat2(const char *, const char *);
char *str_concat3(const char *, const char *, const char *);
bool Str_Match(const char *, const char *);
void Str_Intern_Init(void);
void Str_Intern_End(void);
const char *Str_Intern(const char *);

66
suff.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $ */
/* $NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -115,7 +115,7 @@
#include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */
MAKE_RCSID("$NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $");
MAKE_RCSID("$NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $");
typedef List SuffixList;
typedef ListNode SuffixListNode;
@ -207,20 +207,26 @@ typedef struct Suffix {
typedef struct Candidate {
/* The file or node to look for. */
char *file;
/* The prefix from which file was formed.
* Its memory is shared among all candidates. */
/*
* The prefix from which file was formed. Its memory is shared among
* all candidates.
*/
char *prefix;
/* The suffix on the file. */
Suffix *suff;
/* The candidate that can be made from this,
* or NULL for the top-level candidate. */
/*
* The candidate that can be made from this, or NULL for the
* top-level candidate.
*/
struct Candidate *parent;
/* The node describing the file. */
GNode *node;
/* Count of existing children, only used for memory management, so we
* don't free this candidate too early or too late. */
/*
* Count of existing children, only used for memory management, so we
* don't free this candidate too early or too late.
*/
int numChildren;
#ifdef DEBUG_SRC
CandidateList childrenList;
@ -240,7 +246,7 @@ typedef struct CandidateSearcher {
/* TODO: Document the difference between nullSuff and emptySuff. */
/* The NULL suffix for this run */
/* The NULL suffix is used when a file has no known suffix */
static Suffix *nullSuff;
/* The empty suffix required for POSIX single-suffix transformation rules */
static Suffix *emptySuff;
@ -692,7 +698,9 @@ RebuildGraph(GNode *transform, Suffix *suff)
size_t nameLen = strlen(name);
const char *toName;
/* See if it is a transformation from this suffix to another suffix. */
/*
* See if it is a transformation from this suffix to another suffix.
*/
toName = StrTrimPrefix(suff->name, name);
if (toName != NULL) {
Suffix *to = FindSuffixByName(toName);
@ -702,7 +710,9 @@ RebuildGraph(GNode *transform, Suffix *suff)
}
}
/* See if it is a transformation from another suffix to this suffix. */
/*
* See if it is a transformation from another suffix to this suffix.
*/
toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen);
if (toName != NULL) {
Suffix *from = FindSuffixByNameLen(name,
@ -724,17 +734,15 @@ RebuildGraph(GNode *transform, Suffix *suff)
* true iff a new main target has been selected.
*/
static bool
UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
bool *inout_removedMain)
UpdateTarget(GNode *target, Suffix *suff, bool *inout_removedMain)
{
Suffix *srcSuff, *targSuff;
char *ptr;
if (*inout_main == NULL && *inout_removedMain &&
!(target->type & OP_NOTARGET)) {
if (mainNode == NULL && *inout_removedMain &&
GNode_IsMainCandidate(target)) {
DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name);
*inout_main = target;
Targ_SetMain(target);
mainNode = target;
/*
* XXX: Why could it be a good idea to return true here?
* The main task of this function is to turn ordinary nodes
@ -772,13 +780,12 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
return false;
if (ParseTransform(target->name, &srcSuff, &targSuff)) {
if (*inout_main == target) {
if (mainNode == target) {
DEBUG1(MAKE,
"Setting main node from \"%s\" back to null\n",
target->name);
*inout_removedMain = true;
*inout_main = NULL;
Targ_SetMain(NULL);
mainNode = NULL;
}
Lst_Done(&target->children);
Lst_Init(&target->children);
@ -802,14 +809,14 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
* suffix rules.
*/
static void
UpdateTargets(GNode **inout_main, Suffix *suff)
UpdateTargets(Suffix *suff)
{
bool removedMain = false;
GNodeListNode *ln;
for (ln = Targ_List()->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
if (UpdateTarget(gn, inout_main, suff, &removedMain))
if (UpdateTarget(gn, suff, &removedMain))
break;
}
}
@ -828,7 +835,7 @@ UpdateTargets(GNode **inout_main, Suffix *suff)
* name the name of the suffix to add
*/
void
Suff_AddSuffix(const char *name, GNode **inout_main)
Suff_AddSuffix(const char *name)
{
GNodeListNode *ln;
@ -840,7 +847,7 @@ Suff_AddSuffix(const char *name, GNode **inout_main)
Lst_Append(&sufflist, suff);
DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name);
UpdateTargets(inout_main, suff);
UpdateTargets(suff);
/*
* Look for any existing transformations from or to this suffix.
@ -1197,7 +1204,9 @@ FindCmds(Candidate *targ, CandidateSearcher *cs)
base = str_basename(sgn->name);
if (strncmp(base, targ->prefix, prefLen) != 0)
continue;
/* The node matches the prefix, see if it has a known suffix. */
/*
* The node matches the prefix, see if it has a known suffix.
*/
suff = FindSuffixByName(base + prefLen);
if (suff == NULL)
continue;
@ -1254,7 +1263,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn)
DEBUG1(SUFF, "%s...", cp);
gn = Targ_GetNode(cp);
/* Add gn to the parents child list before the original child */
/* Insert gn before the original child. */
Lst_InsertBefore(&pgn->children, cln, gn);
Lst_Append(&gn->parents, pgn);
pgn->unmade++;
@ -1767,8 +1776,7 @@ FindDepsRegularPath(GNode *gn, Candidate *targ)
free(gn->path);
gn->path = Dir_FindFile(gn->name,
(targ == NULL ? &dirSearchPath :
targ->suff->searchPath));
targ == NULL ? &dirSearchPath : targ->suff->searchPath);
if (gn->path == NULL)
return;
@ -2178,7 +2186,7 @@ Suff_PrintAll(void)
}
}
const char *
char *
Suff_NamesStr(void)
{
Buffer buf;

40
targ.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $ */
/* $NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@ -78,10 +78,8 @@
*
* Targ_List Return the list of all targets so far.
*
* 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.
* GNode_New Create a new GNode with the given name, don't add it
* to allNodes.
*
* Targ_FindNode Find the node, or return NULL.
*
@ -93,9 +91,6 @@
* Targ_FindList Given a list of names, find nodes for all
* of them, creating them as necessary.
*
* Targ_Precious Return true if the target is precious and
* should not be removed if we are interrupted.
*
* Targ_Propagate Propagate information between related nodes.
* Should be called after the makefiles are parsed
* but before any action is taken.
@ -103,8 +98,7 @@
* Debugging:
* Targ_PrintGraph
* Print out the entire graph, all variables and
* statistics for the directory cache. Should print
* something for suffixes, too, but...
* statistics for the directory cache.
*/
#include <time.h>
@ -113,7 +107,7 @@
#include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
MAKE_RCSID("$NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $");
MAKE_RCSID("$NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $");
/*
* All target nodes that appeared on the left-hand side of one of the
@ -346,27 +340,6 @@ Targ_FindList(GNodeList *gns, StringList *names)
}
}
/* See if the given target is precious. */
bool
Targ_Precious(const GNode *gn)
{
/* XXX: Why are '::' targets precious? */
return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
}
/*
* The main target to be made; only for debugging output.
* See mainNode in parse.c for the definitive source.
*/
static GNode *mainTarg;
/* Remember the main target to make; only used for debugging. */
void
Targ_SetMain(GNode *gn)
{
mainTarg = gn;
}
static void
PrintNodeNames(GNodeList *gnodes)
{
@ -508,7 +481,7 @@ Targ_PrintNode(GNode *gn, int pass)
return;
debug_printf("#\n");
if (gn == mainTarg)
if (gn == mainNode)
debug_printf("# *** MAIN TARGET ***\n");
if (pass >= 2) {
@ -556,7 +529,6 @@ Targ_PrintNodes(GNodeList *gnodes, int pass)
Targ_PrintNode(ln->datum, pass);
}
/* Print only those targets that are just a source. */
static void
PrintOnlySources(void)
{

17
trace.c
View File

@ -1,4 +1,4 @@
/* $NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $ */
/* $NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $ */
/*
* Copyright (c) 2000 The NetBSD Foundation, Inc.
@ -48,7 +48,7 @@
#include "job.h"
#include "trace.h"
MAKE_RCSID("$NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $");
MAKE_RCSID("$NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $");
static FILE *trfile;
static pid_t trpid;
@ -69,8 +69,10 @@ Trace_Init(const char *pathname)
if (pathname != NULL) {
FStr curDir;
trpid = getpid();
/* XXX: This variable may get overwritten later, which
* would make trwd point to undefined behavior. */
/*
* XXX: This variable may get overwritten later, which would
* make trwd point to undefined behavior.
*/
curDir = Var_Value(SCOPE_GLOBAL, ".CURDIR");
trwd = curDir.str;
@ -88,10 +90,17 @@ Trace_Log(TrEvent event, Job *job)
gettimeofday(&rightnow, NULL);
#if __STDC__ >= 199901L
fprintf(trfile, "%lld.%06ld %d %s %d %s",
(long long)rightnow.tv_sec, (long)rightnow.tv_usec,
jobTokensRunning,
evname[event], trpid, trwd);
#else
fprintf(trfile, "%ld.%06ld %d %s %d %s",
(long)rightnow.tv_sec, (long)rightnow.tv_usec,
jobTokensRunning,
evname[event], trpid, trwd);
#endif
if (job != NULL) {
char flags[4];

View File

@ -1,6 +1,6 @@
# $Id: Makefile,v 1.164 2021/12/12 22:50:00 sjg Exp $
# $Id: Makefile,v 1.171 2022/01/28 21:33:18 sjg Exp $
#
# $NetBSD: Makefile,v 1.288 2021/12/12 22:16:48 rillig Exp $
# $NetBSD: Makefile,v 1.302 2022/01/27 21:50:50 sjg Exp $
#
# Unit tests for make(1)
#
@ -91,8 +91,10 @@ TESTS+= dep-colon
TESTS+= dep-colon-bug-cross-file
TESTS+= dep-double-colon
TESTS+= dep-double-colon-indep
TESTS+= dep-duplicate
TESTS+= dep-exclam
TESTS+= dep-none
TESTS+= dep-op-missing
TESTS+= dep-percent
TESTS+= dep-var
TESTS+= dep-wildcards
@ -189,7 +191,6 @@ TESTS+= directive-warning
TESTS+= dollar
TESTS+= doterror
TESTS+= dotwait
TESTS+= envfirst
TESTS+= error
TESTS+= # escape # broken by reverting POSIX changes
TESTS+= export
@ -216,8 +217,6 @@ TESTS+= meta-cmd-cmp
TESTS+= moderrs
TESTS+= modmatch
TESTS+= modmisc
TESTS+= modts
TESTS+= modword
.if ${.MAKE.UID} > 0
TESTS+= objdir-writable
.endif
@ -273,10 +272,12 @@ TESTS+= opt-touch-jobs
TESTS+= opt-tracefile
TESTS+= opt-var-expanded
TESTS+= opt-var-literal
TESTS+= opt-version
TESTS+= opt-warnings-as-errors
TESTS+= opt-where-am-i
TESTS+= opt-x-reduce-exported
TESTS+= order
TESTS+= parse
TESTS+= parse-var
TESTS+= phony-end
TESTS+= posix
@ -319,12 +320,12 @@ TESTS+= ternary
TESTS+= unexport
TESTS+= unexport-env
TESTS+= use-inference
TESTS+= var-class
TESTS+= var-class-cmdline
TESTS+= var-class-env
TESTS+= var-class-global
TESTS+= var-class-local
TESTS+= var-class-local-legacy
TESTS+= var-scope
TESTS+= var-scope-cmdline
TESTS+= var-scope-env
TESTS+= var-scope-global
TESTS+= var-scope-local
TESTS+= var-scope-local-legacy
TESTS+= var-eval-short
TESTS+= var-op
TESTS+= var-op-append
@ -340,6 +341,7 @@ TESTS+= varfind
TESTS+= varmisc
TESTS+= varmod
TESTS+= varmod-assign
TESTS+= varmod-assign-shell
TESTS+= varmod-defined
TESTS+= varmod-edge
TESTS+= varmod-exclam-shell
@ -498,7 +500,6 @@ ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# 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, especially not -k
FLAGS.jobs-error-indirect= # none, especially not -k
FLAGS.jobs-error-nested= # none, especially not -k
@ -531,6 +532,7 @@ SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \
SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1}
SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2}
SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3}
SED_CMDS.opt-debug-hash= -e 's,\(numEntries\)=[1-9][0-9],\1=<entries>,'
SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(<pid>),'
SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,'
@ -541,6 +543,7 @@ SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: <shell>\) -q,\1,'
SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex}
SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output}
SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output}
SED_CMDS.opt-where-am-i= -e '/usr.obj/d'
# For Compat_RunCommand, useShell == false.
SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<not found: ...>,'
# For Compat_RunCommand, useShell == true.
@ -736,6 +739,9 @@ _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,'
# Allow the test files to be placed anywhere.
_SED_CMDS+= -e 's,\(\.PARSEDIR}\) = `'"/[^']*'"',\1 = <some-dir>,'
_SED_CMDS+= -e 's,\(\.INCLUDEDFROMDIR}\) = `'"/[^']*'"',\1 = <some-dir>,'
_SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g'
# canonicalize ${.OBJDIR} and ${.CURDIR}
.if ${.OBJDIR} != ${.CURDIR}

View File

@ -1,4 +1,4 @@
# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
# $NetBSD: comment.mk,v 1.4 2022/01/23 18:00:53 rillig Exp $
#
# Demonstrate how comments are written in makefiles.
@ -15,7 +15,9 @@ on and on.
# 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.
# Since parse.c 1.127 from 2007-01-01, these are not shell commands,
# they are just makefile comments. Before that commit, these comments
# triggered the error message "Unassociated shell command".
.if 1 # There can be comments after conditions.
.endif # And after the closing directive.

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-func-empty.mk,v 1.16 2021/12/11 10:41:31 rillig Exp $
# $NetBSD: cond-func-empty.mk,v 1.17 2021/12/28 22:13:56 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness.
@ -189,5 +189,16 @@ VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif
all:
@:;
# If the word 'empty' is not followed by '(', it is not a function call but an
# ordinary bare word. This bare word is interpreted as 'defined(empty)', and
# since there is no variable named 'empty', the condition evaluates to false.
.if empty
. error
.endif
empty= # defined but empty
.if empty
.else
. error
.endif

View File

@ -2,11 +2,11 @@ make: "cond-func.mk" line 36: Missing closing parenthesis for defined()
make: "cond-func.mk" line 51: Missing closing parenthesis for defined()
make: "cond-func.mk" line 54: Missing closing parenthesis for defined()
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: Missing closing parenthesis for defined()
make: "cond-func.mk" line 103: A plain function name is parsed as defined(...).
make: "cond-func.mk" line 110: A plain function name is parsed as defined(...).
make: "cond-func.mk" line 120: Symbols may start with a function name.
make: "cond-func.mk" line 125: Symbols may start with a function name.
make: "cond-func.mk" line 131: Missing closing parenthesis for defined()
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $
# $NetBSD: cond-func.mk,v 1.11 2022/01/07 19:30:17 rillig Exp $
#
# Tests for those parts of the functions in .if conditions that are common
# among several functions.
@ -94,19 +94,20 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
. info The empty variable is never defined.
.endif
# The plain word 'defined' is interpreted as '!empty(defined)'.
# The plain word 'defined' is interpreted as 'defined(defined)', see
# CondParser_ComparisonOrLeaf.
# That variable is not defined (yet).
.if defined
. error
.else
. info A plain function name is parsed as !empty(...).
. info A plain function name is parsed as defined(...).
.endif
# If a variable named 'defined' is actually defined and not empty, the plain
# symbol 'defined' evaluates to true.
defined= non-empty
# If a variable named 'defined' is actually defined, the bare word 'defined'
# is interpreted as 'defined(defined)', and the condition evaluates to true.
defined= # defined but empty
.if defined
. info A plain function name is parsed as !empty(...).
. info A plain function name is parsed as defined(...).
.else
. error
.endif
@ -119,7 +120,7 @@ defined= non-empty
. info Symbols may start with a function name.
.endif
defined-var= non-empty
defined-var= # defined but empty
.if defined-var
. info Symbols may start with a function name.
.else
@ -132,6 +133,3 @@ defined-var= non-empty
.else
. error
.endif
all:
@:;

View File

@ -1,6 +1,7 @@
make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112.
make: "cond-op-parentheses.mk" line 19: Malformed conditional (()
make: "cond-op-parentheses.mk" line 29: Malformed conditional ())
make: "cond-op-parentheses.mk" line 19: String comparison operator must be either == or !=
make: "cond-op-parentheses.mk" line 22: Malformed conditional ((3) > 2)
make: "cond-op-parentheses.mk" line 40: Malformed conditional (()
make: "cond-op-parentheses.mk" line 53: Malformed conditional ())
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,8 +1,26 @@
# $NetBSD: cond-op-parentheses.mk,v 1.4 2021/01/19 17:49:13 rillig Exp $
# $NetBSD: cond-op-parentheses.mk,v 1.5 2022/01/22 21:50:41 rillig Exp $
#
# Tests for parentheses in .if conditions.
# Tests for parentheses in .if conditions, which group expressions to override
# the precedence of the operators '!', '&&' and '||'. Parentheses cannot be
# used to form arithmetic expressions such as '(3+4)' though.
# TODO: Implementation
# Contrary to the C family of programming languages, the outermost condition
# does not have to be enclosed in parentheses.
.if defined(VAR)
. error
.elif !1
. error
.endif
# Parentheses cannot enclose numbers as there is no need for it. Make does
# not implement any arithmetic functions in its condition parser. If
# absolutely necessary, use expr(1).
# expect+1: String comparison operator must be either == or !=
.if 3 > (2)
.endif
# expect+1: Malformed conditional ((3) > 2)
.if (3) > 2
.endif
# Test for deeply nested conditions.
.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
@ -10,7 +28,10 @@
1 \
)))))))))))))))))))))))))))))))))))))))))))))))))))))))) \
))))))))))))))))))))))))))))))))))))))))))))))))))))))))
. info Parentheses can be nested at least to depth 112.
# Parentheses can be nested at least to depth 112. There is nothing special
# about this number though, much higher numbers work as well, at least on
# NetBSD. The actual limit depends on the allowed call stack depth for C code
# of the platform. Anyway, 112 should be enough for all practical purposes.
.else
. error
.endif
@ -24,8 +45,11 @@
# An unbalanced closing parenthesis is a parse error.
#
# As of 2021-01-19, CondParser_Term returned TOK_RPAREN even though this
# function promised to only ever return TOK_TRUE, TOK_FALSE or TOK_ERROR.
# Before cond.c 1.237 from 2021-01-19, CondParser_Term returned TOK_RPAREN
# even though the documentation of that function promised to only ever return
# TOK_TRUE, TOK_FALSE or TOK_ERROR. In cond.c 1.241, the return type of that
# function was changed to a properly restricted enum type, to prevent this bug
# from occurring again.
.if )
. error
.else

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-short.mk,v 1.18 2021/12/12 09:49:09 rillig Exp $
# $NetBSD: cond-short.mk,v 1.19 2021/12/27 18:54:19 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@ -14,14 +14,14 @@
# relevant variable expressions was that in the irrelevant variable
# expressions, undefined variables were allowed. This allowed for conditions
# like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an
# error message 'Malformed conditional', but it still evaluated the
# expression, even though the expression was irrelevant.
# error message 'Malformed conditional', but the irrelevant expression was
# still evaluated.
#
# Since the initial commit on 1993-03-21, the manual page has been saying that
# make 'will only evaluate a conditional as far as is necessary to determine',
# but that was wrong. The code in cond.c 1.1 from 1993-03-21 looks good since
# it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the
# definition of Var_Parse does not call the third parameter 'doEval', as would
# definition of Var_Parse did not call the third parameter 'doEval', as would
# be expected, but instead 'err', accompanied by the comment 'TRUE if
# undefined variables are an error'. This subtle difference between 'do not
# evaluate at all' and 'allow undefined variables' led to the unexpected
@ -122,7 +122,9 @@ VAR= # empty again, for the following tests
.if 0 || empty(${echo "expected or empty" 1>&2 :L:sh})
.endif
# Unreachable nested conditions are skipped completely as well.
# Unreachable nested conditions are skipped completely as well. These skipped
# lines may even contain syntax errors. This allows to skip syntactically
# incompatible new features in older versions of make.
.if 0
. if ${echo "unexpected nested and" 1>&2 :L:sh}

View File

@ -2,7 +2,7 @@ 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: "cond-token-number.mk" line 89: End of the tests.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
# $NetBSD: cond-token-number.mk,v 1.7 2022/01/02 02:57:39 rillig Exp $
#
# Tests for number tokens in .if conditions.
#
@ -69,13 +69,22 @@
. 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).
# This is not a hexadecimal number, even though it has an x. It is
# interpreted as a string instead. In a plain '.if', such a token evaluates
# to true if it is non-empty. In other '.if' directives, such a token is
# evaluated by either FuncDefined or FuncMake.
.if 3x4
.else
. error
.endif
# Make can do radix conversion from hex.
HEX= dead
.if 0x${HEX} == 57005
.else
. error
.endif
# Ensure that parsing continues until here.
.info End of the tests.

View File

@ -27,7 +27,7 @@ lhs = "var&&name", rhs = "var&&name", op = !=
CondParser_Eval: ${:Uvar}||name != "var||name"
lhs = "var||name", rhs = "var||name", op = !=
CondParser_Eval: bare
make: "cond-token-plain.mk" line 106: A bare word is treated like defined(...), and the variable 'bare' is not defined.
make: "cond-token-plain.mk" line 105: A bare word is treated like defined(...), and the variable 'bare' is not defined.
CondParser_Eval: VAR
make: "cond-token-plain.mk" line 111: A bare word is treated like defined(...).
CondParser_Eval: V${:UA}R
@ -56,6 +56,7 @@ CondParser_Eval: 0
make: "cond-token-plain.mk" line 201: Malformed conditional (${0:?:} || left == right)
CondParser_Eval: left == right || ${0:?:}
make: "cond-token-plain.mk" line 206: Malformed conditional (left == right || ${0:?:})
make: "cond-token-plain.mk" line 225: Malformed conditional (VAR.${IF_COUNT::+=1} != "")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: cond-token-plain.mk,v 1.14 2021/12/12 09:36:00 rillig Exp $
# $NetBSD: cond-token-plain.mk,v 1.15 2021/12/30 02:14:55 rillig Exp $
#
# Tests for plain tokens (that is, string literals without quotes)
# in .if conditions. These are also called bare words.
@ -209,5 +209,48 @@ ${:U\\\\}= backslash
# See cond-token-string.mk for similar tests where the condition is enclosed
# in "quotes".
all:
@:;
.MAKEFLAGS: -d0
# As of cond.c 1.320 from 2021-12-30, the code in CondParser_ComparisonOrLeaf
# looks suspicious of evaluating the expression twice: first for parsing a
# bare word and second for parsing the left-hand side of a comparison.
#
# In '.if' directives, the left-hand side of a comparison must not be a bare
# word though, and this keeps CondParser_Leaf from evaluating the expression
# for the second time. The right-hand side of a comparison may be a bare
# word, but that side has no risk of being parsed more than once.
#
# expect+1: Malformed conditional (VAR.${IF_COUNT::+=1} != "")
.if VAR.${IF_COUNT::+=1} != ""
. error
.else
. error
.endif
.if ${IF_COUNT} != "1"
. error
.endif
# A different situation is when CondParser.leftUnquotedOK is true. This
# situation arises in expressions of the form ${cond:?yes:no}. As of
# 2021-12-30, the condition in such an expression is evaluated before parsing
# the condition, see varmod-ifelse.mk. To pass a variable expression to the
# condition parser, it needs to be escaped. This rarely happens in practice,
# in most cases the conditions are simple enough that it doesn't matter
# whether the condition is first evaluated and then parsed, or vice versa.
# A half-baked attempt at hiding this implementation detail is
# CondParser.leftUnquotedOK, but that is a rather leaky abstraction.
#.MAKEFLAGS: -dcv
COND= VAR.$${MOD_COUNT::+=1}
.if ${${COND} == "VAR.":?yes:no} != "yes"
. error
.endif
# The value "1 1" demonstrates that the expression ${MOD_COUNT::+=1} was
# evaluated twice. In practice, expressions that occur in conditions do not
# have side effects, making this problem rather academic, but it is there.
.if ${MOD_COUNT} != "1 1"
. error
.endif
#.MAKEFLAGS: -d0

View File

@ -6,9 +6,9 @@ make: "cond-token-string.mk" line 37: Expected.
CondParser_Eval: "UNDEF"
make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty.
CondParser_Eval: " "
make: "cond-token-string.mk" line 55: The string literal " " is not empty, even though it consists of whitespace only.
make: "cond-token-string.mk" line 54: The string literal " " is not empty, even though it consists of whitespace only.
CondParser_Eval: "${UNDEF}"
make: "cond-token-string.mk" line 64: An undefined variable in quotes expands to an empty string, which then evaluates to false.
make: "cond-token-string.mk" line 63: An undefined variable in quotes expands to an empty string, which then evaluates to false.
CondParser_Eval: "${:Uvalue}"
make: "cond-token-string.mk" line 68: A nonempty variable expression evaluates to true.
CondParser_Eval: "${:U}"

View File

@ -0,0 +1,4 @@
make: "dep-duplicate.inc" line 1: warning: duplicate script for target "all" ignored
make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here
main-output
exit status 0

View File

@ -0,0 +1,27 @@
# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $
#
# Test for a target whose commands are defined twice. This generates a
# warning, not an error, so ensure that the correct commands are kept.
#
# Also ensure that the diagnostics mention the correct file in case of
# included files. Since parse.c 1.231 from 2018-12-22 and before parse.c
# 1.653 from 2022-01-20, the wrong filename had been printed if the file of
# the first commands section was included by its relative path.
all: .PHONY
@exec > dep-duplicate.main; \
echo '# empty line 1'; \
echo '# empty line 2'; \
echo 'all:; @echo main-output'; \
echo '.include "dep-duplicate.inc"'
@exec > dep-duplicate.inc; \
echo 'all:; @echo inc-output'
# The main file must be specified using a relative path, just like the
# default 'makefile' or 'Makefile', to produce the same result when
# run via ATF or 'make test'.
@${MAKE} -r -f dep-duplicate.main
@rm -f dep-duplicate.main
@rm -f dep-duplicate.inc

View File

@ -0,0 +1,4 @@
make: "dep-op-missing.tmp" line 1: Invalid line type
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 0

View File

@ -0,0 +1,13 @@
# $NetBSD: dep-op-missing.mk,v 1.1 2021/12/14 00:02:57 rillig Exp $
#
# Test for a missing dependency operator, in a line with trailing whitespace.
# Before parse.c 1.578 from 2021-12-14, there was some unreachable error
# handling code in ParseDependencyOp. This test tried to reach it and failed.
# To reach that point, there would have to be trailing whitespace in the line,
# but that is removed in an early stage of parsing.
all: .PHONY
@printf 'target ' > dep-op-missing.tmp
@${MAKE} -r -f dep-op-missing.tmp || exit 0
@rm dep-op-missing.tmp

View File

@ -2,8 +2,10 @@ dep-colon-bug-cross-file.mk
dep-colon.mk
dep-double-colon-indep.mk
dep-double-colon.mk
dep-duplicate.mk
dep-exclam.mk
dep-none.mk
dep-op-missing.mk
dep-percent.mk
dep-var.mk
dep-wildcards.mk

View File

@ -1 +1,5 @@
exit status 0
make: "dep.mk" line 11: Inconsistent operator for only-colon
make: "dep.mk" line 13: Inconsistent operator for only-colon
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,8 +1,18 @@
# $NetBSD: dep.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: dep.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
# Tests for dependency declarations, such as "target: sources".
# TODO: Implementation
.MAIN: all
# As soon as a target is defined using one of the dependency operators, it is
# restricted to this dependency operator and cannot use the others anymore.
only-colon:
# expect+1: Inconsistent operator for only-colon
only-colon!
# expect+1: Inconsistent operator for only-colon
only-colon::
# Ensure that the target still has the original operator. If it hadn't, there
# would be another error message.
only-colon:
all:
@:;

View File

@ -2,4 +2,6 @@ Skipping meta for actual-test: no commands
Skipping meta for .END: .SPECIAL
Targets from meta mode:
| TARGET depsrc-meta-target
Targets from meta mode in jobs mode:
| TARGET depsrc-meta-target
exit status 0

View File

@ -1,31 +1,30 @@
# $NetBSD: depsrc-meta.mk,v 1.4 2020/11/27 08:39:07 rillig Exp $
# $NetBSD: depsrc-meta.mk,v 1.6 2022/01/26 22:47:03 rillig Exp $
#
# Tests for the special source .META in dependency declarations.
# TODO: Implementation
# TODO: Explanation
.if make(actual-test)
.MAIN: all
.if make(actual-test)
.MAKEFLAGS: -dM
.MAKE.MODE= meta curDirOk=true
.endif
actual-test: depsrc-meta-target
depsrc-meta-target: .META
@> ${.TARGET}-file
@rm -f ${.TARGET}-file
.elif make(check-results)
check-results:
@echo 'Targets from meta mode:'
@echo 'Targets from meta mode${.MAKE.JOBS:D in jobs mode}:'
@awk '/^TARGET/ { print "| " $$0 }' depsrc-meta-target.meta
@rm depsrc-meta-target.meta
.else
all:
@${MAKE} -f ${MAKEFILE} actual-test
@${MAKE} -f ${MAKEFILE} check-results
@${MAKE} -r -f ${MAKEFILE} actual-test
@${MAKE} -r -f ${MAKEFILE} check-results
.endif
@${MAKE} -r -f ${MAKEFILE} actual-test -j1
@${MAKE} -r -f ${MAKEFILE} check-results -j1

View File

@ -1,8 +1,13 @@
# $NetBSD: depsrc-use.mk,v 1.4 2020/08/22 12:30:57 rillig Exp $
# $NetBSD: depsrc-use.mk,v 1.5 2021/12/28 14:22:51 rillig Exp $
#
# Tests for the special source .USE in dependency declarations,
# which allows to append common commands to other targets.
# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally
# regarded as a candidate for the main target. On the other hand, a .USE
# target was not.
not-a-main-candidate: .USE
all: action directly
first: .USE

View File

@ -1,4 +1,4 @@
# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
# $NetBSD: depsrc-usebefore.mk,v 1.7 2021/12/28 14:22:51 rillig Exp $
#
# Tests for the special source .USEBEFORE in dependency declarations,
# which allows to prepend common commands to other targets.
@ -7,6 +7,11 @@
# .USE
# depsrc-use.mk
# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally
# regarded as a candidate for the main target. On the other hand, a .USE
# target was not.
not-a-main-candidate: .USEBEFORE
all: action directly
first: .USEBEFORE

View File

@ -1,4 +1,5 @@
: 'Undefined variables are expanded directly in the dependency'
: 'declaration. They are not preserved and maybe expanded later.'
: 'This is in contrast to local variables such as ${.TARGET}.'
: Making .UNKNOWN from nothing.
exit status 0

View File

@ -1,7 +1,7 @@
# $NetBSD: depsrc.mk,v 1.4 2020/12/22 19:38:44 rillig Exp $
# $NetBSD: depsrc.mk,v 1.5 2021/12/13 23:38:54 rillig Exp $
#
# Tests for special sources (those starting with a dot, followed by
# uppercase letters) in dependency declarations, such as .PHONY.
# uppercase letters) in dependency declarations, such as '.PHONY'.
# TODO: Implementation
@ -14,13 +14,19 @@ target: .PHONY source-${DEFINED_LATER}
DEFINED_LATER= later
#
source-: .PHONY
# This section applies.
: 'Undefined variables are expanded directly in the dependency'
: 'declaration. They are not preserved and maybe expanded later.'
: 'This is in contrast to local variables such as $${.TARGET}.'
source-later: .PHONY
# This section doesn't apply.
: 'Undefined variables are tried to be expanded in a dependency'
: 'declaration. If that fails because the variable is undefined,'
: 'the expression is preserved and tried to be expanded later.'
all:
@:;
# Sources that look like keywords but are not known are interpreted as
# ordinary sources.
target: .UNKNOWN
.UNKNOWN:
: Making ${.TARGET} from ${.ALLSRC:S,^$,nothing,W}.

View File

@ -1 +1,9 @@
exit status 0
false fails
*** Error code 1 (continuing)
Stop.
make: stopped in unit-tests
ERROR_INFO='This information is printed on 'errors'.'
Making sub-error as prerequisite.
Making .ERROR out of nothing.
exit status 1

View File

@ -1,9 +1,21 @@
# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
# $NetBSD: deptgt-error.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .ERROR in dependency declarations, which
# collects commands that are run when another target fails.
# is made when another target fails.
# TODO: Implementation
all: .PHONY
false fails
all:
@:;
.ERROR:
@echo 'Making ${.TARGET} out of nothing.'
.ERROR: sub-error
sub-error: .PHONY
@echo 'Making ${.TARGET} as prerequisite.'
# Before making the '.ERROR' target, these variable values are printed.
MAKE_PRINT_VAR_ON_ERROR= ERROR_INFO
# Use single quotes to demonstrate that the output is only informational, it
# does not use any established escaping mechanism.
ERROR_INFO= This information is ${:Uprinted} on 'errors'.

View File

@ -1 +1,11 @@
exit status 0
error-failed before
*** Error code 1 (continuing)
error-ignored before
*** Error code 1 (ignored)
error-ignored after
Making depends-on-ignored from error-ignored.
`all' not remade because of errors.
Stop.
make: stopped in unit-tests
exit status 1

View File

@ -1,9 +1,31 @@
# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
# $NetBSD: deptgt-ignore.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .IGNORE in dependency declarations, which
# does not stop if a command from this target exits with a non-zero status.
#
# This test only applies to compatibility mode. In jobs mode such as with
# '-j1', all commands for a single target are bundled into a single shell
# program, which is a different implementation technique, the .IGNORE applies
# there as well.
# TODO: Implementation
.MAKEFLAGS: -d0 # force stdout to be unbuffered
all:
@:;
all: depends-on-failed depends-on-ignored
.PHONY: all depends-on-failed depends-on-ignored error-failed error-ignored
error-failed error-ignored:
@echo '${.TARGET} before'
@false
@echo '${.TARGET} after'
depends-on-failed: error-failed
@echo 'Making ${.TARGET} from ${.ALLSRC}.'
depends-on-ignored: error-ignored
@echo 'Making ${.TARGET} from ${.ALLSRC}.'
# Even though the command 'false' in the middle fails, the remaining commands
# are still run. After that, the target is marked made, so targets depending
# on the target with the ignored commands are made.
.IGNORE: error-ignored
#.MAKEFLAGS: -dg2

View File

@ -1 +1,2 @@
exit status 0
Ctrl-C
exit status 130

View File

@ -1,10 +1,11 @@
# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
# $NetBSD: deptgt-interrupt.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .INTERRUPT in dependency declarations, which
# collects commands to be run when make is interrupted while building another
# target.
# TODO: Implementation
all:
@:;
@kill -INT ${.MAKE.PID}
.INTERRUPT:
@echo 'Ctrl-C'

View File

@ -1 +1,2 @@
This target real-main is the one that is made.
exit status 0

View File

@ -1,10 +1,29 @@
# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
# $NetBSD: deptgt-main.mk,v 1.4 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the special target .MAIN in dependency declarations, which defines
# the main target. This main target is built if no target has been specified
# on the command line or via MAKEFLAGS.
# TODO: Implementation
# The first target becomes the main target by default. It can be overridden
# though.
all: .PHONY
@echo 'This target is not made.'
all:
@:;
# This target is not the first to be defined, but it lists '.MAIN' as one of
# its sources. The word '.MAIN' only has a special meaning when it appears as
# a _target_ in a dependency declaration, not as a _source_. It is thus
# ignored.
depsrc-main: .PHONY .MAIN
@echo 'This target is not made either.'
# This target is the first to be marked with '.MAIN', so it replaces the
# previous main target, which was 'all'.
.MAIN: real-main
real-main: .PHONY
@echo 'This target ${.TARGET} is the one that is made.'
# This target is marked with '.MAIN' but there already is a main target. The
# attribute '.MAIN' is thus ignored.
.MAIN: too-late
too-late: .PHONY
@echo 'This target comes too late, there is already a .MAIN target.'

View File

@ -1 +1,9 @@
: Making 1 out of nothing.
: Making 2 out of nothing.
: Making 3 out of nothing.
: Making 4 out of nothing.
: Making 5 out of nothing.
: Making 6 out of nothing.
: Making 7 out of nothing.
: Making 8 out of nothing.
exit status 0

View File

@ -1,8 +1,16 @@
# $NetBSD: deptgt-notparallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: deptgt-notparallel.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
# Tests for the special target .NOTPARALLEL in dependency declarations.
# Tests for the special target .NOTPARALLEL in dependency declarations, which
# prevents the job module from doing anything in parallel, by setting the
# maximum jobs to 1. This only applies to the current make, it is not
# exported to submakes.
# TODO: Implementation
.MAKEFLAGS: -j4
all:
@:;
# Set opts.maxJobs back to 1. Without this line, the output would be in
# random order, interleaved with separators like '--- 1 ---'.
.NOTPARALLEL:
all: 1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8: .PHONY
: Making ${.TARGET} out of nothing.

View File

@ -1,3 +1,10 @@
Parsing line 15: .ORDER: three one
ParseDependency(.ORDER: three one)
# .ORDER forces 'three' to be made before 'one'
# three, unmade, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS, flags none
# one, unmade, type OP_DEPENDS|OP_PHONY, flags none
Parsing line 16: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
: 'Making two out of one.'
: 'Making three out of two.'
: 'Making all out of three.'

View File

@ -1,4 +1,4 @@
# $NetBSD: deptgt-order.mk,v 1.3 2021/06/17 15:25:33 rillig Exp $
# $NetBSD: deptgt-order.mk,v 1.4 2021/12/13 23:38:54 rillig Exp $
#
# Tests for the special target .ORDER in dependency declarations.
@ -11,7 +11,9 @@ three: two
# This .ORDER creates a circular dependency since 'three' depends on 'one'
# but 'one' is supposed to be built after 'three'.
.MAKEFLAGS: -dp
.ORDER: three one
.MAKEFLAGS: -d0
# XXX: The circular dependency should be detected here.
all: three

View File

@ -1 +1,4 @@
exit status 0
make: "deptgt-path-suffix.mk" line 8: Suffix '.c' not defined (yet)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,8 +1,16 @@
# $NetBSD: deptgt-path-suffix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: deptgt-path-suffix.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
# Tests for the special target .PATH.suffix in dependency declarations.
# TODO: Implementation
# expect+1: Suffix '.c' not defined (yet)
.PATH.c: ..
.SUFFIXES: .c
# Now the suffix is defined, and the path is recorded.
.PATH.c: ..
all:
@:;

View File

@ -1,14 +1,17 @@
make: "deptgt.mk" line 10: warning: Extra target ignored
make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL"
ParseReadLine (34): '${:U}: empty-source'
Parsing line 34: ${:U}: empty-source
ParseDependency(: empty-source)
ParseReadLine (35): ' : command for empty targets list'
ParseReadLine (36): ': empty-source'
Parsing line 35: : command for empty targets list
Parsing line 36: : empty-source
ParseDependency(: empty-source)
ParseReadLine (37): ' : command for empty targets list'
ParseReadLine (38): '.MAKEFLAGS: -d0'
Parsing line 37: : command for empty targets list
Parsing line 38: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
make: "deptgt.mk" line 46: Unknown modifier "Z"
make: "deptgt.mk" line 49: warning: Extra target ignored
make: "deptgt.mk" line 52: warning: Extra target (ordinary) ignored
make: "deptgt.mk" line 55: warning: Special and mundane targets don't mix. Mundane ones ignored
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: deptgt.mk,v 1.11 2021/04/04 10:13:09 rillig Exp $
# $NetBSD: deptgt.mk,v 1.12 2021/12/13 23:38:54 rillig Exp $
#
# Tests for special targets like .BEGIN or .SUFFIXES in dependency
# declarations.
@ -45,5 +45,14 @@ ${:U}: empty-source
# that nobody uses it.
$$$$$$$${:U:Z}:
# expect+1: warning: Extra target ignored
.END ordinary:
# expect+1: warning: Extra target (ordinary) ignored
.PATH ordinary:
# expect+1: Special and mundane targets don't mix. Mundane ones ignored
ordinary .PATH:
all:
@:;

View File

@ -1 +1,4 @@
exit status 0
make: "directive-dinclude-error.inc" line 1: Invalid line type
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,9 +1,24 @@
# $NetBSD: directive-dinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $
# $NetBSD: directive-dinclude.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .dinclude directive, which includes another file,
# typically named .depend.
# silently skipping it if it cannot be opened. This is primarily used for
# including '.depend' files, that's where the 'd' comes from.
#
# The 'silently skipping' only applies to the case where the file cannot be
# opened. Parse errors and other errors are handled the same way as in the
# other .include directives.
# TODO: Implementation
# No complaint that there is no such file.
.dinclude "${.CURDIR}/directive-dinclude-nonexistent.inc"
all:
@:;
# No complaint either, even though the operating system error is ENOTDIR, not
# ENOENT.
.dinclude "${MAKEFILE}/subdir"
# Errors that are not related to opening the file are still reported.
# expect: make: "directive-dinclude-error.inc" line 1: Invalid line type
_!= echo 'syntax error' > directive-dinclude-error.inc
.dinclude "${.CURDIR}/directive-dinclude-error.inc"
_!= rm directive-dinclude-error.inc
all: .PHONY

View File

@ -1,8 +1,21 @@
# $NetBSD: directive-elifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: directive-elifdef.mk,v 1.3 2022/01/22 16:23:56 rillig Exp $
#
# Tests for the .elifdef directive.
# Tests for the .elifdef directive, which is seldom used. Instead of writing
# '.elifdef VAR', the usual form is the more versatile '.elif defined(VAR)'.
# TODO: Implementation
# At this point, VAR is not defined, so the condition evaluates to false.
.if 0
.elifdef VAR
. error
.endif
VAR= # defined
# At this point, VAR is defined, so the condition evaluates to true.
.if 0
.elifdef VAR
.else
. error
.endif
all:
@:;

View File

@ -1,8 +1,23 @@
# $NetBSD: directive-elifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
# $NetBSD: directive-elifndef.mk,v 1.3 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the .elifndef directive.
# Tests for the .elifndef directive, which is an obscure form of writing the
# more usual '.elif !defined(VAR)'.
# TODO: Implementation
# At this point, VAR is not yet defined, and due to the 'n' in 'elifndef' the
# condition evaluates to true.
.if 0
.elifndef VAR && VAR || VAR
.else
. error
.endif
VAR= # defined
# At this point, VAR is defined, and due to the 'n' in 'elifndef' the
# condition evaluates to false.
.if 0
.elifndef VAR && VAR || VAR
. error
.endif
all:
@:;

View File

@ -1,8 +1,8 @@
ParseReadLine (21): 'UT_VAR= <${REF}>'
Parsing line 21: UT_VAR= <${REF}>
Global: UT_VAR = <${REF}>
ParseReadLine (28): '.export UT_VAR'
Parsing line 28: .export UT_VAR
Global: .MAKE.EXPORTED = UT_VAR
ParseReadLine (32): ': ${UT_VAR:N*}'
Parsing line 32: : ${UT_VAR:N*}
Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
@ -14,6 +14,7 @@ CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>"
Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined)
Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
Modifier part: "echo "$UT_VAR""
Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:O} is "UT_VAR"
@ -23,7 +24,7 @@ Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined)
lhs = "<>", rhs = "<>", op = !=
ParseReadLine (50): ': ${UT_VAR:N*}'
Parsing line 50: : ${UT_VAR:N*}
Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
@ -31,12 +32,13 @@ Pattern for ':N' is "*"
ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
ParseReadLine (54): 'REF= defined'
Parsing line 54: REF= defined
Global: REF = defined
CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<defined>"
Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" (eval-defined)
Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
Modifier part: "echo "$UT_VAR""
Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:O} is "UT_VAR"
@ -46,10 +48,10 @@ Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
Result of ${:!echo "\$UT_VAR"!} is "<defined>" (eval-defined, defined)
lhs = "<defined>", rhs = "<defined>", op = !=
ParseReadLine (62): 'all:'
Parsing line 62: all:
ParseDependency(all:)
Global: .ALLTARGETS = all
ParseReadLine (63): '.MAKEFLAGS: -d0'
Parsing line 63: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0

View File

@ -27,29 +27,29 @@ make: "directive-for-escape.mk" line 43: value-with-modifier
For: end for 1
For: loop body:
. info ${:U\${UNDEF\:U\\$\\$}
make: "directive-for-escape.mk" line 57: ${UNDEF:U\$
make: "directive-for-escape.mk" line 72: ${UNDEF:U\backslash$
For: loop body:
. info ${:U{{\}\}}
make: "directive-for-escape.mk" line 57: {{}}
make: "directive-for-escape.mk" line 72: {{}}
For: loop body:
. info ${:Uend\}}
make: "directive-for-escape.mk" line 57: end}
make: "directive-for-escape.mk" line 72: end}
For: end for 1
For: loop body:
. info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end}
make: "directive-for-escape.mk" line 69: begin<fallback>end
make: "directive-for-escape.mk" line 84: begin<fallback>end
For: end for 1
For: loop body:
. info ${:U\$}
make: "directive-for-escape.mk" line 77: $
make: "directive-for-escape.mk" line 92: $
For: end for 1
For: loop body:
. info ${NUMBERS} ${:Ureplaced}
make: "directive-for-escape.mk" line 85: one two three replaced
make: "directive-for-escape.mk" line 100: one two three replaced
For: end for 1
For: loop body:
. info ${:Ureplaced}
make: "directive-for-escape.mk" line 95: replaced
make: "directive-for-escape.mk" line 110: replaced
For: end for 1
For: loop body:
. info . $$i: ${:Uinner}
@ -62,31 +62,85 @@ For: loop body:
. info . $${i2}: ${i2}
. info . $${i,}: ${i,}
. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
make: "directive-for-escape.mk" line 103: . $i: inner
make: "directive-for-escape.mk" line 104: . ${i}: inner
make: "directive-for-escape.mk" line 105: . ${i:M*}: inner
make: "directive-for-escape.mk" line 106: . $(i): inner
make: "directive-for-escape.mk" line 107: . $(i:M*): inner
make: "directive-for-escape.mk" line 108: . ${i${:U}}: outer
make: "directive-for-escape.mk" line 109: . ${i\}}: inner}
make: "directive-for-escape.mk" line 110: . ${i2}: two
make: "directive-for-escape.mk" line 111: . ${i,}: comma
make: "directive-for-escape.mk" line 112: . adjacent: innerinnerinnerinner
make: "directive-for-escape.mk" line 118: . $i: inner
make: "directive-for-escape.mk" line 119: . ${i}: inner
make: "directive-for-escape.mk" line 120: . ${i:M*}: inner
make: "directive-for-escape.mk" line 121: . $(i): inner
make: "directive-for-escape.mk" line 122: . $(i:M*): inner
make: "directive-for-escape.mk" line 123: . ${i${:U}}: outer
make: "directive-for-escape.mk" line 124: . ${i\}}: inner}
make: "directive-for-escape.mk" line 125: . ${i2}: two
make: "directive-for-escape.mk" line 126: . ${i,}: comma
make: "directive-for-escape.mk" line 127: . adjacent: innerinnerinnerinner
For: end for 1
For: loop body:
. info eight $$$$$$$$ and no cents.
. info eight ${:Udollar}${:Udollar}${:Udollar}${:Udollar} and no cents.
make: "directive-for-escape.mk" line 120: eight $$$$ and no cents.
make: "directive-for-escape.mk" line 121: eight dollardollardollardollar and no cents.
make: "directive-for-escape.mk" line 130: eight and no cents.
make: "directive-for-escape.mk" line 135: eight $$$$ and no cents.
make: "directive-for-escape.mk" line 136: eight dollardollardollardollar and no cents.
make: "directive-for-escape.mk" line 145: eight and no cents.
For: end for 1
make: "directive-for-escape.mk" line 137: newline in .for value
make: "directive-for-escape.mk" line 137: newline in .for value
make: "directive-for-escape.mk" line 152: newline in .for value
make: "directive-for-escape.mk" line 152: newline in .for value
For: loop body:
. info short: ${:U" "}
. info long: ${:U" "}
make: "directive-for-escape.mk" line 138: short: " "
make: "directive-for-escape.mk" line 139: long: " "
make: "directive-for-escape.mk" line 153: short: " "
make: "directive-for-escape.mk" line 154: long: " "
For: end for 1
For: loop body:
For: end for 1
Parse_PushInput: .for loop in directive-for-escape.mk, line 167
make: "directive-for-escape.mk" line 167: newline in .for value
in .for loop from directive-for-escape.mk:167 with i = "
"
For: loop body:
: ${:U" "}
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
Parsing line 168: : ${:U" "}
ParseDependency(: " ")
ParseEOF: returning to file directive-for-escape.mk, line 170
SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
Parsing line 170: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
For: end for 1
For: loop body:
# ${:U#}
For: loop body:
# ${:U\\\\#}
For: end for 1
For: loop body:
# ${:U\$}
For: loop body:
# ${:U$i}
For: loop body:
# ${:U$(i)}
For: loop body:
# ${:U${i}}
For: loop body:
# ${:U$$}
For: loop body:
# ${:U$$$$}
For: loop body:
# ${:U${:U\$\$}}
For: end for 1
For: loop body:
# ${:U${.TARGET}}
For: loop body:
# ${:U${.TARGET}}
For: loop body:
# ${:U$${.TARGET\}}
For: loop body:
# ${:U$${.TARGET\}}
For: end for 1
For: loop body:
# ${:U(((}
For: loop body:
# ${:U{{{}
For: loop body:
# ${:U)))}
For: loop body:
# ${:U\}\}\}}
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-for-escape.mk,v 1.12 2021/12/05 11:40:03 rillig Exp $
# $NetBSD: directive-for-escape.mk,v 1.15 2022/01/27 20:15:14 rillig Exp $
#
# Test escaping of special characters in the iteration values of a .for loop.
# These values get expanded later using the :U variable modifier, and this
@ -50,7 +50,22 @@ VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
# being that each '$' is written as '$$'.
#
# The .for loop splits ${VALUES} into 3 words, at the space characters, since
# these are not escaped.
# the '$$' is an ordinary character and the spaces are not escaped.
# Word 1 is '${UNDEF:U\$\$'
# Word 2 is '{{}}'
# Word 3 is 'end}'
# The first iteration expands the body of the .for loop to:
# expect: . info ${:U\${UNDEF\:U\\$\\$}
# The modifier ':U' unescapes the '\$' to a simple '$'.
# The modifier ':U' unescapes the '\:' to a simple ':'.
# The modifier ':U' unescapes the '\\' to a simple '\'.
# The modifier ':U' resolves the expression '$\' to the word 'backslash', due
# to the following variable definition.
${:U\\}= backslash
# FIXME: There was no expression '$\' in the original text of the previous
# line, that's a surprise in the parser.
# The modifier ':U' unescapes the '\$' to a simple '$'.
# expect+4: ${UNDEF:U\backslash$
VALUES= $${UNDEF:U\$$\$$ {{}} end}
# XXX: Where in the code does the '\$\$' get converted into a single '\$'?
.for i in ${VALUES}
@ -139,4 +154,47 @@ ${closing-brace}= <closing-brace> # alternative interpretation
. info long: ${i}
.endfor
# No error since the newline character is not actually used.
.for i in "${.newline}"
.endfor
# Between for.c 1.161 from 2022-01-08 and before for.c 1.163 from 2022-01-09,
# a newline character in a .for loop led to a crash since at the point where
# the error message including the stack trace is printed, the body of the .for
# loop is assembled, and at that point, ForLoop.nextItem had already been
# advanced.
.MAKEFLAGS: -dp
.for i in "${.newline}"
: $i
.endfor
.MAKEFLAGS: -d0
.MAKEFLAGS: -df
.for i in \# \\\#
# $i
.endfor
.for i in $$ $$i $$(i) $${i} $$$$ $$$$$$$$ $${:U\$$\$$}
# $i
.endfor
# The expression '${.TARGET}' must be preserved as it is one of the 7 built-in
# target-local variables. See for.c 1.45 from 2009-01-14.
.for i in ${.TARGET} $${.TARGET} $$${.TARGET} $$$${.TARGET}
# $i
.endfor
# expect: # ${:U${.TARGET}}
# XXX: Why does '$' result in the same text as '$$'?
# expect: # ${:U${.TARGET}}
# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'?
# expect: # ${:U$${.TARGET\}}
# XXX: Why does '$' result in the same text as '$$'?
# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'?
# expect: # ${:U$${.TARGET\}}
.for i in ((( {{{ ))) }}}
# $i
.endfor
.MAKEFLAGS: -d0
all:

View File

@ -1,7 +1,7 @@
make: "directive-for-generating-endif.mk" line 21: if-less endif
make: "directive-for-generating-endif.mk" line 21: if-less endif
make: "directive-for-generating-endif.mk" line 21: if-less endif
make: "directive-for-generating-endif.mk" line 0: 3 open conditionals
make: "directive-for-generating-endif.mk" line 26: 3 open conditionals
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -19,6 +19,24 @@ make: "directive-for.mk" line 148: outer "quoted" \"quoted\"
make: "directive-for.mk" line 154: Unknown modifier "Z"
make: "directive-for.mk" line 155: XXX: Not reached word1
make: "directive-for.mk" line 155: XXX: Not reached word3
make: "directive-for.mk" line 160: no iteration variables in for
make: "directive-for.mk" line 162: Missing argument for ".error"
make: "directive-for.mk" line 163: for-less endfor
make: "directive-for.mk" line 187: 1 open conditional
make: "directive-for.mk" line 203: for-less endfor
make: "directive-for.mk" line 204: if-less endif
make: "directive-for.mk" line 212: if-less endif
For: end for 1
For: loop body:
.\
for inner in i
.\
endfor
make: "directive-for.mk" line 229: Unexpected end of file in .for loop
For: loop body:
.\
endfor
make: "directive-for.mk" line 227: for-less endfor
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,4 +1,4 @@
# $NetBSD: directive-for.mk,v 1.10 2020/12/27 09:58:35 rillig Exp $
# $NetBSD: directive-for.mk,v 1.13 2022/01/15 12:35:18 rillig Exp $
#
# Tests for the .for directive.
#
@ -11,7 +11,7 @@
# Using the .for loop, lists of values can be produced.
# In simple cases, the :@var@${var}@ variable modifier can be used to
# reach the same effects.
# achieve the same effects.
#
.undef NUMBERS
.for num in 1 2 3
@ -135,7 +135,7 @@ EXPANSION${plus}= value
# Ensure that braces and parentheses are properly escaped by the .for loop.
# Each line must print the same word 3 times.
# See GetEscapes.
# See ForLoop_SubstBody.
.for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{
. info $v ${v} $(v)
.endfor
@ -155,5 +155,76 @@ var= outer
. info XXX: Not reached ${var}
.endfor
all:
@:;
# An empty list of variables to the left of the 'in' is a parse error.
.for in value # expect+0: no iteration variables in for
# XXX: The loop body is evaluated once, even with the parse error above.
. error # expect+0: Missing argument for ".error"
.endfor # expect+0: for-less endfor
# An empty list of iteration values to the right of the 'in' is accepted.
# Unlike in the shell, it is not a parse error.
.for var in
. error
.endfor
# If the iteration values become empty after expanding the expressions, the
# body of the loop is not evaluated. It is not a parse error.
.for var in ${:U}
. error
.endfor
# The loop body can be empty.
.for var in 1 2 3
.endfor
# A mismatched .if inside a .for loop is detected each time when the loop body
# is processed.
.for var in value
. if 0
.endfor # expect+0: 1 open conditional
# If there are no iteration values, the loop body is not processed, and the
# check for mismatched conditionals is not performed.
.for var in ${:U}
. if 0
.endfor
# When a .for without the corresponding .endfor occurs in an inactive branch
# of an .if, the .for directive is just skipped, it does not even need a
# corresponding .endfor. In other words, the behavior of the parser depends
# on the actual values of the conditions in the .if clauses.
.if 0
. for var in value # does not need a corresponding .endfor
.endif
.endfor # expect+0: for-less endfor
.endif # expect+0: if-less endif
# When a .for without the corresponding .endfor occurs in an active branch of
# an .if, the parser just counts the number of .for and .endfor directives,
# without looking at any other directives.
.if 1
. for var in value
. endif # expect+0: if-less endif
. endfor # no 'for-less endfor'
.endif # no 'if-less endif'
# When make parses a .for loop, it assumes that there is no line break between
# the '.' and the 'for' or 'endfor', as there is no practical reason to break
# the line at this point. When make scans the outer .for loop, it does not
# recognize the inner directives as such. When make scans the inner .for
# loop, it recognizes the '.\n for' but does not recognize the '.\n endfor',
# as LK_FOR_BODY preserves the backslash-newline sequences.
.MAKEFLAGS: -df
.for outer in o
.\
for inner in i
.\
endfor
.endfor
.MAKEFLAGS: -d0

View File

@ -1 +1,4 @@
exit status 0
make: "directive-hyphen-include-error.inc" line 1: Invalid line type
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1

View File

@ -1,9 +1,23 @@
# $NetBSD: directive-hyphen-include.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $
# $NetBSD: directive-hyphen-include.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .-include directive, which includes another file,
# silently skipping it if it cannot be opened.
#
# The 'silently skipping' only applies to the case where the file cannot be
# opened. Parse errors and other errors are handled the same way as in the
# other .include directives.
# TODO: Implementation
# No complaint that there is no such file.
.-include "${.CURDIR}/directive-hyphen-include-nonexistent.inc"
all:
@:;
# No complaint either, even though the operating system error is ENOTDIR, not
# ENOENT.
.-include "${MAKEFILE}/subdir"
# Errors that are not related to opening the file are still reported.
# expect: make: "directive-hyphen-include-error.inc" line 1: Invalid line type
_!= echo 'syntax error' > directive-hyphen-include-error.inc
.-include "${.CURDIR}/directive-hyphen-include-error.inc"
_!= rm directive-hyphen-include-error.inc
all: .PHONY

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