summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon J. Gerraty <sjg@FreeBSD.org>2020-11-20 03:54:37 +0000
committerSimon J. Gerraty <sjg@FreeBSD.org>2020-11-20 03:54:37 +0000
commit1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d (patch)
tree90c374b8513ec5109e1ec4e2228e2edf648e8756
parent302da1a3d35c15cb29d76e0a939f8bcb13f7ad80 (diff)
downloadsrc-test2-1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d.tar.gz
src-test2-1b65f0bd2bda7121a90f8cb4c1cacaa20f1b681d.zip
Notes
-rw-r--r--ChangeLog75
-rw-r--r--FILES28
-rw-r--r--Makefile8
-rw-r--r--Makefile.config.in4
-rw-r--r--VERSION2
-rw-r--r--arch.c557
-rw-r--r--bmake.119
-rw-r--r--bmake.cat113
-rwxr-xr-xboot-strap6
-rw-r--r--buf.c17
-rw-r--r--buf.h11
-rw-r--r--compat.c99
-rw-r--r--cond.c547
-rwxr-xr-xconfigure30
-rw-r--r--configure.in15
-rw-r--r--dir.c244
-rw-r--r--dir.h14
-rw-r--r--filemon/filemon_dev.c4
-rw-r--r--filemon/filemon_ktrace.c4
-rw-r--r--for.c68
-rw-r--r--hash.c18
-rw-r--r--hash.h7
-rw-r--r--job.c540
-rw-r--r--job.h24
-rw-r--r--lst.c144
-rw-r--r--lst.h6
-rw-r--r--main.c1412
-rwxr-xr-xmake-bootstrap.sh.in5
-rw-r--r--make.119
-rw-r--r--make.c345
-rw-r--r--make.h274
-rw-r--r--make_malloc.h4
-rw-r--r--meta.c118
-rw-r--r--metachar.h4
-rw-r--r--missing/sys/cdefs.h186
-rw-r--r--mk/ChangeLog7
-rw-r--r--mk/install-mk4
-rw-r--r--mk/meta.autodep.mk5
-rwxr-xr-xmk/meta2deps.sh4
-rw-r--r--nonints.h63
-rw-r--r--parse.c706
-rw-r--r--str.c60
-rw-r--r--suff.c545
-rw-r--r--targ.c166
-rw-r--r--unit-tests/Makefile79
-rwxr-xr-xunit-tests/archive-suffix.mk6
-rw-r--r--unit-tests/archive.mk30
-rw-r--r--unit-tests/cmd-errors-lint.exp9
-rw-r--r--unit-tests/cmd-errors-lint.mk32
-rw-r--r--unit-tests/cmd-errors.exp9
-rw-r--r--unit-tests/cmd-errors.mk30
-rwxr-xr-xunit-tests/cmd-interrupt.mk4
-rw-r--r--unit-tests/cmdline-undefined.exp17
-rw-r--r--unit-tests/cmdline-undefined.mk40
-rw-r--r--unit-tests/cmdline.mk11
-rw-r--r--unit-tests/comment.mk12
-rw-r--r--unit-tests/cond-cmp-numeric-eq.exp6
-rwxr-xr-xunit-tests/cond-cmp-numeric-eq.mk15
-rw-r--r--unit-tests/cond-cmp-numeric.exp4
-rw-r--r--unit-tests/cond-cmp-numeric.mk14
-rw-r--r--unit-tests/cond-cmp-string.exp8
-rw-r--r--unit-tests/cond-cmp-string.mk30
-rwxr-xr-xunit-tests/cond-cmp-unary.exp1
-rwxr-xr-xunit-tests/cond-cmp-unary.mk17
-rw-r--r--unit-tests/cond-func-commands.mk5
-rw-r--r--unit-tests/cond-func-defined.exp5
-rw-r--r--unit-tests/cond-func-defined.mk21
-rw-r--r--unit-tests/cond-func-empty.exp6
-rw-r--r--unit-tests/cond-func-empty.mk43
-rw-r--r--unit-tests/cond-func.exp18
-rw-r--r--unit-tests/cond-func.mk72
-rw-r--r--unit-tests/cond-late.mk12
-rw-r--r--unit-tests/cond-op-and-lint.exp4
-rw-r--r--unit-tests/cond-op-and-lint.mk13
-rw-r--r--unit-tests/cond-op-not.exp5
-rw-r--r--unit-tests/cond-op-not.mk41
-rw-r--r--unit-tests/cond-op-or-lint.exp4
-rw-r--r--unit-tests/cond-op-or-lint.mk13
-rw-r--r--unit-tests/cond-op-parentheses.exp1
-rw-r--r--unit-tests/cond-op-parentheses.mk13
-rw-r--r--unit-tests/cond-op.exp17
-rw-r--r--unit-tests/cond-op.mk35
-rw-r--r--unit-tests/cond-short.mk21
-rw-r--r--unit-tests/cond-token-number.exp10
-rw-r--r--unit-tests/cond-token-number.mk28
-rw-r--r--unit-tests/cond-token-plain.mk13
-rw-r--r--unit-tests/cond-token-string.exp9
-rw-r--r--unit-tests/cond-token-string.mk33
-rw-r--r--unit-tests/cond-token-var.exp8
-rw-r--r--unit-tests/cond-token-var.mk22
-rwxr-xr-xunit-tests/cond-undef-lint.exp2
-rwxr-xr-xunit-tests/cond-undef-lint.mk6
-rw-r--r--unit-tests/cond1.exp4
-rw-r--r--unit-tests/cond1.mk7
-rw-r--r--unit-tests/dep-double-colon.mk7
-rw-r--r--unit-tests/dep-exclam.mk9
-rw-r--r--unit-tests/depsrc-ignore.mk11
-rw-r--r--unit-tests/depsrc-make.mk4
-rw-r--r--unit-tests/depsrc-optional.exp20
-rw-r--r--unit-tests/depsrc-optional.mk15
-rw-r--r--unit-tests/depsrc-precious.mk12
-rw-r--r--unit-tests/depsrc-usebefore.mk6
-rw-r--r--unit-tests/depsrc.mk4
-rw-r--r--unit-tests/deptgt-begin.exp3
-rw-r--r--unit-tests/deptgt-begin.mk36
-rw-r--r--unit-tests/deptgt-error.mk5
-rw-r--r--unit-tests/deptgt-ignore.mk5
-rw-r--r--unit-tests/deptgt-interrupt.mk6
-rw-r--r--unit-tests/deptgt-main.mk6
-rw-r--r--unit-tests/deptgt-makeflags.exp1
-rw-r--r--unit-tests/deptgt-makeflags.mk54
-rw-r--r--unit-tests/deptgt-silent.exp2
-rw-r--r--unit-tests/deptgt-silent.mk9
-rw-r--r--unit-tests/deptgt.exp8
-rw-r--r--unit-tests/deptgt.mk12
-rw-r--r--unit-tests/dir.mk5
-rw-r--r--unit-tests/directive-elif.exp18
-rw-r--r--unit-tests/directive-elif.mk68
-rw-r--r--unit-tests/directive-else.exp13
-rw-r--r--unit-tests/directive-else.mk16
-rw-r--r--unit-tests/directive-endif.mk21
-rw-r--r--unit-tests/directive-export-env.mk6
-rw-r--r--unit-tests/directive-export-gmake.mk6
-rw-r--r--unit-tests/directive-export-literal.mk6
-rw-r--r--unit-tests/directive-export.exp5
-rw-r--r--unit-tests/directive-export.mk8
-rwxr-xr-xunit-tests/directive-for.exp36
-rwxr-xr-xunit-tests/directive-for.mk12
-rw-r--r--unit-tests/directive-if-nested.exp2
-rw-r--r--unit-tests/directive-if-nested.mk25
-rw-r--r--unit-tests/directive-if.exp16
-rw-r--r--unit-tests/directive-if.mk77
-rw-r--r--unit-tests/directive-ifdef.exp1
-rw-r--r--unit-tests/directive-ifdef.mk12
-rw-r--r--unit-tests/directive-ifmake.exp13
-rw-r--r--unit-tests/directive-ifmake.mk55
-rwxr-xr-xunit-tests/directive-include.exp5
-rwxr-xr-xunit-tests/directive-include.mk7
-rw-r--r--unit-tests/directive-info.exp15
-rw-r--r--unit-tests/directive-info.mk21
-rwxr-xr-xunit-tests/directive-sinclude.mk6
-rw-r--r--unit-tests/directive-undef.exp5
-rw-r--r--unit-tests/directive-undef.mk6
-rw-r--r--unit-tests/directive-unexport-env.mk6
-rw-r--r--unit-tests/directive-unexport.exp5
-rw-r--r--unit-tests/directive-unexport.mk6
-rw-r--r--unit-tests/directive-warning.exp12
-rw-r--r--unit-tests/directive-warning.mk11
-rw-r--r--unit-tests/directive.exp13
-rw-r--r--unit-tests/directive.mk29
-rw-r--r--unit-tests/directives.exp42
-rw-r--r--unit-tests/directives.mk163
-rw-r--r--unit-tests/dollar.exp2
-rw-r--r--unit-tests/dollar.mk6
-rw-r--r--unit-tests/envfirst.mk4
-rw-r--r--unit-tests/error.exp6
-rw-r--r--unit-tests/error.mk10
-rw-r--r--unit-tests/escape.mk6
-rw-r--r--unit-tests/forloop.exp28
-rw-r--r--unit-tests/forloop.mk16
-rw-r--r--unit-tests/forsubst.mk14
-rw-r--r--unit-tests/gnode-submake.exp11
-rw-r--r--unit-tests/gnode-submake.mk42
-rw-r--r--unit-tests/include-sub.mk4
-rw-r--r--unit-tests/job-flags.exp12
-rw-r--r--unit-tests/job-flags.mk32
-rw-r--r--unit-tests/moderrs.mk4
-rw-r--r--unit-tests/modmisc.mk12
-rw-r--r--unit-tests/modts.mk5
-rw-r--r--unit-tests/modword.mk3
-rw-r--r--unit-tests/objdir-writable.exp5
-rw-r--r--unit-tests/objdir-writable.mk31
-rw-r--r--unit-tests/opt-chdir.exp5
-rw-r--r--unit-tests/opt-chdir.mk29
-rw-r--r--unit-tests/opt-debug-jobs.exp4
-rw-r--r--unit-tests/opt-debug-jobs.mk9
-rw-r--r--unit-tests/opt-ignore.mk3
-rw-r--r--unit-tests/opt-keep-going.mk3
-rw-r--r--unit-tests/opt-no-action.mk4
-rw-r--r--unit-tests/opt-query.mk4
-rw-r--r--unit-tests/opt-touch-jobs.exp4
-rw-r--r--unit-tests/opt-touch-jobs.mk30
-rw-r--r--unit-tests/opt-touch.exp3
-rw-r--r--unit-tests/opt-touch.mk21
-rw-r--r--unit-tests/opt-var-expanded.mk4
-rw-r--r--unit-tests/opt-var-literal.mk4
-rw-r--r--unit-tests/opt-warnings-as-errors.exp4
-rw-r--r--unit-tests/opt-warnings-as-errors.mk4
-rw-r--r--unit-tests/opt.exp21
-rw-r--r--unit-tests/opt.mk28
-rw-r--r--unit-tests/order.mk4
-rw-r--r--unit-tests/recursive.exp4
-rw-r--r--unit-tests/recursive.mk4
-rw-r--r--unit-tests/sh-leading-at.exp1
-rw-r--r--unit-tests/sh-leading-at.mk10
-rw-r--r--unit-tests/sh-leading-hyphen.mk7
-rw-r--r--unit-tests/sh-leading-plus.mk4
-rw-r--r--unit-tests/sh-meta-chars.mk8
-rw-r--r--unit-tests/suff-self.exp3
-rw-r--r--unit-tests/suff-self.mk11
-rw-r--r--unit-tests/use-inference.mk5
-rw-r--r--unit-tests/var-class-local.exp3
-rw-r--r--unit-tests/var-class-local.mk16
-rw-r--r--unit-tests/var-op-assign.exp4
-rw-r--r--unit-tests/var-op-assign.mk19
-rw-r--r--unit-tests/var-op-expand.exp9
-rw-r--r--unit-tests/var-op-expand.mk20
-rw-r--r--unit-tests/var-op-shell.exp6
-rw-r--r--unit-tests/var-op-shell.mk81
-rw-r--r--unit-tests/var-op-sunsh.mk4
-rw-r--r--unit-tests/vardebug.exp2
-rw-r--r--unit-tests/varmisc.mk6
-rw-r--r--unit-tests/varmod-defined.exp22
-rw-r--r--unit-tests/varmod-defined.mk18
-rw-r--r--unit-tests/varmod-exclam-shell.mk23
-rw-r--r--unit-tests/varmod-ifelse.exp8
-rw-r--r--unit-tests/varmod-ifelse.mk38
-rw-r--r--unit-tests/varmod-loop.exp8
-rw-r--r--unit-tests/varmod-loop.mk49
-rw-r--r--unit-tests/varmod-match.mk7
-rw-r--r--unit-tests/varmod-order-shuffle.mk4
-rw-r--r--unit-tests/varmod-shell.exp2
-rw-r--r--unit-tests/varmod-shell.mk28
-rw-r--r--unit-tests/varmod-subst.exp45
-rw-r--r--unit-tests/varmod-subst.mk68
-rw-r--r--unit-tests/varmod-to-abs.exp4
-rw-r--r--unit-tests/varmod-to-abs.mk21
-rw-r--r--unit-tests/varmod-to-lower.mk4
-rw-r--r--unit-tests/varmod-to-separator.mk8
-rw-r--r--unit-tests/varmod-undefined.mk3
-rw-r--r--unit-tests/varmod.exp2
-rw-r--r--unit-tests/varmod.mk11
-rwxr-xr-xunit-tests/varname-dot-shell.exp2
-rw-r--r--unit-tests/varname-empty.exp2
-rwxr-xr-xunit-tests/varname-makefile.exp1
-rwxr-xr-xunit-tests/varname-makefile.mk9
-rw-r--r--unit-tests/varname-vpath.exp11
-rw-r--r--unit-tests/varname-vpath.mk42
-rw-r--r--unit-tests/varname.exp25
-rw-r--r--unit-tests/varname.mk42
-rw-r--r--unit-tests/varparse-errors.exp1
-rw-r--r--unit-tests/varparse-errors.mk35
-rw-r--r--unit-tests/varparse-undef-partial.mk12
-rw-r--r--unit-tests/varshell.exp10
-rw-r--r--unit-tests/varshell.mk20
-rw-r--r--util.c22
-rw-r--r--var.c740
247 files changed, 6143 insertions, 4176 deletions
diff --git a/ChangeLog b/ChangeLog
index 82995f735b2d..ac723511d75a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,78 @@
+2020-11-17 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201117
+ Merge with NetBSD make, pick up
+ o fix some unit-tests when dash is .SHELL
+ o rename Targ_NewGN to GNode_New
+ o make some GNode functions const
+ o main.c: call Targ_Init before Var_Init
+ cleanup PrintOnError, getTmpdir and ParseBoolean
+ o var.c: fix error message of failed :!cmd! modifier
+
+2020-11-14 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201114
+ Merge with NetBSD make, pick up
+ o replace a few HashTable_CreateEntry with HashTable_Set
+ o clean up cached_stats
+ o rename DEFAULT to defaultNode
+ o remove redundant struct make_stat
+ o cond.c: in lint mode, check for ".else <cond>"
+ use bitset for IfState
+ replace large switch with if-else in Cond_EvalLine
+ o job.c: clean up JobExec, JobStart, JobDoOutput
+ use stderr for error message about failed touch
+ clean up Job_Touch
+ replace macro DBPRINTF with JobPrintln
+ rename JobState to JobStatus
+ main.c: switch cache for realpath from GNode to HashTable
+ clean up Fatal
+ clean up InitDefSysIncPath
+ use progname instead of hard-coded 'make' in warning
+ rename Main_SetVarObjdir to SetVarObjdir
+ make.1: document the -S option
+ make.c: fix debug output for GNode details
+ use symbolic names in debug output of GNodes
+
+2020-11-12 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * configure.in: fix --with-force-machine-arch
+
+ * VERSION (_MAKE_VERSION): 20201112
+ Merge with NetBSD make, pick up
+ o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable
+ checks in InitObjdir. Explicit .OBJDIR target always allows
+ read-only directory.
+ o cond.c: clean up Cond_EvalLine
+
+2020-11-11 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20201111
+ Merge with NetBSD make, pick up
+ o more unit-tests
+ o style cleanup
+ remove redundant parentheses from sizeof operator
+ replace character literal 0 with '\0'.
+ replace pointer literal 0 with NULL.
+ remove redundant parentheses.
+ replace (expr & mask) == 0 with !(expr & mask).
+ use strict typing in conditions of the form !var
+ o rename Make_OODate to GNode_IsOODate
+ o rename Make_TimeStamp to GNode_UpdateYoungestChild
+ o rename Var_Set_with_flags to Var_SetWithFlags
+ o rename dieQuietly to shouldDieQuietly
+ o buf.c: make API of Buf_Init simpler
+ o compat.c: clean up Compat_Make, Compat_RunCommand,
+ CompatDeleteTarget and CompatInterrupt
+ o cond.c: in lint mode, only allow '&&' and '||', not '&' and '|'
+ clean up CondParser_Comparison
+ o main.c: rename getBoolean and s2Boolean
+ rename MAKEFILE_PREFERENCE for consistency
+ o parse.c: replace strstr in ParseMaybeSubMake with optimized code
+ o var.c: rename VARE_ASSIGN to VARE_KEEP_DOLLAR
+ replace emptyString with allocated empty string
+ error out on unclosed expressions after the colon
+
2020-11-01 Simon J Gerraty <sjg@beast.crufty.net>
* VERSION (_MAKE_VERSION): 20201101
diff --git a/FILES b/FILES
index f8687ad3a1d9..dedc29767658 100644
--- a/FILES
+++ b/FILES
@@ -75,8 +75,14 @@ unit-tests/archive-suffix.exp
unit-tests/archive-suffix.mk
unit-tests/archive.exp
unit-tests/archive.mk
+unit-tests/cmd-errors-lint.exp
+unit-tests/cmd-errors-lint.mk
+unit-tests/cmd-errors.exp
+unit-tests/cmd-errors.mk
unit-tests/cmd-interrupt.exp
unit-tests/cmd-interrupt.mk
+unit-tests/cmdline-undefined.exp
+unit-tests/cmdline-undefined.mk
unit-tests/cmdline.exp
unit-tests/cmdline.mk
unit-tests/comment.exp
@@ -115,10 +121,14 @@ unit-tests/cond-func.exp
unit-tests/cond-func.mk
unit-tests/cond-late.exp
unit-tests/cond-late.mk
+unit-tests/cond-op-and-lint.exp
+unit-tests/cond-op-and-lint.mk
unit-tests/cond-op-and.exp
unit-tests/cond-op-and.mk
unit-tests/cond-op-not.exp
unit-tests/cond-op-not.mk
+unit-tests/cond-op-or-lint.exp
+unit-tests/cond-op-or-lint.mk
unit-tests/cond-op-or.exp
unit-tests/cond-op-or.mk
unit-tests/cond-op-parentheses.exp
@@ -287,6 +297,8 @@ unit-tests/directive-for.exp
unit-tests/directive-for.mk
unit-tests/directive-hyphen-include.exp
unit-tests/directive-hyphen-include.mk
+unit-tests/directive-if-nested.exp
+unit-tests/directive-if-nested.mk
unit-tests/directive-if.exp
unit-tests/directive-if.mk
unit-tests/directive-ifdef.exp
@@ -315,8 +327,6 @@ unit-tests/directive-warning.exp
unit-tests/directive-warning.mk
unit-tests/directive.exp
unit-tests/directive.mk
-unit-tests/directives.exp
-unit-tests/directives.mk
unit-tests/dollar.exp
unit-tests/dollar.mk
unit-tests/doterror.exp
@@ -341,6 +351,8 @@ unit-tests/forloop.exp
unit-tests/forloop.mk
unit-tests/forsubst.exp
unit-tests/forsubst.mk
+unit-tests/gnode-submake.exp
+unit-tests/gnode-submake.mk
unit-tests/hanoi-include.exp
unit-tests/hanoi-include.mk
unit-tests/impsrc.exp
@@ -349,6 +361,8 @@ unit-tests/include-main.exp
unit-tests/include-main.mk
unit-tests/include-sub.mk
unit-tests/include-subsub.mk
+unit-tests/job-flags.exp
+unit-tests/job-flags.mk
unit-tests/job-output-long-lines.exp
unit-tests/job-output-long-lines.mk
unit-tests/lint.exp
@@ -365,6 +379,8 @@ unit-tests/modts.exp
unit-tests/modts.mk
unit-tests/modword.exp
unit-tests/modword.mk
+unit-tests/objdir-writable.exp
+unit-tests/objdir-writable.mk
unit-tests/opt-backwards.exp
unit-tests/opt-backwards.mk
unit-tests/opt-chdir.exp
@@ -447,6 +463,8 @@ unit-tests/opt-raw.exp
unit-tests/opt-raw.mk
unit-tests/opt-silent.exp
unit-tests/opt-silent.mk
+unit-tests/opt-touch-jobs.exp
+unit-tests/opt-touch-jobs.mk
unit-tests/opt-touch.exp
unit-tests/opt-touch.mk
unit-tests/opt-tracefile.exp
@@ -517,6 +535,8 @@ unit-tests/suff-main.exp
unit-tests/suff-main.mk
unit-tests/suff-rebuild.exp
unit-tests/suff-rebuild.mk
+unit-tests/suff-self.exp
+unit-tests/suff-self.mk
unit-tests/suff-transform-endless.exp
unit-tests/suff-transform-endless.mk
unit-tests/suff-transform-expand.exp
@@ -737,14 +757,14 @@ unit-tests/varname.exp
unit-tests/varname.mk
unit-tests/varparse-dynamic.exp
unit-tests/varparse-dynamic.mk
+unit-tests/varparse-errors.exp
+unit-tests/varparse-errors.mk
unit-tests/varparse-mod.exp
unit-tests/varparse-mod.mk
unit-tests/varparse-undef-partial.exp
unit-tests/varparse-undef-partial.mk
unit-tests/varquote.exp
unit-tests/varquote.mk
-unit-tests/varshell.exp
-unit-tests/varshell.mk
util.c
var.c
wait.h
diff --git a/Makefile b/Makefile
index 248af9a8bcdf..38ccb8a6a636 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# $Id: Makefile,v 1.113 2020/10/26 17:55:09 sjg Exp $
+# $Id: Makefile,v 1.114 2020/11/13 21:47:25 sjg Exp $
PROG= bmake
@@ -49,6 +49,12 @@ CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE
CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}}
COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\""
+.for x in FORCE_MACHINE FORCE_MACHINE_ARCH
+.ifdef $x
+COPTS.main.c+= "-D$x=\"${$x}\""
+.endif
+.endfor
+
# meta mode can be useful even without filemon
# should be set by now
USE_FILEMON ?= no
diff --git a/Makefile.config.in b/Makefile.config.in
index 38de8f5dd3c9..55cd60ca80ba 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -5,8 +5,8 @@ _MAKE_VERSION?=@_MAKE_VERSION@
prefix?= @prefix@
srcdir= @srcdir@
CC?= @CC@
-MACHINE?= @machine@
-MACHINE_ARCH?= @machine_arch@
+@force_machine@MACHINE?= @machine@
+@force_machine_arch@MACHINE_ARCH?= @machine_arch@
DEFAULT_SYS_PATH?= @default_sys_path@
CPPFLAGS+= @CPPFLAGS@
diff --git a/VERSION b/VERSION
index 51afe049fb7a..6dfd755ffdc8 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
# keep this compatible with sh and make
-_MAKE_VERSION=20201101
+_MAKE_VERSION=20201117
diff --git a/arch.c b/arch.c
index d0bc901171f6..81552dee2bb9 100644
--- a/arch.c
+++ b/arch.c
@@ -1,4 +1,4 @@
-/* $NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $ */
+/* $NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,38 +68,38 @@
* SUCH DAMAGE.
*/
-/*-
- * arch.c --
- * Functions to manipulate libraries, archives and their members.
+/* Manipulate libraries, archives and their members.
*
- * Once again, cacheing/hashing comes into play in the manipulation
- * of archives. The first time an archive is referenced, all of its members'
- * headers are read and hashed and the archive closed again. All hashed
- * archives are kept on a list which is searched each time an archive member
- * is referenced.
+ * The first time an archive is referenced, all of its members' headers are
+ * read and cached and the archive closed again. All cached archives are kept
+ * on a list which is searched each time an archive member is referenced.
*
* The interface to this module is:
+ *
+ * Arch_Init Initialize this module.
+ *
+ * Arch_End Clean up this module.
+ *
* Arch_ParseArchive
- * Given an archive specification, return a list
- * of GNode's, one for each member in the spec.
- * FALSE is returned if the specification is
- * invalid for some reason.
+ * Parse an archive specification such as
+ * "archive.a(member1 member2)".
*
* Arch_Touch Alter the modification time of the archive
* member described by the given node to be
- * the current time.
+ * the time when make was started.
*
* Arch_TouchLib Update the modification time of the library
* described by the given node. This is special
* because it also updates the modification time
* of the library's table of contents.
*
- * Arch_MTime Find the modification time of a member of
- * an archive *in the archive*. The time is also
- * placed in the member's GNode. Returns the
- * modification time.
+ * Arch_UpdateMTime
+ * Find the modification time of a member of
+ * an archive *in the archive* and place it in the
+ * member's GNode.
*
- * Arch_MemTime Find the modification time of a member of
+ * Arch_UpdateMemberMTime
+ * Find the modification time of a member of
* an archive. Called when the member doesn't
* already exist. Looks in the archive for the
* modification time. Returns the modification
@@ -109,12 +109,7 @@
* library name in the GNode should be in
* -l<name> format.
*
- * Arch_LibOODate Special function to decide if a library node
- * is out-of-date.
- *
- * Arch_Init Initialize this module.
- *
- * Arch_End Clean up this module.
+ * Arch_LibOODate Decide if a library node is out-of-date.
*/
#ifdef HAVE_CONFIG_H
@@ -151,16 +146,7 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: arch.c,v 1.151 2020/10/31 18:41:07 rillig Exp $");
-
-#ifdef TARGET_MACHINE
-#undef MAKE_MACHINE
-#define MAKE_MACHINE TARGET_MACHINE
-#endif
-#ifdef TARGET_MACHINE_ARCH
-#undef MAKE_MACHINE_ARCH
-#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
-#endif
+MAKE_RCSID("$NetBSD: arch.c,v 1.177 2020/11/14 21:29:44 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
@@ -230,38 +216,36 @@ ArchFree(void *ap)
#endif
-/*-
- *-----------------------------------------------------------------------
- * Arch_ParseArchive --
- * Parse the archive specification in the given line and find/create
- * the nodes for the specified archive members, placing their nodes
- * on the given list.
+/*
+ * Parse an archive specification such as "archive.a(member1 member2.${EXT})",
+ * adding nodes for the expanded members to nodeLst. Nodes are created as
+ * necessary.
*
* Input:
- * linePtr Pointer to start of specification
- * nodeLst Lst on which to place the nodes
- * ctxt Context in which to expand variables
+ * pp The start of the specification.
+ * nodeLst The list on which to place the nodes.
+ * ctxt The context in which to expand variables.
*
- * Results:
- * TRUE if it was a valid specification. The linePtr is updated
- * to point to the first non-space after the archive spec. The
- * nodes for the members are placed on the given list.
- *-----------------------------------------------------------------------
+ * Output:
+ * return TRUE if it was a valid specification.
+ * *pp Points to the first non-space after the archive spec.
+ * *nodeLst Nodes for the members have been added.
*/
Boolean
-Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
+Arch_ParseArchive(char **pp, GNodeList *nodeLst, GNode *ctxt)
{
char *cp; /* Pointer into line */
GNode *gn; /* New node */
char *libName; /* Library-part of specification */
+ char *libName_freeIt = NULL;
char *memName; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */
- Boolean subLibName; /* TRUE if libName should have/had
- * variable substitution performed on it */
+ Boolean expandLibName; /* Whether the parsed libName contains
+ * variable expressions that need to be
+ * expanded */
- libName = *linePtr;
-
- subLibName = FALSE;
+ libName = *pp;
+ expandLibName = FALSE;
for (cp = libName; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
@@ -274,7 +258,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
const char *result;
Boolean isError;
- (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES,
+ /* XXX: is expanded twice: once here and once below */
+ (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &result_freeIt);
/* TODO: handle errors */
isError = result == var_Error;
@@ -282,16 +267,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
if (isError)
return FALSE;
- subLibName = TRUE;
+ expandLibName = TRUE;
cp += nested_p - cp;
} else
cp++;
}
*cp++ = '\0';
- if (subLibName) {
- (void)Var_Subst(libName, ctxt, VARE_UNDEFERR|VARE_WANTRES, &libName);
+ if (expandLibName) {
+ (void)Var_Subst(libName, ctxt, VARE_WANTRES | VARE_UNDEFERR, &libName);
/* TODO: handle errors */
+ libName_freeIt = libName;
}
@@ -317,7 +303,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
Boolean isError;
const char *nested_p = cp;
- (void)Var_Parse(&nested_p, ctxt, VARE_UNDEFERR|VARE_WANTRES,
+ (void)Var_Parse(&nested_p, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&result, &freeIt);
/* TODO: handle errors */
isError = result == var_Error;
@@ -339,7 +325,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
* so it's better to return failure than allow such things to happen
*/
if (*cp == '\0') {
- printf("No closing parenthesis in archive specification\n");
+ Parse_Error(PARSE_FATAL, "No closing parenthesis in archive specification");
return FALSE;
}
@@ -370,7 +356,7 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
char *sacrifice;
char *oldMemName = memName;
- (void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES,
+ (void)Var_Subst(memName, ctxt, VARE_WANTRES | VARE_UNDEFERR,
&memName);
/* TODO: handle errors */
@@ -381,7 +367,8 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*/
buf = sacrifice = str_concat4(libName, "(", memName, ")");
- if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
+ if (strchr(memName, '$') != NULL &&
+ strcmp(memName, oldMemName) == 0) {
/*
* Must contain dynamic sources, so we can't deal with it now.
* Just create an ARCHV node for the thing and let
@@ -437,17 +424,12 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
*cp = saveChar;
}
- /*
- * If substituted libName, free it now, since we need it no longer.
- */
- if (subLibName) {
- free(libName);
- }
+ free(libName_freeIt);
cp++; /* skip the ')' */
- /* We promised that linePtr would be set up at the next non-space. */
+ /* We promised that pp would be set up at the next non-space. */
pp_skip_whitespace(&cp);
- *linePtr = cp;
+ *pp = cp;
return TRUE;
}
@@ -457,15 +439,17 @@ Arch_ParseArchive(char **linePtr, GNodeList *nodeLst, GNode *ctxt)
* Input:
* archive Path to the archive
* member Name of member; only its basename is used.
- * hash TRUE if archive should be hashed if not already so.
+ * addToCache TRUE if archive should be cached if not already so.
*
* Results:
- * The ar_hdr for the member.
+ * The ar_hdr for the member, or NULL.
+ *
+ * See ArchFindMember for an almost identical copy of this code.
*/
static struct ar_hdr *
-ArchStatMember(const char *archive, const char *member, Boolean hash)
+ArchStatMember(const char *archive, const char *member, Boolean addToCache)
{
-#define AR_MAX_NAME_LEN (sizeof(arh.AR_NAME) - 1)
+#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1)
FILE *arch; /* Stream to archive */
size_t size; /* Size of archive member */
char magic[SARMAG];
@@ -484,8 +468,8 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
member = lastSlash + 1;
for (ln = archives->first; ln != NULL; ln = ln->next) {
- const Arch *archPtr = ln->datum;
- if (strcmp(archPtr->name, archive) == 0)
+ const Arch *a = ln->datum;
+ if (strcmp(a->name, archive) == 0)
break;
}
@@ -505,17 +489,17 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
if (len > AR_MAX_NAME_LEN) {
len = AR_MAX_NAME_LEN;
snprintf(copy, sizeof copy, "%s", member);
+ hdr = HashTable_FindValue(&ar->members, copy);
}
- hdr = HashTable_FindValue(&ar->members, copy);
return hdr;
}
}
- if (!hash) {
+ if (!addToCache) {
/*
- * Caller doesn't want the thing hashed, just use ArchFindMember
+ * Caller doesn't want the thing cached, just use ArchFindMember
* to read the header for the member out and close down the stream
- * again. Since the archive is not to be hashed, we assume there's
+ * again. Since the archive is not to be cached, we assume there's
* no need to allocate extra room for the header we're returning,
* so just declare it static.
*/
@@ -541,98 +525,92 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
* We use the ARMAG string to make sure this is an archive we
* can handle...
*/
- if ((fread(magic, SARMAG, 1, arch) != 1) ||
- (strncmp(magic, ARMAG, SARMAG) != 0)) {
- fclose(arch);
+ if (fread(magic, SARMAG, 1, arch) != 1 ||
+ strncmp(magic, ARMAG, SARMAG) != 0) {
+ (void)fclose(arch);
return NULL;
}
- ar = bmake_malloc(sizeof(Arch));
+ ar = bmake_malloc(sizeof *ar);
ar->name = bmake_strdup(archive);
ar->fnametab = NULL;
ar->fnamesize = 0;
HashTable_Init(&ar->members);
memName[AR_MAX_NAME_LEN] = '\0';
- while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) {
- if (strncmp(arh.AR_FMAG, ARFMAG, sizeof(arh.AR_FMAG)) != 0) {
- /*
- * The header is bogus, so the archive is bad
- * and there's no way we can recover...
- */
+ while (fread(&arh, sizeof arh, 1, arch) == 1) {
+ char *nameend;
+
+ /* If the header is bogus, there's no way we can recover. */
+ if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0)
goto badarch;
- } else {
- char *nameend;
- /*
- * We need to advance the stream's pointer to the start of the
- * next header. Files are padded with newlines to an even-byte
- * boundary, so we need to extract the size of the file from the
- * 'size' field of the header and round it up during the seek.
- */
- arh.AR_SIZE[sizeof(arh.AR_SIZE) - 1] = '\0';
- size = (size_t)strtol(arh.ar_size, NULL, 10);
+ /*
+ * We need to advance the stream's pointer to the start of the
+ * next header. Files are padded with newlines to an even-byte
+ * boundary, so we need to extract the size of the file from the
+ * 'size' field of the header and round it up during the seek.
+ */
+ arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0';
+ size = (size_t)strtol(arh.AR_SIZE, NULL, 10);
- memcpy(memName, arh.AR_NAME, sizeof(arh.AR_NAME));
- nameend = memName + AR_MAX_NAME_LEN;
- while (*nameend == ' ') {
- nameend--;
- }
- nameend[1] = '\0';
+ memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME);
+ nameend = memName + AR_MAX_NAME_LEN;
+ while (nameend > memName && *nameend == ' ')
+ nameend--;
+ nameend[1] = '\0';
#ifdef SVR4ARCHIVES
+ /*
+ * svr4 names are slash terminated. Also svr4 extended AR format.
+ */
+ if (memName[0] == '/') {
/*
- * svr4 names are slash terminated. Also svr4 extended AR format.
+ * svr4 magic mode; handle it
*/
- if (memName[0] == '/') {
- /*
- * svr4 magic mode; handle it
- */
- switch (ArchSVR4Entry(ar, memName, size, arch)) {
- case -1: /* Invalid data */
- goto badarch;
- case 0: /* List of files entry */
- continue;
- default: /* Got the entry */
- break;
- }
- } else {
- if (nameend[0] == '/')
- nameend[0] = '\0';
+ switch (ArchSVR4Entry(ar, memName, size, arch)) {
+ case -1: /* Invalid data */
+ goto badarch;
+ case 0: /* List of files entry */
+ continue;
+ default: /* Got the entry */
+ break;
}
+ } else {
+ if (nameend[0] == '/')
+ nameend[0] = '\0';
+ }
#endif
#ifdef AR_EFMT1
- /*
- * BSD 4.4 extended AR format: #1/<namelen>, with name as the
- * first <namelen> bytes of the file
- */
- if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
- ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) {
-
- int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]);
-
- if ((unsigned int)elen > MAXPATHLEN)
- goto badarch;
- if (fread(memName, (size_t)elen, 1, arch) != 1)
- goto badarch;
- memName[elen] = '\0';
- if (fseek(arch, -elen, SEEK_CUR) != 0)
- goto badarch;
- if (DEBUG(ARCH) || DEBUG(MAKE)) {
- debug_printf("ArchStat: Extended format entry for %s\n",
- memName);
- }
- }
+ /*
+ * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+ * first <namelen> bytes of the file
+ */
+ if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
+ ch_isdigit(memName[sizeof AR_EFMT1 - 1])) {
+
+ int elen = atoi(memName + sizeof AR_EFMT1 - 1);
+
+ if ((unsigned int)elen > MAXPATHLEN)
+ goto badarch;
+ if (fread(memName, (size_t)elen, 1, arch) != 1)
+ goto badarch;
+ memName[elen] = '\0';
+ if (fseek(arch, -elen, SEEK_CUR) != 0)
+ goto badarch;
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("ArchStatMember: Extended format entry for %s\n",
+ memName);
+ }
#endif
- {
- HashEntry *he;
- he = HashTable_CreateEntry(&ar->members, memName, NULL);
- HashEntry_Set(he, bmake_malloc(sizeof(struct ar_hdr)));
- memcpy(HashEntry_Get(he), &arh, sizeof(struct ar_hdr));
- }
+ {
+ struct ar_hdr *cached_hdr = bmake_malloc(sizeof *cached_hdr);
+ memcpy(cached_hdr, &arh, sizeof arh);
+ HashTable_Set(&ar->members, memName, cached_hdr);
}
+
if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0)
goto badarch;
}
@@ -643,7 +621,7 @@ ArchStatMember(const char *archive, const char *member, Boolean hash)
/*
* Now that the archive has been read and cached, we can look into
- * the hash table to find the desired member's header.
+ * the addToCache table to find the desired member's header.
*/
return HashTable_FindValue(&ar->members, member);
@@ -674,15 +652,15 @@ badarch:
*-----------------------------------------------------------------------
*/
static int
-ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
+ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
{
#define ARLONGNAMES1 "//"
#define ARLONGNAMES2 "/ARFILENAMES"
size_t entry;
char *ptr, *eptr;
- if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
- strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
+ if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 ||
+ strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) {
if (ar->fnametab != NULL) {
DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n");
@@ -711,51 +689,74 @@ ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
return 0;
}
- if (name[1] == ' ' || name[1] == '\0')
+ if (inout_name[1] == ' ' || inout_name[1] == '\0')
return 2;
- entry = (size_t)strtol(&name[1], &eptr, 0);
- if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
- DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name);
+ entry = (size_t)strtol(&inout_name[1], &eptr, 0);
+ if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) {
+ DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name);
return 2;
}
if (entry >= ar->fnamesize) {
DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
- name, (unsigned long)ar->fnamesize);
+ inout_name, (unsigned long)ar->fnamesize);
return 2;
}
- DEBUG2(ARCH, "Replaced %s with %s\n", name, &ar->fnametab[entry]);
+ DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]);
- snprintf(name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]);
+ snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]);
return 1;
}
#endif
-/*-
- *-----------------------------------------------------------------------
- * ArchFindMember --
- * Locate a member of an archive, given the path of the archive and
- * the path of the desired member. If the archive is to be modified,
- * the mode should be "r+", if not, it should be "r".
- * The passed struct ar_hdr structure is filled in.
+static Boolean
+ArchiveMember_HasName(const struct ar_hdr *hdr,
+ const char *name, size_t namelen)
+{
+ const size_t ar_name_len = sizeof hdr->AR_NAME;
+ const char *ar_name = hdr->AR_NAME;
+
+ if (strncmp(ar_name, name, namelen) != 0)
+ return FALSE;
+
+ if (namelen >= ar_name_len)
+ return namelen == ar_name_len;
+
+ /* hdr->AR_NAME is space-padded to the right. */
+ if (ar_name[namelen] == ' ')
+ return TRUE;
+
+ /* In archives created by GNU binutils 2.27, the member names end with
+ * a slash. */
+ if (ar_name[namelen] == '/' &&
+ (namelen == ar_name_len || ar_name[namelen + 1] == ' '))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Locate a member of an archive, given the path of the archive and the path
+ * of the desired member.
*
* Input:
* archive Path to the archive
* member Name of member. If it is a path, only the last
* component is used.
- * arhPtr Pointer to header structure to be filled in
- * mode The mode for opening the stream
+ * out_arh Archive header to be filled in
+ * mode "r" for read-only access, "r+" for read-write access
*
- * Results:
- * An FILE *, opened for reading and writing, positioned at the
- * start of the member's struct ar_hdr, or NULL if the member was
- * nonexistent. The current struct ar_hdr for member.
- *-----------------------------------------------------------------------
+ * Output:
+ * return The archive file, positioned at the start of the
+ * member's struct ar_hdr, or NULL if the member doesn't
+ * exist.
+ * *out_arh The current struct ar_hdr for member.
+ *
+ * See ArchStatMember for an almost identical copy of this code.
*/
static FILE *
-ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
+ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
const char *mode)
{
FILE *arch; /* Stream to archive */
@@ -772,8 +773,8 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
* We use the ARMAG string to make sure this is an archive we
* can handle...
*/
- if ((fread(magic, SARMAG, 1, arch) != 1) ||
- (strncmp(magic, ARMAG, SARMAG) != 0)) {
+ if (fread(magic, SARMAG, 1, arch) != 1 ||
+ strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch);
return NULL;
}
@@ -787,13 +788,13 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
member = lastSlash + 1;
len = tlen = strlen(member);
- if (len > sizeof(arhPtr->AR_NAME)) {
- tlen = sizeof(arhPtr->AR_NAME);
+ if (len > sizeof out_arh->AR_NAME) {
+ tlen = sizeof out_arh->AR_NAME;
}
- while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
+ while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) {
- if (strncmp(arhPtr->AR_FMAG, ARFMAG, sizeof(arhPtr->AR_FMAG)) != 0) {
+ if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) {
/*
* The header is bogus, so the archive is bad
* and there's no way we can recover...
@@ -802,25 +803,21 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
return NULL;
}
- if (strncmp(member, arhPtr->AR_NAME, tlen) == 0) {
- /*
- * If the member's name doesn't take up the entire 'name' field,
- * we have to be careful of matching prefixes. Names are space-
- * padded to the right, so if the character in 'name' at the end
- * of the matched string is anything but a space, this isn't the
- * member we sought.
- */
- if (tlen != sizeof arhPtr->AR_NAME && arhPtr->AR_NAME[tlen] != ' ')
- goto skip;
+ DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
+ archive,
+ (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
+ (int)sizeof out_arh->ar_date, out_arh->ar_date);
+ if (ArchiveMember_HasName(out_arh, member, len)) {
/*
- * To make life easier, we reposition the file at the start
+ * To make life easier for callers that want to update the
+ * archive, we reposition the file at the start
* of the header we just read before we return the stream.
* In a more general situation, it might be better to leave
* the file at the actual member, rather than its header, but
- * not here...
+ * not here.
*/
- if (fseek(arch, -(long)sizeof(struct ar_hdr), SEEK_CUR) != 0) {
+ if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) {
fclose(arch);
return NULL;
}
@@ -832,10 +829,10 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
* BSD 4.4 extended AR format: #1/<namelen>, with name as the
* first <namelen> bytes of the file
*/
- if (strncmp(arhPtr->AR_NAME, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
- ch_isdigit(arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]))
+ if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 &&
+ ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))
{
- int elen = atoi(&arhPtr->AR_NAME[sizeof(AR_EFMT1) - 1]);
+ int elen = atoi(&out_arh->AR_NAME[sizeof AR_EFMT1 - 1]);
char ename[MAXPATHLEN + 1];
if ((unsigned int)elen > MAXPATHLEN) {
@@ -847,9 +844,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
return NULL;
}
ename[elen] = '\0';
- if (DEBUG(ARCH) || DEBUG(MAKE)) {
- debug_printf("ArchFind: Extended format entry for %s\n", ename);
- }
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("ArchFindMember: Extended format entry for %s\n",
+ ename);
if (strncmp(ename, member, len) == 0) {
/* Found as extended name */
if (fseek(arch, -(long)sizeof(struct ar_hdr) - elen,
@@ -866,7 +863,6 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
}
#endif
-skip:
/*
* This isn't the member we're after, so we need to advance the
* stream's pointer to the start of the next header. Files are
@@ -874,113 +870,89 @@ skip:
* extract the size of the file from the 'size' field of the
* header and round it up during the seek.
*/
- arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0';
- size = (int)strtol(arhPtr->ar_size, NULL, 10);
+ out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0';
+ size = (int)strtol(out_arh->AR_SIZE, NULL, 10);
if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
fclose(arch);
return NULL;
}
}
- /*
- * We've looked everywhere, but the member is not to be found. Close the
- * archive and return NULL -- an error.
- */
fclose(arch);
return NULL;
}
-/*-
- *-----------------------------------------------------------------------
- * Arch_Touch --
- * Touch a member of an archive.
- * The modification time of the entire archive is also changed.
- * For a library, this could necessitate the re-ranlib'ing of the
- * whole thing.
+/* Touch a member of an archive, on disk.
+ * The GNode's modification time is left as-is.
+ *
+ * The st_mtime of the entire archive is also changed.
+ * For a library, it may be required to run ranlib after this.
*
* Input:
* gn Node of member to touch
*
* Results:
* The 'time' field of the member's header is updated.
- *-----------------------------------------------------------------------
*/
void
Arch_Touch(GNode *gn)
{
- FILE *arch; /* Stream open to archive, positioned properly */
- struct ar_hdr arh; /* Current header describing member */
-
- arch = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn),
- &arh, "r+");
+ FILE *f;
+ struct ar_hdr arh;
- snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long)now);
+ f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+");
+ if (f == NULL)
+ return;
- if (arch != NULL) {
- (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
- fclose(arch);
- }
+ snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
+ (void)fwrite(&arh, sizeof arh, 1, f);
+ fclose(f); /* TODO: handle errors */
}
/* Given a node which represents a library, touch the thing, making sure that
- * the table of contents also is touched.
+ * the table of contents is also touched.
*
* Both the modification time of the library and of the RANLIBMAG member are
- * set to 'now'.
- *
- * Input:
- * gn The node of the library to touch
- */
+ * set to 'now'. */
void
-Arch_TouchLib(GNode *gn)
+Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED)
{
#ifdef RANLIBMAG
- FILE * arch; /* Stream open to archive */
- struct ar_hdr arh; /* Header describing table of contents */
- struct utimbuf times; /* Times for utime() call */
+ FILE *f;
+ struct ar_hdr arh; /* Header describing table of contents */
+ struct utimbuf times;
- arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
- snprintf(arh.AR_DATE, sizeof(arh.AR_DATE), "%-12ld", (long) now);
+ f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+");
+ if (f == NULL)
+ return;
- if (arch != NULL) {
- (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
- fclose(arch);
+ snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now);
+ (void)fwrite(&arh, sizeof arh, 1, f);
+ fclose(f); /* TODO: handle errors */
- times.actime = times.modtime = now;
- utime(gn->path, &times);
- }
-#else
- (void)gn;
+ times.actime = times.modtime = now;
+ utime(gn->path, &times); /* TODO: handle errors */
#endif
}
-/* Return the modification time of a member of an archive. The mtime field
- * of the given node is filled in with the value returned by the function.
- *
- * Input:
- * gn Node describing archive member
- */
-time_t
-Arch_MTime(GNode *gn)
+/* Update the mtime of the GNode with the mtime from the archive member on
+ * disk (or in the cache). */
+void
+Arch_UpdateMTime(GNode *gn)
{
- struct ar_hdr *arhPtr; /* Header of desired member */
- time_t modTime; /* Modification time as an integer */
-
- arhPtr = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
- if (arhPtr != NULL) {
- modTime = (time_t)strtol(arhPtr->AR_DATE, NULL, 10);
- } else {
- modTime = 0;
- }
+ struct ar_hdr *arh;
- gn->mtime = modTime;
- return modTime;
+ arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE);
+ if (arh != NULL)
+ gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10);
+ else
+ gn->mtime = 0;
}
-/* Given a non-existent archive member's node, get its modification time from
- * its archived form, if it exists. gn->mtime is filled in as well. */
-time_t
-Arch_MemMTime(GNode *gn)
+/* Given a non-existent archive member's node, update gn->mtime from its
+ * archived form, if it exists. */
+void
+Arch_UpdateMemberMTime(GNode *gn)
{
GNodeListNode *ln;
@@ -1001,7 +973,8 @@ Arch_MemMTime(GNode *gn)
if ((pgn->flags & REMAKE) &&
strncmp(nameStart, gn->name, nameLen) == 0) {
- gn->mtime = Arch_MTime(pgn);
+ Arch_UpdateMTime(pgn);
+ gn->mtime = pgn->mtime;
}
} else if (pgn->flags & REMAKE) {
/*
@@ -1012,8 +985,6 @@ Arch_MemMTime(GNode *gn)
break;
}
}
-
- return gn->mtime;
}
/* Search for a library along the given search path.
@@ -1045,13 +1016,15 @@ Arch_FindLib(GNode *gn, SearchPath *path)
}
/* Decide if a node with the OP_LIB attribute is out-of-date. Called from
- * Make_OODate to make its life easier.
- * The library will be hashed if it hasn't been already.
+ * GNode_IsOODate to make its life easier.
+ * The library is cached if it hasn't been already.
*
* There are several ways for a library to be out-of-date that are
* not available to ordinary files. In addition, there are ways
* that are open to regular files that are not available to
- * libraries. A library that is only used as a source is never
+ * libraries.
+ *
+ * A library that is only used as a source is never
* considered out-of-date by itself. This does not preclude the
* library's modification time from making its parent be out-of-date.
* A library will be considered out-of-date for any of these reasons,
@@ -1066,16 +1039,10 @@ Arch_FindLib(GNode *gn, SearchPath *path)
*
* The modification time of one of its sources is greater than the one
* of its RANLIBMAG member (i.e. its table of contents is out-of-date).
- * We don't compare of the archive time vs. TOC time because they can be
+ * We don't compare the archive time vs. TOC time because they can be
* too close. In my opinion we should not bother with the TOC at all
* since this is used by 'ar' rules that affect the data contents of the
* archive, not by ranlib rules, which affect the TOC.
- *
- * Input:
- * gn The library's graph node
- *
- * Results:
- * TRUE if the library is out-of-date. FALSE otherwise.
*/
Boolean
Arch_LibOODate(GNode *gn)
@@ -1093,25 +1060,23 @@ Arch_LibOODate(GNode *gn)
oodate = TRUE;
} else {
#ifdef RANLIBMAG
- struct ar_hdr *arhPtr; /* Header for __.SYMDEF */
+ struct ar_hdr *arh; /* Header for __.SYMDEF */
int modTimeTOC; /* The table-of-contents's mod time */
- arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE);
+ arh = ArchStatMember(gn->path, RANLIBMAG, FALSE);
- if (arhPtr != NULL) {
- modTimeTOC = (int)strtol(arhPtr->AR_DATE, NULL, 10);
+ if (arh != NULL) {
+ modTimeTOC = (int)strtol(arh->ar_date, NULL, 10);
- if (DEBUG(ARCH) || DEBUG(MAKE)) {
- debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
- }
- oodate = (gn->youngestChild == NULL || gn->youngestChild->mtime > modTimeTOC);
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("%s modified %s...",
+ RANLIBMAG, Targ_FmtTime(modTimeTOC));
+ oodate = gn->youngestChild == NULL ||
+ gn->youngestChild->mtime > modTimeTOC;
} else {
- /*
- * A library w/o a table of contents is out-of-date
- */
- if (DEBUG(ARCH) || DEBUG(MAKE)) {
- debug_printf("No t.o.c....");
- }
+ /* A library without a table of contents is out-of-date. */
+ if (DEBUG(ARCH) || DEBUG(MAKE))
+ debug_printf("no toc...");
oodate = TRUE;
}
#else
diff --git a/bmake.1 b/bmake.1
index d086fc7cdec0..cbd1ae9f8677 100644
--- a/bmake.1
+++ b/bmake.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $
+.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd November 1, 2020
+.Dd November 14, 2020
.Dt BMAKE 1
.Os
.Sh NAME
@@ -37,7 +37,7 @@
.Nd maintain program dependencies
.Sh SYNOPSIS
.Nm
-.Op Fl BeikNnqrstWwX
+.Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory
.Op Fl D Ar variable
.Op Fl d Ar flags
@@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are
up-to-date and 1, otherwise.
.It Fl r
Do not use the built-in rules specified in the system makefile.
+.It Fl S
+Stop processing if an error is encountered.
+This is the default behavior and the opposite of
+.Fl k .
.It Fl s
Do not echo any commands as they are executed.
Equivalent to specifying
@@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and
.Ql Ev PWD
to that directory before executing any targets.
+.Pp
+Except in the case of an explicit
+.Ql Ic .OBJDIR
+target,
+.Nm
+will check that the specified directory is writable and ignore it if not.
+This check can be skipped by setting the environment variable
+.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE
+to "no".
.
.It Va .PARSEDIR
A path to the directory of the current
diff --git a/bmake.cat1 b/bmake.cat1
index 5e78b65c13c5..46e07ba9cc4f 100644
--- a/bmake.cat1
+++ b/bmake.cat1
@@ -4,7 +4,7 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
bmake -- maintain program dependencies
SYNOPSIS
- bmake [-BeikNnqrstWwX] [-C directory] [-D variable] [-d flags]
+ bmake [-BeikNnqrSstWwX] [-C directory] [-D variable] [-d flags]
[-f makefile] [-I directory] [-J private] [-j max_jobs]
[-m directory] [-T file] [-V variable] [-v variable]
[variable=value] [target ...]
@@ -205,6 +205,9 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
-r Do not use the built-in rules specified in the system makefile.
+ -S Stop processing if an error is encountered. This is the default
+ behavior and the opposite of -k.
+
-s Do not echo any commands as they are executed. Equivalent to
specifying `@' before each command line in the makefile.
@@ -718,6 +721,12 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
the specified directory if it exists, and set `.OBJDIR'
and `PWD' to that directory before executing any targets.
+ Except in the case of an explicit `.OBJDIR' target, bmake
+ will check that the specified directory is writable and
+ ignore it if not. This check can be skipped by setting
+ the environment variable `MAKE_OBJDIR_CHECK_WRITABLE' to
+ "no".
+
.PARSEDIR A path to the directory of the current `Makefile' being
parsed.
@@ -1568,4 +1577,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
There is no way of escaping a space character in a filename.
-FreeBSD 11.3 November 1, 2020 FreeBSD 11.3
+FreeBSD 11.3 November 14, 2020 FreeBSD 11.3
diff --git a/boot-strap b/boot-strap
index f3b98f2a7b23..d251649db670 100755
--- a/boot-strap
+++ b/boot-strap
@@ -107,6 +107,10 @@
# set "machine_arch" to override that determined by
# machine.sh
#
+# --with-force_machine_arch="machine_arch"
+# force "machine_arch" to override that determined by
+# machine.sh
+#
# --with-default-sys-path="syspath"
# set an explicit default "syspath" which is where bmake
# will look for sys.mk and friends.
@@ -115,7 +119,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
-# $Id: boot-strap,v 1.53 2020/09/16 02:12:01 sjg Exp $
+# $Id: boot-strap,v 1.54 2020/11/13 21:47:25 sjg Exp $
#
# @(#) Copyright (c) 2001 Simon J. Gerraty
#
diff --git a/buf.c b/buf.c
index 6b4ec07670e4..6cbf31a2bd60 100644
--- a/buf.c
+++ b/buf.c
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $ */
+/* $NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -75,7 +75,7 @@
#include "make.h"
/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: buf.c,v 1.42 2020/10/24 20:51:49 rillig Exp $");
+MAKE_RCSID("$NetBSD: buf.c,v 1.44 2020/11/07 14:11:58 rillig Exp $");
/* Make space in the buffer for adding a single byte. */
void
@@ -155,19 +155,22 @@ Buf_Empty(Buffer *buf)
buf->data[0] = '\0';
}
-/* Initialize a buffer.
- * If the given initial capacity is 0, a reasonable default is used. */
+/* Initialize a buffer. */
void
-Buf_Init(Buffer *buf, size_t cap)
+Buf_InitSize(Buffer *buf, size_t cap)
{
- if (cap <= 0)
- cap = 256;
buf->cap = cap;
buf->len = 0;
buf->data = bmake_malloc(cap);
buf->data[0] = '\0';
}
+void
+Buf_Init(Buffer *buf)
+{
+ Buf_InitSize(buf, 256);
+}
+
/* Reset the buffer.
* If freeData is TRUE, the data from the buffer is freed as well.
* Otherwise it is kept and returned. */
diff --git a/buf.h b/buf.h
index 6b50e36e04f4..6ab3d3288a43 100644
--- a/buf.h
+++ b/buf.h
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.h,v 1.34 2020/09/27 16:59:02 rillig Exp $ */
+/* $NetBSD: buf.h,v 1.36 2020/11/10 00:32:12 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -94,7 +94,7 @@ typedef struct Buffer {
void Buf_Expand_1(Buffer *);
/* Buf_AddByte adds a single byte to a buffer. */
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE void
Buf_AddByte(Buffer *buf, char byte)
{
size_t old_len = buf->len++;
@@ -106,13 +106,13 @@ Buf_AddByte(Buffer *buf, char byte)
end[1] = '\0';
}
-static inline MAKE_ATTR_UNUSED size_t
+MAKE_INLINE size_t
Buf_Len(const Buffer *buf)
{
return buf->len;
}
-static inline MAKE_ATTR_UNUSED Boolean
+MAKE_INLINE Boolean
Buf_EndsWith(const Buffer *buf, char ch)
{
return buf->len > 0 && buf->data[buf->len - 1] == ch;
@@ -124,7 +124,8 @@ void Buf_AddStr(Buffer *, const char *);
void Buf_AddInt(Buffer *, int);
char *Buf_GetAll(Buffer *, size_t *);
void Buf_Empty(Buffer *);
-void Buf_Init(Buffer *, size_t);
+void Buf_Init(Buffer *);
+void Buf_InitSize(Buffer *, size_t);
char *Buf_Destroy(Buffer *, Boolean);
char *Buf_DestroyCompact(Buffer *);
diff --git a/compat.c b/compat.c
index 1bce02bf5c08..2307e9f2d935 100644
--- a/compat.c
+++ b/compat.c
@@ -1,4 +1,4 @@
-/* $NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $ */
+/* $NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -99,15 +99,15 @@
#include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: compat.c,v 1.173 2020/11/01 17:47:26 rillig Exp $");
+MAKE_RCSID("$NetBSD: compat.c,v 1.183 2020/11/15 22:31:03 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
static int compatSigno;
/*
- * CompatDeleteTarget -- delete a failed, interrupted, or otherwise
- * duffed target if not inhibited by .PRECIOUS.
+ * CompatDeleteTarget -- delete the file of a failed, interrupted, or
+ * otherwise duffed target if not inhibited by .PRECIOUS.
*/
static void
CompatDeleteTarget(GNode *gn)
@@ -132,8 +132,6 @@ CompatDeleteTarget(GNode *gn)
static void
CompatInterrupt(int signo)
{
- GNode *gn;
-
CompatDeleteTarget(curTarg);
if (curTarg != NULL && !Targ_Precious(curTarg)) {
@@ -141,7 +139,7 @@ CompatInterrupt(int signo)
* Run .INTERRUPT only if hit with interrupt signal
*/
if (signo == SIGINT) {
- gn = Targ_FindNode(".INTERRUPT");
+ GNode *gn = Targ_FindNode(".INTERRUPT");
if (gn != NULL) {
Compat_Make(gn, gn);
}
@@ -206,7 +204,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
(void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
/* TODO: handle errors */
- if (*cmdStart == '\0') {
+ if (cmdStart[0] == '\0') {
free(cmdStart);
return 0;
}
@@ -225,20 +223,17 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
return 0;
}
- while (*cmd == '@' || *cmd == '-' || *cmd == '+') {
- switch (*cmd) {
- case '@':
+ for (;;) {
+ if (*cmd == '@')
silent = !DEBUG(LOUD);
- break;
- case '-':
+ else if (*cmd == '-')
errCheck = FALSE;
- break;
- case '+':
+ else if (*cmd == '+') {
doIt = TRUE;
- if (!shellName) /* we came here from jobs */
+ if (!shellName) /* we came here from jobs */
Shell_Init();
+ } else
break;
- }
cmd++;
}
@@ -248,7 +243,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
/*
* If we did not end up with a command, just skip it.
*/
- if (!*cmd)
+ if (cmd[0] == '\0')
return 0;
#if !defined(MAKE_NATIVE)
@@ -286,9 +281,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
* If we're not supposed to execute any commands, this is as far as
* we go...
*/
- if (!doIt && !GNode_ShouldExecute(gn)) {
+ if (!doIt && !GNode_ShouldExecute(gn))
return 0;
- }
+
DEBUG1(JOB, "Execute: '%s'\n", cmd);
if (useShell) {
@@ -297,20 +292,13 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
* because the command contains a "meta" character.
*/
static const char *shargv[5];
- int shargc;
- shargc = 0;
+ /* The following work for any of the builtin shell specs. */
+ int shargc = 0;
shargv[shargc++] = shellPath;
- /*
- * The following work for any of the builtin shell specs.
- */
- if (errCheck && shellErrFlag) {
+ if (errCheck && shellErrFlag)
shargv[shargc++] = shellErrFlag;
- }
- if (DEBUG(SHELL))
- shargv[shargc++] = "-xc";
- else
- shargv[shargc++] = "-c";
+ shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c";
shargv[shargc++] = cmd;
shargv[shargc] = NULL;
av = shargv;
@@ -389,17 +377,19 @@ Compat_RunCommand(const char *cmdp, GNode *gn)
#endif
if (status != 0) {
if (DEBUG(ERROR)) {
- const char *cp;
+ const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
gn->name);
- for (cp = cmd; *cp; ) {
- if (ch_isspace(*cp)) {
+
+ /* Replace runs of whitespace with a single space, to reduce
+ * the amount of whitespace for multi-line command lines. */
+ while (*p != '\0') {
+ if (ch_isspace(*p)) {
debug_printf(" ");
- while (ch_isspace(*cp))
- cp++;
+ cpp_skip_whitespace(&p);
} else {
- debug_printf("%c", *cp);
- cp++;
+ debug_printf("%c", *p);
+ p++;
}
}
debug_printf("\n");
@@ -480,16 +470,17 @@ MakeNodes(GNodeList *gnodes, GNode *pgn)
void
Compat_Make(GNode *gn, GNode *pgn)
{
- if (!shellName) /* we came here from jobs */
+ if (shellName == NULL) /* we came here from jobs */
Shell_Init();
+
if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
/*
* First mark ourselves to be made, then apply whatever transformations
* the suffix module thinks are necessary. Once that's done, we can
* descend and make all our children. If any of them has an error
- * but the -k flag was given, our 'make' field will be set FALSE again.
- * This is our signal to not attempt to do anything but abort our
- * parent as well.
+ * but the -k flag was given, our 'make' field will be set to FALSE
+ * again. This is our signal to not attempt to do anything but abort
+ * our parent as well.
*/
gn->flags |= REMAKE;
gn->made = BEINGMADE;
@@ -509,10 +500,10 @@ Compat_Make(GNode *gn, GNode *pgn)
* All the children were made ok. Now youngestChild->mtime contains the
* modification time of the newest child, we need to find out if we
* exist and when we were modified last. The criteria for datedness
- * are defined by the Make_OODate function.
+ * are defined by GNode_IsOODate.
*/
DEBUG1(MAKE, "Examining %s...", gn->name);
- if (!Make_OODate(gn)) {
+ if (!GNode_IsOODate(gn)) {
gn->made = UPTODATE;
DEBUG0(MAKE, "up-to-date.\n");
goto cohorts;
@@ -523,9 +514,8 @@ Compat_Make(GNode *gn, GNode *pgn)
* If the user is just seeing if something is out-of-date, exit now
* to tell him/her "yes".
*/
- if (opts.queryFlag) {
+ if (opts.queryFlag)
exit(1);
- }
/*
* We need to be re-made. We also have to make sure we've got a $?
@@ -573,15 +563,15 @@ Compat_Make(GNode *gn, GNode *pgn)
if (gn->made != ERROR) {
/*
* If the node was made successfully, mark it so, update
- * its modification time and timestamp all its parents. Note
- * that for .ZEROTIME targets, the timestamping isn't done.
+ * its modification time and timestamp all its parents.
* This is to keep its state from affecting that of its parent.
*/
gn->made = MADE;
- pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
+ if (Make_Recheck(gn) == 0)
+ pgn->flags |= FORCE;
if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE;
- Make_TimeStamp(pgn, gn);
+ GNode_UpdateYoungestChild(pgn, gn);
}
} else if (opts.keepgoing) {
pgn->flags &= ~(unsigned)REMAKE;
@@ -604,15 +594,14 @@ Compat_Make(GNode *gn, GNode *pgn)
pgn->flags &= ~(unsigned)REMAKE;
break;
case MADE:
- if ((gn->type & OP_EXEC) == 0) {
+ if (!(gn->type & OP_EXEC)) {
pgn->flags |= CHILDMADE;
- Make_TimeStamp(pgn, gn);
+ GNode_UpdateYoungestChild(pgn, gn);
}
break;
case UPTODATE:
- if ((gn->type & OP_EXEC) == 0) {
- Make_TimeStamp(pgn, gn);
- }
+ if (!(gn->type & OP_EXEC))
+ GNode_UpdateYoungestChild(pgn, gn);
break;
default:
break;
diff --git a/cond.c b/cond.c
index 678dded0fc1e..a1b0d75dcb09 100644
--- a/cond.c
+++ b/cond.c
@@ -1,4 +1,4 @@
-/* $NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $ */
+/* $NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -72,7 +72,8 @@
/* Handling of conditionals in a makefile.
*
* Interface:
- * Cond_EvalLine Evaluate the conditional.
+ * Cond_EvalLine Evaluate the conditional directive, such as
+ * '.if <cond>', '.elifnmake <cond>', '.else', '.endif'.
*
* Cond_EvalCondition
* Evaluate the conditional, which is either the argument
@@ -93,7 +94,7 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: cond.c,v 1.173 2020/10/30 20:30:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.214 2020/11/13 09:01:59 rillig Exp $");
/*
* The parsing of conditional expressions is based on this grammar:
@@ -162,7 +163,7 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
*
* TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc)
* FALSE when CondEvalExpression is called from ApplyModifier_IfElse
- * since lhs is already expanded and we cannot tell if
+ * since lhs is already expanded, and at that point we cannot tell if
* it was a variable reference or not.
*/
static Boolean lhsStrict;
@@ -173,6 +174,12 @@ is_token(const char *str, const char *tok, size_t len)
return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]);
}
+static Token
+ToToken(Boolean cond)
+{
+ return cond ? TOK_TRUE : TOK_FALSE;
+}
+
/* Push back the most recent token read. We only need one level of this. */
static void
CondParser_PushBack(CondParser *par, Token t)
@@ -200,7 +207,7 @@ CondParser_SkipWhitespace(CondParser *par)
* func says whether the argument belongs to an actual function, or
* whether the parsed argument is passed to the default function.
*
- * Return the length of the argument. */
+ * Return the length of the argument, or 0 on error. */
static size_t
ParseFuncArg(const char **pp, Boolean doEval, const char *func,
char **out_arg) {
@@ -213,26 +220,18 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
p++; /* Skip opening '(' - verified by caller */
if (*p == '\0') {
- /*
- * No arguments whatsoever. Because 'make' and 'defined' aren't really
- * "reserved words", we don't print a message. I think this is better
- * than hitting the user with a warning message every time s/he uses
- * the word 'make' or 'defined' at the beginning of a symbol...
- */
- *out_arg = NULL;
- return 0;
+ *out_arg = NULL; /* Missing closing parenthesis: */
+ return 0; /* .if defined( */
}
- while (*p == ' ' || *p == '\t') {
- p++;
- }
+ cpp_skip_hspace(&p);
- Buf_Init(&argBuf, 16);
+ Buf_InitSize(&argBuf, 16);
paren_depth = 0;
for (;;) {
char ch = *p;
- if (ch == 0 || ch == ' ' || ch == '\t')
+ if (ch == '\0' || ch == ' ' || ch == '\t')
break;
if ((ch == '&' || ch == '|') && paren_depth == 0)
break;
@@ -244,7 +243,8 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
* though perhaps we should...
*/
void *nestedVal_freeIt;
- VarEvalFlags eflags = VARE_UNDEFERR | (doEval ? VARE_WANTRES : 0);
+ VarEvalFlags eflags = doEval ? VARE_WANTRES | VARE_UNDEFERR
+ : VARE_NONE;
const char *nestedVal;
(void)Var_Parse(&p, VAR_CMDLINE, eflags, &nestedVal,
&nestedVal_freeIt);
@@ -264,9 +264,7 @@ ParseFuncArg(const char **pp, Boolean doEval, const char *func,
*out_arg = Buf_GetAll(&argBuf, &argLen);
Buf_Destroy(&argBuf, FALSE);
- while (*p == ' ' || *p == '\t') {
- p++;
- }
+ cpp_skip_hspace(&p);
if (func != NULL && *p++ != ')') {
Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
@@ -309,13 +307,10 @@ FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
char *path;
path = Dir_FindFile(arg, dirSearchPath);
- DEBUG2(COND, "exists(%s) result is \"%s\"\n", arg, path ? path : "");
- if (path != NULL) {
- result = TRUE;
- free(path);
- } else {
- result = FALSE;
- }
+ DEBUG2(COND, "exists(%s) result is \"%s\"\n",
+ arg, path != NULL ? path : "");
+ result = path != NULL;
+ free(path);
return result;
}
@@ -336,40 +331,41 @@ FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg)
return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(gn->commands);
}
-/*-
+/*
* Convert the given number into a double.
* We try a base 10 or 16 integer conversion first, if that fails
* then we try a floating point conversion instead.
*
* Results:
- * Sets 'value' to double value of string.
* Returns TRUE if the conversion succeeded.
+ * Sets 'out_value' to the converted number.
*/
static Boolean
-TryParseNumber(const char *str, double *value)
+TryParseNumber(const char *str, double *out_value)
{
- char *eptr, ech;
- unsigned long l_val;
- double d_val;
+ char *end;
+ unsigned long ul_val;
+ double dbl_val;
errno = 0;
- if (!*str) {
- *value = 0.0;
+ if (str[0] == '\0') { /* XXX: why is an empty string a number? */
+ *out_value = 0.0;
return TRUE;
}
- l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
- ech = *eptr;
- if (ech == '\0' && errno != ERANGE) {
- d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
- } else {
- if (ech != '\0' && ech != '.' && ech != 'e' && ech != 'E')
- return FALSE;
- d_val = strtod(str, &eptr);
- if (*eptr)
- return FALSE;
+
+ ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10);
+ if (*end == '\0' && errno != ERANGE) {
+ *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val;
+ return TRUE;
}
- *value = d_val;
+ if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E')
+ return FALSE; /* skip the expensive strtod call */
+ dbl_val = strtod(str, &end);
+ if (*end != '\0')
+ return FALSE;
+
+ *out_value = dbl_val;
return TRUE;
}
@@ -385,31 +381,31 @@ is_separator(char ch)
*
* Results:
* Returns the string, absent any quotes, or NULL on error.
- * Sets quoted if the string was quoted.
- * Sets freeIt if needed.
+ * Sets out_quoted if the string was quoted.
+ * Sets out_freeIt.
*/
/* coverity:[+alloc : arg-*4] */
static const char *
CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
- Boolean *quoted, void **freeIt)
+ Boolean *out_quoted, void **out_freeIt)
{
Buffer buf;
const char *str;
Boolean atStart;
const char *nested_p;
- Boolean qt;
+ Boolean quoted;
const char *start;
VarEvalFlags eflags;
VarParseResult parseResult;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
str = NULL;
- *freeIt = NULL;
- *quoted = qt = par->p[0] == '"' ? 1 : 0;
+ *out_freeIt = NULL;
+ *out_quoted = quoted = par->p[0] == '"';
start = par->p;
- if (qt)
+ if (quoted)
par->p++;
- while (par->p[0] && str == NULL) {
+ while (par->p[0] != '\0' && str == NULL) {
switch (par->p[0]) {
case '\\':
par->p++;
@@ -419,40 +415,44 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
}
continue;
case '"':
- if (qt) {
- par->p++; /* we don't want the quotes */
+ if (quoted) {
+ par->p++; /* skip the closing quote */
goto got_str;
}
Buf_AddByte(&buf, par->p[0]); /* likely? */
par->p++;
continue;
- case ')':
+ case ')': /* see is_separator */
case '!':
case '=':
case '>':
case '<':
case ' ':
case '\t':
- if (!qt)
+ if (!quoted)
goto got_str;
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
case '$':
/* if we are in quotes, an undefined variable is ok */
- eflags = ((!qt && doEval) ? VARE_UNDEFERR : 0) |
- (doEval ? VARE_WANTRES : 0);
+ eflags = doEval && !quoted ? VARE_WANTRES | VARE_UNDEFERR :
+ doEval ? VARE_WANTRES :
+ VARE_NONE;
+
nested_p = par->p;
atStart = nested_p == start;
parseResult = Var_Parse(&nested_p, VAR_CMDLINE, eflags, &str,
- freeIt);
+ out_freeIt);
/* TODO: handle errors */
if (str == var_Error) {
if (parseResult & VPR_ANY_MSG)
par->printedError = TRUE;
- if (*freeIt) {
- free(*freeIt);
- *freeIt = NULL;
+ if (*out_freeIt != NULL) {
+ /* XXX: Can there be any situation in which a returned
+ * var_Error requires freeIt? */
+ free(*out_freeIt);
+ *out_freeIt = NULL;
}
/*
* Even if !doEval, we still report syntax errors, which
@@ -473,19 +473,15 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
goto cleanup;
Buf_AddStr(&buf, str);
- if (*freeIt) {
- free(*freeIt);
- *freeIt = NULL;
+ if (*out_freeIt) {
+ free(*out_freeIt);
+ *out_freeIt = NULL;
}
str = NULL; /* not finished yet */
continue;
default:
- if (strictLHS && !qt && *start != '$' && !ch_isdigit(*start)) {
+ if (strictLHS && !quoted && *start != '$' && !ch_isdigit(*start)) {
/* lhs must be quoted, a variable reference or number */
- if (*freeIt) {
- free(*freeIt);
- *freeIt = NULL;
- }
str = NULL;
goto cleanup;
}
@@ -495,20 +491,22 @@ CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS,
}
}
got_str:
- *freeIt = Buf_GetAll(&buf, NULL);
- str = *freeIt;
+ *out_freeIt = Buf_GetAll(&buf, NULL);
+ str = *out_freeIt;
cleanup:
Buf_Destroy(&buf, FALSE);
return str;
}
-/* The different forms of .if directives. */
-static const struct If {
+struct If {
const char *form; /* Form of if */
size_t formlen; /* Length of form */
Boolean doNot; /* TRUE if default function should be negated */
Boolean (*defProc)(size_t, const char *); /* Default function to apply */
-} ifs[] = {
+};
+
+/* The different forms of .if directives. */
+static const struct If ifs[] = {
{ "def", 3, FALSE, FuncDefined },
{ "ndef", 4, TRUE, FuncDefined },
{ "make", 4, FALSE, FuncMake },
@@ -516,28 +514,39 @@ static const struct If {
{ "", 0, FALSE, FuncDefined },
{ NULL, 0, FALSE, NULL }
};
+enum { PLAIN_IF_INDEX = 4 };
+
+static Boolean
+If_Eval(const struct If *if_info, const char *arg, size_t arglen)
+{
+ Boolean res = if_info->defProc(arglen, arg);
+ return if_info->doNot ? !res : res;
+}
/* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0". */
-static Token
-EvalNotEmpty(CondParser *par, const char *lhs, Boolean lhsQuoted)
+static Boolean
+EvalNotEmpty(CondParser *par, const char *value, Boolean quoted)
{
- double left;
+ double num;
- /* For .ifxxx "..." check for non-empty string. */
- if (lhsQuoted)
- return lhs[0] != '\0';
+ /* For .ifxxx "...", check for non-empty string. */
+ if (quoted)
+ return value[0] != '\0';
- /* For .ifxxx <number> compare against zero */
- if (TryParseNumber(lhs, &left))
- return left != 0.0;
+ /* For .ifxxx <number>, compare against zero */
+ if (TryParseNumber(value, &num))
+ return num != 0.0;
- /* For .if ${...} check for non-empty string (defProc is ifdef). */
+ /* For .if ${...}, check for non-empty string. This is different from
+ * the evaluation function from that .if variant, which would test
+ * whether a variable of the given name were defined. */
+ /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */
if (par->if_info->form[0] == '\0')
- return lhs[0] != 0;
+ return value[0] != '\0';
- /* Otherwise action default test ... */
- return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot;
+ /* For the other variants of .ifxxx ${...}, use its default function. */
+ return If_Eval(par->if_info, value, strlen(value));
}
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
@@ -553,18 +562,18 @@ EvalCompareNum(double lhs, const char *op, double rhs)
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
return TOK_ERROR;
}
- return lhs != rhs;
+ return ToToken(lhs != rhs);
case '=':
if (op[1] != '=') {
Parse_Error(PARSE_WARNING, "Unknown operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
return TOK_ERROR;
}
- return lhs == rhs;
+ return ToToken(lhs == rhs);
case '<':
- return op[1] == '=' ? lhs <= rhs : lhs < rhs;
+ return ToToken(op[1] == '=' ? lhs <= rhs : lhs < rhs);
case '>':
- return op[1] == '=' ? lhs >= rhs : lhs > rhs;
+ return ToToken(op[1] == '=' ? lhs >= rhs : lhs > rhs);
}
return TOK_ERROR;
}
@@ -580,7 +589,7 @@ EvalCompareStr(const char *lhs, const char *op, const char *rhs)
}
DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, rhs, op);
- return (*op == '=') == (strcmp(lhs, rhs) == 0);
+ return ToToken((*op == '=') == (strcmp(lhs, rhs) == 0));
}
/* Evaluate a comparison, such as "${VAR} == 12345". */
@@ -609,43 +618,34 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
{
Token t = TOK_ERROR;
const char *lhs, *op, *rhs;
- void *lhsFree, *rhsFree;
+ void *lhs_freeIt, *rhs_freeIt;
Boolean lhsQuoted, rhsQuoted;
- rhs = NULL;
- lhsFree = rhsFree = NULL;
- lhsQuoted = rhsQuoted = FALSE;
-
/*
* Parse the variable spec and skip over it, saving its
* value in lhs.
*/
- lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhsFree);
- if (!lhs)
- goto done;
+ lhs = CondParser_String(par, doEval, lhsStrict, &lhsQuoted, &lhs_freeIt);
+ if (lhs == NULL)
+ goto done_lhs;
CondParser_SkipWhitespace(par);
- /*
- * Make sure the operator is a valid one. If it isn't a
- * known relational operator, pretend we got a
- * != 0 comparison.
- */
op = par->p;
switch (par->p[0]) {
case '!':
case '=':
case '<':
case '>':
- if (par->p[1] == '=') {
+ if (par->p[1] == '=')
par->p += 2;
- } else {
+ else
par->p++;
- }
break;
default:
- t = doEval ? EvalNotEmpty(par, lhs, lhsQuoted) : TOK_FALSE;
- goto done;
+ /* Unknown operator, compare against an empty string or 0. */
+ t = ToToken(doEval && EvalNotEmpty(par, lhs, lhsQuoted));
+ goto done_lhs;
}
CondParser_SkipWhitespace(par);
@@ -653,42 +653,45 @@ CondParser_Comparison(CondParser *par, Boolean doEval)
if (par->p[0] == '\0') {
Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator");
/* The PARSE_FATAL is done as a follow-up by CondEvalExpression. */
- goto done;
+ goto done_lhs;
}
- rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhsFree);
+ rhs = CondParser_String(par, doEval, FALSE, &rhsQuoted, &rhs_freeIt);
if (rhs == NULL)
- goto done;
+ goto done_rhs;
if (!doEval) {
t = TOK_FALSE;
- goto done;
+ goto done_rhs;
}
t = EvalCompare(lhs, lhsQuoted, op, rhs, rhsQuoted);
-done:
- free(lhsFree);
- free(rhsFree);
+done_rhs:
+ free(rhs_freeIt);
+done_lhs:
+ free(lhs_freeIt);
return t;
}
+/* The argument to empty() is a variable name, optionally followed by
+ * variable modifiers. */
static size_t
-ParseEmptyArg(const char **linePtr, Boolean doEval,
- const char *func MAKE_ATTR_UNUSED, char **argPtr)
+ParseEmptyArg(const char **pp, Boolean doEval,
+ const char *func MAKE_ATTR_UNUSED, char **out_arg)
{
void *val_freeIt;
const char *val;
size_t magic_res;
/* We do all the work here and return the result as the length */
- *argPtr = NULL;
+ *out_arg = NULL;
- (*linePtr)--; /* Make (*linePtr)[1] point to the '('. */
- (void)Var_Parse(linePtr, VAR_CMDLINE, doEval ? VARE_WANTRES : 0,
+ (*pp)--; /* Make (*pp)[1] point to the '('. */
+ (void)Var_Parse(pp, VAR_CMDLINE, doEval ? VARE_WANTRES : VARE_NONE,
&val, &val_freeIt);
/* TODO: handle errors */
- /* If successful, *linePtr points beyond the closing ')' now. */
+ /* If successful, *pp points beyond the closing ')' now. */
if (val == var_Error) {
free(val_freeIt);
@@ -714,54 +717,71 @@ FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED)
return arglen == 1;
}
-static Token
-CondParser_Func(CondParser *par, Boolean doEval)
+static Boolean
+CondParser_Func(CondParser *par, Boolean doEval, Token *out_token)
{
static const struct fn_def {
const char *fn_name;
size_t fn_name_len;
size_t (*fn_parse)(const char **, Boolean, const char *, char **);
Boolean (*fn_eval)(size_t, const char *);
- } fn_defs[] = {
+ } fns[] = {
{ "defined", 7, ParseFuncArg, FuncDefined },
{ "make", 4, ParseFuncArg, FuncMake },
{ "exists", 6, ParseFuncArg, FuncExists },
{ "empty", 5, ParseEmptyArg, FuncEmpty },
{ "target", 6, ParseFuncArg, FuncTarget },
- { "commands", 8, ParseFuncArg, FuncCommands },
- { NULL, 0, NULL, NULL },
+ { "commands", 8, ParseFuncArg, FuncCommands }
};
- const struct fn_def *fn_def;
- Token t;
+ const struct fn_def *fn;
char *arg = NULL;
size_t arglen;
const char *cp = par->p;
- const char *cp1;
+ const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0];
- for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
- if (!is_token(cp, fn_def->fn_name, fn_def->fn_name_len))
+ for (fn = fns; fn != fns_end; fn++) {
+ if (!is_token(cp, fn->fn_name, fn->fn_name_len))
continue;
- cp += fn_def->fn_name_len;
- /* There can only be whitespace before the '(' */
+
+ cp += fn->fn_name_len;
cpp_skip_whitespace(&cp);
if (*cp != '(')
break;
- arglen = fn_def->fn_parse(&cp, doEval, fn_def->fn_name, &arg);
+ arglen = fn->fn_parse(&cp, doEval, fn->fn_name, &arg);
if (arglen == 0 || arglen == (size_t)-1) {
par->p = cp;
- return arglen == 0 ? TOK_FALSE : TOK_ERROR;
+ *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR;
+ return TRUE;
}
+
/* Evaluate the argument using the required function. */
- t = !doEval || fn_def->fn_eval(arglen, arg);
+ *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg));
free(arg);
par->p = cp;
- return t;
+ return TRUE;
}
+ return FALSE;
+}
+
+/* Parse a function call, a number, a variable expression or a string
+ * literal. */
+static Token
+CondParser_LeafToken(CondParser *par, Boolean doEval)
+{
+ Token t;
+ char *arg = NULL;
+ size_t arglen;
+ const char *cp = par->p;
+ const char *cp1;
+
+ if (CondParser_Func(par, doEval, &t))
+ return t;
+
/* Push anything numeric through the compare expression */
cp = par->p;
- if (ch_isdigit(cp[0]) || strchr("+-", cp[0]))
+ if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+')
return CondParser_Comparison(par, doEval);
/*
@@ -785,7 +805,7 @@ CondParser_Func(CondParser *par, Boolean doEval)
* after .if must have been taken literally, so the argument cannot
* be empty - even if it contained a variable expansion.
*/
- t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot;
+ t = ToToken(!doEval || If_Eval(par->if_info, arg, arglen));
free(arg);
return t;
}
@@ -802,9 +822,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return t;
}
- while (par->p[0] == ' ' || par->p[0] == '\t') {
- par->p++;
- }
+ cpp_skip_hspace(&par->p);
switch (par->p[0]) {
@@ -818,15 +836,23 @@ CondParser_Token(CondParser *par, Boolean doEval)
case '|':
par->p++;
- if (par->p[0] == '|') {
+ if (par->p[0] == '|')
par->p++;
+ else if (opts.lint) {
+ Parse_Error(PARSE_FATAL, "Unknown operator '|'");
+ par->printedError = TRUE;
+ return TOK_ERROR;
}
return TOK_OR;
case '&':
par->p++;
- if (par->p[0] == '&') {
+ if (par->p[0] == '&')
par->p++;
+ else if (opts.lint) {
+ Parse_Error(PARSE_FATAL, "Unknown operator '&'");
+ par->printedError = TRUE;
+ return TOK_ERROR;
}
return TOK_AND;
@@ -834,8 +860,9 @@ CondParser_Token(CondParser *par, Boolean doEval)
par->p++;
return TOK_NOT;
- case '#':
- case '\n':
+ case '#': /* XXX: see unit-tests/cond-token-plain.mk */
+ case '\n': /* XXX: why should this end the condition? */
+ /* Probably obsolete now, from 1993-03-21. */
case '\0':
return TOK_EOF;
@@ -844,7 +871,7 @@ CondParser_Token(CondParser *par, Boolean doEval)
return CondParser_Comparison(par, doEval);
default:
- return CondParser_Func(par, doEval);
+ return CondParser_LeafToken(par, doEval);
}
}
@@ -1003,25 +1030,14 @@ static CondEvalResult
CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
Boolean eprint, Boolean strictLHS)
{
- static const struct If *dflt_info;
CondParser par;
- int rval;
+ CondEvalResult rval;
lhsStrict = strictLHS;
- while (*cond == ' ' || *cond == '\t')
- cond++;
-
- if (info == NULL && (info = dflt_info) == NULL) {
- /* Scan for the entry for .if - it can't be first */
- for (info = ifs;; info++)
- if (info->form[0] == 0)
- break;
- dflt_info = info;
- }
- assert(info != NULL);
+ cpp_skip_hspace(&cond);
- par.if_info = info;
+ par.if_info = info != NULL ? info : ifs + PLAIN_IF_INDEX;
par.p = cond;
par.curr = TOK_NONE;
par.printedError = FALSE;
@@ -1034,123 +1050,154 @@ CondEvalExpression(const struct If *info, const char *cond, Boolean *value,
return rval;
}
+/* Evaluate a condition in a :? modifier, such as
+ * ${"${VAR}" == value:?yes:no}. */
CondEvalResult
Cond_EvalCondition(const char *cond, Boolean *out_value)
{
return CondEvalExpression(NULL, cond, out_value, FALSE, FALSE);
}
-/* Evaluate the conditional in the passed line. The line looks like this:
- * .<cond-type> <expr>
- * In this line, <cond-type> is any of if, ifmake, ifnmake, ifdef, ifndef,
- * elif, elifmake, elifnmake, elifdef, elifndef.
- * In this line, <expr> consists of &&, ||, !, function(arg), comparisons
- * and parenthetical groupings thereof.
+/* Evaluate the conditional directive in the line, which is one of:
*
- * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
- * to detect spurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF),
- * otherwise .else could be treated as '.elif 1'.
+ * .if <cond>
+ * .ifmake <cond>
+ * .ifnmake <cond>
+ * .ifdef <cond>
+ * .ifndef <cond>
+ * .elif <cond>
+ * .elifmake <cond>
+ * .elifnmake <cond>
+ * .elifdef <cond>
+ * .elifndef <cond>
+ * .else
+ * .endif
+ *
+ * In these directives, <cond> consists of &&, ||, !, function(arg),
+ * comparisons, expressions, bare words, numbers and strings, and
+ * parenthetical groupings thereof.
*
* Results:
- * COND_PARSE to continue parsing the lines after the conditional
- * (when .if or .else returns TRUE)
+ * COND_PARSE to continue parsing the lines that follow the
+ * conditional (when <cond> evaluates to TRUE)
* COND_SKIP to skip the lines after the conditional
- * (when .if or .elif returns FALSE, or when a previous
+ * (when <cond> evaluates to FALSE, or when a previous
* branch has already been taken)
* COND_INVALID if the conditional was not valid, either because of
* a syntax error or because some variable was undefined
* or because the condition could not be evaluated
*/
CondEvalResult
-Cond_EvalLine(const char *line)
+Cond_EvalLine(const char *const line)
{
- enum { MAXIF = 128 }; /* maximum depth of .if'ing */
- enum { MAXIF_BUMP = 32 }; /* how much to grow by */
- enum if_states {
- IF_ACTIVE, /* .if or .elif part active */
- ELSE_ACTIVE, /* .else part active */
- SEARCH_FOR_ELIF, /* searching for .elif/else to execute */
- SKIP_TO_ELSE, /* has been true, but not seen '.else' */
- SKIP_TO_ENDIF /* nothing else to execute */
- };
- static enum if_states *cond_state = NULL;
- static unsigned int max_if_depth = MAXIF;
+ typedef enum IfState {
+
+ /* None of the previous <cond> evaluated to TRUE. */
+ IFS_INITIAL = 0,
+
+ /* The previous <cond> evaluated to TRUE.
+ * The lines following this condition are interpreted. */
+ IFS_ACTIVE = 1 << 0,
+
+ /* The previous directive was an '.else'. */
+ IFS_SEEN_ELSE = 1 << 1,
+
+ /* One of the previous <cond> evaluated to TRUE. */
+ IFS_WAS_ACTIVE = 1 << 2
+
+ } IfState;
+
+ static enum IfState *cond_states = NULL;
+ static unsigned int cond_states_cap = 128;
const struct If *ifp;
Boolean isElif;
Boolean value;
- enum if_states state;
+ IfState state;
+ const char *p = line;
- if (!cond_state) {
- cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state));
- cond_state[0] = IF_ACTIVE;
+ if (cond_states == NULL) {
+ cond_states = bmake_malloc(cond_states_cap * sizeof *cond_states);
+ cond_states[0] = IFS_ACTIVE;
}
- /* skip leading character (the '.') and any whitespace */
- for (line++; *line == ' ' || *line == '\t'; line++)
- continue;
-
- /* Find what type of if we're dealing with. */
- if (line[0] == 'e') {
- if (line[1] != 'l') {
- if (!is_token(line + 1, "ndif", 4))
+
+ p++; /* skip the leading '.' */
+ cpp_skip_hspace(&p);
+
+ /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
+ if (p[0] == 'e') {
+ if (p[1] != 'l') {
+ if (!is_token(p + 1, "ndif", 4)) {
+ /* Unknown directive. It might still be a transformation
+ * rule like '.elisp.scm', therefore no error message here. */
return COND_INVALID;
- /* End of conditional section */
+ }
+
+ /* It is an '.endif'. */
+ /* TODO: check for extraneous <cond> */
+
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif");
return COND_PARSE;
}
+
/* Return state for previous conditional */
cond_depth--;
- return cond_state[cond_depth] <= ELSE_ACTIVE
+ return cond_states[cond_depth] & IFS_ACTIVE
? COND_PARSE : COND_SKIP;
}
/* Quite likely this is 'else' or 'elif' */
- line += 2;
- if (is_token(line, "se", 2)) {
- /* It is else... */
+ p += 2;
+ if (is_token(p, "se", 2)) { /* It is an 'else'. */
+
+ if (opts.lint && p[2] != '\0')
+ Parse_Error(PARSE_FATAL,
+ "The .else directive does not take arguments.");
+
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else");
return COND_PARSE;
}
- state = cond_state[cond_depth];
- switch (state) {
- case SEARCH_FOR_ELIF:
- state = ELSE_ACTIVE;
- break;
- case ELSE_ACTIVE:
- case SKIP_TO_ENDIF:
- Parse_Error(PARSE_WARNING, "extra else");
- /* FALLTHROUGH */
- default:
- case IF_ACTIVE:
- case SKIP_TO_ELSE:
- state = SKIP_TO_ENDIF;
- break;
+ state = cond_states[cond_depth];
+ if (state == IFS_INITIAL) {
+ state = IFS_ACTIVE | IFS_SEEN_ELSE;
+ } else {
+ if (state & IFS_SEEN_ELSE)
+ Parse_Error(PARSE_WARNING, "extra else");
+ state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
}
- cond_state[cond_depth] = state;
- return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+ cond_states[cond_depth] = state;
+
+ return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
}
/* Assume for now it is an elif */
isElif = TRUE;
} else
isElif = FALSE;
- if (line[0] != 'i' || line[1] != 'f')
- /* Not an ifxxx or elifxxx line */
- return COND_INVALID;
+ if (p[0] != 'i' || p[1] != 'f') {
+ /* Unknown directive. It might still be a transformation rule like
+ * '.elisp.scm', therefore no error message here. */
+ return COND_INVALID; /* Not an ifxxx or elifxxx line */
+ }
/*
* Figure out what sort of conditional it is -- what its default
* function is, etc. -- by looking in the table of valid "ifs"
*/
- line += 2;
+ p += 2;
for (ifp = ifs;; ifp++) {
- if (ifp->form == NULL)
+ if (ifp->form == NULL) {
+ /* TODO: Add error message about unknown directive,
+ * since there is no other known directive that starts with 'el'
+ * or 'if'.
+ * Example: .elifx 123 */
return COND_INVALID;
- if (is_token(ifp->form, line, ifp->formlen)) {
- line += ifp->formlen;
+ }
+ if (is_token(p, ifp->form, ifp->formlen)) {
+ p += ifp->formlen;
break;
}
}
@@ -1162,51 +1209,51 @@ Cond_EvalLine(const char *line)
Parse_Error(PARSE_FATAL, "if-less elif");
return COND_PARSE;
}
- state = cond_state[cond_depth];
- if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
+ state = cond_states[cond_depth];
+ if (state & IFS_SEEN_ELSE) {
Parse_Error(PARSE_WARNING, "extra elif");
- cond_state[cond_depth] = SKIP_TO_ENDIF;
+ cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
return COND_SKIP;
}
- if (state != SEARCH_FOR_ELIF) {
- /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
- cond_state[cond_depth] = SKIP_TO_ELSE;
+ if (state != IFS_INITIAL) {
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
}
} else {
/* Normal .if */
- if (cond_depth + 1 >= max_if_depth) {
+ if (cond_depth + 1 >= cond_states_cap) {
/*
* This is rare, but not impossible.
* In meta mode, dirdeps.mk (only runs at level 0)
* can need more than the default.
*/
- max_if_depth += MAXIF_BUMP;
- cond_state = bmake_realloc(cond_state,
- max_if_depth * sizeof(*cond_state));
+ cond_states_cap += 32;
+ cond_states = bmake_realloc(cond_states,
+ cond_states_cap * sizeof *cond_states);
}
- state = cond_state[cond_depth];
+ state = cond_states[cond_depth];
cond_depth++;
- if (state > ELSE_ACTIVE) {
+ if (!(state & IFS_ACTIVE)) {
/* If we aren't parsing the data, treat as always false */
- cond_state[cond_depth] = SKIP_TO_ELSE;
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
}
}
/* And evaluate the conditional expression */
- if (CondEvalExpression(ifp, line, &value, TRUE, TRUE) == COND_INVALID) {
+ if (CondEvalExpression(ifp, p, &value, TRUE, TRUE) == COND_INVALID) {
/* Syntax error in conditional, error message already output. */
/* Skip everything to matching .endif */
- cond_state[cond_depth] = SKIP_TO_ELSE;
+ /* XXX: An extra '.else' is not detected in this case. */
+ cond_states[cond_depth] = IFS_WAS_ACTIVE;
return COND_SKIP;
}
if (!value) {
- cond_state[cond_depth] = SEARCH_FOR_ELIF;
+ cond_states[cond_depth] = IFS_INITIAL;
return COND_SKIP;
}
- cond_state[cond_depth] = IF_ACTIVE;
+ cond_states[cond_depth] = IFS_ACTIVE;
return COND_PARSE;
}
diff --git a/configure b/configure
index 814157f4e75f..bf729ee239ae 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for bmake 20201018.
+# Generated by GNU Autoconf 2.69 for bmake 20201112.
#
# Report bugs to <sjg@NetBSD.org>.
#
@@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='bmake'
PACKAGE_TARNAME='bmake'
-PACKAGE_VERSION='20201018'
-PACKAGE_STRING='bmake 20201018'
+PACKAGE_VERSION='20201112'
+PACKAGE_STRING='bmake 20201112'
PACKAGE_BUGREPORT='sjg@NetBSD.org'
PACKAGE_URL=''
@@ -631,6 +631,7 @@ GCC
INSTALL
default_sys_path
mksrc
+force_machine_arch
machine_arch
force_machine
machine
@@ -1254,7 +1255,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures bmake 20201018 to adapt to many kinds of systems.
+\`configure' configures bmake 20201112 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1315,7 +1316,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of bmake 20201018:";;
+ short | recursive ) echo "Configuration of bmake 20201112:";;
esac
cat <<\_ACEOF
@@ -1331,9 +1332,9 @@ Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-defshell=SHELL use SHELL by default - must be sh compatible, use sh or ksh to pick the internal definitions
- --without-makefile disable use of generated makefile
- --without-meta disable use of meta-mode
- --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev
+ --without-makefile disable use of generated makefile
+ --without-meta disable use of meta-mode
+ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev
--with-machine=MACHINE explicitly set MACHINE
--with-force-machine=MACHINE set FORCE_MACHINE
--with-force-machine-arch=MACHINE set FORCE_MACHINE_ARCH
@@ -1421,7 +1422,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-bmake configure 20201018
+bmake configure 20201112
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2001,7 +2002,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by bmake $as_me 20201018, which was
+It was created by bmake $as_me 20201112, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -6045,7 +6046,7 @@ if test "${with_force_machine_arch+set}" = set; then :
withval=$with_force_machine_arch; case "${withval}" in
yes) force_machine_arch=FORCE_;;
no) ;;
-*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;;
+*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac
fi
@@ -6059,7 +6060,7 @@ no) ;;
esac
fi
-echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6
+echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6
default_sys_path=\${prefix}/share/mk
# Check whether --with-default-sys-path was given.
@@ -6151,6 +6152,7 @@ fi
+
bm_outfiles="Makefile.config unit-tests/Makefile.config make-bootstrap.sh"
if test $use_makefile = yes; then
bm_outfiles="makefile $bm_outfiles"
@@ -6664,7 +6666,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by bmake $as_me 20201018, which was
+This file was extended by bmake $as_me 20201112, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -6726,7 +6728,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-bmake config.status 20201018
+bmake config.status 20201112
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.in b/configure.in
index 384c403e544a..c52546068198 100644
--- a/configure.in
+++ b/configure.in
@@ -1,11 +1,11 @@
dnl
dnl RCSid:
-dnl $Id: configure.in,v 1.67 2020/10/19 19:47:50 sjg Exp $
+dnl $Id: configure.in,v 1.69 2020/11/14 07:40:43 sjg Exp $
dnl
dnl Process this file with autoconf to produce a configure script
dnl
AC_PREREQ(2.50)
-AC_INIT([bmake], [20201018], [sjg@NetBSD.org])
+AC_INIT([bmake], [20201112], [sjg@NetBSD.org])
AC_CONFIG_HEADERS(config.h)
dnl make srcdir absolute
@@ -38,7 +38,7 @@ CYGWIN*|MINGW*) use_makefile=no;;
*) use_makefile=yes;;
esac
AC_ARG_WITH(makefile,
-[ --without-makefile disable use of generated makefile],
+[ --without-makefile disable use of generated makefile],
[case "${withval}" in
yes|no) use_makefile=${withval};;
*) AC_MSG_ERROR(bad value ${withval} given for makefile) ;;
@@ -46,14 +46,14 @@ esac])
dnl
use_meta=yes
AC_ARG_WITH(meta,
-[ --without-meta disable use of meta-mode],
+[ --without-meta disable use of meta-mode],
[case "${withval}" in
yes|no) use_meta=${withval};;
*) AC_MSG_ERROR(bad value ${withval} given for meta) ;;
esac])
dnl
AC_ARG_WITH(filemon,
-[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev],
+[ --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev],
[ case "/${withval}" in
/no) use_filemon=no;;
/*trace) filemon_h=no use_filemon="${withval}";;
@@ -308,7 +308,7 @@ AC_ARG_WITH(force_machine_arch,
[case "${withval}" in
yes) force_machine_arch=FORCE_;;
no) ;;
-*) force_machine_arch=FORCE_; machine_arch=$with_force_machine;;
+*) force_machine_arch=FORCE_; machine_arch=$with_force_machine_arch;;
esac])
dnl
AC_ARG_WITH(machine_arch,
@@ -321,7 +321,7 @@ esac])
dnl
dnl Tell them what we ended up with
dnl
-echo "Using: ${force_machine}MACHINE=$machine, MACHINE_ARCH=$machine_arch" 1>&6
+echo "Using: ${force_machine}MACHINE=$machine, ${force_machine_arch}MACHINE_ARCH=$machine_arch" 1>&6
dnl
dnl Allow folk to control _PATH_DEFSYSPATH
dnl
@@ -407,6 +407,7 @@ dnl
AC_SUBST(machine)
AC_SUBST(force_machine)
AC_SUBST(machine_arch)
+AC_SUBST(force_machine_arch)
AC_SUBST(mksrc)
AC_SUBST(default_sys_path)
AC_SUBST(INSTALL)
diff --git a/dir.c b/dir.c
index d1778d127b29..359e61adb669 100644
--- a/dir.c
+++ b/dir.c
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $ */
+/* $NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -100,9 +100,9 @@
* then all the directories above it in turn until
* the path is found or we reach the root ("/").
*
- * Dir_MTime Return the modification time of a node. The file
- * is searched for along the default search path.
- * The path and mtime fields of the node are filled in.
+ * Dir_UpdateMTime
+ * Update the modification time and path of a node with
+ * data from the file corresponding to the node.
*
* Dir_AddDir Add a directory to a search path.
*
@@ -134,7 +134,7 @@
#include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
+MAKE_RCSID("$NetBSD: dir.c,v 1.210 2020/11/14 21:29:44 rillig Exp $");
#define DIR_DEBUG0(text) DEBUG0(DIR, text)
#define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1)
@@ -168,9 +168,9 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* the process too much, it could severely affect the amount of
* parallelism available as each directory open would take another file
* descriptor out of play for handling I/O for another job. Given that
- * it is only recently that UNIX OS's have taken to allowing more than
- * 20 or 32 file descriptors for a process, this doesn't seem acceptable
- * to me.
+ * it is only recently (as of 1993 or earlier) that UNIX OS's have taken
+ * to allowing more than 20 or 32 file descriptors for a process, this
+ * doesn't seem acceptable to me.
*
* 3) record the mtime of the directory in the CachedDir structure and
* verify the directory hasn't changed since the contents were cached.
@@ -184,11 +184,11 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* resort to using stat in its place.
*
* An additional thing to consider is that pmake is used primarily to create
- * C programs and until recently pcc-based compilers refused to allow you to
- * specify where the resulting object file should be placed. This forced all
- * objects to be created in the current directory. This isn't meant as a full
- * excuse, just an explanation of some of the reasons for the caching used
- * here.
+ * C programs and until recently (as of 1993 or earlier) pcc-based compilers
+ * refused to allow you to specify where the resulting object file should be
+ * placed. This forced all objects to be created in the current directory.
+ * This isn't meant as a full excuse, just an explanation of some of the
+ * reasons for the caching used here.
*
* One more note: the location of a target's file is only performed on the
* downward traversal of the graph and then only for terminal nodes in the
@@ -204,7 +204,7 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.193 2020/10/31 17:39:20 rillig Exp $");
* Given that an access() is essentially a stat() without the copyout() call,
* and that the same filesystem overhead would have to be incurred in
* Dir_MTime, it made sense to replace the access() with a stat() and record
- * the mtime in a cache for when Dir_MTime was actually called.
+ * the mtime in a cache for when Dir_UpdateMTime was actually called.
*/
typedef List CachedDirList;
@@ -253,12 +253,10 @@ OpenDirs_Find(OpenDirs *odirs, const char *name)
static void
OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir)
{
- HashEntry *he = HashTable_FindEntry(&odirs->table, cdir->name);
- if (he != NULL)
+ if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL)
return;
- he = HashTable_CreateEntry(&odirs->table, cdir->name, NULL);
Lst_Append(odirs->list, cdir);
- HashEntry_Set(he, odirs->list->last);
+ HashTable_Set(&odirs->table, cdir->name, odirs->list->last);
}
static void
@@ -273,10 +271,10 @@ OpenDirs_Remove(OpenDirs *odirs, const char *name)
Lst_Remove(odirs->list, ln);
}
-static OpenDirs openDirs; /* the list of all open directories */
+static OpenDirs openDirs; /* all cached directories */
/*
- * Variables for gathering statistics on the efficiency of the cashing
+ * Variables for gathering statistics on the efficiency of the caching
* mechanism.
*/
static int hits; /* Found in directory cache */
@@ -300,74 +298,50 @@ static HashTable mtimes;
static HashTable lmtimes; /* same as mtimes but for lstat */
-/*
- * We use stat(2) a lot, cache the results.
- * mtime and mode are all we care about.
- */
-struct cache_st {
- time_t lmtime; /* lstat */
- time_t mtime; /* stat */
- mode_t mode;
-};
-
-/* minimize changes below */
typedef enum CachedStatsFlags {
- CST_LSTAT = 0x01, /* call lstat(2) instead of stat(2) */
- CST_UPDATE = 0x02 /* ignore existing cached entry */
+ CST_NONE = 0,
+ CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */
+ CST_UPDATE = 1 << 1 /* ignore existing cached entry */
} CachedStatsFlags;
-/* Returns 0 and the result of stat(2) or lstat(2) in *mst, or -1 on error. */
+/* Returns 0 and the result of stat(2) or lstat(2) in *out_cst,
+ * or -1 on error. */
static int
-cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst,
+cached_stats(const char *pathname, struct cached_stat *out_cst,
CachedStatsFlags flags)
{
- HashEntry *entry;
+ HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes;
struct stat sys_st;
- struct cache_st *cst;
+ struct cached_stat *cst;
int rc;
- if (!pathname || !pathname[0])
- return -1;
+ if (pathname == NULL || pathname[0] == '\0')
+ return -1; /* This can happen in meta mode. */
- entry = HashTable_FindEntry(htp, pathname);
-
- if (entry && !(flags & CST_UPDATE)) {
- cst = HashEntry_Get(entry);
-
- mst->mst_mode = cst->mode;
- mst->mst_mtime = (flags & CST_LSTAT) ? cst->lmtime : cst->mtime;
- if (mst->mst_mtime) {
- DIR_DEBUG2("Using cached time %s for %s\n",
- Targ_FmtTime(mst->mst_mtime), pathname);
- return 0;
- }
+ cst = HashTable_FindValue(tbl, pathname);
+ if (cst != NULL && !(flags & CST_UPDATE)) {
+ *out_cst = *cst;
+ DIR_DEBUG2("Using cached time %s for %s\n",
+ Targ_FmtTime(cst->cst_mtime), pathname);
+ return 0;
}
- rc = (flags & CST_LSTAT)
- ? lstat(pathname, &sys_st)
- : stat(pathname, &sys_st);
+ rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st);
if (rc == -1)
- return -1;
+ return -1; /* don't cache negative lookups */
if (sys_st.st_mtime == 0)
sys_st.st_mtime = 1; /* avoid confusion with missing file */
- mst->mst_mode = sys_st.st_mode;
- mst->mst_mtime = sys_st.st_mtime;
-
- if (entry == NULL)
- entry = HashTable_CreateEntry(htp, pathname, NULL);
- if (HashEntry_Get(entry) == NULL) {
- HashEntry_Set(entry, bmake_malloc(sizeof(*cst)));
- memset(HashEntry_Get(entry), 0, sizeof(*cst));
- }
- cst = HashEntry_Get(entry);
- if (flags & CST_LSTAT) {
- cst->lmtime = sys_st.st_mtime;
- } else {
- cst->mtime = sys_st.st_mtime;
+ if (cst == NULL) {
+ cst = bmake_malloc(sizeof *cst);
+ HashTable_Set(tbl, pathname, cst);
}
- cst->mode = sys_st.st_mode;
+
+ cst->cst_mtime = sys_st.st_mtime;
+ cst->cst_mode = sys_st.st_mode;
+
+ *out_cst = *cst;
DIR_DEBUG2(" Caching %s for %s\n",
Targ_FmtTime(sys_st.st_mtime), pathname);
@@ -375,15 +349,15 @@ cached_stats(HashTable *htp, const char *pathname, struct make_stat *mst,
}
int
-cached_stat(const char *pathname, struct make_stat *st)
+cached_stat(const char *pathname, struct cached_stat *cst)
{
- return cached_stats(&mtimes, pathname, st, 0);
+ return cached_stats(pathname, cst, CST_NONE);
}
int
-cached_lstat(const char *pathname, struct make_stat *st)
+cached_lstat(const char *pathname, struct cached_stat *cst)
{
- return cached_stats(&lmtimes, pathname, st, CST_LSTAT);
+ return cached_stats(pathname, cst, CST_LSTAT);
}
/* Initialize the directories module. */
@@ -401,7 +375,7 @@ Dir_InitDir(const char *cdname)
{
Dir_InitCur(cdname);
- dotLast = bmake_malloc(sizeof(CachedDir));
+ dotLast = bmake_malloc(sizeof *dotLast);
dotLast->refCount = 1;
dotLast->hits = 0;
dotLast->name = bmake_strdup(".DOTLAST");
@@ -416,23 +390,31 @@ Dir_InitCur(const char *cdname)
{
CachedDir *dir;
- if (cdname != NULL) {
+ if (cdname == NULL)
+ return;
+
+ /*
+ * Our build directory is not the same as our source directory.
+ * Keep this one around too.
+ */
+ dir = Dir_AddDir(NULL, cdname);
+ if (dir == NULL)
+ return;
+
+ /* XXX: Reference counting is wrong here.
+ * If this function is called repeatedly with the same directory name,
+ * its reference count increases each time even though the number of
+ * actual references stays the same. */
+
+ dir->refCount++;
+ if (cur != NULL && cur != dir) {
/*
- * Our build directory is not the same as our source directory.
- * Keep this one around too.
+ * We've been here before, clean up.
*/
- if ((dir = Dir_AddDir(NULL, cdname))) {
- dir->refCount++;
- if (cur && cur != dir) {
- /*
- * We've been here before, clean up.
- */
- cur->refCount--;
- Dir_Destroy(cur);
- }
- cur = dir;
- }
+ cur->refCount--;
+ Dir_Destroy(cur);
}
+ cur = dir;
}
/* (Re)initialize "dot" (current/object directory) path hash.
@@ -588,6 +570,9 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
Boolean isDot = dirName[0] == '.' && dirName[1] == '\0';
HashIter hi;
+ /* XXX: Iterating over all hash entries is inefficient. If the pattern
+ * is a plain string without any wildcards, a direct lookup is faster. */
+
HashIter_Init(&hi, &dir->files);
while (HashIter_Next(&hi) != NULL) {
const char *base = hi.entry->key;
@@ -879,13 +864,13 @@ DirLookup(CachedDir *dir, const char *base)
static char *
DirLookupSubdir(CachedDir *dir, const char *name)
{
- struct make_stat mst;
+ struct cached_stat cst;
char *file = dir == dot ? bmake_strdup(name)
: str_concat3(dir->name, "/", name);
DIR_DEBUG1("checking %s ...\n", file);
- if (cached_stat(file, &mst) == 0) {
+ if (cached_stat(file, &cst) == 0) {
nearmisses++;
return file;
}
@@ -974,7 +959,7 @@ Dir_FindFile(const char *name, SearchPath *path)
const char *base; /* Terminal name of file */
Boolean hasLastDot = FALSE; /* true if we should search dot last */
Boolean hasSlash; /* true if 'name' contains a / */
- struct make_stat mst; /* Buffer for stat, if necessary */
+ struct cached_stat cst; /* Buffer for stat, if necessary */
const char *trailing_dot = ".";
/*
@@ -1176,7 +1161,7 @@ Dir_FindFile(const char *name, SearchPath *path)
* When searching for $(FILE), we will find it in $(INSTALLDIR)
* b/c we added it here. This is not good...
*/
-#ifdef notdef
+#if 0
if (base == trailing_dot) {
base = strrchr(name, '/');
base++;
@@ -1198,17 +1183,17 @@ Dir_FindFile(const char *name, SearchPath *path)
} else {
return NULL;
}
-#else /* !notdef */
+#else
DIR_DEBUG1(" Looking for \"%s\" ...\n", name);
bigmisses++;
- if (cached_stat(name, &mst) == 0) {
+ if (cached_stat(name, &cst) == 0) {
return bmake_strdup(name);
}
DIR_DEBUG0(" failed. Returning NULL\n");
return NULL;
-#endif /* notdef */
+#endif
}
@@ -1225,7 +1210,7 @@ Dir_FindFile(const char *name, SearchPath *path)
char *
Dir_FindHereOrAbove(const char *here, const char *search_path)
{
- struct make_stat mst;
+ struct cached_stat cst;
char *dirbase, *dirbase_end;
char *try, *try_end;
@@ -1238,12 +1223,12 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
/* try and stat(2) it ... */
try = str_concat3(dirbase, "/", search_path);
- if (cached_stat(try, &mst) != -1) {
+ if (cached_stat(try, &cst) != -1) {
/*
* success! if we found a file, chop off
* the filename so we return a directory.
*/
- if ((mst.mst_mode & S_IFMT) != S_IFDIR) {
+ if ((cst.cst_mode & S_IFMT) != S_IFDIR) {
try_end = try + strlen(try);
while (try_end > try && *try_end != '/')
try_end--;
@@ -1275,36 +1260,27 @@ Dir_FindHereOrAbove(const char *here, const char *search_path)
return NULL;
}
-/*-
- *-----------------------------------------------------------------------
- * Dir_MTime --
- * Find the modification time of the file described by gn along the
- * search path dirSearchPath.
+/* Search gn along dirSearchPath and store its modification time in gn->mtime.
+ * If no file is found, store 0 instead.
*
- * Input:
- * gn the file whose modification time is desired
- *
- * Results:
- * The modification time or 0 if it doesn't exist
- *
- * Side Effects:
- * The modification time is placed in the node's mtime slot.
- * If the node didn't have a path entry before, and Dir_FindFile
- * found one for it, the full name is placed in the path slot.
- *-----------------------------------------------------------------------
- */
-time_t
-Dir_MTime(GNode *gn, Boolean recheck)
+ * The found file is stored in gn->path, unless the node already had a path. */
+void
+Dir_UpdateMTime(GNode *gn, Boolean recheck)
{
- char *fullName; /* the full pathname of name */
- struct make_stat mst; /* buffer for finding the mod time */
+ char *fullName;
+ struct cached_stat cst;
if (gn->type & OP_ARCHV) {
- return Arch_MTime(gn);
- } else if (gn->type & OP_PHONY) {
+ Arch_UpdateMTime(gn);
+ return;
+ }
+
+ if (gn->type & OP_PHONY) {
gn->mtime = 0;
- return 0;
- } else if (gn->path == NULL) {
+ return;
+ }
+
+ if (gn->path == NULL) {
if (gn->type & OP_NOPATH)
fullName = NULL;
else {
@@ -1344,25 +1320,24 @@ Dir_MTime(GNode *gn, Boolean recheck)
fullName = gn->path;
}
- if (fullName == NULL) {
+ if (fullName == NULL)
fullName = bmake_strdup(gn->name);
- }
- if (cached_stats(&mtimes, fullName, &mst, recheck ? CST_UPDATE : 0) < 0) {
+ if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) {
if (gn->type & OP_MEMBER) {
if (fullName != gn->path)
free(fullName);
- return Arch_MemMTime(gn);
- } else {
- mst.mst_mtime = 0;
+ Arch_UpdateMemberMTime(gn);
+ return;
}
+
+ cst.cst_mtime = 0;
}
if (fullName != NULL && gn->path == NULL)
gn->path = fullName;
- gn->mtime = mst.mst_mtime;
- return gn->mtime;
+ gn->mtime = cst.cst_mtime;
}
/* Read the list of filenames in the directory and store the result
@@ -1387,6 +1362,7 @@ Dir_AddDir(SearchPath *path, const char *name)
if (path != NULL && strcmp(name, ".DOTLAST") == 0) {
SearchPathNode *ln;
+ /* XXX: Linear search gets slow with thousands of entries. */
for (ln = path->first; ln != NULL; ln = ln->next) {
CachedDir *pathDir = ln->datum;
if (strcmp(pathDir->name, name) == 0)
@@ -1410,7 +1386,7 @@ Dir_AddDir(SearchPath *path, const char *name)
DIR_DEBUG1("Caching %s ...", name);
if ((d = opendir(name)) != NULL) {
- dir = bmake_malloc(sizeof(CachedDir));
+ dir = bmake_malloc(sizeof *dir);
dir->name = bmake_strdup(name);
dir->hits = 0;
dir->refCount = 1;
@@ -1480,7 +1456,7 @@ Dir_MakeFlags(const char *flag, SearchPath *path)
Buffer buf;
SearchPathNode *ln;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
if (path != NULL) {
for (ln = path->first; ln != NULL; ln = ln->next) {
diff --git a/dir.h b/dir.h
index 886b26e18b47..d0badcd1dec7 100644
--- a/dir.h
+++ b/dir.h
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.h,v 1.32 2020/10/25 10:00:20 rillig Exp $ */
+/* $NetBSD: dir.h,v 1.34 2020/11/14 19:24:24 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -101,7 +101,7 @@ Boolean Dir_HasWildcards(const char *);
void Dir_Expand(const char *, SearchPath *, StringList *);
char *Dir_FindFile(const char *, SearchPath *);
char *Dir_FindHereOrAbove(const char *, const char *);
-time_t Dir_MTime(GNode *, Boolean);
+void Dir_UpdateMTime(GNode *, Boolean);
CachedDir *Dir_AddDir(SearchPath *, const char *);
char *Dir_MakeFlags(const char *, SearchPath *);
void Dir_ClearPath(SearchPath *);
@@ -112,12 +112,12 @@ void Dir_Destroy(void *);
SearchPath *Dir_CopyDirSearchPath(void);
/* Stripped-down variant of struct stat. */
-struct make_stat {
- time_t mst_mtime;
- mode_t mst_mode;
+struct cached_stat {
+ time_t cst_mtime;
+ mode_t cst_mode;
};
-int cached_lstat(const char *, struct make_stat *);
-int cached_stat(const char *, struct make_stat *);
+int cached_lstat(const char *, struct cached_stat *);
+int cached_stat(const char *, struct cached_stat *);
#endif /* MAKE_DIR_H */
diff --git a/filemon/filemon_dev.c b/filemon/filemon_dev.c
index 85e56689f10d..afedb3c57e58 100644
--- a/filemon/filemon_dev.c
+++ b/filemon/filemon_dev.c
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon_dev.c,v 1.3 2020/07/10 15:53:30 sjg Exp $ */
+/* $NetBSD: filemon_dev.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
/*-
* Copyright (c) 2020 The NetBSD Foundation, Inc.
@@ -65,7 +65,7 @@ filemon_open(void)
int error;
/* Allocate and zero a struct filemon object. */
- F = calloc(1, sizeof(*F));
+ F = calloc(1, sizeof *F);
if (F == NULL)
return NULL;
diff --git a/filemon/filemon_ktrace.c b/filemon/filemon_ktrace.c
index 4d2a5450c093..857e72e78028 100644
--- a/filemon/filemon_ktrace.c
+++ b/filemon/filemon_ktrace.c
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon_ktrace.c,v 1.3 2020/10/18 11:54:43 rillig Exp $ */
+/* $NetBSD: filemon_ktrace.c,v 1.4 2020/11/05 17:27:16 rillig Exp $ */
/*-
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -198,7 +198,7 @@ filemon_open(void)
int error;
/* Allocate and zero a struct filemon object. */
- F = calloc(1, sizeof(*F));
+ F = calloc(1, sizeof *F);
if (F == NULL)
return NULL;
diff --git a/for.c b/for.c
index e46bdf460702..8730ab5c157d 100644
--- a/for.c
+++ b/for.c
@@ -1,4 +1,4 @@
-/* $NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $ */
+/* $NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $ */
/*
* Copyright (c) 1992, The Regents of the University of California.
@@ -60,15 +60,7 @@
#include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: for.c,v 1.112 2020/10/31 18:41:07 rillig Exp $");
-
-/* The .for loop substitutes the items as ${:U<value>...}, which means
- * that characters that break this syntax must be backslash-escaped. */
-typedef enum ForEscapes {
- FOR_SUB_ESCAPE_CHAR = 0x0001,
- FOR_SUB_ESCAPE_BRACE = 0x0002,
- FOR_SUB_ESCAPE_PAREN = 0x0004
-} ForEscapes;
+MAKE_RCSID("$NetBSD: for.c,v 1.115 2020/11/07 21:04:43 rillig Exp $");
static int forLevel = 0; /* Nesting level */
@@ -120,30 +112,6 @@ For_Free(For *f)
free(f);
}
-static ForEscapes
-GetEscapes(const char *word)
-{
- const char *p;
- ForEscapes escapes = 0;
-
- for (p = word; *p != '\0'; p++) {
- switch (*p) {
- case ':':
- case '$':
- case '\\':
- escapes |= FOR_SUB_ESCAPE_CHAR;
- break;
- case ')':
- escapes |= FOR_SUB_ESCAPE_PAREN;
- break;
- case '}':
- escapes |= FOR_SUB_ESCAPE_BRACE;
- break;
- }
- }
- return escapes;
-}
-
static Boolean
IsFor(const char *p)
{
@@ -191,11 +159,11 @@ For_Eval(const char *line)
*/
f = bmake_malloc(sizeof *f);
- Buf_Init(&f->body, 0);
+ Buf_Init(&f->body);
Vector_Init(&f->vars, sizeof(ForVar));
f->items.words = NULL;
f->items.freeIt = NULL;
- Buf_Init(&f->curBody, 0);
+ Buf_Init(&f->curBody);
f->short_var = FALSE;
f->sub_next = 0;
@@ -302,7 +270,7 @@ for_var_len(const char *var)
size_t len;
var_start = *var;
- if (var_start == 0)
+ if (var_start == '\0')
/* just escape the $ */
return 0;
@@ -315,7 +283,7 @@ for_var_len(const char *var)
return 1;
depth = 1;
- for (len = 1; (ch = var[len++]) != 0;) {
+ for (len = 1; (ch = var[len++]) != '\0';) {
if (ch == var_start)
depth++;
else if (ch == var_end && --depth == 0)
@@ -326,18 +294,30 @@ for_var_len(const char *var)
return 0;
}
+/* The .for loop substitutes the items as ${:U<value>...}, which means
+ * that characters that break this syntax must be backslash-escaped. */
+static Boolean
+NeedsEscapes(const char *word, char endc)
+{
+ const char *p;
+
+ for (p = word; *p != '\0'; p++) {
+ if (*p == ':' || *p == '$' || *p == '\\' || *p == endc)
+ return TRUE;
+ }
+ return FALSE;
+}
+
/* While expanding the body of a .for loop, write the item in the ${:U...}
- * expression, escaping characters as needed. See ApplyModifier_Defined. */
+ * expression, escaping characters as needed.
+ *
+ * The result is later unescaped by ApplyModifier_Defined. */
static void
Buf_AddEscaped(Buffer *cmds, const char *item, char ech)
{
- ForEscapes escapes = GetEscapes(item);
char ch;
- /* If there were no escapes, or the only escape is the other variable
- * terminator, then just substitute the full string */
- if (!(escapes & (ech == ')' ? ~(unsigned)FOR_SUB_ESCAPE_BRACE
- : ~(unsigned)FOR_SUB_ESCAPE_PAREN))) {
+ if (!NeedsEscapes(item, ech)) {
Buf_AddStr(cmds, item);
return;
}
diff --git a/hash.c b/hash.c
index 2a75fb32b50e..a1e3ad3b45b4 100644
--- a/hash.c
+++ b/hash.c
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $ */
+/* $NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -74,7 +74,7 @@
#include "make.h"
/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: hash.c,v 1.55 2020/10/25 19:28:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: hash.c,v 1.57 2020/11/14 21:29:44 rillig Exp $");
/*
* The ratio of # entries to # buckets at which we rebuild the table to
@@ -128,7 +128,7 @@ void
HashTable_Init(HashTable *t)
{
unsigned int n = 16, i;
- HashEntry **buckets = bmake_malloc(sizeof(*buckets) * n);
+ HashEntry **buckets = bmake_malloc(sizeof *buckets * n);
for (i = 0; i < n; i++)
buckets[i] = NULL;
@@ -195,7 +195,7 @@ HashTable_Enlarge(HashTable *t)
HashEntry **oldBuckets = t->buckets;
unsigned int newSize = 2 * oldSize;
unsigned int newMask = newSize - 1;
- HashEntry **newBuckets = bmake_malloc(sizeof(*newBuckets) * newSize);
+ HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize);
size_t i;
for (i = 0; i < newSize; i++)
@@ -239,7 +239,7 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
if (t->numEntries >= rebuildLimit * t->bucketsSize)
HashTable_Enlarge(t);
- he = bmake_malloc(sizeof(*he) + keylen);
+ he = bmake_malloc(sizeof *he + keylen);
he->value = NULL;
he->key_hash = h;
memcpy(he->key, key, keylen + 1);
@@ -253,6 +253,14 @@ HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew)
return he;
}
+HashEntry *
+HashTable_Set(HashTable *t, const char *key, void *value)
+{
+ HashEntry *he = HashTable_CreateEntry(t, key, NULL);
+ HashEntry_Set(he, value);
+ return he;
+}
+
/* Delete the entry from the table and free the associated memory. */
void
HashTable_DeleteEntry(HashTable *t, HashEntry *he)
diff --git a/hash.h b/hash.h
index a05a9f4f4732..30a8485bbd2e 100644
--- a/hash.h
+++ b/hash.h
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.h,v 1.31 2020/10/25 19:19:07 rillig Exp $ */
+/* $NetBSD: hash.h,v 1.33 2020/11/14 21:29:44 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -103,13 +103,13 @@ typedef struct HashIter {
HashEntry *entry; /* Next entry to check in current bucket. */
} HashIter;
-static inline MAKE_ATTR_UNUSED void *
+MAKE_INLINE void *
HashEntry_Get(HashEntry *h)
{
return h->value;
}
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE void
HashEntry_Set(HashEntry *h, void *datum)
{
h->value = datum;
@@ -122,6 +122,7 @@ void *HashTable_FindValue(HashTable *, const char *);
unsigned int Hash_Hash(const char *);
void *HashTable_FindValueHash(HashTable *, const char *, unsigned int);
HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *);
+HashEntry *HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *);
diff --git a/job.c b/job.c
index b3f68209dd59..ff0f1bcb8f4c 100644
--- a/job.c
+++ b/job.c
@@ -1,4 +1,4 @@
-/* $NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $ */
+/* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -156,7 +156,7 @@
#include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $");
+MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $");
/* A shell defines how the commands are run. All commands for a target are
* written into a single file, which is then given to the shell to execute
@@ -182,7 +182,7 @@ MAKE_RCSID("$NetBSD: job.c,v 1.302 2020/11/01 18:45:49 rillig Exp $");
* status. Finally errExit is a printf template for running the command and
* causing the shell to exit on error. If any of these strings are empty when
* hasErrCtl is FALSE, the command will be executed anyway as is, and if it
- * causes an error, so be it. Any templates setup to echo the command will
+ * causes an error, so be it. Any templates set up to echo the command will
* escape any '$ ` \ "' characters in the command string to avoid common
* problems with echo "%s\n" as a template.
*
@@ -388,8 +388,8 @@ static char *shellArgv = NULL; /* Custom shell args */
static Job *job_table; /* The structures that describe them */
static Job *job_table_end; /* job_table + maxJobs */
static unsigned int wantToken; /* we want a token */
-static int lurking_children = 0;
-static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */
+static Boolean lurking_children = FALSE;
+static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */
/*
* Set of descriptors of pipes connected to
@@ -443,7 +443,7 @@ job_table_dump(const char *where)
debug_printf("job table @ %s\n", where);
for (job = job_table; job < job_table_end; job++) {
debug_printf("job %d, status %d, flags %d, pid %d\n",
- (int)(job - job_table), job->job_state, job->flags, job->pid);
+ (int)(job - job_table), job->status, job->flags, job->pid);
}
}
@@ -539,7 +539,7 @@ JobCondPassSig(int signo)
DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo);
for (job = job_table; job < job_table_end; job++) {
- if (job->job_state != JOB_ST_RUNNING)
+ if (job->status != JOB_ST_RUNNING)
continue;
DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n",
signo, job->pid);
@@ -596,7 +596,7 @@ JobPassSig_suspend(int signo)
struct sigaction act;
/* Suppress job started/continued messages */
- make_suspended = 1;
+ make_suspended = TRUE;
/* Pass the signal onto every job */
JobCondPassSig(signo);
@@ -646,12 +646,12 @@ JobPassSig_suspend(int signo)
}
static Job *
-JobFindPid(int pid, JobState status, Boolean isJobs)
+JobFindPid(int pid, JobStatus status, Boolean isJobs)
{
Job *job;
for (job = job_table; job < job_table_end; job++) {
- if ((job->job_state == status) && job->pid == pid)
+ if (job->status == status && job->pid == pid)
return job;
}
if (DEBUG(JOB) && isJobs)
@@ -705,6 +705,22 @@ EscapeShellDblQuot(const char *cmd)
return esc;
}
+static void
+JobPrintf(Job *job, const char *fmt, const char *arg)
+{
+ if (DEBUG(JOB))
+ debug_printf(fmt, arg);
+
+ (void)fprintf(job->cmdFILE, fmt, arg);
+ (void)fflush(job->cmdFILE);
+}
+
+static void
+JobPrintln(Job *job, const char *line)
+{
+ JobPrintf(job, "%s\n", line);
+}
+
/*-
*-----------------------------------------------------------------------
* JobPrintCommand --
@@ -717,7 +733,7 @@ EscapeShellDblQuot(const char *cmd)
* If the command is just "..." we take all future commands for this
* job to be commands to be executed once the entire graph has been
* made and return non-zero to signal that the end of the commands
- * was reached. These commands are later attached to the postCommands
+ * was reached. These commands are later attached to the .END
* node and executed by Job_End when all things are done.
*
* Side Effects:
@@ -746,12 +762,6 @@ JobPrintCommand(Job *job, char *cmd)
noSpecials = !GNode_ShouldExecute(job->node);
-#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \
- debug_printf(fmt, arg); \
- } \
- (void)fprintf(job->cmdFILE, fmt, arg); \
- (void)fflush(job->cmdFILE);
-
numCommands++;
Var_Subst(cmd, job->node, VARE_WANTRES, &cmd);
@@ -783,8 +793,8 @@ JobPrintCommand(Job *job, char *cmd)
if (shutUp) {
if (!(job->flags & JOB_SILENT) && !noSpecials &&
- commandShell->hasEchoCtl) {
- DBPRINTF("%s\n", commandShell->echoOff);
+ (commandShell->hasEchoCtl)) {
+ JobPrintln(job, commandShell->echoOff);
} else {
if (commandShell->hasErrCtl)
shutUp = FALSE;
@@ -803,16 +813,15 @@ JobPrintCommand(Job *job, char *cmd)
* it already is?
*/
if (!(job->flags & JOB_SILENT) && !shutUp &&
- commandShell->hasEchoCtl) {
- DBPRINTF("%s\n", commandShell->echoOff);
- DBPRINTF("%s\n", commandShell->errOffOrExecIgnore);
- DBPRINTF("%s\n", commandShell->echoOn);
+ (commandShell->hasEchoCtl)) {
+ JobPrintln(job, commandShell->echoOff);
+ JobPrintln(job, commandShell->errOffOrExecIgnore);
+ JobPrintln(job, commandShell->echoOn);
} else {
- DBPRINTF("%s\n", commandShell->errOffOrExecIgnore);
+ JobPrintln(job, commandShell->errOffOrExecIgnore);
}
} else if (commandShell->errOffOrExecIgnore &&
- commandShell->errOffOrExecIgnore[0] != '\0')
- {
+ commandShell->errOffOrExecIgnore[0] != '\0') {
/*
* The shell has no error control, so we need to be
* weird to get it to ignore any errors from the command.
@@ -824,15 +833,14 @@ JobPrintCommand(Job *job, char *cmd)
*/
job->flags |= JOB_IGNERR;
if (!(job->flags & JOB_SILENT) && !shutUp) {
- if (commandShell->hasEchoCtl) {
- DBPRINTF("%s\n", commandShell->echoOff);
- }
- DBPRINTF(commandShell->errOnOrEcho, escCmd);
- shutUp = TRUE;
+ if (commandShell->hasEchoCtl) {
+ JobPrintln(job, commandShell->echoOff);
+ }
+ JobPrintf(job, commandShell->errOnOrEcho, escCmd);
+ shutUp = TRUE;
} else {
- if (!shutUp) {
- DBPRINTF(commandShell->errOnOrEcho, escCmd);
- }
+ if (!shutUp)
+ JobPrintf(job, commandShell->errOnOrEcho, escCmd);
}
cmdTemplate = commandShell->errOffOrExecIgnore;
/*
@@ -851,36 +859,35 @@ JobPrintCommand(Job *job, char *cmd)
/*
* If errors are being checked and the shell doesn't have error control
- * but does supply an errExit template, then setup commands to run
+ * but does supply an errExit template, then set up commands to run
* through it.
*/
if (!commandShell->hasErrCtl && commandShell->errExit &&
commandShell->errExit[0] != '\0') {
- if (!(job->flags & JOB_SILENT) && !shutUp) {
- if (commandShell->hasEchoCtl) {
- DBPRINTF("%s\n", commandShell->echoOff);
- }
- DBPRINTF(commandShell->errOnOrEcho, escCmd);
- shutUp = TRUE;
- }
- /* If it's a comment line or blank, treat as an ignored error */
- if ((escCmd[0] == commandShell->commentChar) ||
- (escCmd[0] == 0))
- cmdTemplate = commandShell->errOffOrExecIgnore;
- else
- cmdTemplate = commandShell->errExit;
- errOff = FALSE;
+ if (!(job->flags & JOB_SILENT) && !shutUp) {
+ if (commandShell->hasEchoCtl)
+ JobPrintln(job, commandShell->echoOff);
+ JobPrintf(job, commandShell->errOnOrEcho, escCmd);
+ shutUp = TRUE;
+ }
+ /* If it's a comment line or blank, treat as an ignored error */
+ if (escCmd[0] == commandShell->commentChar ||
+ (escCmd[0] == '\0'))
+ cmdTemplate = commandShell->errOffOrExecIgnore;
+ else
+ cmdTemplate = commandShell->errExit;
+ errOff = FALSE;
}
}
if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
- (job->flags & JOB_TRACED) == 0) {
- DBPRINTF("set -%s\n", "x");
- job->flags |= JOB_TRACED;
+ !(job->flags & JOB_TRACED)) {
+ JobPrintln(job, "set -x");
+ job->flags |= JOB_TRACED;
}
- DBPRINTF(cmdTemplate, cmd);
+ JobPrintf(job, cmdTemplate, cmd);
free(cmdStart);
free(escCmd);
if (errOff) {
@@ -889,15 +896,14 @@ JobPrintCommand(Job *job, char *cmd)
* echoOff command. Otherwise we issue it and pretend it was on
* for the whole command...
*/
- if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
- DBPRINTF("%s\n", commandShell->echoOff);
+ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) {
+ JobPrintln(job, commandShell->echoOff);
shutUp = TRUE;
}
- DBPRINTF("%s\n", commandShell->errOnOrEcho);
- }
- if (shutUp && commandShell->hasEchoCtl) {
- DBPRINTF("%s\n", commandShell->echoOn);
+ JobPrintln(job, commandShell->errOnOrEcho);
}
+ if (shutUp && commandShell->hasEchoCtl)
+ JobPrintln(job, commandShell->echoOn);
}
/* Print all commands to the shell file that is later executed.
@@ -914,12 +920,11 @@ JobPrintCommands(Job *job)
if (strcmp(cmd, "...") == 0) {
job->node->type |= OP_SAVE_CMDS;
- if ((job->flags & JOB_IGNDOTS) == 0) {
- job->tailCmds = ln->next;
- break;
- }
- } else
- JobPrintCommand(job, ln->datum);
+ job->tailCmds = ln->next;
+ break;
+ }
+
+ JobPrintCommand(job, ln->datum);
}
}
@@ -944,7 +949,7 @@ JobSaveCommands(Job *job)
/* Called to close both input and output pipes when a job is finished. */
static void
-JobClose(Job *job)
+JobClosePipes(Job *job)
{
clearfd(job);
(void)close(job->outPipe);
@@ -955,28 +960,17 @@ JobClose(Job *job)
job->inPipe = -1;
}
-/*-
- *-----------------------------------------------------------------------
- * JobFinish --
- * Do final processing for the given job including updating
- * parents and starting new jobs as available/necessary. Note
- * that we pay no attention to the JOB_IGNERR flag here.
- * This is because when we're called because of a noexecute flag
- * or something, jstat.w_status is 0 and when called from
- * Job_CatchChildren, the status is zeroed if it s/b ignored.
+/* Do final processing for the given job including updating parent nodes and
+ * starting new jobs as available/necessary.
+ *
+ * Deferred commands for the job are placed on the .END node.
+ *
+ * If there was a serious error (errors != 0; not an ignored one), no more
+ * jobs will be started.
*
* Input:
* job job to finish
* status sub-why job went away
- *
- * Side Effects:
- * Final commands for the job are placed on postCommands.
- *
- * If we got an error and are aborting (aborting == ABORT_ERROR) and
- * the job list is now empty, we are done for the day.
- * If we recognized an error (errors !=0), we set the aborting flag
- * to ABORT_ERROR so no more jobs will be started.
- *-----------------------------------------------------------------------
*/
static void
JobFinish (Job *job, WAIT_T status)
@@ -987,7 +981,7 @@ JobFinish (Job *job, WAIT_T status)
job->pid, job->node->name, status);
if ((WIFEXITED(status) &&
- (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) ||
+ ((WEXITSTATUS(status) != 0 && !(job->flags & JOB_IGNERR)))) ||
WIFSIGNALED(status))
{
/*
@@ -998,7 +992,7 @@ JobFinish (Job *job, WAIT_T status)
* cases, finish out the job's output before printing the exit
* status...
*/
- JobClose(job);
+ JobClosePipes(job);
if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
(void)fclose(job->cmdFILE);
job->cmdFILE = NULL;
@@ -1007,19 +1001,11 @@ JobFinish (Job *job, WAIT_T status)
} else if (WIFEXITED(status)) {
/*
* Deal with ignored errors in -B mode. We need to print a message
- * telling of the ignored error as well as setting status.w_status
- * to 0 so the next command gets run. To do this, we set done to be
- * TRUE if in -B mode and the job exited non-zero.
+ * telling of the ignored error as well as to run the next command.
+ *
*/
done = WEXITSTATUS(status) != 0;
- /*
- * Old comment said: "Note we don't
- * want to close down any of the streams until we know we're at the
- * end."
- * But we do. Otherwise when are we going to print the rest of the
- * stuff?
- */
- JobClose(job);
+ JobClosePipes(job);
} else {
/*
* No need to close things down or anything.
@@ -1041,7 +1027,7 @@ JobFinish (Job *job, WAIT_T status)
meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
}
#endif
- if (!dieQuietly(job->node, -1))
+ if (!shouldDieQuietly(job->node, -1))
(void)printf("*** [%s] Error code %d%s\n",
job->node->name,
WEXITSTATUS(status),
@@ -1078,11 +1064,9 @@ JobFinish (Job *job, WAIT_T status)
#ifdef USE_META
if (useMeta) {
- int x;
-
- if ((x = meta_job_finish(job)) != 0 && status == 0) {
- status = x;
- }
+ int meta_status = meta_job_finish(job);
+ if (meta_status != 0 && status == 0)
+ status = meta_status;
}
#endif
@@ -1090,13 +1074,12 @@ JobFinish (Job *job, WAIT_T status)
Trace_Log(JOBEND, job);
if (!(job->flags & JOB_SPECIAL)) {
- if ((WAIT_STATUS(status) != 0) ||
- (aborting == ABORT_ERROR) ||
- (aborting == ABORT_INTERRUPT))
+ if (WAIT_STATUS(status) != 0 ||
+ (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT)
return_job_token = TRUE;
}
- if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) &&
+ if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT &&
(WAIT_STATUS(status) == 0)) {
/*
* As long as we aren't aborting and the job didn't return a non-zero
@@ -1108,33 +1091,50 @@ JobFinish (Job *job, WAIT_T status)
if (!(job->flags & JOB_SPECIAL))
return_job_token = TRUE;
Make_Update(job->node);
- job->job_state = JOB_ST_FREE;
+ job->status = JOB_ST_FREE;
} else if (WAIT_STATUS(status)) {
errors++;
- job->job_state = JOB_ST_FREE;
+ job->status = JOB_ST_FREE;
}
- /*
- * Set aborting if any error.
- */
- if (errors && !opts.keepgoing && (aborting != ABORT_INTERRUPT)) {
- /*
- * If we found any errors in this batch of children and the -k flag
- * wasn't given, we set the aborting flag so no more jobs get
- * started.
- */
- aborting = ABORT_ERROR;
- }
+ if (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT)
+ aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */
if (return_job_token)
Job_TokenReturn();
- if (aborting == ABORT_ERROR && jobTokensRunning == 0) {
- /*
- * If we are aborting and the job table is now empty, we finish.
- */
+ if (aborting == ABORT_ERROR && jobTokensRunning == 0)
Finish(errors);
+}
+
+static void
+TouchRegular(GNode *gn)
+{
+ const char *file = GNode_Path(gn);
+ struct utimbuf times = { now, now };
+ int fd;
+ char c;
+
+ if (utime(file, &times) >= 0)
+ return;
+
+ fd = open(file, O_RDWR | O_CREAT, 0666);
+ if (fd < 0) {
+ (void)fprintf(stderr, "*** couldn't touch %s: %s\n",
+ file, strerror(errno));
+ (void)fflush(stderr);
+ return; /* XXX: What about propagating the error? */
}
+
+ /* Last resort: update the file's time stamps in the traditional way.
+ * XXX: This doesn't work for empty files, which are sometimes used
+ * as marker files. */
+ if (read(fd, &c, 1) == 1) {
+ (void)lseek(fd, 0, SEEK_SET);
+ while (write(fd, &c, 1) == -1 && errno == EAGAIN)
+ continue;
+ }
+ (void)close(fd); /* XXX: What about propagating the error? */
}
/* Touch the given target. Called by JobStart when the -t flag was given.
@@ -1144,15 +1144,9 @@ JobFinish (Job *job, WAIT_T status)
void
Job_Touch(GNode *gn, Boolean silent)
{
- int streamID; /* ID of stream opened to do the touch */
- struct utimbuf times; /* Times for utime() call */
-
if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL|
OP_SPECIAL|OP_PHONY)) {
- /*
- * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
- * and, as such, shouldn't really be created.
- */
+ /* These are "virtual" targets and should not really be created. */
return;
}
@@ -1161,42 +1155,20 @@ Job_Touch(GNode *gn, Boolean silent)
(void)fflush(stdout);
}
- if (!GNode_ShouldExecute(gn)) {
+ if (!GNode_ShouldExecute(gn))
return;
- }
if (gn->type & OP_ARCHV) {
Arch_Touch(gn);
- } else if (gn->type & OP_LIB) {
- Arch_TouchLib(gn);
- } else {
- const char *file = GNode_Path(gn);
-
- times.actime = times.modtime = now;
- if (utime(file, &times) < 0){
- streamID = open(file, O_RDWR | O_CREAT, 0666);
-
- if (streamID >= 0) {
- char c;
-
- /*
- * Read and write a byte to the file to change the
- * modification time, then close the file.
- */
- if (read(streamID, &c, 1) == 1) {
- (void)lseek(streamID, (off_t)0, SEEK_SET);
- while (write(streamID, &c, 1) == -1 && errno == EAGAIN)
- continue;
- }
+ return;
+ }
- (void)close(streamID);
- } else {
- (void)fprintf(stdout, "*** couldn't touch %s: %s",
- file, strerror(errno));
- (void)fflush(stdout);
- }
- }
+ if (gn->type & OP_LIB) {
+ Arch_TouchLib(gn);
+ return;
}
+
+ TouchRegular(gn);
}
/* Make sure the given node has all the commands it needs.
@@ -1223,25 +1195,25 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
/*
* No commands. Look for .DEFAULT rule from which we might infer
- * commands
+ * commands.
*/
- if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) &&
- (gn->type & OP_SPECIAL) == 0) {
+ if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) &&
+ !(gn->type & OP_SPECIAL)) {
/*
- * Make only looks for a .DEFAULT if the node was never the
- * target of an operator, so that's what we do too. If
- * a .DEFAULT was given, we substitute its commands for gn's
- * commands and set the IMPSRC variable to be the target's name
- * The DEFAULT node acts like a transformation rule, in that
+ * The traditional Make only looks for a .DEFAULT if the node was
+ * never the target of an operator, so that's what we do too.
+ *
+ * The .DEFAULT node acts like a transformation rule, in that
* gn also inherits any attributes or sources attached to
* .DEFAULT itself.
*/
- Make_HandleUse(DEFAULT, gn);
+ Make_HandleUse(defaultNode, gn);
Var_Set(IMPSRC, GNode_VarTarget(gn), gn);
return TRUE;
}
- if (Dir_MTime(gn, 0) != 0 || (gn->type & OP_SPECIAL))
+ Dir_UpdateMTime(gn, FALSE);
+ if (gn->mtime != 0 || (gn->type & OP_SPECIAL))
return TRUE;
/*
@@ -1280,21 +1252,19 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
/* Execute the shell for the given job.
*
- * A shell is executed, its output is altered and the Job structure added
- * to the job table.
- */
+ * See Job_CatchOutput for handling the output of the shell. */
static void
JobExec(Job *job, char **argv)
{
int cpid; /* ID of new child */
- sigset_t mask;
+ sigset_t mask;
job->flags &= ~JOB_TRACED;
if (DEBUG(JOB)) {
int i;
- debug_printf("Running %s %sly\n", job->node->name, "local");
+ debug_printf("Running %s\n", job->node->name);
debug_printf("\tCommand: ");
for (i = 0; argv[i] != NULL; i++) {
debug_printf("%s ", argv[i]);
@@ -1317,7 +1287,7 @@ JobExec(Job *job, char **argv)
JobSigLock(&mask);
/* Pre-emptively mark job running, pid still zero though */
- job->job_state = JOB_ST_RUNNING;
+ job->status = JOB_ST_RUNNING;
cpid = vFork();
if (cpid == -1)
@@ -1351,17 +1321,17 @@ JobExec(Job *job, char **argv)
execDie("dup2", "job->cmdFILE");
if (fcntl(0, F_SETFD, 0) == -1)
execDie("fcntl clear close-on-exec", "stdin");
- if (lseek(0, (off_t)0, SEEK_SET) == -1)
+ if (lseek(0, 0, SEEK_SET) == -1)
execDie("lseek to 0", "stdin");
if (job->node->type & (OP_MAKE | OP_SUBMAKE)) {
- /*
- * Pass job token pipe to submakes.
- */
- if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1)
- execDie("clear close-on-exec", "tokenWaitJob.inPipe");
- if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1)
- execDie("clear close-on-exec", "tokenWaitJob.outPipe");
+ /*
+ * Pass job token pipe to submakes.
+ */
+ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1)
+ execDie("clear close-on-exec", "tokenWaitJob.inPipe");
+ if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1)
+ execDie("clear close-on-exec", "tokenWaitJob.outPipe");
}
/*
@@ -1458,7 +1428,7 @@ JobMakeArgv(Job *job, char **argv)
* Bourne shell thinks its second argument is a file to source.
* Grrrr. Note the ten-character limitation on the combined arguments.
*/
- (void)snprintf(args, sizeof(args), "-%s%s",
+ (void)snprintf(args, sizeof args, "-%s%s",
((job->flags & JOB_IGNERR) ? "" :
(commandShell->exit ? commandShell->exit : "")),
((job->flags & JOB_SILENT) ? "" :
@@ -1490,7 +1460,6 @@ JobMakeArgv(Job *job, char **argv)
* Input:
* gn target to create
* flags flags for the job to override normal ones.
- * e.g. JOB_SPECIAL or JOB_IGNDOTS
* previous The previous Job structure for this node, if any.
*
* Results:
@@ -1502,13 +1471,11 @@ JobMakeArgv(Job *job, char **argv)
* A new Job node is created and added to the list of running
* jobs. PMake is forked and a child shell created.
*
- * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set
- * JOB_IGNDOTS is never set (dsl)
- * Also the return value is ignored by everyone.
+ * NB: The return value is ignored by everyone.
*-----------------------------------------------------------------------
*/
static JobStartResult
-JobStart(GNode *gn, int flags)
+JobStart(GNode *gn, JobFlags flags)
{
Job *job; /* new job descriptor */
char *argv[10]; /* Argument vector to shell */
@@ -1517,33 +1484,24 @@ JobStart(GNode *gn, int flags)
int tfd; /* File descriptor to the temp file */
for (job = job_table; job < job_table_end; job++) {
- if (job->job_state == JOB_ST_FREE)
+ if (job->status == JOB_ST_FREE)
break;
}
if (job >= job_table_end)
Punt("JobStart no job slots vacant");
memset(job, 0, sizeof *job);
- job->job_state = JOB_ST_SETUP;
- if (gn->type & OP_SPECIAL)
- flags |= JOB_SPECIAL;
-
job->node = gn;
job->tailCmds = NULL;
+ job->status = JOB_ST_SET_UP;
- /*
- * Set the initial value of the flags for this job based on the global
- * ones and the node's attributes... Any flags supplied by the caller
- * are also added to the field.
- */
- job->flags = 0;
- if (Targ_Ignore(gn)) {
- job->flags |= JOB_IGNERR;
- }
- if (Targ_Silent(gn)) {
- job->flags |= JOB_SILENT;
- }
- job->flags |= flags;
+ if (gn->type & OP_SPECIAL)
+ flags |= JOB_SPECIAL;
+ if (Targ_Ignore(gn))
+ flags |= JOB_IGNERR;
+ if (Targ_Silent(gn))
+ flags |= JOB_SILENT;
+ job->flags = flags;
/*
* Check the commands now so any attributes from .DEFAULT have a chance
@@ -1559,7 +1517,7 @@ JobStart(GNode *gn, int flags)
* we just set the file to be stdout. Cute, huh?
*/
if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) ||
- (!opts.noExecute && !opts.touchFlag)) {
+ (!opts.noExecute && !opts.touchFlag)) {
/*
* tfile is the name of a file into which all shell commands are
* put. It is removed before the child shell is executed, unless
@@ -1579,13 +1537,13 @@ JobStart(GNode *gn, int flags)
JobSigLock(&mask);
tfd = mkTempFile(TMPPAT, &tfile);
if (!DEBUG(SCRIPT))
- (void)eunlink(tfile);
+ (void)eunlink(tfile);
JobSigUnlock(&mask);
job->cmdFILE = fdopen(tfd, "w+");
- if (job->cmdFILE == NULL) {
+ if (job->cmdFILE == NULL)
Punt("Could not fdopen %s", tfile);
- }
+
(void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC);
/*
* Send the commands to the command file, flush all its buffers then
@@ -1596,9 +1554,8 @@ JobStart(GNode *gn, int flags)
#ifdef USE_META
if (useMeta) {
meta_job_start(job, gn);
- if (Targ_Silent(gn)) { /* might have changed */
+ if (Targ_Silent(gn)) /* might have changed */
job->flags |= JOB_SILENT;
- }
}
#endif
/*
@@ -1645,7 +1602,7 @@ JobStart(GNode *gn, int flags)
* up the graph.
*/
job->cmdFILE = stdout;
- Job_Touch(gn, job->flags&JOB_SILENT);
+ Job_Touch(gn, job->flags & JOB_SILENT);
noExec = TRUE;
}
/* Just in case it isn't already... */
@@ -1660,11 +1617,9 @@ JobStart(GNode *gn, int flags)
/*
* Unlink and close the command file if we opened one
*/
- if (job->cmdFILE != stdout) {
- if (job->cmdFILE != NULL) {
- (void)fclose(job->cmdFILE);
- job->cmdFILE = NULL;
- }
+ if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+ (void)fclose(job->cmdFILE);
+ job->cmdFILE = NULL;
}
/*
@@ -1676,7 +1631,7 @@ JobStart(GNode *gn, int flags)
job->node->made = MADE;
Make_Update(job->node);
}
- job->job_state = JOB_ST_FREE;
+ job->status = JOB_ST_FREE;
return cmdsOK ? JOB_FINISHED : JOB_ERROR;
}
@@ -1693,71 +1648,58 @@ JobStart(GNode *gn, int flags)
return JOB_RUNNING;
}
+/* Print the output of the shell command, skipping the noPrint command of
+ * the shell, if any. */
static char *
JobOutput(Job *job, char *cp, char *endp)
{
char *ecp;
- if (commandShell->noPrint && commandShell->noPrint[0] != '\0') {
- while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) {
- if (cp != ecp) {
- *ecp = '\0';
- /*
- * The only way there wouldn't be a newline after
- * this line is if it were the last in the buffer.
- * however, since the non-printable comes after it,
- * there must be a newline, so we don't print one.
- */
- (void)fprintf(stdout, "%s", cp);
- (void)fflush(stdout);
- }
- cp = ecp + commandShell->noPrintLen;
- if (cp != endp) {
- /*
- * Still more to print, look again after skipping
- * the whitespace following the non-printable
- * command....
- */
- cp++;
- while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
- cp++;
- }
- } else {
- return cp;
- }
+ if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0')
+ return cp;
+
+ while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) {
+ if (ecp != cp) {
+ *ecp = '\0';
+ /*
+ * The only way there wouldn't be a newline after
+ * this line is if it were the last in the buffer.
+ * however, since the non-printable comes after it,
+ * there must be a newline, so we don't print one.
+ */
+ (void)fprintf(stdout, "%s", cp);
+ (void)fflush(stdout);
+ }
+ cp = ecp + commandShell->noPrintLen;
+ if (cp != endp) {
+ /*
+ * Still more to print, look again after skipping
+ * the whitespace following the non-printable
+ * command....
+ */
+ cp++;
+ pp_skip_whitespace(&cp);
+ } else {
+ return cp;
}
}
return cp;
}
-/*-
- *-----------------------------------------------------------------------
- * JobDoOutput --
- * This function is called at different times depending on
- * whether the user has specified that output is to be collected
- * via pipes or temporary files. In the former case, we are called
- * whenever there is something to read on the pipe. We collect more
- * output from the given job and store it in the job's outBuf. If
- * this makes up a line, we print it tagged by the job's identifier,
- * as necessary.
- * If output has been collected in a temporary file, we open the
- * file and read it line by line, transferring it to our own
- * output channel until the file is empty. At which point we
- * remove the temporary file.
- * In both cases, however, we keep our figurative eye out for the
- * 'noPrint' line for the shell from which the output came. If
- * we recognize a line, we don't print it. If the command is not
- * alone on the line (the character after it is not \0 or \n), we
- * do print whatever follows it.
+/*
+ * This function is called whenever there is something to read on the pipe.
+ * We collect more output from the given job and store it in the job's
+ * outBuf. If this makes up a line, we print it tagged by the job's
+ * identifier, as necessary.
+ *
+ * In the output of the shell, the 'noPrint' lines are removed. If the
+ * command is not alone on the line (the character after it is not \0 or
+ * \n), we do print whatever follows it.
*
* Input:
* job the job whose output needs printing
* finish TRUE if this is the last time we'll be called
* for this job
- *
- * Side Effects:
- * curPos may be shifted as may the contents of outBuf.
- *-----------------------------------------------------------------------
*/
static void
JobDoOutput(Job *job, Boolean finish)
@@ -1772,12 +1714,12 @@ JobDoOutput(Job *job, Boolean finish)
/*
* Read as many bytes as will fit in the buffer.
*/
-end_loop:
+again:
gotNL = FALSE;
fbuf = FALSE;
nRead = read(job->inPipe, &job->outBuf[job->curPos],
- JOB_BUFSIZE - job->curPos);
+ JOB_BUFSIZE - job->curPos);
if (nRead < 0) {
if (errno == EAGAIN)
return;
@@ -1795,7 +1737,7 @@ end_loop:
* output remaining in the buffer.
* Also clear the 'finish' flag so we stop looping.
*/
- if ((nr == 0) && (job->curPos != 0)) {
+ if (nr == 0 && job->curPos != 0) {
job->outBuf[job->curPos] = '\n';
nr = 1;
finish = FALSE;
@@ -1890,7 +1832,7 @@ end_loop:
* we do get an EOF, finish will be set FALSE and we'll fall
* through and out.
*/
- goto end_loop;
+ goto again;
}
}
@@ -1988,13 +1930,13 @@ JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs)
(void)printf("*** [%s] Stopped -- signal %d\n",
job->node->name, WSTOPSIG(status));
}
- job->job_suspended = 1;
+ job->suspended = TRUE;
}
(void)fflush(stdout);
return;
}
- job->job_state = JOB_ST_FINISHED;
+ job->status = JOB_ST_FINISHED;
job->exit_status = WAIT_STATUS(status);
JobFinish(job, status);
@@ -2038,7 +1980,7 @@ Job_CatchOutput(void)
default:
abort();
}
- --nready;
+ nready--;
}
Job_CatchChildren();
@@ -2049,7 +1991,7 @@ Job_CatchOutput(void)
if (!fds[i].revents)
continue;
job = jobfds[i];
- if (job->job_state == JOB_ST_RUNNING)
+ if (job->status == JOB_ST_RUNNING)
JobDoOutput(job, FALSE);
#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV)
/*
@@ -2073,7 +2015,7 @@ Job_CatchOutput(void)
void
Job_Make(GNode *gn)
{
- (void)JobStart(gn, 0);
+ (void)JobStart(gn, JOB_NONE);
}
void
@@ -2094,7 +2036,7 @@ Shell_Init(void)
#endif
shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName);
}
- Var_Set_with_flags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY);
+ Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY);
if (commandShell->exit == NULL) {
commandShell->exit = "";
}
@@ -2172,7 +2114,7 @@ Job_Init(void)
if (rval > 0)
continue;
if (rval == 0)
- lurking_children = 1;
+ lurking_children = TRUE;
break;
}
@@ -2181,9 +2123,9 @@ Job_Init(void)
JobCreatePipe(&childExitJob, 3);
/* Preallocate enough for the maximum number of jobs. */
- fds = bmake_malloc(sizeof(*fds) *
+ fds = bmake_malloc(sizeof *fds *
(npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
- jobfds = bmake_malloc(sizeof(*jobfds) *
+ jobfds = bmake_malloc(sizeof *jobfds *
(npseudojobs + (size_t)opts.maxJobs) * nfds_per_job());
/* These are permanent entries and take slots 0 and 1 */
@@ -2328,7 +2270,7 @@ Job_ParseShell(char *line)
free(shellArgv);
- memset(&newShell, 0, sizeof(newShell));
+ memset(&newShell, 0, sizeof newShell);
/*
* Parse the specification by keyword
@@ -2440,7 +2382,7 @@ Job_ParseShell(char *line)
}
commandShell = sh;
} else {
- commandShell = bmake_malloc(sizeof(Shell));
+ commandShell = bmake_malloc(sizeof *commandShell);
*commandShell = newShell;
}
/* this will take care of shellErrFlag */
@@ -2491,7 +2433,7 @@ JobInterrupt(int runINTERRUPT, int signo)
JobSigLock(&mask);
for (job = job_table; job < job_table_end; job++) {
- if (job->job_state != JOB_ST_RUNNING)
+ if (job->status != JOB_ST_RUNNING)
continue;
gn = job->node;
@@ -2513,7 +2455,7 @@ JobInterrupt(int runINTERRUPT, int signo)
JobRun(interrupt);
}
}
- Trace_Log(MAKEINTR, 0);
+ Trace_Log(MAKEINTR, NULL);
exit(signo);
}
@@ -2570,7 +2512,7 @@ Job_AbortAll(void)
if (jobTokensRunning) {
for (job = job_table; job < job_table_end; job++) {
- if (job->job_state != JOB_ST_RUNNING)
+ if (job->status != JOB_ST_RUNNING)
continue;
/*
* kill the child process with increasingly drastic signals to make
@@ -2596,23 +2538,23 @@ JobRestartJobs(void)
Job *job;
for (job = job_table; job < job_table_end; job++) {
- if (job->job_state == JOB_ST_RUNNING &&
- (make_suspended || job->job_suspended)) {
+ if (job->status == JOB_ST_RUNNING &&
+ (make_suspended || job->suspended)) {
DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid);
- if (job->job_suspended) {
+ if (job->suspended) {
(void)printf("*** [%s] Continued\n", job->node->name);
(void)fflush(stdout);
}
- job->job_suspended = 0;
+ job->suspended = FALSE;
if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
debug_printf("Failed to send SIGCONT to %d\n", job->pid);
}
}
- if (job->job_state == JOB_ST_FINISHED)
+ if (job->status == JOB_ST_FINISHED)
/* Job exit deferred after calling waitpid() in a signal handler */
JobFinish(job, job->exit_status);
}
- make_suspended = 0;
+ make_suspended = FALSE;
}
static void
@@ -2716,7 +2658,7 @@ Job_ServerStart(int max_tokens, int jp_0, int jp_1)
JobCreatePipe(&tokenWaitJob, 15);
- snprintf(jobarg, sizeof(jobarg), "%d,%d",
+ snprintf(jobarg, sizeof jobarg, "%d,%d",
tokenWaitJob.inPipe, tokenWaitJob.outPipe);
Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
@@ -2784,7 +2726,7 @@ Job_TokenWithdraw(void)
/* And put the stopper back */
while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN)
continue;
- if (dieQuietly(NULL, 1))
+ if (shouldDieQuietly(NULL, 1))
exit(2);
Fatal("A failure has been detected in another branch of the parallel make");
}
@@ -2860,7 +2802,7 @@ emul_poll(struct pollfd *fd, int nfd, int timeout)
tvp = &tv;
}
- nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp);
+ nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp);
if (nselect <= 0)
return nselect;
diff --git a/job.h b/job.h
index a412365de572..d48424858201 100644
--- a/job.h
+++ b/job.h
@@ -1,4 +1,4 @@
-/* $NetBSD: job.h,v 1.58 2020/10/26 21:34:10 rillig Exp $ */
+/* $NetBSD: job.h,v 1.63 2020/11/14 13:27:01 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -117,25 +117,25 @@ struct pollfd;
# include "meta.h"
#endif
-typedef enum JobState {
+typedef enum JobStatus {
JOB_ST_FREE = 0, /* Job is available */
- JOB_ST_SETUP = 1, /* Job is allocated but otherwise invalid */
+ JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */
+ /* XXX: What about the 2? */
JOB_ST_RUNNING = 3, /* Job is running, pid valid */
JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */
-} JobState;
+} JobStatus;
typedef enum JobFlags {
+ JOB_NONE = 0,
/* Ignore non-zero exits */
- JOB_IGNERR = 0x001,
+ JOB_IGNERR = 1 << 0,
/* no output */
- JOB_SILENT = 0x002,
+ JOB_SILENT = 1 << 1,
/* Target is a special one. i.e. run it locally
* if we can't export it and maxLocal is 0 */
- JOB_SPECIAL = 0x004,
- /* Ignore "..." lines when processing commands */
- JOB_IGNDOTS = 0x008,
+ JOB_SPECIAL = 1 << 2,
/* we've sent 'set -x' */
- JOB_TRACED = 0x400
+ JOB_TRACED = 1 << 10
} JobFlags;
/* A Job manages the shell commands that are run to create a single target.
@@ -167,9 +167,9 @@ typedef struct Job {
int exit_status; /* from wait4() in signal handler */
- JobState job_state; /* status of the job entry */
+ JobStatus status;
- char job_suspended;
+ Boolean suspended;
JobFlags flags; /* Flags to control treatment of job */
diff --git a/lst.c b/lst.c
index d8b2d0efd7ea..71a0b41c1077 100644
--- a/lst.c
+++ b/lst.c
@@ -1,4 +1,4 @@
-/* $NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $ */
+/* $NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -34,7 +34,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $");
+MAKE_RCSID("$NetBSD: lst.c,v 1.92 2020/11/08 01:29:26 rillig Exp $");
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
@@ -45,11 +45,11 @@ MAKE_RCSID("$NetBSD: lst.c,v 1.91 2020/10/28 02:43:16 rillig Exp $");
static ListNode *
LstNodeNew(ListNode *prev, ListNode *next, void *datum)
{
- ListNode *node = bmake_malloc(sizeof *node);
- node->prev = prev;
- node->next = next;
- node->datum = datum;
- return node;
+ ListNode *ln = bmake_malloc(sizeof *ln);
+ ln->prev = prev;
+ ln->next = next;
+ ln->datum = datum;
+ return ln;
}
/* Create and initialize a new, empty list. */
@@ -68,12 +68,11 @@ Lst_New(void)
void
Lst_Free(List *list)
{
- ListNode *node;
- ListNode *next;
+ ListNode *ln, *next;
- for (node = list->first; node != NULL; node = next) {
- next = node->next;
- free(node);
+ for (ln = list->first; ln != NULL; ln = next) {
+ next = ln->next;
+ free(ln);
}
free(list);
@@ -84,37 +83,32 @@ Lst_Free(List *list)
void
Lst_Destroy(List *list, LstFreeProc freeProc)
{
- ListNode *node;
- ListNode *next;
+ ListNode *ln, *next;
- for (node = list->first; node != NULL; node = next) {
- next = node->next;
- freeProc(node->datum);
- free(node);
+ for (ln = list->first; ln != NULL; ln = next) {
+ next = ln->next;
+ freeProc(ln->datum);
+ free(ln);
}
free(list);
}
-/*
- * Functions to modify a list
- */
-
/* Insert a new node with the datum before the given node. */
void
-Lst_InsertBefore(List *list, ListNode *node, void *datum)
+Lst_InsertBefore(List *list, ListNode *ln, void *datum)
{
ListNode *newNode;
assert(datum != NULL);
- newNode = LstNodeNew(node->prev, node, datum);
+ newNode = LstNodeNew(ln->prev, ln, datum);
- if (node->prev != NULL)
- node->prev->next = newNode;
- node->prev = newNode;
+ if (ln->prev != NULL)
+ ln->prev->next = newNode;
+ ln->prev = newNode;
- if (node == list->first)
+ if (ln == list->first)
list->first = newNode;
}
@@ -122,18 +116,18 @@ Lst_InsertBefore(List *list, ListNode *node, void *datum)
void
Lst_Prepend(List *list, void *datum)
{
- ListNode *node;
+ ListNode *ln;
assert(datum != NULL);
- node = LstNodeNew(NULL, list->first, datum);
+ ln = LstNodeNew(NULL, list->first, datum);
if (list->first == NULL) {
- list->first = node;
- list->last = node;
+ list->first = ln;
+ list->last = ln;
} else {
- list->first->prev = node;
- list->first = node;
+ list->first->prev = ln;
+ list->first = ln;
}
}
@@ -141,71 +135,69 @@ Lst_Prepend(List *list, void *datum)
void
Lst_Append(List *list, void *datum)
{
- ListNode *node;
+ ListNode *ln;
assert(datum != NULL);
- node = LstNodeNew(list->last, NULL, datum);
+ ln = LstNodeNew(list->last, NULL, datum);
if (list->last == NULL) {
- list->first = node;
- list->last = node;
+ list->first = ln;
+ list->last = ln;
} else {
- list->last->next = node;
- list->last = node;
+ list->last->next = ln;
+ list->last = ln;
}
}
/* Remove the given node from the given list.
* The datum stored in the node must be freed by the caller, if necessary. */
void
-Lst_Remove(List *list, ListNode *node)
+Lst_Remove(List *list, ListNode *ln)
{
/* unlink it from its neighbors */
- if (node->next != NULL)
- node->next->prev = node->prev;
- if (node->prev != NULL)
- node->prev->next = node->next;
+ if (ln->next != NULL)
+ ln->next->prev = ln->prev;
+ if (ln->prev != NULL)
+ ln->prev->next = ln->next;
/* unlink it from the list */
- if (list->first == node)
- list->first = node->next;
- if (list->last == node)
- list->last = node->prev;
+ if (list->first == ln)
+ list->first = ln->next;
+ if (list->last == ln)
+ list->last = ln->prev;
}
/* Replace the datum in the given node with the new datum. */
void
-LstNode_Set(ListNode *node, void *datum)
+LstNode_Set(ListNode *ln, void *datum)
{
assert(datum != NULL);
- node->datum = datum;
+ ln->datum = datum;
}
-/* Replace the datum in the given node to NULL.
+/* Replace the datum in the given node with NULL.
* Having NULL values in a list is unusual though. */
void
-LstNode_SetNull(ListNode *node)
+LstNode_SetNull(ListNode *ln)
{
- node->datum = NULL;
+ ln->datum = NULL;
}
-/*
- * Functions for entire lists
- */
-
-/* Return the first node that contains the given datum, or NULL. */
+/* Return the first node that contains the given datum, or NULL.
+ *
+ * Time complexity: O(length(list)) */
ListNode *
Lst_FindDatum(List *list, const void *datum)
{
- ListNode *node;
+ ListNode *ln;
assert(datum != NULL);
- for (node = list->first; node != NULL; node = node->next)
- if (node->datum == datum)
- return node;
+ for (ln = list->first; ln != NULL; ln = ln->next)
+ if (ln->datum == datum)
+ return ln;
return NULL;
}
@@ -213,32 +205,32 @@ Lst_FindDatum(List *list, const void *datum)
int
Lst_ForEachUntil(List *list, LstActionUntilProc proc, void *procData)
{
- ListNode *node;
+ ListNode *ln;
int result = 0;
- for (node = list->first; node != NULL; node = node->next) {
- result = proc(node->datum, procData);
+ for (ln = list->first; ln != NULL; ln = ln->next) {
+ result = proc(ln->datum, procData);
if (result != 0)
break;
}
return result;
}
-/* Move all nodes from list2 to the end of list1.
- * List2 is destroyed and freed. */
+/* Move all nodes from src to the end of dst.
+ * The source list is destroyed and freed. */
void
-Lst_MoveAll(List *list1, List *list2)
+Lst_MoveAll(List *dst, List *src)
{
- if (list2->first != NULL) {
- list2->first->prev = list1->last;
- if (list1->last != NULL)
- list1->last->next = list2->first;
+ if (src->first != NULL) {
+ src->first->prev = dst->last;
+ if (dst->last != NULL)
+ dst->last->next = src->first;
else
- list1->first = list2->first;
+ dst->first = src->first;
- list1->last = list2->last;
+ dst->last = src->last;
}
- free(list2);
+ free(src);
}
/* Copy the element data from src to the start of dst. */
diff --git a/lst.h b/lst.h
index 85418e0d0531..6965678c1e09 100644
--- a/lst.h
+++ b/lst.h
@@ -1,4 +1,4 @@
-/* $NetBSD: lst.h,v 1.84 2020/10/28 02:43:16 rillig Exp $ */
+/* $NetBSD: lst.h,v 1.85 2020/11/10 00:32:12 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -118,7 +118,7 @@ void Lst_Destroy(List *, LstFreeProc);
/* Get information about a list */
-static inline MAKE_ATTR_UNUSED Boolean
+MAKE_INLINE Boolean
Lst_IsEmpty(List *list) { return list->first == NULL; }
/* Find the first node that contains the given datum, or NULL. */
@@ -173,7 +173,7 @@ void Vector_Init(Vector *, size_t);
/* Return the pointer to the given item in the vector.
* The returned data is valid until the next modifying operation. */
-static inline MAKE_ATTR_UNUSED void *
+MAKE_INLINE void *
Vector_Get(Vector *v, size_t i)
{
unsigned char *items = v->items;
diff --git a/main.c b/main.c
index 729c225d4bc4..6e97a9a12541 100644
--- a/main.c
+++ b/main.c
@@ -1,4 +1,4 @@
-/* $NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $ */
+/* $NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,31 +68,22 @@
* SUCH DAMAGE.
*/
-/*-
- * main.c --
- * The main file for this entire program. Exit routines etc
- * reside here.
+/* The main file for this entire program. Exit routines etc. reside here.
*
* Utility functions defined in this file:
- * Main_ParseArgLine Takes a line of arguments, breaks them and
- * treats them as if they were given when first
- * invoked. Used by the parse module to implement
- * the .MFLAGS target.
*
- * Error Print a tagged error message. The global
- * MAKE variable must have been defined. This
- * takes a format string and optional arguments
- * for it.
+ * Main_ParseArgLine Parse and process command line arguments from
+ * a single string. Used to implement the
+ * special targets .MFLAGS and .MAKEFLAGS.
+ *
+ * Error Print a tagged error message.
*
- * Fatal Print an error message and exit. Also takes
- * a format string and arguments for it.
+ * Fatal Print an error message and exit.
*
- * Punt Aborts all jobs and exits with a message. Also
- * takes a format string and arguments for it.
+ * Punt Abort all jobs and exit with a message.
*
* Finish Finish things up by printing the number of
- * errors which occurred, as passed to it, and
- * exiting.
+ * errors which occurred, and exit.
*/
#include <sys/types.h>
@@ -118,15 +109,15 @@
#include "trace.h"
/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: main.c,v 1.421 2020/11/01 00:24:57 rillig Exp $");
+MAKE_RCSID("$NetBSD: main.c,v 1.476 2020/11/16 22:08:20 rillig Exp $");
#if defined(MAKE_NATIVE) && !defined(lint)
__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
"The Regents of the University of California. "
"All rights reserved.");
#endif
-#ifndef DEFMAXLOCAL
-#define DEFMAXLOCAL DEFMAXJOBS
+#ifndef DEFMAXLOCAL
+#define DEFMAXLOCAL DEFMAXJOBS
#endif
#ifndef __arraycount
@@ -134,202 +125,208 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
#endif
CmdOpts opts;
-time_t now; /* Time at start of make */
-GNode *DEFAULT; /* .DEFAULT node */
-Boolean allPrecious; /* .PRECIOUS given on line by itself */
-Boolean deleteOnError; /* .DELETE_ON_ERROR: set */
-
-static int maxJobTokens; /* -j argument */
-Boolean enterFlagObj; /* -w and objdir != srcdir */
-
-Boolean oldVars; /* variable substitution style */
-static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
-Boolean doing_depend; /* Set while reading .depend */
-static Boolean jobsRunning; /* TRUE if the jobs might be running */
-static const char * tracefile;
-static int ReadMakefile(const char *);
-static void usage(void) MAKE_ATTR_DEAD;
-static void purge_cached_realpaths(void);
-
-static Boolean ignorePWD; /* if we use -C, PWD is meaningless */
-static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
-char curdir[MAXPATHLEN + 1]; /* Startup directory */
-char *progname; /* the program name */
+time_t now; /* Time at start of make */
+GNode *defaultNode; /* .DEFAULT node */
+Boolean allPrecious; /* .PRECIOUS given on line by itself */
+Boolean deleteOnError; /* .DELETE_ON_ERROR: set */
+
+static int maxJobTokens; /* -j argument */
+Boolean enterFlagObj; /* -w and objdir != srcdir */
+
+Boolean preserveUndefined;
+static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
+Boolean doing_depend; /* Set while reading .depend */
+static Boolean jobsRunning; /* TRUE if the jobs might be running */
+static const char *tracefile;
+static int ReadMakefile(const char *);
+static void purge_relative_cached_realpaths(void);
+
+static Boolean ignorePWD; /* if we use -C, PWD is meaningless */
+static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
+char curdir[MAXPATHLEN + 1]; /* Startup directory */
+char *progname; /* the program name */
char *makeDependfile;
pid_t myPid;
int makelevel;
Boolean forceJobs = FALSE;
static int errors = 0;
-
-/*
- * On some systems MACHINE is defined as something other than
- * what we want.
- */
-#ifdef FORCE_MACHINE
-# undef MACHINE
-# define MACHINE FORCE_MACHINE
-#endif
-
-extern SearchPath *parseIncPath;
+static HashTable cached_realpaths;
/*
* For compatibility with the POSIX version of MAKEFLAGS that includes
- * all the options with out -, convert flags to -f -l -a -g -s.
+ * all the options without '-', convert 'flags' to '-f -l -a -g -s'.
*/
static char *
explode(const char *flags)
{
- size_t len;
- char *nf, *st;
- const char *f;
+ size_t len;
+ char *nf, *st;
+ const char *f;
- if (flags == NULL)
- return NULL;
+ if (flags == NULL)
+ return NULL;
+
+ for (f = flags; *f; f++)
+ if (!ch_isalpha(*f))
+ break;
+
+ if (*f)
+ return bmake_strdup(flags);
- for (f = flags; *f; f++)
- if (!ch_isalpha(*f))
- break;
-
- if (*f)
- return bmake_strdup(flags);
-
- len = strlen(flags);
- st = nf = bmake_malloc(len * 3 + 1);
- while (*flags) {
- *nf++ = '-';
- *nf++ = *flags++;
- *nf++ = ' ';
- }
- *nf = '\0';
- return st;
+ len = strlen(flags);
+ st = nf = bmake_malloc(len * 3 + 1);
+ while (*flags) {
+ *nf++ = '-';
+ *nf++ = *flags++;
+ *nf++ = ' ';
+ }
+ *nf = '\0';
+ return st;
+}
+
+/*
+ * usage --
+ * exit with usage message
+ */
+MAKE_ATTR_DEAD static void
+usage(void)
+{
+ size_t prognameLen = strcspn(progname, "[");
+
+ (void)fprintf(stderr,
+"usage: %.*s [-BeikNnqrSstWwX]\n"
+" [-C directory] [-D variable] [-d flags] [-f makefile]\n"
+" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n"
+" [-V variable] [-v variable] [variable=value] [target ...]\n",
+ (int)prognameLen, progname);
+ exit(2);
}
static void
parse_debug_option_F(const char *modules)
{
- const char *mode;
- size_t len;
- char *fname;
+ const char *mode;
+ size_t len;
+ char *fname;
- if (opts.debug_file != stdout && opts.debug_file != stderr)
- fclose(opts.debug_file);
+ if (opts.debug_file != stdout && opts.debug_file != stderr)
+ fclose(opts.debug_file);
- if (*modules == '+') {
- modules++;
- mode = "a";
- } else
- mode = "w";
+ if (*modules == '+') {
+ modules++;
+ mode = "a";
+ } else
+ mode = "w";
- if (strcmp(modules, "stdout") == 0) {
- opts.debug_file = stdout;
- return;
- }
- if (strcmp(modules, "stderr") == 0) {
- opts.debug_file = stderr;
- return;
- }
+ if (strcmp(modules, "stdout") == 0) {
+ opts.debug_file = stdout;
+ return;
+ }
+ if (strcmp(modules, "stderr") == 0) {
+ opts.debug_file = stderr;
+ return;
+ }
- len = strlen(modules);
- fname = bmake_malloc(len + 20);
- memcpy(fname, modules, len + 1);
+ len = strlen(modules);
+ fname = bmake_malloc(len + 20);
+ memcpy(fname, modules, len + 1);
- /* Let the filename be modified by the pid */
- if (strcmp(fname + len - 3, ".%d") == 0)
- snprintf(fname + len - 2, 20, "%d", getpid());
+ /* Let the filename be modified by the pid */
+ if (strcmp(fname + len - 3, ".%d") == 0)
+ snprintf(fname + len - 2, 20, "%d", getpid());
- opts.debug_file = fopen(fname, mode);
- if (!opts.debug_file) {
- fprintf(stderr, "Cannot open debug file %s\n",
- fname);
- usage();
- }
- free(fname);
+ opts.debug_file = fopen(fname, mode);
+ if (opts.debug_file == NULL) {
+ fprintf(stderr, "Cannot open debug file %s\n",
+ fname);
+ usage();
+ }
+ free(fname);
}
static void
parse_debug_options(const char *argvalue)
{
const char *modules;
+ DebugFlags debug = opts.debug;
for (modules = argvalue; *modules; ++modules) {
switch (*modules) {
case '0': /* undocumented, only intended for tests */
- opts.debug &= DEBUG_LINT;
+ debug = DEBUG_NONE;
break;
case 'A':
- opts.debug = ~(0|DEBUG_LINT);
+ debug = DEBUG_ALL;
break;
case 'a':
- opts.debug |= DEBUG_ARCH;
+ debug |= DEBUG_ARCH;
break;
case 'C':
- opts.debug |= DEBUG_CWD;
+ debug |= DEBUG_CWD;
break;
case 'c':
- opts.debug |= DEBUG_COND;
+ debug |= DEBUG_COND;
break;
case 'd':
- opts.debug |= DEBUG_DIR;
+ debug |= DEBUG_DIR;
break;
case 'e':
- opts.debug |= DEBUG_ERROR;
+ debug |= DEBUG_ERROR;
break;
case 'f':
- opts.debug |= DEBUG_FOR;
+ debug |= DEBUG_FOR;
break;
case 'g':
if (modules[1] == '1') {
- opts.debug |= DEBUG_GRAPH1;
- ++modules;
- }
- else if (modules[1] == '2') {
- opts.debug |= DEBUG_GRAPH2;
- ++modules;
- }
- else if (modules[1] == '3') {
- opts.debug |= DEBUG_GRAPH3;
- ++modules;
+ debug |= DEBUG_GRAPH1;
+ modules++;
+ } else if (modules[1] == '2') {
+ debug |= DEBUG_GRAPH2;
+ modules++;
+ } else if (modules[1] == '3') {
+ debug |= DEBUG_GRAPH3;
+ modules++;
}
break;
case 'h':
- opts.debug |= DEBUG_HASH;
+ debug |= DEBUG_HASH;
break;
case 'j':
- opts.debug |= DEBUG_JOB;
+ debug |= DEBUG_JOB;
break;
case 'L':
- opts.debug |= DEBUG_LINT;
+ opts.lint = TRUE;
break;
case 'l':
- opts.debug |= DEBUG_LOUD;
+ debug |= DEBUG_LOUD;
break;
case 'M':
- opts.debug |= DEBUG_META;
+ debug |= DEBUG_META;
break;
case 'm':
- opts.debug |= DEBUG_MAKE;
+ debug |= DEBUG_MAKE;
break;
case 'n':
- opts.debug |= DEBUG_SCRIPT;
+ debug |= DEBUG_SCRIPT;
break;
case 'p':
- opts.debug |= DEBUG_PARSE;
+ debug |= DEBUG_PARSE;
break;
case 's':
- opts.debug |= DEBUG_SUFF;
+ debug |= DEBUG_SUFF;
break;
case 't':
- opts.debug |= DEBUG_TARG;
+ debug |= DEBUG_TARG;
break;
case 'V':
opts.debugVflag = TRUE;
break;
case 'v':
- opts.debug |= DEBUG_VAR;
+ debug |= DEBUG_VAR;
break;
case 'x':
- opts.debug |= DEBUG_SHELL;
+ debug |= DEBUG_SHELL;
break;
case 'F':
parse_debug_option_F(modules + 1);
@@ -341,7 +338,10 @@ parse_debug_options(const char *argvalue)
usage();
}
}
+
debug_setbuf:
+ opts.debug = debug;
+
/*
* Make the debug_file unbuffered, and make
* stdout line buffered (unless debugfile == stdout).
@@ -365,12 +365,10 @@ is_relpath(const char *path)
cp = path;
while ((cp = strstr(cp, "/.")) != NULL) {
cp += 2;
+ if (*cp == '.')
+ cp++;
if (cp[0] == '/' || cp[0] == '\0')
return TRUE;
- else if (cp[0] == '.') {
- if (cp[1] == '/' || cp[1] == '\0')
- return TRUE;
- }
}
return FALSE;
}
@@ -401,10 +399,11 @@ MainParseArgChdir(const char *argvalue)
static void
MainParseArgJobsInternal(const char *argvalue)
{
- if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) {
+ char end;
+ if (sscanf(argvalue, "%d,%d%c", &jp_0, &jp_1, &end) != 2) {
(void)fprintf(stderr,
- "%s: internal error -- J option malformed (%s)\n",
- progname, argvalue);
+ "%s: internal error -- J option malformed (%s)\n",
+ progname, argvalue);
usage();
}
if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
@@ -504,7 +503,7 @@ MainParseArg(char c, const char *argvalue)
break;
case 'V':
case 'v':
- opts.printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS;
+ opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED;
Lst_Append(opts.variables, bmake_strdup(argvalue));
/* XXX: Why always -V? */
Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
@@ -512,6 +511,7 @@ MainParseArg(char c, const char *argvalue)
break;
case 'W':
opts.parseWarnFatal = TRUE;
+ /* XXX: why no Var_Append? */
break;
case 'X':
opts.varNoExportEnv = TRUE;
@@ -547,6 +547,7 @@ MainParseArg(char c, const char *argvalue)
break;
case 'm':
MainParseArgSysInc(argvalue);
+ /* XXX: why no Var_Append? */
break;
case 'n':
opts.noExecute = TRUE;
@@ -610,8 +611,8 @@ rearg:
arginc = 0;
if (inOption) {
if (c == '\0') {
- ++argv;
- --argc;
+ argv++;
+ argc--;
inOption = FALSE;
continue;
}
@@ -653,8 +654,6 @@ rearg:
argc -= arginc;
}
- oldVars = TRUE;
-
/*
* See if the rest of the arguments are variable assignments and
* perform them if so. Else take them to be targets and stuff them
@@ -665,9 +664,9 @@ rearg:
if (Parse_IsVar(argv[1], &var)) {
Parse_DoVar(&var, VAR_CMDLINE);
} else {
- if (!*argv[1])
+ if (argv[1][0] == '\0')
Punt("illegal (null) argument.");
- if (*argv[1] == '-' && !dashDash)
+ if (argv[1][0] == '-' && !dashDash)
goto rearg;
Lst_Append(opts.create, bmake_strdup(argv[1]));
}
@@ -688,15 +687,13 @@ void
Main_ParseArgLine(const char *line)
{
Words words;
- void *p1;
- const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1);
char *buf;
if (line == NULL)
return;
for (; *line == ' '; ++line)
continue;
- if (!*line)
+ if (line[0] == '\0')
return;
#ifndef POSIX
@@ -713,8 +710,12 @@ Main_ParseArgLine(const char *line)
return;
}
#endif
- buf = str_concat3(argv0, " ", line);
- free(p1);
+ {
+ void *freeIt;
+ const char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &freeIt);
+ buf = str_concat3(argv0, " ", line);
+ free(freeIt);
+ }
words = Str_Words(buf, TRUE);
if (words.words == NULL) {
@@ -729,7 +730,7 @@ Main_ParseArgLine(const char *line)
}
Boolean
-Main_SetObjdir(const char *fmt, ...)
+Main_SetObjdir(Boolean writable, const char *fmt, ...)
{
struct stat sb;
char *path;
@@ -749,17 +750,16 @@ Main_SetObjdir(const char *fmt, ...)
/* look for the directory and try to chdir there */
if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
- /* if not .CURDIR it must be writable */
- if ((strcmp(path, curdir) != 0 && access(path, W_OK) != 0) ||
- chdir(path)) {
- (void)fprintf(stderr, "make warning: %s: %s.\n",
- path, strerror(errno));
+ if ((writable && access(path, W_OK) != 0) ||
+ (chdir(path) != 0)) {
+ (void)fprintf(stderr, "%s warning: %s: %s.\n",
+ progname, path, strerror(errno));
} else {
snprintf(objdir, sizeof objdir, "%s", path);
Var_Set(".OBJDIR", objdir, VAR_GLOBAL);
setenv("PWD", objdir, 1);
Dir_InitDot();
- purge_cached_realpaths();
+ purge_relative_cached_realpaths();
rc = TRUE;
if (opts.enterFlag && strcmp(objdir, curdir) != 0)
enterFlagObj = TRUE;
@@ -770,7 +770,7 @@ Main_SetObjdir(const char *fmt, ...)
}
static Boolean
-Main_SetVarObjdir(const char *var, const char *suffix)
+SetVarObjdir(Boolean writable, const char *var, const char *suffix)
{
void *path_freeIt;
const char *path = Var_Value(var, VAR_CMDLINE, &path_freeIt);
@@ -791,35 +791,28 @@ Main_SetVarObjdir(const char *var, const char *suffix)
xpath = xpath_freeIt;
}
- (void)Main_SetObjdir("%s%s", xpath, suffix);
+ (void)Main_SetObjdir(writable, "%s%s", xpath, suffix);
bmake_free(xpath_freeIt);
bmake_free(path_freeIt);
return TRUE;
}
-/* Read and parse the makefile.
- * Return TRUE if reading the makefile succeeded. */
-static int
-ReadMakefileSucceeded(void *fname, void *unused)
-{
- return ReadMakefile(fname) == 0;
-}
-
+/* Splits str into words, adding them to the list.
+ * The string must be kept alive as long as the list. */
int
-str2Lst_Append(StringList *lp, char *str, const char *sep)
+str2Lst_Append(StringList *lp, char *str)
{
- char *cp;
- int n;
+ char *cp;
+ int n;
- if (!sep)
- sep = " \t";
+ const char *sep = " \t";
- for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
- Lst_Append(lp, cp);
- n++;
- }
- return n;
+ for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) {
+ Lst_Append(lp, cp);
+ n++;
+ }
+ return n;
}
#ifdef SIGINFO
@@ -830,9 +823,9 @@ siginfo(int signo MAKE_ATTR_UNUSED)
char dir[MAXPATHLEN];
char str[2 * MAXPATHLEN];
int len;
- if (getcwd(dir, sizeof(dir)) == NULL)
+ if (getcwd(dir, sizeof dir) == NULL)
return;
- len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir);
+ len = snprintf(str, sizeof str, "%s: Working in: %s\n", progname, dir);
if (len > 0)
(void)write(STDERR_FILENO, str, (size_t)len);
}
@@ -844,27 +837,27 @@ siginfo(int signo MAKE_ATTR_UNUSED)
void
MakeMode(const char *mode)
{
- char *mode_freeIt = NULL;
-
- if (mode == NULL) {
- (void)Var_Subst("${" MAKE_MODE ":tl}",
- VAR_GLOBAL, VARE_WANTRES, &mode_freeIt);
- /* TODO: handle errors */
- mode = mode_freeIt;
- }
+ char *mode_freeIt = NULL;
- if (mode[0] != '\0') {
- if (strstr(mode, "compat")) {
- opts.compatMake = TRUE;
- forceJobs = FALSE;
+ if (mode == NULL) {
+ (void)Var_Subst("${" MAKE_MODE ":tl}",
+ VAR_GLOBAL, VARE_WANTRES, &mode_freeIt);
+ /* TODO: handle errors */
+ mode = mode_freeIt;
}
+
+ if (mode[0] != '\0') {
+ if (strstr(mode, "compat")) {
+ opts.compatMake = TRUE;
+ forceJobs = FALSE;
+ }
#if USE_META
- if (strstr(mode, "meta"))
- meta_mode_init(mode);
+ if (strstr(mode, "meta"))
+ meta_mode_init(mode);
#endif
- }
+ }
- free(mode_freeIt);
+ free(mode_freeIt);
}
static void
@@ -894,18 +887,40 @@ PrintVar(const char *varname, Boolean expandVars)
}
}
+/*
+ * Return a Boolean based on a variable.
+ *
+ * If the knob is not set, return the fallback.
+ * If set, anything that looks or smells like "No", "False", "Off", "0", etc.
+ * is FALSE, otherwise TRUE.
+ */
+Boolean
+GetBooleanVar(const char *varname, Boolean fallback)
+{
+ char *expr = str_concat3("${", varname, ":U}");
+ char *value;
+ Boolean res;
+
+ (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value);
+ /* TODO: handle errors */
+ res = ParseBoolean(value, fallback);
+ free(value);
+ free(expr);
+ return res;
+}
+
static void
doPrintVars(void)
{
StringListNode *ln;
Boolean expandVars;
- if (opts.printVars == EXPAND_VARS)
+ if (opts.printVars == PVM_EXPANDED)
expandVars = TRUE;
else if (opts.debugVflag)
expandVars = FALSE;
else
- expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE);
+ expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE);
for (ln = opts.variables->first; ln != NULL; ln = ln->next) {
const char *varname = ln->datum;
@@ -916,7 +931,7 @@ doPrintVars(void)
static Boolean
runTargets(void)
{
- GNodeList *targs; /* target nodes to create -- passed to Make_Init */
+ GNodeList *targs; /* target nodes to create */
Boolean outOfDate; /* FALSE if all targets up to date */
/*
@@ -988,20 +1003,19 @@ InitRandom(void)
}
static const char *
-init_machine(const struct utsname *utsname)
+InitVarMachine(const struct utsname *utsname)
{
#ifdef FORCE_MACHINE
- const char *machine = FORCE_MACHINE;
+ return FORCE_MACHINE;
#else
const char *machine = getenv("MACHINE");
-#endif
+
if (machine != NULL)
return machine;
-#ifdef MAKE_NATIVE
+#if defined(MAKE_NATIVE)
return utsname->machine;
-#else
-#ifdef MAKE_MACHINE
+#elif defined(MAKE_MACHINE)
return MAKE_MACHINE;
#else
return "unknown";
@@ -1010,8 +1024,11 @@ init_machine(const struct utsname *utsname)
}
static const char *
-init_machine_arch(void)
+InitVarMachineArch(void)
{
+#ifdef FORCE_MACHINE_ARCH
+ return FORCE_MACHINE_ARCH;
+#else
const char *env = getenv("MACHINE_ARCH");
if (env != NULL)
return env;
@@ -1019,9 +1036,9 @@ init_machine_arch(void)
#if defined(MAKE_NATIVE) && defined(CTL_HW)
{
struct utsname utsname;
- static char machine_arch_buf[sizeof(utsname.machine)];
+ static char machine_arch_buf[sizeof utsname.machine];
const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
- size_t len = sizeof(machine_arch_buf);
+ size_t len = sizeof machine_arch_buf;
if (sysctl(mib, __arraycount(mib), machine_arch_buf,
&len, NULL, 0) < 0) {
@@ -1032,16 +1049,13 @@ init_machine_arch(void)
return machine_arch_buf;
}
-#else
-#ifndef MACHINE_ARCH
-#ifdef MAKE_MACHINE_ARCH
+#elif defined(MACHINE_ARCH)
+ return MACHINE_ARCH;
+#elif defined(MAKE_MACHINE_ARCH)
return MAKE_MACHINE_ARCH;
#else
return "unknown";
#endif
-#else
- return MACHINE_ARCH;
-#endif
#endif
}
@@ -1070,7 +1084,8 @@ HandlePWD(const struct stat *curdir_st)
if (ignorePWD || (pwd = getenv("PWD")) == NULL)
return;
- if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) != NULL) {
+ if (Var_Value("MAKEOBJDIRPREFIX", VAR_CMDLINE, &prefix_freeIt) !=
+ NULL) {
bmake_free(prefix_freeIt);
return;
}
@@ -1101,15 +1116,18 @@ ignore_pwd:
static void
InitObjdir(const char *machine, const char *machine_arch)
{
+ Boolean writable;
+
Dir_InitDir(curdir);
- (void)Main_SetObjdir("%s", curdir);
-
- if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) &&
- !Main_SetVarObjdir("MAKEOBJDIR", "") &&
- !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) &&
- !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) &&
- !Main_SetObjdir("%s", _PATH_OBJDIR))
- (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir);
+ writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE);
+ (void)Main_SetObjdir(FALSE, "%s", curdir);
+
+ if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) &&
+ !SetVarObjdir(writable, "MAKEOBJDIR", "") &&
+ !Main_SetObjdir(writable, "%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) &&
+ !Main_SetObjdir(writable, "%s.%s", _PATH_OBJDIR, machine) &&
+ !Main_SetObjdir(writable, "%s", _PATH_OBJDIR))
+ (void)Main_SetObjdir(writable, "%s%s", _PATH_OBJDIRPREFIX, curdir);
}
/* get rid of resource limit on file descriptors */
@@ -1132,6 +1150,7 @@ CmdOpts_Init(void)
opts.compatMake = FALSE; /* No compat mode */
opts.debug = 0; /* No debug verbosity, please. */
/* opts.debug_file has been initialized earlier */
+ opts.lint = FALSE;
opts.debugVflag = FALSE;
opts.checkEnvFirst = FALSE;
opts.makefiles = Lst_New();
@@ -1144,7 +1163,7 @@ CmdOpts_Init(void)
opts.noBuiltins = FALSE; /* Read the built-in rules */
opts.beSilent = FALSE; /* Print commands as executed */
opts.touchFlag = FALSE; /* Actually update targets */
- opts.printVars = 0;
+ opts.printVars = PVM_NONE;
opts.variables = Lst_New();
opts.parseWarnFatal = FALSE;
opts.enterFlag = FALSE;
@@ -1174,6 +1193,8 @@ InitVarMake(const char *argv0)
Var_Set(".MAKE", make, VAR_GLOBAL);
}
+/* Add the directories from the colon-separated syspath to defSysIncPath.
+ * After returning, the contents of syspath is unspecified. */
static void
InitDefSysIncPath(char *syspath)
{
@@ -1185,7 +1206,6 @@ InitDefSysIncPath(char *syspath)
* add the directories from the DEFSYSPATH (more than one may be given
* as dir1:...:dirn) to the system include path.
*/
- /* XXX: mismatch: the -m option sets sysIncPath, not syspath */
if (syspath == NULL || syspath[0] == '\0')
syspath = defsyspath;
else
@@ -1194,18 +1214,18 @@ InitDefSysIncPath(char *syspath)
for (start = syspath; *start != '\0'; start = cp) {
for (cp = start; *cp != '\0' && *cp != ':'; cp++)
continue;
- if (*cp == ':') {
+ if (*cp == ':')
*cp++ = '\0';
- }
+
/* look for magic parent directory search string */
- if (strncmp(".../", start, 4) != 0) {
- (void)Dir_AddDir(defSysIncPath, start);
- } else {
+ if (strncmp(start, ".../", 4) == 0) {
char *dir = Dir_FindHereOrAbove(curdir, start + 4);
if (dir != NULL) {
(void)Dir_AddDir(defSysIncPath, dir);
free(dir);
}
+ } else {
+ (void)Dir_AddDir(defSysIncPath, start);
}
}
@@ -1216,16 +1236,26 @@ InitDefSysIncPath(char *syspath)
static void
ReadBuiltinRules(void)
{
+ StringListNode *ln;
StringList *sysMkPath = Lst_New();
+
Dir_Expand(_PATH_DEFSYSMK,
- Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath,
- sysMkPath);
+ Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath,
+ sysMkPath);
if (Lst_IsEmpty(sysMkPath))
Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK);
- if (!Lst_ForEachUntil(sysMkPath, ReadMakefileSucceeded, NULL))
- Fatal("%s: cannot open %s.", progname,
- (char *)sysMkPath->first->datum);
- /* XXX: sysMkPath is not freed */
+
+ for (ln = sysMkPath->first; ln != NULL; ln = ln->next)
+ if (ReadMakefile(ln->datum) == 0)
+ break;
+
+ if (ln == NULL)
+ Fatal("%s: cannot open %s.",
+ progname, (const char *)sysMkPath->first->datum);
+
+ /* Free the list but not the actual filenames since these may still
+ * be used in GNodes. */
+ Lst_Free(sysMkPath);
}
static void
@@ -1243,9 +1273,9 @@ InitMaxJobs(void)
n = (int)strtol(value, NULL, 0);
if (n < 1) {
(void)fprintf(stderr,
- "%s: illegal value for .MAKE.JOBS "
- "-- must be positive integer!\n",
- progname);
+ "%s: illegal value for .MAKE.JOBS "
+ "-- must be positive integer!\n",
+ progname);
exit(1);
}
@@ -1293,82 +1323,46 @@ InitVpath(void)
}
static void
-ReadMakefiles(void)
+ReadAllMakefiles(StringList *makefiles)
{
- if (opts.makefiles->first != NULL) {
- StringListNode *ln;
+ StringListNode *ln;
- for (ln = opts.makefiles->first; ln != NULL; ln = ln->next) {
- if (ReadMakefile(ln->datum) != 0)
- Fatal("%s: cannot open %s.",
- progname, (char *)ln->datum);
- }
- } else {
- char *p1;
- (void)Var_Subst("${" MAKEFILE_PREFERENCE "}",
- VAR_CMDLINE, VARE_WANTRES, &p1);
- /* TODO: handle errors */
- (void)str2Lst_Append(opts.makefiles, p1, NULL);
- (void)Lst_ForEachUntil(opts.makefiles,
- ReadMakefileSucceeded, NULL);
- free(p1);
+ for (ln = makefiles->first; ln != NULL; ln = ln->next) {
+ const char *fname = ln->datum;
+ if (ReadMakefile(fname) != 0)
+ Fatal("%s: cannot open %s.", progname, fname);
}
}
static void
-CleanUp(void)
+ReadFirstDefaultMakefile(void)
{
-#ifdef CLEANUP
- Lst_Destroy(opts.variables, free);
- Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */
- Lst_Destroy(opts.create, free);
-#endif
+ StringListNode *ln;
+ char *prefs;
- /* print the graph now it's been processed if the user requested it */
- if (DEBUG(GRAPH2))
- Targ_PrintGraph(2);
+ (void)Var_Subst("${" MAKE_MAKEFILE_PREFERENCE "}",
+ VAR_CMDLINE, VARE_WANTRES, &prefs);
+ /* TODO: handle errors */
- Trace_Log(MAKEEND, 0);
+ /* XXX: This should use a local list instead of opts.makefiles
+ * since these makefiles do not come from the command line. They
+ * also have different semantics in that only the first file that
+ * is found is processed. See ReadAllMakefiles. */
+ (void)str2Lst_Append(opts.makefiles, prefs);
- if (enterFlagObj)
- printf("%s: Leaving directory `%s'\n", progname, objdir);
- if (opts.enterFlag)
- printf("%s: Leaving directory `%s'\n", progname, curdir);
+ for (ln = opts.makefiles->first; ln != NULL; ln = ln->next)
+ if (ReadMakefile(ln->datum) == 0)
+ break;
-#ifdef USE_META
- meta_finish();
-#endif
- Suff_End();
- Targ_End();
- Arch_End();
- Var_End();
- Parse_End();
- Dir_End();
- Job_End();
- Trace_End();
+ free(prefs);
}
-/*-
- * main --
- * The main function, for obvious reasons. Initializes variables
- * and a few modules, then parses the arguments give it in the
- * environment and on the command line. Reads the system makefile
- * followed by either Makefile, makefile or the file given by the
- * -f argument. Sets the .MAKEFLAGS PMake variable based on all the
- * flags it has received by then uses either the Make or the Compat
- * module to create the initial list of targets.
- *
- * Results:
- * If -q was given, exits -1 if anything was out-of-date. Else it exits
- * 0.
- *
- * Side Effects:
- * The program exits when done. Targets are created. etc. etc. etc.
- */
-int
-main(int argc, char **argv)
+/* Initialize variables such as MAKE, MACHINE, .MAKEFLAGS.
+ * Initialize a few modules.
+ * Parse the arguments from MAKEFLAGS and the command line. */
+static void
+main_Init(int argc, char **argv)
{
- Boolean outOfDate; /* FALSE if all targets up to date */
struct stat sa;
const char *machine;
const char *machine_arch;
@@ -1378,6 +1372,8 @@ main(int argc, char **argv)
/* default to writing debug to stderr */
opts.debug_file = stderr;
+ HashTable_Init(&cached_realpaths);
+
#ifdef SIGINFO
(void)bmake_signal(SIGINFO, siginfo);
#endif
@@ -1392,9 +1388,9 @@ main(int argc, char **argv)
UnlimitFiles();
if (uname(&utsname) == -1) {
- (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
- strerror(errno));
- exit(2);
+ (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
+ strerror(errno));
+ exit(2);
}
/*
@@ -1405,16 +1401,16 @@ main(int argc, char **argv)
* Note that both MACHINE and MACHINE_ARCH are decided at
* run-time.
*/
- machine = init_machine(&utsname);
- machine_arch = init_machine_arch();
+ machine = InitVarMachine(&utsname);
+ machine_arch = InitVarMachineArch();
- myPid = getpid(); /* remember this for vFork() */
+ myPid = getpid(); /* remember this for vFork() */
/*
* Just in case MAKEOBJDIR wants us to do something tricky.
*/
- Var_Init(); /* Initialize the lists of variables for
- * parsing arguments */
+ Targ_Init();
+ Var_Init();
Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL);
Var_Set("MACHINE", machine, VAR_GLOBAL);
Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL);
@@ -1428,13 +1424,12 @@ main(int argc, char **argv)
#ifndef MAKEFILE_PREFERENCE_LIST
# define MAKEFILE_PREFERENCE_LIST "makefile Makefile"
#endif
- Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST,
- VAR_GLOBAL);
+ Var_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, VAR_GLOBAL);
Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL);
CmdOpts_Init();
- allPrecious = FALSE; /* Remove targets when interrupted */
- deleteOnError = FALSE; /* Historical default behavior */
+ allPrecious = FALSE; /* Remove targets when interrupted */
+ deleteOnError = FALSE; /* Historical default behavior */
jobsRunning = FALSE;
maxJobTokens = opts.maxJobs;
@@ -1461,25 +1456,23 @@ main(int argc, char **argv)
/* some makefiles need to know this */
Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMDLINE);
- /*
- * Set some other useful macros
- */
+ /* Set some other useful variables. */
{
- char tmp[64], *ep;
-
- makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0;
- if (makelevel < 0)
- makelevel = 0;
- snprintf(tmp, sizeof(tmp), "%d", makelevel);
- Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL);
- snprintf(tmp, sizeof(tmp), "%u", myPid);
- Var_Set(".MAKE.PID", tmp, VAR_GLOBAL);
- snprintf(tmp, sizeof(tmp), "%u", getppid());
- Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL);
+ char tmp[64], *ep = getenv(MAKE_LEVEL_ENV);
+
+ makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0;
+ if (makelevel < 0)
+ makelevel = 0;
+ snprintf(tmp, sizeof tmp, "%d", makelevel);
+ Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL);
+ snprintf(tmp, sizeof tmp, "%u", myPid);
+ Var_Set(".MAKE.PID", tmp, VAR_GLOBAL);
+ snprintf(tmp, sizeof tmp, "%u", getppid());
+ Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL);
}
if (makelevel > 0) {
char pn[1024];
- snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel);
+ snprintf(pn, sizeof pn, "%s[%d]", progname, makelevel);
progname = bmake_strdup(pn);
}
@@ -1495,9 +1488,9 @@ main(int argc, char **argv)
*/
#ifdef POSIX
{
- char *p1 = explode(getenv("MAKEFLAGS"));
- Main_ParseArgLine(p1);
- free(p1);
+ char *p1 = explode(getenv("MAKEFLAGS"));
+ Main_ParseArgLine(p1);
+ free(p1);
}
#else
Main_ParseArgLine(getenv("MAKE"));
@@ -1522,9 +1515,9 @@ main(int argc, char **argv)
* Verify that cwd is sane.
*/
if (stat(curdir, &sa) == -1) {
- (void)fprintf(stderr, "%s: %s: %s.\n",
- progname, curdir, strerror(errno));
- exit(2);
+ (void)fprintf(stderr, "%s: %s: %s.\n",
+ progname, curdir, strerror(errno));
+ exit(2);
}
#ifndef NO_PWD_OVERRIDE
@@ -1539,11 +1532,10 @@ main(int argc, char **argv)
* parsing the makefile(s)
*/
Arch_Init();
- Targ_Init();
Suff_Init();
Trace_Init(tracefile);
- DEFAULT = NULL;
+ defaultNode = NULL;
(void)time(&now);
Trace_Log(MAKESTART, NULL);
@@ -1551,27 +1543,38 @@ main(int argc, char **argv)
InitVarTargets();
InitDefSysIncPath(syspath);
+}
+
+/* Read the system makefile followed by either makefile, Makefile or the
+ * files given by the -f option. Exit on parse errors. */
+static void
+main_ReadFiles(void)
+{
- /*
- * Read in the built-in rules first, followed by the specified
- * makefiles, or the default makefile and Makefile, in that order,
- * if no makefiles were given on the command line.
- */
if (!opts.noBuiltins)
ReadBuiltinRules();
- ReadMakefiles();
-
+
+ if (!Lst_IsEmpty(opts.makefiles))
+ ReadAllMakefiles(opts.makefiles);
+ else
+ ReadFirstDefaultMakefile();
+}
+
+/* Compute the dependency graph. */
+static void
+main_PrepareMaking(void)
+{
/* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
- if (!opts.noBuiltins || !opts.printVars) {
- /* ignore /dev/null and anything starting with "no" */
- (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}",
- VAR_CMDLINE, VARE_WANTRES, &makeDependfile);
- if (makeDependfile[0] != '\0') {
- /* TODO: handle errors */
- doing_depend = TRUE;
- (void)ReadMakefile(makeDependfile);
- doing_depend = FALSE;
- }
+ if (!opts.noBuiltins || opts.printVars == PVM_NONE) {
+ /* ignore /dev/null and anything starting with "no" */
+ (void)Var_Subst("${.MAKE.DEPENDFILE:N/dev/null:Nno*:T}",
+ VAR_CMDLINE, VARE_WANTRES, &makeDependfile);
+ if (makeDependfile[0] != '\0') {
+ /* TODO: handle errors */
+ doing_depend = TRUE;
+ (void)ReadMakefile(makeDependfile);
+ doing_depend = FALSE;
+ }
}
if (enterFlagObj)
@@ -1580,30 +1583,28 @@ main(int argc, char **argv)
MakeMode(NULL);
{
- void *freeIt;
- Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt),
- VAR_GLOBAL);
- bmake_free(freeIt);
-
+ void *freeIt;
+ Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &freeIt),
+ VAR_GLOBAL);
+ bmake_free(freeIt);
}
InitMaxJobs();
/*
- * Be compatible if user did not specify -j and did not explicitly
- * turned compatibility on
+ * Be compatible if the user did not specify -j and did not explicitly
+ * turn compatibility on.
*/
- if (!opts.compatMake && !forceJobs) {
- opts.compatMake = TRUE;
- }
+ if (!opts.compatMake && !forceJobs)
+ opts.compatMake = TRUE;
if (!opts.compatMake)
- Job_ServerStart(maxJobTokens, jp_0, jp_1);
+ Job_ServerStart(maxJobTokens, jp_0, jp_1);
DEBUG5(JOB, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n",
- jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0);
+ jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0);
- if (!opts.printVars)
- Main_ExportMAKEFLAGS(TRUE); /* initial export */
+ if (opts.printVars == PVM_NONE)
+ Main_ExportMAKEFLAGS(TRUE); /* initial export */
InitVpath();
@@ -1621,22 +1622,79 @@ main(int argc, char **argv)
/* print the initial graph, if the user requested it */
if (DEBUG(GRAPH1))
Targ_PrintGraph(1);
+}
- /* print the values of any variables requested by the user */
- if (opts.printVars) {
+/* Make the targets.
+ * If the -v or -V options are given, print variables instead.
+ * Return whether any of the targets is out-of-date. */
+static Boolean
+main_Run(void)
+{
+ if (opts.printVars != PVM_NONE) {
+ /* print the values of any variables requested by the user */
doPrintVars();
- outOfDate = FALSE;
+ return FALSE;
} else {
- outOfDate = runTargets();
+ return runTargets();
}
+}
- CleanUp();
+/* Clean up after making the targets. */
+static void
+main_CleanUp(void)
+{
+#ifdef CLEANUP
+ Lst_Destroy(opts.variables, free);
+ Lst_Free(opts.makefiles); /* don't free, may be used in GNodes */
+ Lst_Destroy(opts.create, free);
+#endif
+
+ /* print the graph now it's been processed if the user requested it */
+ if (DEBUG(GRAPH2))
+ Targ_PrintGraph(2);
+
+ Trace_Log(MAKEEND, NULL);
+
+ if (enterFlagObj)
+ printf("%s: Leaving directory `%s'\n", progname, objdir);
+ if (opts.enterFlag)
+ printf("%s: Leaving directory `%s'\n", progname, curdir);
+
+#ifdef USE_META
+ meta_finish();
+#endif
+ Suff_End();
+ Targ_End();
+ Arch_End();
+ Var_End();
+ Parse_End();
+ Dir_End();
+ Job_End();
+ Trace_End();
+}
- if (DEBUG(LINT) && (errors > 0 || Parse_GetFatals() > 0))
- return 2; /* Not 1 so -q can distinguish error */
+/* Determine the exit code. */
+static int
+main_Exit(Boolean outOfDate)
+{
+ if (opts.lint && (errors > 0 || Parse_GetFatals() > 0))
+ return 2; /* Not 1 so -q can distinguish error */
return outOfDate ? 1 : 0;
}
+int
+main(int argc, char **argv)
+{
+ Boolean outOfDate;
+
+ main_Init(argc, argv);
+ main_ReadFiles();
+ main_PrepareMaking();
+ outOfDate = main_Run();
+ main_CleanUp();
+ return main_Exit(outOfDate);
+}
+
/* Open and parse the given makefile, with all its side effects.
*
* Results:
@@ -1648,12 +1706,12 @@ ReadMakefile(const char *fname)
int fd;
char *name, *path = NULL;
- if (!strcmp(fname, "-")) {
+ if (strcmp(fname, "-") == 0) {
Parse_File(NULL /*stdin*/, -1);
Var_Set("MAKEFILE", "", VAR_INTERNAL);
} else {
/* if we've chdir'd, rebuild the path name */
- if (strcmp(curdir, objdir) && *fname != '/') {
+ if (strcmp(curdir, objdir) != 0 && *fname != '/') {
path = str_concat3(curdir, "/", fname);
fd = open(path, O_RDONLY);
if (fd != -1) {
@@ -1676,12 +1734,12 @@ ReadMakefile(const char *fname)
}
/* look in -I and system include directories. */
name = Dir_FindFile(fname, parseIncPath);
- if (!name) {
+ if (name == NULL) {
SearchPath *sysInc = Lst_IsEmpty(sysIncPath)
? defSysIncPath : sysIncPath;
name = Dir_FindFile(fname, sysInc);
}
- if (!name || (fd = open(name, O_RDONLY)) == -1) {
+ if (name == NULL || (fd = open(name, O_RDONLY)) == -1) {
free(name);
free(path);
return -1;
@@ -1701,8 +1759,6 @@ found:
return 0;
}
-
-
/*-
* Cmd_Exec --
* Execute the command in cmd, and return the output of that command
@@ -1719,125 +1775,114 @@ found:
char *
Cmd_Exec(const char *cmd, const char **errfmt)
{
- const char *args[4]; /* Args for invoking the shell */
- int fds[2]; /* Pipe streams */
- int cpid; /* Child PID */
- int pid; /* PID from wait() */
- WAIT_T status; /* command exit status */
- Buffer buf; /* buffer to store the result */
- ssize_t bytes_read;
- char *res; /* result */
- size_t res_len;
- char *cp;
- int savederr; /* saved errno */
-
- *errfmt = NULL;
-
- if (!shellName)
- Shell_Init();
- /*
- * Set up arguments for shell
- */
- args[0] = shellName;
- args[1] = "-c";
- args[2] = cmd;
- args[3] = NULL;
-
- /*
- * Open a pipe for fetching its output
- */
- if (pipe(fds) == -1) {
- *errfmt = "Couldn't create pipe for \"%s\"";
- goto bad;
- }
-
- /*
- * Fork
- */
- switch (cpid = vFork()) {
- case 0:
+ const char *args[4]; /* Args for invoking the shell */
+ int fds[2]; /* Pipe streams */
+ int cpid; /* Child PID */
+ int pid; /* PID from wait() */
+ int status; /* command exit status */
+ Buffer buf; /* buffer to store the result */
+ ssize_t bytes_read;
+ char *res; /* result */
+ size_t res_len;
+ char *cp;
+ int savederr; /* saved errno */
+
+ *errfmt = NULL;
+
+ if (!shellName)
+ Shell_Init();
/*
- * Close input side of pipe
+ * Set up arguments for shell
*/
- (void)close(fds[0]);
+ args[0] = shellName;
+ args[1] = "-c";
+ args[2] = cmd;
+ args[3] = NULL;
/*
- * Duplicate the output stream to the shell's output, then
- * shut the extra thing down. Note we don't fetch the error
- * stream...why not? Why?
+ * Open a pipe for fetching its output
*/
- (void)dup2(fds[1], 1);
- (void)close(fds[1]);
-
- Var_ExportVars();
-
- (void)execv(shellPath, UNCONST(args));
- _exit(1);
- /*NOTREACHED*/
-
- case -1:
- *errfmt = "Couldn't exec \"%s\"";
- goto bad;
+ if (pipe(fds) == -1) {
+ *errfmt = "Couldn't create pipe for \"%s\"";
+ goto bad;
+ }
- default:
/*
- * No need for the writing half
+ * Fork
*/
- (void)close(fds[1]);
+ switch (cpid = vFork()) {
+ case 0:
+ (void)close(fds[0]); /* Close input side of pipe */
- savederr = 0;
- Buf_Init(&buf, 0);
+ /*
+ * Duplicate the output stream to the shell's output, then
+ * shut the extra thing down. Note we don't fetch the error
+ * stream...why not? Why?
+ */
+ (void)dup2(fds[1], 1);
+ (void)close(fds[1]);
- do {
- char result[BUFSIZ];
- bytes_read = read(fds[0], result, sizeof(result));
- if (bytes_read > 0)
- Buf_AddBytes(&buf, result, (size_t)bytes_read);
- }
- while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR));
- if (bytes_read == -1)
- savederr = errno;
+ Var_ExportVars();
- /*
- * Close the input side of the pipe.
- */
- (void)close(fds[0]);
+ (void)execv(shellPath, UNCONST(args));
+ _exit(1);
+ /*NOTREACHED*/
- /*
- * Wait for the process to exit.
- */
- while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) {
- JobReapChild(pid, status, FALSE);
- continue;
- }
- res_len = Buf_Len(&buf);
- res = Buf_Destroy(&buf, FALSE);
-
- if (savederr != 0)
- *errfmt = "Couldn't read shell's output for \"%s\"";
-
- if (WIFSIGNALED(status))
- *errfmt = "\"%s\" exited on a signal";
- else if (WEXITSTATUS(status) != 0)
- *errfmt = "\"%s\" returned non-zero status";
-
- /* Convert newlines to spaces. A final newline is just stripped */
- if (res_len > 0 && res[res_len - 1] == '\n')
- res[res_len - 1] = '\0';
- for (cp = res; *cp != '\0'; cp++)
- if (*cp == '\n')
- *cp = ' ';
- break;
- }
- return res;
+ case -1:
+ *errfmt = "Couldn't exec \"%s\"";
+ goto bad;
+
+ default:
+ (void)close(fds[1]); /* No need for the writing half */
+
+ savederr = 0;
+ Buf_Init(&buf);
+
+ do {
+ char result[BUFSIZ];
+ bytes_read = read(fds[0], result, sizeof result);
+ if (bytes_read > 0)
+ Buf_AddBytes(&buf, result, (size_t)bytes_read);
+ } while (bytes_read > 0 ||
+ (bytes_read == -1 && errno == EINTR));
+ if (bytes_read == -1)
+ savederr = errno;
+
+ (void)close(
+ fds[0]); /* Close the input side of the pipe. */
+
+ /* Wait for the process to exit. */
+ while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0)
+ JobReapChild(pid, status, FALSE);
+
+ res_len = Buf_Len(&buf);
+ res = Buf_Destroy(&buf, FALSE);
+
+ if (savederr != 0)
+ *errfmt = "Couldn't read shell's output for \"%s\"";
+
+ if (WIFSIGNALED(status))
+ *errfmt = "\"%s\" exited on a signal";
+ else if (WEXITSTATUS(status) != 0)
+ *errfmt = "\"%s\" returned non-zero status";
+
+ /* Convert newlines to spaces. A final newline is just stripped */
+ if (res_len > 0 && res[res_len - 1] == '\n')
+ res[res_len - 1] = '\0';
+ for (cp = res; *cp != '\0'; cp++)
+ if (*cp == '\n')
+ *cp = ' ';
+ break;
+ }
+ return res;
bad:
- return bmake_strdup("");
+ return bmake_strdup("");
}
/* Print a printf-style error message.
*
- * This error message has no consequences, in particular it does not affect
- * the exit status. */
+ * In default mode, this error message has no consequences, in particular it
+ * does not affect the exit status. Only in lint mode (-dL) it does. */
void
Error(const char *fmt, ...)
{
@@ -1862,19 +1907,21 @@ Error(const char *fmt, ...)
errors++;
}
-/* Produce a Fatal error message, then exit immediately.
+/* Wait for any running jobs to finish, then produce an error message,
+ * finally exit immediately.
*
- * If jobs are running, wait for them to finish. */
+ * Exiting immediately differs from Parse_Error, which exits only after the
+ * current top-level makefile has been parsed completely. */
void
Fatal(const char *fmt, ...)
{
va_list ap;
- va_start(ap, fmt);
if (jobsRunning)
Job_Wait();
(void)fflush(stdout);
+ va_start(ap, fmt);
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
(void)fprintf(stderr, "\n");
@@ -1884,7 +1931,7 @@ Fatal(const char *fmt, ...)
if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
Targ_PrintGraph(2);
- Trace_Log(MAKEERROR, 0);
+ Trace_Log(MAKEERROR, NULL);
exit(2); /* Not 1 so -q can distinguish error */
}
@@ -1916,7 +1963,7 @@ DieHorribly(void)
Job_AbortAll();
if (DEBUG(GRAPH2))
Targ_PrintGraph(2);
- Trace_Log(MAKEERROR, 0);
+ Trace_Log(MAKEERROR, NULL);
exit(2); /* Not 1, so -q can distinguish error */
}
@@ -1926,7 +1973,7 @@ DieHorribly(void)
void
Finish(int errs)
{
- if (dieQuietly(NULL, -1))
+ if (shouldDieQuietly(NULL, -1))
exit(2);
Fatal("%d error%s", errs, errs == 1 ? "" : "s");
}
@@ -1975,7 +2022,7 @@ execDie(const char *af, const char *av)
{
Buffer buf;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
Buf_AddStr(&buf, progname);
Buf_AddStr(&buf, ": ");
Buf_AddStr(&buf, af);
@@ -1991,216 +2038,183 @@ execDie(const char *af, const char *av)
_exit(1);
}
-/*
- * usage --
- * exit with usage message
- */
-static void
-usage(void)
-{
- char *p;
- if ((p = strchr(progname, '[')) != NULL)
- *p = '\0';
-
- (void)fprintf(stderr,
-"usage: %s [-BeikNnqrstWwX] \n"
-" [-C directory] [-D variable] [-d flags] [-f makefile]\n"
-" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n"
-" [-V variable] [-v variable] [variable=value] [target ...]\n",
- progname);
- exit(2);
-}
-
-/*
- * realpath(3) can get expensive, cache results...
- */
-static GNode *cached_realpaths = NULL;
-
-static GNode *
-get_cached_realpaths(void)
-{
-
- if (!cached_realpaths) {
- cached_realpaths = Targ_NewGN("Realpath");
-#ifndef DEBUG_REALPATH_CACHE
- cached_realpaths->flags = INTERNAL;
-#endif
- }
-
- return cached_realpaths;
-}
-
/* purge any relative paths */
static void
-purge_cached_realpaths(void)
+purge_relative_cached_realpaths(void)
{
- GNode *cache = get_cached_realpaths();
- HashEntry *he, *nhe;
- HashIter hi;
-
- HashIter_Init(&hi, &cache->context);
- he = HashIter_Next(&hi);
- while (he != NULL) {
- nhe = HashIter_Next(&hi);
- if (he->key[0] != '/') {
- if (DEBUG(DIR))
- fprintf(stderr, "cached_realpath: purging %s\n", he->key);
- HashTable_DeleteEntry(&cache->context, he);
- }
- he = nhe;
- }
+ HashEntry *he, *nhe;
+ HashIter hi;
+
+ HashIter_Init(&hi, &cached_realpaths);
+ he = HashIter_Next(&hi);
+ while (he != NULL) {
+ nhe = HashIter_Next(&hi);
+ if (he->key[0] != '/') {
+ DEBUG1(DIR, "cached_realpath: purging %s\n", he->key);
+ HashTable_DeleteEntry(&cached_realpaths, he);
+ /* XXX: What about the allocated he->value? Either
+ * free them or document why they cannot be freed. */
+ }
+ he = nhe;
+ }
}
char *
cached_realpath(const char *pathname, char *resolved)
{
- GNode *cache;
- const char *rp;
- void *freeIt;
+ const char *rp;
- if (!pathname || !pathname[0])
- return NULL;
+ if (pathname == NULL || pathname[0] == '\0')
+ return NULL;
- cache = get_cached_realpaths();
+ rp = HashTable_FindValue(&cached_realpaths, pathname);
+ if (rp != NULL) {
+ /* a hit */
+ strncpy(resolved, rp, MAXPATHLEN);
+ resolved[MAXPATHLEN - 1] = '\0';
+ return resolved;
+ }
- if ((rp = Var_Value(pathname, cache, &freeIt)) != NULL) {
- /* a hit */
- strlcpy(resolved, rp, MAXPATHLEN);
- } else if ((rp = realpath(pathname, resolved)) != NULL) {
- Var_Set(pathname, rp, cache);
- } /* else should we negative-cache? */
+ rp = realpath(pathname, resolved);
+ if (rp != NULL) {
+ HashTable_Set(&cached_realpaths, pathname, bmake_strdup(rp));
+ DEBUG2(DIR, "cached_realpath: %s -> %s\n", pathname, rp);
+ return resolved;
+ }
- bmake_free(freeIt);
- return rp ? resolved : NULL;
+ /* should we negative-cache? */
+ return NULL;
}
/*
* Return true if we should die without noise.
- * For example our failing child was a sub-make
- * or failure happend elsewhere.
+ * For example our failing child was a sub-make or failure happened elsewhere.
*/
-int
-dieQuietly(GNode *gn, int bf)
+Boolean
+shouldDieQuietly(GNode *gn, int bf)
{
- static int quietly = -1;
-
- if (quietly < 0) {
- if (DEBUG(JOB) || !getBoolean(".MAKE.DIE_QUIETLY", TRUE))
- quietly = 0;
- else if (bf >= 0)
- quietly = bf;
- else
- quietly = gn != NULL ? ((gn->type & (OP_MAKE)) != 0) : 0;
- }
- return quietly;
+ static int quietly = -1;
+
+ if (quietly < 0) {
+ if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE))
+ quietly = 0;
+ else if (bf >= 0)
+ quietly = bf;
+ else
+ quietly = gn != NULL && (gn->type & OP_MAKE);
+ }
+ return quietly;
}
static void
SetErrorVars(GNode *gn)
{
- StringListNode *ln;
+ StringListNode *ln;
- /*
- * We can print this even if there is no .ERROR target.
- */
- Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL);
- Var_Delete(".ERROR_CMD", VAR_GLOBAL);
+ /*
+ * We can print this even if there is no .ERROR target.
+ */
+ Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL);
+ Var_Delete(".ERROR_CMD", VAR_GLOBAL);
- for (ln = gn->commands->first; ln != NULL; ln = ln->next) {
- const char *cmd = ln->datum;
+ for (ln = gn->commands->first; ln != NULL; ln = ln->next) {
+ const char *cmd = ln->datum;
- if (cmd == NULL)
- break;
- Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL);
- }
+ if (cmd == NULL)
+ break;
+ Var_Append(".ERROR_CMD", cmd, VAR_GLOBAL);
+ }
}
+/* Print some helpful information in case of an error.
+ * The caller should exit soon after calling this function. */
void
-PrintOnError(GNode *gn, const char *s)
+PrintOnError(GNode *gn, const char *msg)
{
- static GNode *en = NULL;
- const char *expr;
- char *cp;
+ static GNode *errorNode = NULL;
- if (DEBUG(HASH)) {
- Targ_Stats();
- Var_Stats();
- }
+ if (DEBUG(HASH)) {
+ Targ_Stats();
+ Var_Stats();
+ }
- /* we generally want to keep quiet if a sub-make died */
- if (dieQuietly(gn, -1))
- return;
+ /* we generally want to keep quiet if a sub-make died */
+ if (shouldDieQuietly(gn, -1))
+ return;
+
+ if (msg != NULL)
+ printf("%s", msg);
+ printf("\n%s: stopped in %s\n", progname, curdir);
- if (s)
- printf("%s", s);
-
- printf("\n%s: stopped in %s\n", progname, curdir);
-
- if (en)
- return; /* we've been here! */
- if (gn)
- SetErrorVars(gn);
- expr = "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}";
- (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &cp);
- /* TODO: handle errors */
- printf("%s", cp);
- free(cp);
- fflush(stdout);
-
- /*
- * Finally, see if there is a .ERROR target, and run it if so.
- */
- en = Targ_FindNode(".ERROR");
- if (en) {
- en->type |= OP_SPECIAL;
- Compat_Make(en, en);
- }
+ if (errorNode != NULL)
+ return; /* we've been here! */
+
+ if (gn != NULL)
+ SetErrorVars(gn);
+
+ {
+ char *errorVarsValues;
+ (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
+ VAR_GLOBAL, VARE_WANTRES, &errorVarsValues);
+ /* TODO: handle errors */
+ printf("%s", errorVarsValues);
+ free(errorVarsValues);
+ }
+
+ fflush(stdout);
+
+ /*
+ * Finally, see if there is a .ERROR target, and run it if so.
+ */
+ errorNode = Targ_FindNode(".ERROR");
+ if (errorNode != NULL) {
+ errorNode->type |= OP_SPECIAL;
+ Compat_Make(errorNode, errorNode);
+ }
}
void
Main_ExportMAKEFLAGS(Boolean first)
{
- static Boolean once = TRUE;
- const char *expr;
- char *s;
+ static Boolean once = TRUE;
+ const char *expr;
+ char *s;
- if (once != first)
- return;
- once = FALSE;
+ if (once != first)
+ return;
+ once = FALSE;
- expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}";
- (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s);
- /* TODO: handle errors */
- if (s[0] != '\0') {
+ expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}";
+ (void)Var_Subst(expr, VAR_CMDLINE, VARE_WANTRES, &s);
+ /* TODO: handle errors */
+ if (s[0] != '\0') {
#ifdef POSIX
- setenv("MAKEFLAGS", s, 1);
+ setenv("MAKEFLAGS", s, 1);
#else
- setenv("MAKE", s, 1);
+ setenv("MAKE", s, 1);
#endif
- }
+ }
}
char *
getTmpdir(void)
{
- static char *tmpdir = NULL;
-
- if (!tmpdir) {
+ static char *tmpdir = NULL;
struct stat st;
- /*
- * Honor $TMPDIR but only if it is valid.
- * Ensure it ends with /.
- */
- (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL,
- VARE_WANTRES, &tmpdir);
+ if (tmpdir != NULL)
+ return tmpdir;
+
+ /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */
+ (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/",
+ VAR_GLOBAL, VARE_WANTRES, &tmpdir);
/* TODO: handle errors */
+
if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) {
- free(tmpdir);
- tmpdir = bmake_strdup(_PATH_TMP);
+ free(tmpdir);
+ tmpdir = bmake_strdup(_PATH_TMP);
}
- }
- return tmpdir;
+ return tmpdir;
}
/*
@@ -2211,73 +2225,45 @@ getTmpdir(void)
int
mkTempFile(const char *pattern, char **out_fname)
{
- static char *tmpdir = NULL;
- char tfile[MAXPATHLEN];
- int fd;
-
- if (pattern != NULL)
- pattern = TMPPAT;
- if (tmpdir == NULL)
- tmpdir = getTmpdir();
- if (pattern[0] == '/') {
- snprintf(tfile, sizeof(tfile), "%s", pattern);
- } else {
- snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern);
- }
- if ((fd = mkstemp(tfile)) < 0)
- Punt("Could not create temporary file %s: %s", tfile, strerror(errno));
- if (out_fname) {
- *out_fname = bmake_strdup(tfile);
- } else {
- unlink(tfile); /* we just want the descriptor */
- }
- return fd;
-}
+ static char *tmpdir = NULL;
+ char tfile[MAXPATHLEN];
+ int fd;
-/*
- * Convert a string representation of a boolean.
- * Anything that looks like "No", "False", "Off", "0" etc,
- * is FALSE, otherwise TRUE.
- */
-Boolean
-s2Boolean(const char *s, Boolean bf)
-{
- switch(s[0]) {
- case '\0': /* not set - the default wins */
- break;
- case '0':
- case 'F':
- case 'f':
- case 'N':
- case 'n':
- return FALSE;
- case 'O':
- case 'o':
- return s[1] != 'F' && s[1] != 'f';
- default:
- return TRUE;
- }
- return bf;
+ if (pattern == NULL)
+ pattern = TMPPAT;
+ if (tmpdir == NULL)
+ tmpdir = getTmpdir();
+ if (pattern[0] == '/') {
+ snprintf(tfile, sizeof tfile, "%s", pattern);
+ } else {
+ snprintf(tfile, sizeof tfile, "%s%s", tmpdir, pattern);
+ }
+ if ((fd = mkstemp(tfile)) < 0)
+ Punt("Could not create temporary file %s: %s", tfile,
+ strerror(errno));
+ if (out_fname) {
+ *out_fname = bmake_strdup(tfile);
+ } else {
+ unlink(
+ tfile); /* we just want the descriptor */
+ }
+ return fd;
}
/*
- * Return a Boolean based on a variable.
- *
- * If the knob is not set, return the fallback.
- * If set, anything that looks or smells like "No", "False", "Off", "0", etc.
- * is FALSE, otherwise TRUE.
+ * Convert a string representation of a boolean into a boolean value.
+ * Anything that looks like "No", "False", "Off", "0" etc. is FALSE,
+ * the empty string is the fallback, everything else is TRUE.
*/
Boolean
-getBoolean(const char *varname, Boolean fallback)
+ParseBoolean(const char *s, Boolean fallback)
{
- char *expr = str_concat3("${", varname, ":U}");
- char *value;
- Boolean res;
-
- (void)Var_Subst(expr, VAR_GLOBAL, VARE_WANTRES, &value);
- /* TODO: handle errors */
- res = s2Boolean(value, fallback);
- free(value);
- free(expr);
- return res;
+ char ch = ch_tolower(s[0]);
+ if (ch == '\0')
+ return fallback;
+ if (ch == '0' || ch == 'f' || ch == 'n')
+ return FALSE;
+ if (ch == 'o')
+ return ch_tolower(s[1]) != 'f';
+ return TRUE;
}
diff --git a/make-bootstrap.sh.in b/make-bootstrap.sh.in
index a75634b3dae6..0ecce455da74 100755
--- a/make-bootstrap.sh.in
+++ b/make-bootstrap.sh.in
@@ -16,7 +16,8 @@ CFLAGS="@CFLAGS@ -I. -I${srcdir} @DEFS@ @CPPFLAGS@ -DMAKE_NATIVE ${XDEFS} -DBMAK
MAKE_VERSION=@_MAKE_VERSION@
MDEFS="-DMAKE_VERSION=\"$MAKE_VERSION\" \
--D@force_machine@MACHINE=\"@machine@\" -DMACHINE_ARCH=\"@machine_arch@\" \
+-D@force_machine@MACHINE=\"@machine@\" \
+-D@force_machine_arch@MACHINE_ARCH=\"@machine_arch@\" \
-D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\""
@@ -59,7 +60,7 @@ do_link() {
}
BASE_OBJECTS="arch.o buf.o compat.o cond.o dir.o enum.o for.o getopt hash.o \
-lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o strlist.o \
+lst.o make.o make_malloc.o metachar.o parse.o sigcompat.o str.o \
suff.o targ.o trace.o var.o util.o"
LIB_OBJECTS="@LIBOBJS@"
diff --git a/make.1 b/make.1
index 4b1ef9ed0fe8..a18c44442b49 100644
--- a/make.1
+++ b/make.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.290 2020/11/01 20:24:45 rillig Exp $
+.\" $NetBSD: make.1,v 1.292 2020/11/14 22:19:13 rillig Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd November 1, 2020
+.Dd November 14, 2020
.Dt MAKE 1
.Os
.Sh NAME
@@ -37,7 +37,7 @@
.Nd maintain program dependencies
.Sh SYNOPSIS
.Nm
-.Op Fl BeikNnqrstWwX
+.Op Fl BeikNnqrSstWwX
.Op Fl C Ar directory
.Op Fl D Ar variable
.Op Fl d Ar flags
@@ -329,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are
up-to-date and 1, otherwise.
.It Fl r
Do not use the built-in rules specified in the system makefile.
+.It Fl S
+Stop processing if an error is encountered.
+This is the default behavior and the opposite of
+.Fl k .
.It Fl s
Do not echo any commands as they are executed.
Equivalent to specifying
@@ -1090,6 +1094,15 @@ to the specified directory if it exists, and set
and
.Ql Ev PWD
to that directory before executing any targets.
+.Pp
+Except in the case of an explicit
+.Ql Ic .OBJDIR
+target,
+.Nm
+will check that the specified directory is writable and ignore it if not.
+This check can be skipped by setting the environment variable
+.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE
+to "no".
.
.It Va .PARSEDIR
A path to the directory of the current
diff --git a/make.c b/make.c
index 52bc75ac8467..d0502e80c8ef 100644
--- a/make.c
+++ b/make.c
@@ -1,4 +1,4 @@
-/* $NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $ */
+/* $NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,33 +68,28 @@
* SUCH DAMAGE.
*/
-/*-
- * make.c --
- * The functions which perform the examination of targets and
- * their suitability for creation
+/* Examination of targets and their suitability for creation.
*
* Interface:
- * Make_Run Initialize things for the module and recreate
- * whatever needs recreating. Returns TRUE if
- * work was (or would have been) done and FALSE
- * otherwise.
+ * Make_Run Initialize things for the module. Returns TRUE if
+ * work was (or would have been) done.
*
- * Make_Update Update all parents of a given child. Performs
- * various bookkeeping chores like the updating
+ * Make_Update After a target is made, update all its parents.
+ * Perform various bookkeeping chores like the updating
* of the youngestChild field of the parent, filling
- * of the IMPSRC context variable, etc. It will
- * place the parent on the toBeMade queue if it
- * should be.
+ * of the IMPSRC context variable, etc. Place the parent
+ * on the toBeMade queue if it should be.
*
- * Make_TimeStamp Function to set the parent's youngestChild field
- * based on a child's modification time.
+ * GNode_UpdateYoungestChild
+ * Update the node's youngestChild field based on the
+ * child's modification time.
*
* Make_DoAllVar Set up the various local variables for a
* target, including the .ALLSRC variable, making
* sure that any variable that needs to exist
* at the very least has the empty value.
*
- * Make_OODate Determine if a target is out-of-date.
+ * GNode_IsOODate Determine if a target is out-of-date.
*
* Make_HandleUse See if a child is a .USE node for a parent
* and perform the .USE actions if so.
@@ -107,17 +102,16 @@
#include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: make.c,v 1.186 2020/11/01 17:47:26 rillig Exp $");
+MAKE_RCSID("$NetBSD: make.c,v 1.209 2020/11/16 22:31:42 rillig Exp $");
/* Sequence # to detect recursion. */
-static unsigned int checked = 1;
+static unsigned int checked_seqno = 1;
/* The current fringe of the graph.
* These are nodes which await examination by MakeOODate.
* It is added to by Make_Update and subtracted from by MakeStartJobs */
static GNodeList *toBeMade;
-static int MakeCheckOrder(void *, void *);
static int MakeBuildParent(void *, void *);
void
@@ -185,11 +179,37 @@ GNode_ShouldExecute(GNode *gn)
/* Update the youngest child of the node, according to the given child. */
void
-Make_TimeStamp(GNode *pgn, GNode *cgn)
+GNode_UpdateYoungestChild(GNode *gn, GNode *cgn)
{
- if (pgn->youngestChild == NULL || cgn->mtime > pgn->youngestChild->mtime) {
- pgn->youngestChild = cgn;
+ if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime)
+ gn->youngestChild = cgn;
+}
+
+static Boolean
+IsOODateRegular(GNode *gn)
+{
+ /* These rules are inherited from the original Make. */
+
+ if (gn->youngestChild != NULL) {
+ if (gn->mtime < gn->youngestChild->mtime) {
+ DEBUG1(MAKE, "modified before source \"%s\"...",
+ GNode_Path(gn->youngestChild));
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) {
+ DEBUG0(MAKE, "non-existent and no sources...");
+ return TRUE;
}
+
+ if (gn->type & OP_DOUBLEDEP) {
+ DEBUG0(MAKE, ":: operator and no sources...");
+ return TRUE;
+ }
+
+ return FALSE;
}
/* See if the node is out of date with respect to its sources.
@@ -204,7 +224,7 @@ Make_TimeStamp(GNode *pgn, GNode *cgn)
* may be changed.
*/
Boolean
-Make_OODate(GNode *gn)
+GNode_IsOODate(GNode *gn)
{
Boolean oodate;
@@ -212,14 +232,13 @@ Make_OODate(GNode *gn)
* Certain types of targets needn't even be sought as their datedness
* doesn't depend on their modification time...
*/
- if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) {
- (void)Dir_MTime(gn, 1);
+ if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
+ Dir_UpdateMTime(gn, TRUE);
if (DEBUG(MAKE)) {
- if (gn->mtime != 0) {
+ if (gn->mtime != 0)
debug_printf("modified %s...", Targ_FmtTime(gn->mtime));
- } else {
+ else
debug_printf("non-existent...");
- }
}
}
@@ -244,8 +263,7 @@ Make_OODate(GNode *gn)
*/
DEBUG0(MAKE, ".USE node...");
oodate = FALSE;
- } else if ((gn->type & OP_LIB) &&
- ((gn->mtime==0) || Arch_IsLib(gn))) {
+ } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) {
DEBUG0(MAKE, "library...");
/*
@@ -261,7 +279,7 @@ Make_OODate(GNode *gn)
*/
DEBUG0(MAKE, ".JOIN node...");
DEBUG1(MAKE, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
- oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE;
+ oodate = (gn->flags & CHILDMADE) != 0;
} else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
/*
* A node which is the object of the force (!) operator or which has
@@ -277,30 +295,7 @@ Make_OODate(GNode *gn)
}
}
oodate = TRUE;
- } else if ((gn->youngestChild != NULL &&
- gn->mtime < gn->youngestChild->mtime) ||
- (gn->youngestChild == NULL &&
- ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
- || gn->type & OP_DOUBLEDEP)))
- {
- /*
- * A node whose modification time is less than that of its
- * youngest child or that has no children (youngestChild == NULL) and
- * either doesn't exist (mtime == 0) and it isn't optional
- * or was the object of a * :: operator is out-of-date.
- * Why? Because that's the way Make does it.
- */
- if (DEBUG(MAKE)) {
- if (gn->youngestChild != NULL &&
- gn->mtime < gn->youngestChild->mtime) {
- debug_printf("modified before source %s...",
- GNode_Path(gn->youngestChild));
- } else if (gn->mtime == 0) {
- debug_printf("non-existent and no sources...");
- } else {
- debug_printf(":: operator and no sources...");
- }
- }
+ } else if (IsOODateRegular(gn)) {
oodate = TRUE;
} else {
/*
@@ -314,7 +309,7 @@ Make_OODate(GNode *gn)
if (gn->flags & FORCE)
debug_printf("non existing child...");
}
- oodate = (gn->flags & FORCE) ? TRUE : FALSE;
+ oodate = (gn->flags & FORCE) != 0;
}
#ifdef USE_META
@@ -333,46 +328,24 @@ Make_OODate(GNode *gn)
if (!oodate) {
GNodeListNode *ln;
for (ln = gn->parents->first; ln != NULL; ln = ln->next)
- Make_TimeStamp(ln->datum, gn);
+ GNode_UpdateYoungestChild(ln->datum, gn);
}
return oodate;
}
-/* Add the node to the list if it needs to be examined. */
-static int
-MakeAddChild(void *gnp, void *lp)
-{
- GNode *gn = gnp;
- GNodeList *l = lp;
-
- if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
- DEBUG2(MAKE, "MakeAddChild: need to examine %s%s\n",
- gn->name, gn->cohort_num);
- Lst_Enqueue(l, gn);
- }
- return 0;
-}
-
-/* Find the pathname of a child that was already made.
- *
- * The path and mtime of the node and the youngestChild of the parent are
- * updated; the unmade children count of the parent is decremented.
- *
- * Input:
- * gnp the node to find
- */
-static int
-MakeFindChild(void *gnp, void *pgnp)
+static void
+PretendAllChildrenAreMade(GNode *pgn)
{
- GNode *gn = gnp;
- GNode *pgn = pgnp;
+ GNodeListNode *ln;
- (void)Dir_MTime(gn, 0);
- Make_TimeStamp(pgn, gn);
- pgn->unmade--;
+ for (ln = pgn->children->first; ln != NULL; ln = ln->next) {
+ GNode *cgn = ln->datum;
- return 0;
+ Dir_UpdateMTime(cgn, FALSE); /* cgn->path may get updated as well */
+ GNode_UpdateYoungestChild(pgn, cgn);
+ pgn->unmade--;
+ }
}
/* Called by Make_Run and SuffApplyTransform on the downward pass to handle
@@ -394,9 +367,9 @@ Make_HandleUse(GNode *cgn, GNode *pgn)
GNodeListNode *ln; /* An element in the children list */
#ifdef DEBUG_SRC
- if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) {
+ if (!(cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM))) {
debug_printf("Make_HandleUse: called for plain node %s\n", cgn->name);
- return;
+ return; /* XXX: debug mode should not affect control flow */
}
#endif
@@ -457,10 +430,10 @@ MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln)
{
Boolean unmarked;
- unmarked = ((cgn->type & OP_MARK) == 0);
+ unmarked = !(cgn->type & OP_MARK);
cgn->type |= OP_MARK;
- if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
+ if (!(cgn->type & (OP_USE|OP_USEBEFORE)))
return;
if (unmarked)
@@ -493,7 +466,10 @@ HandleUseNodes(GNode *gn)
time_t
Make_Recheck(GNode *gn)
{
- time_t mtime = Dir_MTime(gn, 1);
+ time_t mtime;
+
+ Dir_UpdateMTime(gn, TRUE);
+ mtime = gn->mtime;
#ifndef RECHECK
/*
@@ -512,13 +488,11 @@ Make_Recheck(GNode *gn)
* In this case, if the definitions produced by yacc haven't changed
* from before, parse.h won't have been updated and gn->mtime will
* reflect the current modification time for parse.h. This is
- * something of a kludge, I admit, but it's a useful one..
- * XXX: People like to use a rule like
+ * something of a kludge, I admit, but it's a useful one.
*
- * FRC:
- *
- * To force things that depend on FRC to be made, so we have to
- * check for gn->children being empty as well...
+ * XXX: People like to use a rule like "FRC:" to force things that
+ * depend on FRC to be made, so we have to check for gn->children
+ * being empty as well.
*/
if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
gn->mtime = now;
@@ -535,7 +509,7 @@ Make_Recheck(GNode *gn)
* using the same file from a common server), there are times
* when the modification time of a file created on a remote
* machine will not be modified before the local stat() implied by
- * the Dir_MTime occurs, thus leading us to believe that the file
+ * the Dir_UpdateMTime occurs, thus leading us to believe that the file
* is unchanged, wreaking havoc with files that depend on this one.
*
* I have decided it is better to make too much than to make too
@@ -543,8 +517,8 @@ Make_Recheck(GNode *gn)
* -- ardeb 1/12/88
*/
/*
- * Christos, 4/9/92: If we are saving commands pretend that
- * the target is made now. Otherwise archives with ... rules
+ * Christos, 4/9/92: If we are saving commands, pretend that
+ * the target is made now. Otherwise archives with '...' rules
* don't work!
*/
if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) ||
@@ -552,12 +526,14 @@ Make_Recheck(GNode *gn)
DEBUG2(MAKE, " recheck(%s): update time from %s to now\n",
gn->name, Targ_FmtTime(gn->mtime));
gn->mtime = now;
- }
- else {
+ } else {
DEBUG2(MAKE, " recheck(%s): current update time: %s\n",
gn->name, Targ_FmtTime(gn->mtime));
}
#endif
+
+ /* XXX: The returned mtime may differ from gn->mtime.
+ * Intentionally? */
return mtime;
}
@@ -581,6 +557,25 @@ UpdateImplicitParentsVars(GNode *cgn, const char *cname)
}
}
+/* See if a .ORDER rule stops us from building this node. */
+static Boolean
+IsWaitingForOrder(GNode *gn)
+{
+ GNodeListNode *ln;
+
+ for (ln = gn->order_pred->first; ln != NULL; ln = ln->next) {
+ GNode *ogn = ln->datum;
+
+ if (ogn->made >= MADE || !(ogn->flags & REMAKE))
+ continue;
+
+ DEBUG2(MAKE, "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n",
+ ogn->name, ogn->cohort_num);
+ return TRUE;
+ }
+ return FALSE;
+}
+
/* Perform update on the parents of a node. Used by JobFinish once
* a node has been dealt with and by MakeStartJobs if it finds an
* up-to-date node.
@@ -610,7 +605,7 @@ Make_Update(GNode *cgn)
GNode *centurion;
/* It is save to re-examine any nodes again */
- checked++;
+ checked_seqno++;
cname = GNode_VarTarget(cgn);
@@ -647,11 +642,11 @@ Make_Update(GNode *cgn)
for (ln = parents->first; ln != NULL; ln = ln->next) {
GNode *pgn = ln->datum;
- if (DEBUG(MAKE))
- debug_printf("inspect parent %s%s: flags %x, "
- "type %x, made %d, unmade %d ",
- pgn->name, pgn->cohort_num, pgn->flags,
- pgn->type, pgn->made, pgn->unmade - 1);
+ if (DEBUG(MAKE)) {
+ debug_printf("inspect parent %s%s: ", pgn->name, pgn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, "", pgn, "");
+ debug_printf(", unmade %d ", pgn->unmade - 1);
+ }
if (!(pgn->flags & REMAKE)) {
/* This parent isn't needed */
@@ -674,10 +669,10 @@ Make_Update(GNode *cgn)
continue;
}
- if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
+ if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) {
if (cgn->made == MADE)
pgn->flags |= CHILDMADE;
- (void)Make_TimeStamp(pgn, cgn);
+ GNode_UpdateYoungestChild(pgn, cgn);
}
/*
@@ -716,11 +711,10 @@ Make_Update(GNode *cgn)
DEBUG0(MAKE, "- not deferred\n");
continue;
}
- assert(pgn->order_pred != NULL);
- if (Lst_ForEachUntil(pgn->order_pred, MakeCheckOrder, 0)) {
- /* A .ORDER rule stops us building this */
+
+ if (IsWaitingForOrder(pgn))
continue;
- }
+
if (DEBUG(MAKE)) {
debug_printf("- %s%s made, schedule %s%s (made %d)\n",
cgn->name, cgn->cohort_num,
@@ -771,7 +765,7 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
return;
cgn->type |= OP_MARK;
- if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) {
+ if (!(cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE))) {
const char *child, *allsrc;
if (cgn->type & OP_ARCHV)
@@ -798,9 +792,9 @@ MakeAddAllSrc(GNode *cgn, GNode *pgn)
* the start of the make. This is to keep pmake from getting
* confused if something else updates the parent after the
* make starts (shouldn't happen, I know, but sometimes it
- * does). In such a case, if we've updated the kid, the parent
+ * does). In such a case, if we've updated the child, the parent
* is likely to have a modification time later than that of
- * the kid and anything that relies on the OODATE variable will
+ * the child and anything that relies on the OODATE variable will
* be hosed.
*
* XXX: This will cause all made children to go in the OODATE
@@ -838,12 +832,10 @@ Make_DoAllVar(GNode *gn)
for (ln = gn->children->first; ln != NULL; ln = ln->next)
MakeAddAllSrc(ln->datum, gn);
- if (!Var_Exists(OODATE, gn)) {
+ if (!Var_Exists(OODATE, gn))
Var_Set(OODATE, "", gn);
- }
- if (!Var_Exists(ALLSRC, gn)) {
+ if (!Var_Exists(ALLSRC, gn))
Var_Set(ALLSRC, "", gn);
- }
if (gn->type & OP_JOIN)
Var_Set(TARGET, GNode_VarAllsrc(gn), gn);
@@ -851,31 +843,20 @@ Make_DoAllVar(GNode *gn)
}
static int
-MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED)
-{
- GNode *bn = v_bn;
-
- if (bn->made >= MADE || !(bn->flags & REMAKE))
- return 0;
-
- DEBUG2(MAKE, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
- bn->name, bn->cohort_num);
- return 1;
-}
-
-static int
MakeBuildChild(void *v_cn, void *toBeMade_next)
{
GNode *cn = v_cn;
- DEBUG4(MAKE, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
- cn->name, cn->cohort_num, cn->made, cn->type);
+ if (DEBUG(MAKE)) {
+ debug_printf("MakeBuildChild: inspect %s%s, ",
+ cn->name, cn->cohort_num);
+ GNode_FprintDetails(opts.debug_file, "", cn, "\n");
+ }
if (cn->made > DEFERRED)
return 0;
/* If this node is on the RHS of a .ORDER, check LHSs. */
- assert(cn->order_pred);
- if (Lst_ForEachUntil(cn->order_pred, MakeCheckOrder, 0)) {
+ if (IsWaitingForOrder(cn)) {
/* Can't build this (or anything else in this child list) yet */
cn->made = DEFERRED;
return 0; /* but keep looking */
@@ -899,7 +880,7 @@ MakeBuildChild(void *v_cn, void *toBeMade_next)
return cn->type & OP_WAIT && cn->unmade > 0;
}
-/* When a .ORDER LHS node completes we do this on each RHS */
+/* When a .ORDER LHS node completes, we do this on each RHS. */
static int
MakeBuildParent(void *v_pn, void *toBeMade_next)
{
@@ -918,21 +899,21 @@ MakeBuildParent(void *v_pn, void *toBeMade_next)
/* Start as many jobs as possible, taking them from the toBeMade queue.
*
- * If the query flag was given to pmake, no job will be started,
+ * If the -q option was given, no job will be started,
* but as soon as an out-of-date target is found, this function
- * returns TRUE. At all other times, this function returns FALSE.
+ * returns TRUE. In all other cases, this function returns FALSE.
*/
static Boolean
MakeStartJobs(void)
{
- GNode *gn;
- int have_token = 0;
+ GNode *gn;
+ Boolean have_token = FALSE;
while (!Lst_IsEmpty(toBeMade)) {
/* Get token now to avoid cycling job-list when we only have 1 token */
if (!have_token && !Job_TokenWithdraw())
break;
- have_token = 1;
+ have_token = TRUE;
gn = Lst_Dequeue(toBeMade);
DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num);
@@ -943,13 +924,13 @@ MakeStartJobs(void)
make_abort(gn, __LINE__);
}
- if (gn->checked_seqno == checked) {
+ if (gn->checked_seqno == checked_seqno) {
/* We've already looked at this node since a job finished... */
DEBUG2(MAKE, "already checked %s%s\n", gn->name, gn->cohort_num);
gn->made = DEFERRED;
continue;
}
- gn->checked_seqno = checked;
+ gn->checked_seqno = checked_seqno;
if (gn->unmade != 0) {
/*
@@ -964,14 +945,13 @@ MakeStartJobs(void)
}
gn->made = BEINGMADE;
- if (Make_OODate(gn)) {
+ if (GNode_IsOODate(gn)) {
DEBUG0(MAKE, "out-of-date\n");
- if (opts.queryFlag) {
+ if (opts.queryFlag)
return TRUE;
- }
Make_DoAllVar(gn);
Job_Make(gn);
- have_token = 0;
+ have_token = FALSE;
} else {
DEBUG0(MAKE, "up-to-date\n");
gn->made = UPTODATE;
@@ -994,6 +974,7 @@ MakeStartJobs(void)
return FALSE;
}
+/* Print the status of a .ORDER node. */
static void
MakePrintStatusOrderNode(GNode *ogn, GNode *gn)
{
@@ -1074,7 +1055,7 @@ MakePrintStatus(GNode *gn, int *errors)
* print out the cycle by recursing on its children.
*/
if (!(gn->flags & CYCLE)) {
- /* Fist time we've seen this node, check all children */
+ /* First time we've seen this node, check all children */
gn->flags |= CYCLE;
MakePrintStatusList(gn->children, errors);
/* Mark that this node needn't be processed again */
@@ -1103,6 +1084,25 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
break;
}
+static void
+ExamineLater(GNodeList *examine, GNodeList *toBeExamined)
+{
+ ListNode *ln;
+
+ for (ln = toBeExamined->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
+
+ if (gn->flags & REMAKE)
+ continue;
+ if (gn->type & (OP_USE | OP_USEBEFORE))
+ continue;
+
+ DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n",
+ gn->name, gn->cohort_num);
+ Lst_Enqueue(examine, gn);
+ }
+}
+
/* Expand .USE nodes and create a new targets list.
*
* Input:
@@ -1111,17 +1111,8 @@ MakePrintStatusList(GNodeList *gnodes, int *errors)
void
Make_ExpandUse(GNodeList *targs)
{
- GNodeList *examine; /* List of targets to examine */
-
- {
- /* XXX: Why is it necessary to copy the list? There shouldn't be
- * any modifications to the list, at least the function name
- * ExpandUse doesn't suggest that. */
- GNodeListNode *ln;
- examine = Lst_New();
- for (ln = targs->first; ln != NULL; ln = ln->next)
- Lst_Append(examine, ln->datum);
- }
+ GNodeList *examine = Lst_New(); /* Queue of targets to examine */
+ Lst_AppendAll(examine, targs);
/*
* Make an initial downward pass over the graph, marking nodes to be made
@@ -1151,9 +1142,8 @@ Make_ExpandUse(GNodeList *targs)
* expansions.
*/
if (gn->type & OP_ARCHV) {
- char *eoa, *eon;
- eoa = strchr(gn->name, '(');
- eon = strchr(gn->name, ')');
+ char *eoa = strchr(gn->name, '(');
+ char *eon = strchr(gn->name, ')');
if (eoa == NULL || eon == NULL)
continue;
*eoa = '\0';
@@ -1164,23 +1154,22 @@ Make_ExpandUse(GNodeList *targs)
*eon = ')';
}
- (void)Dir_MTime(gn, 0);
+ Dir_UpdateMTime(gn, FALSE);
Var_Set(TARGET, GNode_Path(gn), gn);
UnmarkChildren(gn);
HandleUseNodes(gn);
- if ((gn->type & OP_MADE) == 0)
+ if (!(gn->type & OP_MADE))
Suff_FindDeps(gn);
else {
- /* Pretend we made all this node's children */
- Lst_ForEachUntil(gn->children, MakeFindChild, gn);
+ PretendAllChildrenAreMade(gn);
if (gn->unmade != 0)
- printf("Warning: %s%s still has %d unmade children\n",
- gn->name, gn->cohort_num, gn->unmade);
+ printf("Warning: %s%s still has %d unmade children\n",
+ gn->name, gn->cohort_num, gn->unmade);
}
if (gn->unmade != 0)
- Lst_ForEachUntil(gn->children, MakeAddChild, examine);
+ ExamineLater(examine, gn->children);
}
Lst_Free(examine);
@@ -1218,7 +1207,7 @@ Make_ProcessWait(GNodeList *targs)
* Perhaps this should be done earlier...
*/
- pgn = Targ_NewGN(".MAIN");
+ pgn = GNode_New(".MAIN");
pgn->flags = REMAKE;
pgn->type = OP_PHONY | OP_DEPENDS;
/* Get it displayed in the diag dumps */
@@ -1353,9 +1342,9 @@ Make_Run(GNodeList *targs)
MakePrintStatusList(targs, &errors);
if (DEBUG(MAKE)) {
debug_printf("done: errors %d\n", errors);
- if (errors)
+ if (errors > 0)
Targ_PrintGraph(4);
}
}
- return errors != 0;
+ return errors > 0;
}
diff --git a/make.h b/make.h
index 0c10706d3632..2803ceac3f95 100644
--- a/make.h
+++ b/make.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make.h,v 1.179 2020/11/01 17:47:26 rillig Exp $ */
+/* $NetBSD: make.h,v 1.210 2020/11/16 21:53:10 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -135,6 +135,8 @@
#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
#endif
+#define MAKE_INLINE static inline MAKE_ATTR_UNUSED
+
/*
* A boolean type is defined as an integer, not an enum, for historic reasons.
* The only allowed values are the constants TRUE and FALSE (1 and 0).
@@ -187,7 +189,7 @@ typedef int Boolean;
#define POSIX_SIGNALS
#endif
-typedef enum {
+typedef enum GNodeMade {
UNMADE, /* Not examined yet */
DEFERRED, /* Examined once (building child) */
REQUESTED, /* on toBeMade list */
@@ -207,6 +209,8 @@ typedef enum {
*
* Some of the OP_ constants can be combined, others cannot. */
typedef enum GNodeType {
+ OP_NONE = 0,
+
/* The dependency operator ':' is the most common one. The commands of
* this node are executed if any child is out-of-date. */
OP_DEPENDS = 1 << 0,
@@ -215,7 +219,8 @@ typedef enum GNodeType {
OP_FORCE = 1 << 1,
/* The dependency operator '::' behaves like ':', except that it allows
* multiple dependency groups to be defined. Each of these groups is
- * executed on its own, independently from the others. */
+ * executed on its own, independently from the others. Each individual
+ * dependency group is called a cohort. */
OP_DOUBLEDEP = 1 << 2,
/* Matches the dependency operators ':', '!' and '::'. */
@@ -246,7 +251,7 @@ typedef enum GNodeType {
/* Like .USE, only prepend commands */
OP_USEBEFORE = 1 << 13,
/* The node is invisible to its parents. I.e. it doesn't show up in the
- * parents' local variables. */
+ * parents' local variables (.IMPSRC, .ALLSRC). */
OP_INVISIBLE = 1 << 14,
/* The node is exempt from normal 'main target' processing in parse.c */
OP_NOTMAIN = 1 << 15,
@@ -254,7 +259,10 @@ typedef enum GNodeType {
OP_PHONY = 1 << 16,
/* Don't search for file in the path */
OP_NOPATH = 1 << 17,
- /* .WAIT phony node */
+ /* In a dependency line "target: source1 .WAIT source2", source1 is made
+ * first, including its children. Once that is finished, source2 is made,
+ * including its children. The .WAIT keyword may appear more than once in
+ * a single dependency declaration. */
OP_WAIT = 1 << 18,
/* .NOMETA do not create a .meta file */
OP_NOMETA = 1 << 19,
@@ -267,7 +275,7 @@ typedef enum GNodeType {
/* Attributes applied by PMake */
- /* The node is a transformation rule */
+ /* The node is a transformation rule, such as ".c.o". */
OP_TRANSFORM = 1 << 31,
/* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */
@@ -337,7 +345,7 @@ typedef struct GNode {
int unmade; /* The number of unmade children */
/* The modification time; 0 means the node does not have a corresponding
- * file; see Make_OODate. */
+ * file; see GNode_IsOODate. */
time_t mtime;
struct GNode *youngestChild;
@@ -346,9 +354,6 @@ typedef struct GNode {
* file.c has the node for file.o in this list. */
GNodeList *implicitParents;
- /* Other nodes of the same name, for the '::' operator. */
- GNodeList *cohorts;
-
/* The nodes that depend on this one, or in other words, the nodes for
* which this is a source. */
GNodeList *parents;
@@ -364,6 +369,8 @@ typedef struct GNode {
* in the normal sense. */
GNodeList *order_succ;
+ /* Other nodes of the same name, for the '::' dependency operator. */
+ GNodeList *cohorts;
/* The "#n" suffix for this cohort, or "" for other nodes */
char cohort_num[8];
/* The number of unmade instances on the cohorts list */
@@ -389,20 +396,20 @@ typedef struct GNode {
* but the Suff module) */
struct Suff *suffix;
- /* filename where the GNode got defined */
+ /* Filename where the GNode got defined */
+ /* XXX: What is the lifetime of this string? */
const char *fname;
- /* line number where the GNode got defined */
+ /* Line number where the GNode got defined */
int lineno;
} GNode;
-/*
- * Error levels for parsing. PARSE_FATAL means the process cannot continue
- * once the top-level makefile has been parsed. PARSE_WARNING and PARSE_INFO
- * mean it can.
- */
+/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
+ /* Exit when the current top-level makefile has been parsed completely. */
PARSE_FATAL = 1,
+ /* Print "warning"; may be upgraded to fatal by the -w option. */
PARSE_WARNING,
+ /* Informational, mainly used during development of makefiles. */
PARSE_INFO
} ParseErrorLevel;
@@ -415,9 +422,7 @@ typedef enum CondEvalResult {
COND_INVALID /* Not a conditional statement */
} CondEvalResult;
-/*
- * Definitions for the "local" variables. Used only for clarity.
- */
+/* Names of the variables that are "local" to a specific target. */
#define TARGET "@" /* Target of dependency */
#define OODATE "?" /* All out-of-date sources */
#define ALLSRC ">" /* All sources */
@@ -426,58 +431,77 @@ typedef enum CondEvalResult {
#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
#define MEMBER "%" /* Member in "archive(member)" syntax */
-#define FTARGET "@F" /* file part of TARGET */
-#define DTARGET "@D" /* directory part of TARGET */
-#define FIMPSRC "<F" /* file part of IMPSRC */
-#define DIMPSRC "<D" /* directory part of IMPSRC */
-#define FPREFIX "*F" /* file part of PREFIX */
-#define DPREFIX "*D" /* directory part of PREFIX */
-
/*
* Global Variables
*/
-extern SearchPath *dirSearchPath;
- /* The list of directories to search when
- * looking for targets */
-extern Boolean allPrecious; /* True if every target is precious */
-extern Boolean deleteOnError; /* True if failed targets should be deleted */
-extern Boolean doing_depend; /* TRUE if processing .depend */
-
-extern GNode *DEFAULT; /* .DEFAULT rule */
-
-extern GNode *VAR_INTERNAL; /* Variables defined internally by make
- * which should not override those set by
- * makefiles.
- */
-extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g
- * in the Makefile itself */
-extern GNode *VAR_CMDLINE; /* Variables defined on the command line */
-extern char var_Error[]; /* Value returned by Var_Parse when an error
- * is encountered. It actually points to
- * an empty string, so naive callers needn't
- * worry about it. */
-
-extern time_t now; /* The time at the start of this whole
- * process */
-
-extern Boolean oldVars; /* Do old-style variable substitution */
-extern SearchPath *sysIncPath; /* The system include path. */
-extern SearchPath *defSysIncPath; /* The default system include path. */
+/* True if every target is precious */
+extern Boolean allPrecious;
+/* True if failed targets should be deleted */
+extern Boolean deleteOnError;
+/* TRUE while processing .depend */
+extern Boolean doing_depend;
+/* .DEFAULT rule */
+extern GNode *defaultNode;
+
+/* Variables defined internally by make which should not override those set
+ * by makefiles. */
+extern GNode *VAR_INTERNAL;
+/* Variables defined in a global context, e.g in the Makefile itself. */
+extern GNode *VAR_GLOBAL;
+/* Variables defined on the command line. */
+extern GNode *VAR_CMDLINE;
+
+/* Value returned by Var_Parse when an error is encountered. It actually
+ * points to an empty string, so naive callers needn't worry about it. */
+extern char var_Error[];
+
+/* The time at the start of this whole process */
+extern time_t now;
-extern char curdir[]; /* Startup directory */
-extern char *progname; /* The program name */
-extern char *makeDependfile; /* .depend */
-extern char **savedEnv; /* if we replaced environ this will be non-NULL */
+/*
+ * If FALSE (the default behavior), undefined subexpressions in a variable
+ * expression are discarded. If TRUE (only during variable assignments using
+ * the ':=' assignment operator, no matter how deeply nested), they are
+ * preserved and possibly expanded later when the variable from the
+ * subexpression has been defined.
+ *
+ * Example for a ':=' assignment:
+ * CFLAGS = $(.INCLUDES)
+ * CFLAGS := -I.. $(CFLAGS)
+ * # If .INCLUDES (an undocumented special variable, by the way) is
+ * # still undefined, the updated CFLAGS becomes "-I.. $(.INCLUDES)".
+ */
+extern Boolean preserveUndefined;
-extern int makelevel;
+/* The list of directories to search when looking for targets (set by the
+ * special target .PATH). */
+extern SearchPath *dirSearchPath;
+/* Used for .include "...". */
+extern SearchPath *parseIncPath;
+/* Used for .include <...>, for the built-in sys.mk and makefiles from the
+ * command line arguments. */
+extern SearchPath *sysIncPath;
+/* The default for sysIncPath. */
+extern SearchPath *defSysIncPath;
+
+/* Startup directory */
+extern char curdir[];
+/* The basename of the program name, suffixed with [n] for sub-makes. */
+extern char *progname;
+/* Name of the .depend makefile */
+extern char *makeDependfile;
+/* If we replaced environ, this will be non-NULL. */
+extern char **savedEnv;
+
+extern int makelevel;
/*
* We cannot vfork() in a child of vfork().
* Most systems do not enforce this but some do.
*/
#define vFork() ((getpid() == myPid) ? vfork() : fork())
-extern pid_t myPid;
+extern pid_t myPid;
#define MAKEFLAGS ".MAKEFLAGS"
#define MAKEOVERRIDES ".MAKEOVERRIDES"
@@ -485,7 +509,7 @@ extern pid_t myPid;
#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */
#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all makefiles already loaded */
#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */
-#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
+#define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE"
#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */
#define MAKE_MODE ".MAKE.MODE"
#ifndef MAKE_LEVEL_ENV
@@ -493,29 +517,28 @@ extern pid_t myPid;
#endif
typedef enum DebugFlags {
+ DEBUG_NONE = 0,
DEBUG_ARCH = 1 << 0,
DEBUG_COND = 1 << 1,
- DEBUG_DIR = 1 << 2,
- DEBUG_GRAPH1 = 1 << 3,
- DEBUG_GRAPH2 = 1 << 4,
- DEBUG_JOB = 1 << 5,
- DEBUG_MAKE = 1 << 6,
- DEBUG_SUFF = 1 << 7,
- DEBUG_TARG = 1 << 8,
- DEBUG_VAR = 1 << 9,
- DEBUG_FOR = 1 << 10,
- DEBUG_SHELL = 1 << 11,
- DEBUG_ERROR = 1 << 12,
- DEBUG_LOUD = 1 << 13,
- DEBUG_META = 1 << 14,
- DEBUG_HASH = 1 << 15,
-
- DEBUG_GRAPH3 = 1 << 16,
- DEBUG_SCRIPT = 1 << 17,
- DEBUG_PARSE = 1 << 18,
- DEBUG_CWD = 1 << 19,
-
- DEBUG_LINT = 1 << 20
+ DEBUG_CWD = 1 << 2,
+ DEBUG_DIR = 1 << 3,
+ DEBUG_ERROR = 1 << 4,
+ DEBUG_FOR = 1 << 5,
+ DEBUG_GRAPH1 = 1 << 6,
+ DEBUG_GRAPH2 = 1 << 7,
+ DEBUG_GRAPH3 = 1 << 8,
+ DEBUG_HASH = 1 << 9,
+ DEBUG_JOB = 1 << 10,
+ DEBUG_LOUD = 1 << 11,
+ DEBUG_MAKE = 1 << 12,
+ DEBUG_META = 1 << 13,
+ DEBUG_PARSE = 1 << 14,
+ DEBUG_SCRIPT = 1 << 15,
+ DEBUG_SHELL = 1 << 16,
+ DEBUG_SUFF = 1 << 17,
+ DEBUG_TARG = 1 << 18,
+ DEBUG_VAR = 1 << 19,
+ DEBUG_ALL = (1 << 20) - 1
} DebugFlags;
#define CONCAT(a,b) a##b
@@ -549,8 +572,9 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
else debug_printf(fmt, arg1, arg2, arg3, arg4, arg5)
typedef enum PrintVarsMode {
- COMPAT_VARS = 1,
- EXPAND_VARS
+ PVM_NONE,
+ PVM_UNEXPANDED,
+ PVM_EXPANDED
} PrintVarsMode;
/* Command line options */
@@ -565,6 +589,12 @@ typedef struct CmdOpts {
/* -df: debug output is written here - default stderr */
FILE *debug_file;
+ /* -dL: lint mode
+ *
+ * Runs make in strict mode, with additional checks and better error
+ * handling. */
+ Boolean lint;
+
/* -dV: for the -V option, print unexpanded variable values */
Boolean debugVflag;
@@ -630,49 +660,49 @@ extern CmdOpts opts;
#include "nonints.h"
-void Make_TimeStamp(GNode *, GNode *);
-Boolean Make_OODate(GNode *);
+void GNode_UpdateYoungestChild(GNode *, GNode *);
+Boolean GNode_IsOODate(GNode *);
void Make_ExpandUse(GNodeList *);
time_t Make_Recheck(GNode *);
void Make_HandleUse(GNode *, GNode *);
void Make_Update(GNode *);
void Make_DoAllVar(GNode *);
Boolean Make_Run(GNodeList *);
-int dieQuietly(GNode *, int);
+Boolean shouldDieQuietly(GNode *, int);
void PrintOnError(GNode *, const char *);
void Main_ExportMAKEFLAGS(Boolean);
-Boolean Main_SetObjdir(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
int mkTempFile(const char *, char **);
-int str2Lst_Append(StringList *, char *, const char *);
+int str2Lst_Append(StringList *, char *);
void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
Boolean GNode_ShouldExecute(GNode *gn);
/* See if the node was seen on the left-hand side of a dependency operator. */
-static MAKE_ATTR_UNUSED Boolean
+MAKE_INLINE Boolean
GNode_IsTarget(const GNode *gn)
{
return (gn->type & OP_OPMASK) != 0;
}
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_Path(const GNode *gn)
{
return gn->path != NULL ? gn->path : gn->name;
}
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarTarget(GNode *gn) { return Var_ValueDirect(TARGET, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarOodate(GNode *gn) { return Var_ValueDirect(OODATE, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarAllsrc(GNode *gn) { return Var_ValueDirect(ALLSRC, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarImpsrc(GNode *gn) { return Var_ValueDirect(IMPSRC, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarPrefix(GNode *gn) { return Var_ValueDirect(PREFIX, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarArchive(GNode *gn) { return Var_ValueDirect(ARCHIVE, gn); }
-static MAKE_ATTR_UNUSED const char *
+MAKE_INLINE const char *
GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#ifdef __GNUC__
@@ -703,35 +733,49 @@ GNode_VarMember(GNode *gn) { return Var_ValueDirect(MEMBER, gn); }
#define KILLPG(pid, sig) killpg((pid), (sig))
#endif
-static inline MAKE_ATTR_UNUSED Boolean ch_isalnum(char ch)
-{ return isalnum((unsigned char)ch) != 0; }
-static inline MAKE_ATTR_UNUSED Boolean ch_isalpha(char ch)
-{ return isalpha((unsigned char)ch) != 0; }
-static inline MAKE_ATTR_UNUSED Boolean ch_isdigit(char ch)
-{ return isdigit((unsigned char)ch) != 0; }
-static inline MAKE_ATTR_UNUSED Boolean ch_isspace(char ch)
-{ return isspace((unsigned char)ch) != 0; }
-static inline MAKE_ATTR_UNUSED Boolean ch_isupper(char ch)
-{ return isupper((unsigned char)ch) != 0; }
-static inline MAKE_ATTR_UNUSED char ch_tolower(char ch)
-{ return (char)tolower((unsigned char)ch); }
-static inline MAKE_ATTR_UNUSED char ch_toupper(char ch)
-{ return (char)toupper((unsigned char)ch); }
-
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE Boolean
+ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; }
+MAKE_INLINE Boolean
+ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; }
+MAKE_INLINE Boolean
+ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; }
+MAKE_INLINE Boolean
+ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; }
+MAKE_INLINE Boolean
+ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; }
+MAKE_INLINE char
+ch_tolower(char ch) { return (char)tolower((unsigned char)ch); }
+MAKE_INLINE char
+ch_toupper(char ch) { return (char)toupper((unsigned char)ch); }
+
+MAKE_INLINE void
cpp_skip_whitespace(const char **pp)
{
while (ch_isspace(**pp))
(*pp)++;
}
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE void
+cpp_skip_hspace(const char **pp)
+{
+ while (**pp == ' ' || **pp == '\t')
+ (*pp)++;
+}
+
+MAKE_INLINE void
pp_skip_whitespace(char **pp)
{
while (ch_isspace(**pp))
(*pp)++;
}
+MAKE_INLINE void
+pp_skip_hspace(char **pp)
+{
+ while (**pp == ' ' || **pp == '\t')
+ (*pp)++;
+}
+
#ifdef MAKE_NATIVE
# include <sys/cdefs.h>
# ifndef lint
diff --git a/make_malloc.h b/make_malloc.h
index aa03f380070e..551495fb6266 100644
--- a/make_malloc.h
+++ b/make_malloc.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make_malloc.h,v 1.12 2020/10/19 23:43:55 rillig Exp $ */
+/* $NetBSD: make_malloc.h,v 1.13 2020/11/10 00:32:12 rillig Exp $ */
/*-
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -46,7 +46,7 @@ char *bmake_strsedup(const char *, const char *);
*
* The case of a NULL pointer happens especially often after Var_Value,
* since only environment variables need to be freed, but not others. */
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE void
bmake_free(void *p)
{
if (p != NULL)
diff --git a/meta.c b/meta.c
index 776621f39ce0..e77fa3d73a7b 100644
--- a/meta.c
+++ b/meta.c
@@ -1,4 +1,4 @@
-/* $NetBSD: meta.c,v 1.136 2020/10/31 12:04:24 rillig Exp $ */
+/* $NetBSD: meta.c,v 1.144 2020/11/15 12:02:44 rillig Exp $ */
/*
* Implement 'meta' mode.
@@ -84,7 +84,6 @@ static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */
static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */
extern Boolean forceJobs;
-extern Boolean comatMake;
extern char **environ;
#define MAKE_META_PREFIX ".MAKE.META.PREFIX"
@@ -97,7 +96,7 @@ extern char **environ;
#endif
#if !defined(HAVE_STRSEP)
-# define strsep(s, d) stresep((s), (d), 0)
+# define strsep(s, d) stresep((s), (d), '\0')
#endif
/*
@@ -184,7 +183,7 @@ filemon_read(FILE *mfp, int fd)
error = 0;
fprintf(mfp, "\n-- filemon acquired metadata --\n");
- while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ while ((n = read(fd, buf, sizeof buf)) > 0) {
if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n)
error = EIO;
}
@@ -276,12 +275,12 @@ meta_name(char *mname, size_t mnamelen,
* next time through.
*/
if (tname[0] == '/') {
- strlcpy(buf, tname, sizeof(buf));
+ strlcpy(buf, tname, sizeof buf);
} else {
- snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
+ snprintf(buf, sizeof buf, "%s/%s", cwd, tname);
}
- eat_dots(buf, sizeof(buf), 1); /* ./ */
- eat_dots(buf, sizeof(buf), 2); /* ../ */
+ eat_dots(buf, sizeof buf, 1); /* ./ */
+ eat_dots(buf, sizeof buf, 2); /* ../ */
tname = buf;
}
}
@@ -329,7 +328,7 @@ is_submake(void *cmdp, void *gnp)
char *cp2;
int rc = 0; /* keep looking */
- if (!p_make) {
+ if (p_make == NULL) {
void *dontFreeIt;
p_make = Var_Value(".MAKE", gn, &dontFreeIt);
p_len = strlen(p_make);
@@ -341,7 +340,7 @@ is_submake(void *cmdp, void *gnp)
cmd = mp;
}
cp2 = strstr(cmd, p_make);
- if ((cp2)) {
+ if (cp2 != NULL) {
switch (cp2[p_len]) {
case '\0':
case ' ':
@@ -415,7 +414,7 @@ static Boolean
meta_needed(GNode *gn, const char *dname,
char *objdir, int verbose)
{
- struct make_stat mst;
+ struct cached_stat cst;
if (verbose)
verbose = DEBUG(META);
@@ -446,7 +445,7 @@ meta_needed(GNode *gn, const char *dname,
}
/* The object directory may not exist. Check it.. */
- if (cached_stat(dname, &mst) != 0) {
+ if (cached_stat(dname, &cst) != 0) {
if (verbose)
debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name);
return FALSE;
@@ -513,7 +512,7 @@ meta_create(BuildMon *pbm, GNode *gn)
/* Don't create meta data. */
goto out;
- fname = meta_name(pbm->meta_fname, sizeof(pbm->meta_fname),
+ fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname,
dname, tname, objdir);
#ifdef DEBUG_META_MODE
@@ -529,7 +528,7 @@ meta_create(BuildMon *pbm, GNode *gn)
printCMDs(gn, &mf);
- fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
+ fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof buf));
fprintf(mf.fp, "TARGET %s\n", tname);
cp = GNode_VarOodate(gn);
if (cp && *cp) {
@@ -585,7 +584,7 @@ meta_init(void)
#define get_mode_bf(bf, token) \
if ((cp = strstr(make_mode, token))) \
- bf = boolValue(&cp[sizeof(token) - 1])
+ bf = boolValue(cp + sizeof (token) - 1)
/*
* Initialization we need after reading makefiles.
@@ -630,7 +629,7 @@ meta_mode_init(const char *make_mode)
if (once)
return;
once = 1;
- memset(&Mybm, 0, sizeof(Mybm));
+ memset(&Mybm, 0, sizeof Mybm);
/*
* We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
*/
@@ -638,7 +637,7 @@ meta_mode_init(const char *make_mode)
(void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaBailiwickStr);
/* TODO: handle errors */
- str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL);
+ str2Lst_Append(metaBailiwick, metaBailiwickStr);
/*
* We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
*/
@@ -648,7 +647,7 @@ meta_mode_init(const char *make_mode)
(void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}",
VAR_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr);
/* TODO: handle errors */
- str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL);
+ str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr);
/*
* We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
@@ -784,7 +783,7 @@ meta_job_error(Job *job, GNode *gn, int flags, int status)
if (job != NULL) {
pbm = &job->bm;
- if (!gn)
+ if (gn == NULL)
gn = job->node;
} else {
pbm = &Mybm;
@@ -798,9 +797,9 @@ meta_job_error(Job *job, GNode *gn, int flags, int status)
if (gn) {
Var_Set(".ERROR_TARGET", GNode_Path(gn), VAR_GLOBAL);
}
- getcwd(cwd, sizeof(cwd));
+ getcwd(cwd, sizeof cwd);
Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL);
- if (pbm->meta_fname[0]) {
+ if (pbm->meta_fname[0] != '\0') {
Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL);
}
meta_job_finish(job);
@@ -821,7 +820,7 @@ meta_job_output(Job *job, char *cp, const char *nl)
static char *meta_prefix = NULL;
static size_t meta_prefix_len;
- if (!meta_prefix) {
+ if (meta_prefix == NULL) {
char *cp2;
(void)Var_Subst("${" MAKE_META_PREFIX "}",
@@ -851,7 +850,7 @@ meta_cmd_finish(void *pbmp)
int x;
#endif
- if (!pbm)
+ if (pbm == NULL)
pbm = &Mybm;
#ifdef USE_FILEMON
@@ -865,9 +864,11 @@ meta_cmd_finish(void *pbmp)
error = x;
pbm->mon_fd = -1;
pbm->filemon = NULL;
- } else
+ return error;
+ }
#endif
- fprintf(pbm->mfp, "\n"); /* ensure end with newline */
+
+ fprintf(pbm->mfp, "\n"); /* ensure end with newline */
return error;
}
@@ -941,7 +942,7 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
*bufp = buf = p;
*szp = bufsz = newsz;
/* fetch the rest */
- if (!fgets(&buf[x], (int)bufsz - x, fp))
+ if (fgets(&buf[x], (int)bufsz - x, fp) == NULL)
return x; /* truncated! */
goto check_newline;
}
@@ -1012,7 +1013,7 @@ meta_ignore(GNode *gn, const char *p)
char *fm;
/* skip if filter result is empty */
- snprintf(fname, sizeof(fname),
+ snprintf(fname, sizeof fname,
"${%s:L:${%s:ts:}}",
p, MAKE_META_IGNORE_FILTER);
(void)Var_Subst(fname, gn, VARE_WANTRES, &fm);
@@ -1090,7 +1091,7 @@ meta_oodate(GNode *gn, Boolean oodate)
FILE *fp;
Boolean needOODATE = FALSE;
StringList *missingFiles;
- int have_filemon = FALSE;
+ Boolean have_filemon = FALSE;
void *objdir_freeIt;
if (oodate)
@@ -1114,7 +1115,7 @@ meta_oodate(GNode *gn, Boolean oodate)
*/
Make_DoAllVar(gn);
- meta_name(fname, sizeof(fname), dname, tname, dname);
+ meta_name(fname, sizeof fname, dname, tname, dname);
#ifdef DEBUG_META_MODE
DEBUG1(META, "meta_oodate: %s\n", fname);
@@ -1128,22 +1129,22 @@ meta_oodate(GNode *gn, Boolean oodate)
int pid;
int x;
StringListNode *cmdNode;
- struct make_stat mst;
+ struct cached_stat cst;
- if (!buf) {
+ if (buf == NULL) {
bufsz = 8 * BUFSIZ;
buf = bmake_malloc(bufsz);
}
- if (!cwdlen) {
- if (getcwd(cwd, sizeof(cwd)) == NULL)
+ if (cwdlen == 0) {
+ if (getcwd(cwd, sizeof cwd) == NULL)
err(1, "Could not get current working directory");
cwdlen = strlen(cwd);
}
- strlcpy(lcwd, cwd, sizeof(lcwd));
- strlcpy(latestdir, cwd, sizeof(latestdir));
+ strlcpy(lcwd, cwd, sizeof lcwd);
+ strlcpy(latestdir, cwd, sizeof latestdir);
- if (!tmpdir) {
+ if (tmpdir == NULL) {
tmpdir = getTmpdir();
tmplen = strlen(tmpdir);
}
@@ -1228,17 +1229,17 @@ meta_oodate(GNode *gn, Boolean oodate)
Var_Set(lcwd_vname, lcwd, VAR_GLOBAL);
Var_Set(ldir_vname, latestdir, VAR_GLOBAL);
}
- snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid);
- snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
+ snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid);
+ snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid);
lastpid = pid;
ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
if (ldir) {
- strlcpy(latestdir, ldir, sizeof(latestdir));
+ strlcpy(latestdir, ldir, sizeof latestdir);
bmake_free(tp);
}
ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp);
if (ldir) {
- strlcpy(lcwd, ldir, sizeof(lcwd));
+ strlcpy(lcwd, ldir, sizeof lcwd);
bmake_free(tp);
}
}
@@ -1271,9 +1272,9 @@ meta_oodate(GNode *gn, Boolean oodate)
child = atoi(p);
if (child > 0) {
- snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child);
+ snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child);
Var_Set(cldir, lcwd, VAR_GLOBAL);
- snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
+ snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child);
Var_Set(cldir, latestdir, VAR_GLOBAL);
#ifdef DEBUG_META_MODE
if (DEBUG(META))
@@ -1288,8 +1289,8 @@ meta_oodate(GNode *gn, Boolean oodate)
case 'C': /* Chdir */
/* Update lcwd and latest directory. */
- strlcpy(latestdir, p, sizeof(latestdir));
- strlcpy(lcwd, p, sizeof(lcwd));
+ strlcpy(latestdir, p, sizeof latestdir);
+ strlcpy(lcwd, p, sizeof lcwd);
Var_Set(lcwd_vname, lcwd, VAR_GLOBAL);
Var_Set(ldir_vname, lcwd, VAR_GLOBAL);
#ifdef DEBUG_META_MODE
@@ -1382,11 +1383,12 @@ meta_oodate(GNode *gn, Boolean oodate)
break;
/* ignore anything containing the string "tmp" */
+ /* XXX: The arguments to strstr must be swapped. */
if ((strstr("tmp", p)))
break;
- if ((link_src != NULL && cached_lstat(p, &mst) < 0) ||
- (link_src == NULL && cached_stat(p, &mst) < 0)) {
+ if ((link_src != NULL && cached_lstat(p, &cst) < 0) ||
+ (link_src == NULL && cached_stat(p, &cst) < 0)) {
if (!meta_ignore(gn, p))
append_if_new(missingFiles, p);
}
@@ -1425,17 +1427,17 @@ meta_oodate(GNode *gn, Boolean oodate)
continue; /* no point */
/* Check vs latestdir */
- snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
+ snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p);
sdirs[sdx++] = fname1;
if (strcmp(latestdir, lcwd) != 0) {
/* Check vs lcwd */
- snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p);
+ snprintf(fname2, sizeof fname2, "%s/%s", lcwd, p);
sdirs[sdx++] = fname2;
}
if (strcmp(lcwd, cwd) != 0) {
/* Check vs cwd */
- snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p);
+ snprintf(fname3, sizeof fname3, "%s/%s", cwd, p);
sdirs[sdx++] = fname3;
}
}
@@ -1446,7 +1448,7 @@ meta_oodate(GNode *gn, Boolean oodate)
DEBUG3(META, "%s: %d: looking for: %s\n",
fname, lineno, *sdp);
#endif
- if (cached_stat(*sdp, &mst) == 0) {
+ if (cached_stat(*sdp, &cst) == 0) {
found = 1;
p = *sdp;
}
@@ -1456,12 +1458,12 @@ meta_oodate(GNode *gn, Boolean oodate)
DEBUG3(META, "%s: %d: found: %s\n",
fname, lineno, p);
#endif
- if (!S_ISDIR(mst.mst_mode) &&
- mst.mst_mtime > gn->mtime) {
+ if (!S_ISDIR(cst.cst_mode) &&
+ cst.cst_mtime > gn->mtime) {
DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n",
fname, lineno, p);
oodate = TRUE;
- } else if (S_ISDIR(mst.mst_mode)) {
+ } else if (S_ISDIR(cst.cst_mode)) {
/* Update the latest directory. */
cached_realpath(p, latestdir);
}
@@ -1476,7 +1478,7 @@ meta_oodate(GNode *gn, Boolean oodate)
}
if (buf[0] == 'E') {
/* previous latestdir is no longer relevant */
- strlcpy(latestdir, lcwd, sizeof(latestdir));
+ strlcpy(latestdir, lcwd, sizeof latestdir);
}
break;
default:
@@ -1537,10 +1539,10 @@ meta_oodate(GNode *gn, Boolean oodate)
if (buf[x - 1] == '\n')
buf[x - 1] = '\0';
}
- if (p &&
+ if (p != NULL &&
!hasOODATE &&
!(gn->type & OP_NOMETA_CMP) &&
- strcmp(p, cmd) != 0) {
+ (strcmp(p, cmd) != 0)) {
DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n",
fname, lineno, p, cmd);
if (!metaIgnoreCMDs)
@@ -1588,7 +1590,7 @@ meta_oodate(GNode *gn, Boolean oodate)
cp = NULL; /* not in .CURDIR */
}
}
- if (!cp) {
+ if (cp == NULL) {
DEBUG1(META, "%s: required but missing\n", fname);
oodate = TRUE;
needOODATE = TRUE; /* assume the worst */
@@ -1686,7 +1688,7 @@ meta_compat_parent(pid_t child)
if (outfd != -1 && FD_ISSET(outfd, &readfds)) do {
/* XXX this is not line-buffered */
- ssize_t nread = read(outfd, buf, sizeof(buf) - 1);
+ ssize_t nread = read(outfd, buf, sizeof buf - 1);
if (nread == -1)
err(1, "read");
if (nread == 0) {
diff --git a/metachar.h b/metachar.h
index f180b483f8c3..ced0648fa19e 100644
--- a/metachar.h
+++ b/metachar.h
@@ -1,4 +1,4 @@
-/* $NetBSD: metachar.h,v 1.11 2020/10/31 18:20:00 rillig Exp $ */
+/* $NetBSD: metachar.h,v 1.12 2020/11/10 00:32:12 rillig Exp $ */
/*-
* Copyright (c) 2015 The NetBSD Foundation, Inc.
@@ -37,7 +37,7 @@ extern unsigned char _metachar[];
#define is_shell_metachar(c) _metachar[(c) & 0x7f]
-static inline MAKE_ATTR_UNUSED int
+MAKE_INLINE int
needshell(const char *cmd)
{
while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=')
diff --git a/missing/sys/cdefs.h b/missing/sys/cdefs.h
new file mode 100644
index 000000000000..56acb3fb2cfc
--- /dev/null
+++ b/missing/sys/cdefs.h
@@ -0,0 +1,186 @@
+/* $NetBSD: cdefs.h,v 1.18 1997/06/18 19:09:50 christos Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Berkeley Software Design, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)cdefs.h 8.7 (Berkeley) 1/21/94
+ */
+
+#ifndef _SYS_CDEFS_H_
+
+#if defined(NEED_HOST_CDEFS_H)
+/*
+ * make sure we don't come past here again.
+ */
+#undef NEED_HOST_CDEFS_H
+/*
+ * Some systems - notably linux, have sys/cdefs.h
+ * which is not really compatible with our's.
+ */
+#ifdef __GNUC__
+# include_next <sys/cdefs.h>
+#else
+/*
+ * It sucks that we have to hard code a path like this.
+ * But systems that have a sys/cdefs.h that don't use gcc
+ * should be few.
+ */
+# include "/usr/include/sys/cdefs.h"
+#endif
+/*
+ * We are about to [re]define these
+ */
+#undef __P
+#undef _SYS_CDEFS_H_
+#endif
+
+#define _SYS_CDEFS_H_
+
+#ifdef NetBSD
+#include <machine/cdefs.h>
+#endif
+
+#if defined(__cplusplus)
+# ifndef __BEGIN_DECLS
+# define __BEGIN_DECLS extern "C" {
+# endif
+# ifndef __END_DECLS
+# define __END_DECLS };
+# endif
+#else
+# ifndef __BEGIN_DECLS
+# define __BEGIN_DECLS
+# endif
+# ifndef __END_DECLS
+# define __END_DECLS
+# endif
+#endif
+
+/*
+ * The __CONCAT macro is used to concatenate parts of symbol names, e.g.
+ * with "#define OLD(foo) __CONCAT(old,foo)", OLD(foo) produces oldfoo.
+ * The __CONCAT macro is a bit tricky -- make sure you don't put spaces
+ * in between its arguments. __CONCAT can also concatenate double-quoted
+ * strings produced by the __STRING macro, but this only works with ANSI C.
+ */
+#if defined(__STDC__) || defined(__cplusplus)
+#define __P(protos) protos /* full-blown ANSI C */
+#ifndef __CONCAT
+#define __CONCAT(x,y) x ## y
+#endif
+#define __STRING(x) #x
+
+#define __const const /* define reserved names to standard */
+#define __signed signed
+#define __volatile volatile
+#if defined(__cplusplus)
+#define __inline inline /* convert to C++ keyword */
+#else
+#ifndef __GNUC__
+#define __inline /* delete GCC keyword */
+#endif /* !__GNUC__ */
+#endif /* !__cplusplus */
+
+#else /* !(__STDC__ || __cplusplus) */
+#define __P(protos) () /* traditional C preprocessor */
+#define __CONCAT(x,y) x/**/y
+#define __STRING(x) "x"
+
+#ifndef __GNUC__
+#define __const /* delete pseudo-ANSI C keywords */
+#define __inline
+#define __signed
+#define __volatile
+#endif /* !__GNUC__ */
+
+/*
+ * In non-ANSI C environments, new programs will want ANSI-only C keywords
+ * deleted from the program and old programs will want them left alone.
+ * Programs using the ANSI C keywords const, inline etc. as normal
+ * identifiers should define -DNO_ANSI_KEYWORDS.
+ */
+#ifndef NO_ANSI_KEYWORDS
+#define const __const /* convert ANSI C keywords */
+#define inline __inline
+#define signed __signed
+#define volatile __volatile
+#endif /* !NO_ANSI_KEYWORDS */
+#endif /* !(__STDC__ || __cplusplus) */
+
+/*
+ * GCC1 and some versions of GCC2 declare dead (non-returning) and
+ * pure (no side effects) functions using "volatile" and "const";
+ * unfortunately, these then cause warnings under "-ansi -pedantic".
+ * GCC2 uses a new, peculiar __attribute__((attrs)) style. All of
+ * these work for GNU C++ (modulo a slight glitch in the C++ grammar
+ * in the distribution version of 2.5.5).
+ */
+#if !defined(__GNUC__) || __GNUC__ < 2 || \
+ (__GNUC__ == 2 && __GNUC_MINOR__ < 5)
+#define __attribute__(x) /* delete __attribute__ if non-gcc or gcc1 */
+#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
+#define __dead __volatile
+#define __pure __const
+#endif
+#endif
+
+#ifdef sun386
+# define __attribute__(x)
+#endif
+
+#ifdef __KPRINTF_ATTRIBUTE__
+#define __kprintf_attribute__(a) __attribute__(a)
+#else
+#define __kprintf_attribute__(a)
+#endif
+
+/* Delete pseudo-keywords wherever they are not available or needed. */
+#ifndef __dead
+#define __dead
+#define __pure
+#endif
+
+#define __IDSTRING(name,string) \
+ static const char name[] __attribute__((__unused__)) = string
+
+#ifndef __RCSID
+#define __RCSID(s) __IDSTRING(rcsid,s)
+#endif
+
+#ifndef __COPYRIGHT
+#define __COPYRIGHT(s) __IDSTRING(copyright,s)
+#endif
+
+#endif /* !_SYS_CDEFS_H_ */
diff --git a/mk/ChangeLog b/mk/ChangeLog
index d72f9ff2bb6f..80abde7ea93f 100644
--- a/mk/ChangeLog
+++ b/mk/ChangeLog
@@ -1,3 +1,10 @@
+2020-11-06 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20201106
+
+ * meta.autodep.mk: use OBJ_EXTENSIONS rather than hardcode sed
+ args to tweak extensions for local deps.
+
2020-11-01 Simon J Gerraty <sjg@beast.crufty.net>
* install-mk (MK_VERSION): 20201101
diff --git a/mk/install-mk b/mk/install-mk
index 01680f401745..cd396f7d5331 100644
--- a/mk/install-mk
+++ b/mk/install-mk
@@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
-# $Id: install-mk,v 1.183 2020/11/02 16:34:12 sjg Exp $
+# $Id: install-mk,v 1.184 2020/11/08 05:47:56 sjg Exp $
#
# @(#) Copyright (c) 1994 Simon J. Gerraty
#
@@ -70,7 +70,7 @@
# sjg@crufty.net
#
-MK_VERSION=20201101
+MK_VERSION=20201106
OWNER=
GROUP=
MODE=444
diff --git a/mk/meta.autodep.mk b/mk/meta.autodep.mk
index 5e18c35fa560..b5cb39a30855 100644
--- a/mk/meta.autodep.mk
+++ b/mk/meta.autodep.mk
@@ -1,4 +1,4 @@
-# $Id: meta.autodep.mk,v 1.52 2020/07/18 05:57:57 sjg Exp $
+# $Id: meta.autodep.mk,v 1.53 2020/11/08 05:47:56 sjg Exp $
#
# @(#) Copyright (c) 2010, Simon J. Gerraty
@@ -178,7 +178,8 @@ DEPEND_SUFFIXES += .c .h .cpp .hpp .cxx .hxx .cc .hh
@case "${.MAKE.META.FILES:T:M*.po.*}" in \
*.po.*) mv $@.${.MAKE.PID} $@;; \
*) { cat $@.${.MAKE.PID}; \
- sed 's,\${PICO}:,.o:,;s,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \
+ sed ${OBJ_EXTENSIONS:N.o:N.po:@o@-e 's,\$o:,.o:,'@} \
+ -e 's,\.o:,.po:,' $@.${.MAKE.PID}; } | sort -u > $@; \
rm -f $@.${.MAKE.PID};; \
esac
.else
diff --git a/mk/meta2deps.sh b/mk/meta2deps.sh
index f01d54774a04..64add8542571 100755
--- a/mk/meta2deps.sh
+++ b/mk/meta2deps.sh
@@ -77,7 +77,7 @@
# RCSid:
-# $Id: meta2deps.sh,v 1.14 2020/10/02 03:11:17 sjg Exp $
+# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $
# Copyright (c) 2010-2013, Juniper Networks, Inc.
# All rights reserved.
@@ -259,7 +259,7 @@ meta2deps() {
0) error "no filemon data";;
*) ;;
esac
- version=0
+ version=0
continue
;;
$pid,$pid) ;;
diff --git a/nonints.h b/nonints.h
index ba2e3bbbe4ed..f089b39ca50e 100644
--- a/nonints.h
+++ b/nonints.h
@@ -1,4 +1,4 @@
-/* $NetBSD: nonints.h,v 1.149 2020/11/01 00:24:57 rillig Exp $ */
+/* $NetBSD: nonints.h,v 1.162 2020/11/16 21:48:18 rillig Exp $ */
/*-
* Copyright (c) 1988, 1989, 1990, 1993
@@ -79,8 +79,8 @@ void Arch_End(void);
Boolean Arch_ParseArchive(char **, GNodeList *, GNode *);
void Arch_Touch(GNode *);
void Arch_TouchLib(GNode *);
-time_t Arch_MTime(GNode *);
-time_t Arch_MemMTime(GNode *);
+void Arch_UpdateMTime(GNode *gn);
+void Arch_UpdateMemberMTime(GNode *gn);
void Arch_FindLib(GNode *, SearchPath *);
Boolean Arch_LibOODate(GNode *);
Boolean Arch_IsLib(GNode *);
@@ -107,6 +107,7 @@ void JobReapChild(pid_t, WAIT_T, Boolean);
#endif
/* main.c */
+Boolean GetBooleanVar(const char *, Boolean);
void Main_ParseArgLine(const char *);
void MakeMode(const char *);
char *Cmd_Exec(const char *, const char **);
@@ -118,8 +119,7 @@ void Finish(int) MAKE_ATTR_DEAD;
int eunlink(const char *);
void execDie(const char *, const char *);
char *getTmpdir(void);
-Boolean s2Boolean(const char *, Boolean);
-Boolean getBoolean(const char *, Boolean);
+Boolean ParseBoolean(const char *, Boolean);
char *cached_realpath(const char *, char *);
/* parse.c */
@@ -159,7 +159,7 @@ typedef struct Words {
} Words;
Words Str_Words(const char *, Boolean);
-static inline MAKE_ATTR_UNUSED void
+MAKE_INLINE void
Words_Free(Words w) {
free(w.words);
free(w.freeIt);
@@ -199,15 +199,15 @@ void Targ_End(void);
void Targ_Stats(void);
GNodeList *Targ_List(void);
-GNode *Targ_NewGN(const char *);
+GNode *GNode_New(const char *);
GNode *Targ_FindNode(const char *);
GNode *Targ_GetNode(const char *);
GNode *Targ_NewInternalNode(const char *);
GNode *Targ_GetEndNode(void);
GNodeList *Targ_FindList(StringList *);
-Boolean Targ_Ignore(GNode *);
-Boolean Targ_Silent(GNode *);
-Boolean Targ_Precious(GNode *);
+Boolean Targ_Ignore(const GNode *);
+Boolean Targ_Silent(const GNode *);
+Boolean Targ_Precious(const GNode *);
void Targ_SetMain(GNode *);
void Targ_PrintCmds(GNode *);
void Targ_PrintNode(GNode *, int);
@@ -223,21 +223,40 @@ void Var_End(void);
typedef enum VarEvalFlags {
VARE_NONE = 0,
- /* Treat undefined variables as errors. */
- VARE_UNDEFERR = 0x01,
- /* Expand and evaluate variables during parsing. */
- VARE_WANTRES = 0x02,
- /* In an assignment using the ':=' operator, keep '$$' as '$$' instead
- * of reducing it to a single '$'. */
- VARE_ASSIGN = 0x04
+
+ /* Expand and evaluate variables during parsing.
+ *
+ * TODO: Document what Var_Parse and Var_Subst return when this flag
+ * is not set. */
+ VARE_WANTRES = 1 << 0,
+
+ /* Treat undefined variables as errors.
+ * Must only be used in combination with VARE_WANTRES. */
+ VARE_UNDEFERR = 1 << 1,
+
+ /* Keep '$$' as '$$' instead of reducing it to a single '$'.
+ *
+ * Used in variable assignments using the ':=' operator. It allows
+ * multiple such assignments to be chained without accidentally expanding
+ * '$$file' to '$file' in the first assignment and interpreting it as
+ * '${f}' followed by 'ile' in the next assignment.
+ *
+ * See also preserveUndefined, which preserves subexpressions that are
+ * based on undefined variables; maybe that can be converted to a flag
+ * as well. */
+ VARE_KEEP_DOLLAR = 1 << 2
} VarEvalFlags;
-typedef enum VarSet_Flags {
- VAR_NO_EXPORT = 0x01, /* do not export */
+typedef enum VarSetFlags {
+ VAR_SET_NONE = 0,
+
+ /* do not export */
+ VAR_SET_NO_EXPORT = 1 << 0,
+
/* Make the variable read-only. No further modification is possible,
* except for another call to Var_Set with the same flag. */
- VAR_SET_READONLY = 0x02
-} VarSet_Flags;
+ VAR_SET_READONLY = 1 << 1
+} VarSetFlags;
/* The state of error handling returned by Var_Parse.
*
@@ -296,7 +315,7 @@ typedef enum VarParseResult {
void Var_Delete(const char *, GNode *);
void Var_Set(const char *, const char *, GNode *);
-void Var_Set_with_flags(const char *, const char *, GNode *, VarSet_Flags);
+void Var_SetWithFlags(const char *, const char *, GNode *, VarSetFlags);
void Var_Append(const char *, const char *, GNode *);
Boolean Var_Exists(const char *, GNode *);
const char *Var_Value(const char *, GNode *, void **);
diff --git a/parse.c b/parse.c
index 9efa5efc627b..d7bd65645aca 100644
--- a/parse.c
+++ b/parse.c
@@ -1,4 +1,4 @@
-/* $NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $ */
+/* $NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -124,7 +124,7 @@
#include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.443 2020/11/16 21:39:22 rillig Exp $");
/* types and constants */
@@ -132,7 +132,7 @@ MAKE_RCSID("$NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $");
* Structure for a file being read ("included file")
*/
typedef struct IFile {
- char *fname; /* name of file */
+ char *fname; /* name of file (relative? absolute?) */
Boolean fromForLoop; /* simulated .include by the .for loop */
int lineno; /* current line number in file */
int first_lineno; /* line number of start of text */
@@ -355,6 +355,7 @@ static const struct {
/* file loader */
struct loadedfile {
+ /* XXX: What is the lifetime of this path? Who manages the memory? */
const char *path; /* name, for error reports */
char *buf; /* contents buffer */
size_t len; /* length of contents */
@@ -362,12 +363,13 @@ struct loadedfile {
Boolean used; /* XXX: have we used the data yet */
};
+/* XXX: What is the lifetime of the path? Who manages the memory? */
static struct loadedfile *
loadedfile_create(const char *path)
{
struct loadedfile *lf;
- lf = bmake_malloc(sizeof(*lf));
+ lf = bmake_malloc(sizeof *lf);
lf->path = path == NULL ? "(stdin)" : path;
lf->buf = NULL;
lf->len = 0;
@@ -380,13 +382,12 @@ static void
loadedfile_destroy(struct loadedfile *lf)
{
if (lf->buf != NULL) {
- if (lf->maplen > 0) {
#ifdef HAVE_MMAP
+ if (lf->maplen > 0)
munmap(lf->buf, lf->maplen);
+ else
#endif
- } else {
free(lf->buf);
- }
}
free(lf);
}
@@ -400,9 +401,9 @@ loadedfile_nextbuf(void *x, size_t *len)
{
struct loadedfile *lf = x;
- if (lf->used) {
+ if (lf->used)
return NULL;
- }
+
lf->used = TRUE;
*len = lf->len;
return lf->buf;
@@ -416,13 +417,11 @@ load_getsize(int fd, size_t *ret)
{
struct stat st;
- if (fstat(fd, &st) < 0) {
+ if (fstat(fd, &st) < 0)
return FALSE;
- }
- if (!S_ISREG(st.st_mode)) {
+ if (!S_ISREG(st.st_mode))
return FALSE;
- }
/*
* st_size is an off_t, which is 64 bits signed; *ret is
@@ -433,9 +432,8 @@ load_getsize(int fd, size_t *ret)
*
* While we're at it reject negative sizes too, just in case.
*/
- if (st.st_size < 0 || st.st_size > 0x7fffffff) {
+ if (st.st_size < 0 || st.st_size > 0x7fffffff)
return FALSE;
- }
*ret = (size_t)st.st_size;
return TRUE;
@@ -447,48 +445,47 @@ loadedfile_mmap(struct loadedfile *lf, int fd)
{
static unsigned long pagesize = 0;
- if (load_getsize(fd, &lf->len)) {
+ if (!load_getsize(fd, &lf->len))
+ return FALSE;
- /* found a size, try mmap */
+ /* found a size, try mmap */
#ifdef _SC_PAGESIZE
- if (pagesize == 0)
- pagesize = (unsigned long)sysconf(_SC_PAGESIZE);
+ if (pagesize == 0)
+ pagesize = (unsigned long)sysconf(_SC_PAGESIZE);
#endif
- if (pagesize == 0 || pagesize == (unsigned long)-1) {
- pagesize = 0x1000;
- }
- /* round size up to a page */
- lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize);
+ if (pagesize == 0 || pagesize == (unsigned long)-1)
+ pagesize = 0x1000;
- /*
- * XXX hack for dealing with empty files; remove when
- * we're no longer limited by interfacing to the old
- * logic elsewhere in this file.
- */
- if (lf->maplen == 0) {
- lf->maplen = pagesize;
- }
+ /* round size up to a page */
+ lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize);
- /*
- * FUTURE: remove PROT_WRITE when the parser no longer
- * needs to scribble on the input.
- */
- lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
- MAP_FILE|MAP_COPY, fd, 0);
- if (lf->buf != MAP_FAILED) {
- /* succeeded */
- if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
- char *b = bmake_malloc(lf->len + 1);
- b[lf->len] = '\n';
- memcpy(b, lf->buf, lf->len++);
- munmap(lf->buf, lf->maplen);
- lf->maplen = 0;
- lf->buf = b;
- }
- return TRUE;
- }
+ /*
+ * XXX hack for dealing with empty files; remove when
+ * we're no longer limited by interfacing to the old
+ * logic elsewhere in this file.
+ */
+ if (lf->maplen == 0)
+ lf->maplen = pagesize;
+
+ /*
+ * FUTURE: remove PROT_WRITE when the parser no longer
+ * needs to scribble on the input.
+ */
+ lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE,
+ MAP_FILE|MAP_COPY, fd, 0);
+ if (lf->buf == MAP_FAILED)
+ return FALSE;
+
+ if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') {
+ char *b = bmake_malloc(lf->len + 1);
+ b[lf->len] = '\n';
+ memcpy(b, lf->buf, lf->len++);
+ munmap(lf->buf, lf->maplen);
+ lf->maplen = 0;
+ lf->buf = b;
}
- return FALSE;
+
+ return TRUE;
}
#endif
@@ -499,8 +496,7 @@ loadedfile_mmap(struct loadedfile *lf, int fd)
* being in the caller in another source file, we need to have the fd
* passed in already open. Bleh.
*
- * If the path is NULL use stdin and (to insure against fd leaks)
- * assert that the caller passed in -1.
+ * If the path is NULL, use stdin.
*/
static struct loadedfile *
loadfile(const char *path, int fd)
@@ -537,7 +533,7 @@ loadfile(const char *path, int fd)
lf->buf = bmake_malloc(lf->len);
bufpos = 0;
- while (1) {
+ for (;;) {
assert(bufpos <= lf->len);
if (bufpos == lf->len) {
if (lf->len > SIZE_MAX/2) {
@@ -554,9 +550,9 @@ loadfile(const char *path, int fd)
Error("%s: read error: %s", path, strerror(errno));
exit(1);
}
- if (result == 0) {
+ if (result == 0)
break;
- }
+
bufpos += (size_t)result;
}
assert(bufpos <= lf->len);
@@ -574,9 +570,9 @@ loadfile(const char *path, int fd)
#ifdef HAVE_MMAP
done:
#endif
- if (path != NULL) {
+ if (path != NULL)
close(fd);
- }
+
return lf;
}
@@ -611,36 +607,33 @@ ParseMark(GNode *gn)
static int
ParseFindKeyword(const char *str)
{
- int start, end, cur;
- int diff;
-
- start = 0;
- end = sizeof parseKeywords / sizeof parseKeywords[0] - 1;
+ int start = 0;
+ int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1;
do {
- cur = start + (end - start) / 2;
- diff = strcmp(str, parseKeywords[cur].name);
+ int cur = start + (end - start) / 2;
+ int diff = strcmp(str, parseKeywords[cur].name);
- if (diff == 0) {
+ if (diff == 0)
return cur;
- } else if (diff < 0) {
+ if (diff < 0)
end = cur - 1;
- } else {
+ else
start = cur + 1;
- }
} while (start <= end);
+
return -1;
}
static void
-PrintLocation(FILE *f, const char *filename, size_t lineno)
+PrintLocation(FILE *f, const char *fname, size_t lineno)
{
char dirbuf[MAXPATHLEN+1];
const char *dir, *base;
void *dir_freeIt, *base_freeIt;
- if (*filename == '/' || strcmp(filename, "(stdin)") == 0) {
- (void)fprintf(f, "\"%s\" line %zu: ", filename, lineno);
+ if (*fname == '/' || strcmp(fname, "(stdin)") == 0) {
+ (void)fprintf(f, "\"%s\" line %zu: ", fname, lineno);
return;
}
@@ -655,8 +648,8 @@ PrintLocation(FILE *f, const char *filename, size_t lineno)
base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt);
if (base == NULL) {
- const char *slash = strrchr(filename, '/');
- base = slash != NULL ? slash + 1 : filename;
+ const char *slash = strrchr(fname, '/');
+ base = slash != NULL ? slash + 1 : fname;
}
(void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno);
@@ -664,21 +657,16 @@ PrintLocation(FILE *f, const char *filename, size_t lineno)
bmake_free(dir_freeIt);
}
-/* Print a parse error message, including location information.
- *
- * Increment "fatals" if the level is PARSE_FATAL, and continue parsing
- * until the end of the current top-level makefile, then exit (see
- * Parse_File). */
static void
-ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno,
+ParseVErrorInternal(FILE *f, const char *fname, size_t lineno,
ParseErrorLevel type, const char *fmt, va_list ap)
{
static Boolean fatal_warning_error_printed = FALSE;
(void)fprintf(f, "%s: ", progname);
- if (cfname != NULL)
- PrintLocation(f, cfname, clineno);
+ if (fname != NULL)
+ PrintLocation(f, fname, lineno);
if (type == PARSE_WARNING)
(void)fprintf(f, "warning: ");
(void)vfprintf(f, fmt, ap);
@@ -696,26 +684,28 @@ ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno,
}
static void
-ParseErrorInternal(const char *cfname, size_t clineno, ParseErrorLevel type,
- const char *fmt, ...)
+ParseErrorInternal(const char *fname, size_t lineno,
+ ParseErrorLevel type, const char *fmt, ...)
{
va_list ap;
- va_start(ap, fmt);
(void)fflush(stdout);
- ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap);
+ va_start(ap, fmt);
+ ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
va_end(ap);
if (opts.debug_file != stderr && opts.debug_file != stdout) {
va_start(ap, fmt);
- ParseVErrorInternal(opts.debug_file, cfname, clineno, type,
+ ParseVErrorInternal(opts.debug_file, fname, lineno, type,
fmt, ap);
va_end(ap);
}
}
-/* External interface to ParseErrorInternal; uses the default filename and
- * line number.
+/* Print a parse error message, including location information.
+ *
+ * If the level is PARSE_FATAL, continue parsing until the end of the
+ * current top-level makefile, then exit (see Parse_File).
*
* Fmt is given without a trailing newline. */
void
@@ -748,12 +738,8 @@ Parse_Error(ParseErrorLevel type, const char *fmt, ...)
}
-/* Parse a .info .warning or .error directive.
- *
- * The input is the line minus the ".". We substitute variables, print the
- * message and exit(1) (for .error) or just print a warning if the directive
- * is malformed.
- */
+/* Parse and handle a .info, .warning or .error directive.
+ * For an .error directive, immediately exit. */
static Boolean
ParseMessage(const char *directive)
{
@@ -832,9 +818,12 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op)
if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) {
/*
- * If the node was the object of a :: operator, we need to create a
- * new instance of it for the children and commands on this dependency
- * line. The new instance is placed on the 'cohorts' list of the
+ * If the node was of the left-hand side of a '::' operator, we need
+ * to create a new instance of it for the children and commands on
+ * this dependency line since each of these dependency groups has its
+ * own attributes and commands, separate from the others.
+ *
+ * The new instance is placed on the 'cohorts' list of the
* initial one (note the initial one is not on its own cohorts list)
* and the new instance is linked to all parents of the initial
* instance.
@@ -866,7 +855,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op)
} else {
/*
* We don't want to nuke any previous flags (whatever they were) so we
- * just OR the new operator into the old
+ * just OR the new operator into the old.
*/
gn->type |= op;
}
@@ -893,7 +882,7 @@ ParseDoSrcKeyword(const char *src, ParseSpecial specType)
if (*src == '.' && ch_isupper(src[1])) {
int keywd = ParseFindKeyword(src);
if (keywd != -1) {
- int op = parseKeywords[keywd].op;
+ GNodeType op = parseKeywords[keywd].op;
if (op != 0) {
ApplyDependencyOperator(op);
return TRUE;
@@ -902,11 +891,12 @@ ParseDoSrcKeyword(const char *src, ParseSpecial specType)
/*
* We add a .WAIT node in the dependency list.
* After any dynamic dependencies (and filename globbing)
- * have happened, it is given a dependency on the each
- * previous child back to and previous .WAIT node.
+ * have happened, it is given a dependency on each
+ * previous child, back until the previous .WAIT node.
* The next child won't be scheduled until the .WAIT node
* is built.
- * We give each .WAIT node a unique name (mainly for diag).
+ * We give each .WAIT node a unique name (mainly for
+ * diagnostics).
*/
snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
gn = Targ_NewInternalNode(wait_src);
@@ -925,12 +915,13 @@ static void
ParseDoSrcMain(const char *src)
{
/*
- * If we have noted the existence of a .MAIN, it means we need
- * to add the sources of said target to the list of things
- * to create. The string 'src' is likely to be free, so we
- * must make a new copy of it. Note that this will only be
- * invoked if the user didn't specify a target on the command
- * line. This is to allow #ifmake's to succeed, or something...
+ * In a line like ".MAIN: source1 source2", it means we need to add
+ * the sources of said target to the list of things to create.
+ *
+ * Note that this will only be invoked if the user didn't specify a
+ * target on the command line. This is to allow .ifmake to succeed.
+ *
+ * XXX: Double-check all of the above comment.
*/
Lst_Append(opts.create, bmake_strdup(src));
/*
@@ -988,11 +979,10 @@ ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType)
gn = Targ_GetNode(src);
if (doing_depend)
ParseMark(gn);
- if (tOp) {
+ if (tOp != OP_NONE)
gn->type |= tOp;
- } else {
+ else
LinkToTargets(gn, specType != SP_NOT);
- }
}
/* Given the name of a source in a dependency line, figure out if it is an
@@ -1090,8 +1080,8 @@ ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart)
const char *nested_val;
void *freeIt;
- (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_UNDEFERR|VARE_WANTRES,
- &nested_val, &freeIt);
+ (void)Var_Parse(&nested_p, VAR_CMDLINE,
+ VARE_WANTRES | VARE_UNDEFERR, &nested_val, &freeIt);
/* TODO: handle errors */
free(freeIt);
cp += nested_p - cp;
@@ -1102,38 +1092,7 @@ ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart)
*pp = cp;
}
-/*
- * Certain special targets have special semantics:
- * .PATH Have to set the dirSearchPath
- * variable too
- * .MAIN Its sources are only used if
- * nothing has been specified to
- * create.
- * .DEFAULT Need to create a node to hang
- * commands on, but we don't want
- * it in the graph, nor do we want
- * it to be the Main Target, so we
- * create it, set OP_NOTMAIN and
- * add it to the list, setting
- * DEFAULT to the new node for
- * later use. We claim the node is
- * A transformation rule to make
- * life easier later, when we'll
- * use Make_HandleUse to actually
- * apply the .DEFAULT commands.
- * .PHONY The list of targets
- * .NOPATH Don't search for file in the path
- * .STALE
- * .BEGIN
- * .END
- * .ERROR
- * .DELETE_ON_ERROR
- * .INTERRUPT Are not to be considered the
- * main target.
- * .NOTPARALLEL Make only one target at a time.
- * .SINGLESHELL Create a shell for each command.
- * .ORDER Must set initial predecessor to NULL
- */
+/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */
static void
ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType,
const char *line,
@@ -1141,15 +1100,14 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType,
{
switch (*inout_specType) {
case SP_PATH:
- if (*inout_paths == NULL) {
+ if (*inout_paths == NULL)
*inout_paths = Lst_New();
- }
Lst_Append(*inout_paths, dirSearchPath);
break;
case SP_MAIN:
- if (!Lst_IsEmpty(opts.create)) {
+ /* Allow targets from the command line to override the .MAIN node. */
+ if (!Lst_IsEmpty(opts.create))
*inout_specType = SP_NOT;
- }
break;
case SP_BEGIN:
case SP_END:
@@ -1164,10 +1122,15 @@ ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType,
break;
}
case SP_DEFAULT: {
- GNode *gn = Targ_NewGN(".DEFAULT");
+ /* Need to create a node to hang commands on, but we don't want it
+ * in the graph, nor do we want it to be the Main Target. We claim
+ * the node is a transformation rule to make life easier later,
+ * when we'll use Make_HandleUse to actually apply the .DEFAULT
+ * commands. */
+ GNode *gn = GNode_New(".DEFAULT");
gn->type |= OP_NOTMAIN|OP_TRANSFORM;
Lst_Append(targets, gn);
- DEFAULT = gn;
+ defaultNode = gn;
break;
}
case SP_DELETE_ON_ERROR:
@@ -1202,12 +1165,12 @@ ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths)
"Suffix '%s' not defined (yet)",
&line[5]);
return FALSE;
- } else {
- if (*inout_paths == NULL) {
- *inout_paths = Lst_New();
- }
- Lst_Append(*inout_paths, path);
}
+
+ if (*inout_paths == NULL)
+ *inout_paths = Lst_New();
+ Lst_Append(*inout_paths, path);
+
return TRUE;
}
@@ -1290,17 +1253,16 @@ ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart)
Boolean warning = FALSE;
char *cp = *pp;
- while (*cp && (ParseIsEscaped(lstart, cp) ||
- (*cp != '!' && *cp != ':'))) {
- if (ParseIsEscaped(lstart, cp) ||
- (*cp != ' ' && *cp != '\t')) {
+ while (*cp != '\0') {
+ if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':'))
+ break;
+ if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t'))
warning = TRUE;
- }
cp++;
}
- if (warning) {
+ if (warning)
Parse_Error(PARSE_WARNING, "Extra target ignored");
- }
+
*pp = cp;
}
@@ -1310,7 +1272,8 @@ ParseDoDependencyCheckSpec(ParseSpecial specType)
switch (specType) {
default:
Parse_Error(PARSE_WARNING,
- "Special and mundane targets don't mix. Mundane ones ignored");
+ "Special and mundane targets don't mix. "
+ "Mundane ones ignored");
break;
case SP_DEFAULT:
case SP_STALE:
@@ -1319,13 +1282,11 @@ ParseDoDependencyCheckSpec(ParseSpecial specType)
case SP_ERROR:
case SP_INTERRUPT:
/*
- * These four create nodes on which to hang commands, so
- * targets shouldn't be empty...
+ * These create nodes on which to hang commands, so targets
+ * shouldn't be empty...
*/
case SP_NOT:
- /*
- * Nothing special here -- targets can be empty if it wants.
- */
+ /* Nothing special here -- targets can be empty if it wants. */
break;
}
}
@@ -1459,7 +1420,7 @@ ParseDoDependencySourceSpecial(ParseSpecial specType, char *word,
Suff_SetNull(word);
break;
case SP_OBJDIR:
- Main_SetObjdir("%s", word);
+ Main_SetObjdir(FALSE, "%s", word);
break;
default:
break;
@@ -1532,11 +1493,10 @@ ParseDoDependencyTargets(char **inout_cp,
* Have word in line. Get or create its node and stick it at
* the end of the targets list
*/
- if (*inout_specType == SP_NOT && *line != '\0') {
+ if (*inout_specType == SP_NOT && *line != '\0')
ParseDoDependencyTargetMundane(line, curTargs);
- } else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') {
+ else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0')
Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
- }
/* Don't need the inserted null terminator any more. */
*cp = savec;
@@ -1545,11 +1505,11 @@ ParseDoDependencyTargets(char **inout_cp,
* If it is a special type and not .PATH, it's the only target we
* allow on this line...
*/
- if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) {
+ if (*inout_specType != SP_NOT && *inout_specType != SP_PATH)
ParseDoDependencyTargetExtraWarn(&cp, lstart);
- } else {
+ else
pp_skip_whitespace(&cp);
- }
+
line = cp;
if (*line == '\0')
break;
@@ -1586,7 +1546,7 @@ static Boolean
ParseDoDependencySourcesMundane(char *start, char *end,
ParseSpecial specType, GNodeType tOp)
{
- while (*start) {
+ while (*start != '\0') {
/*
* The targets take real sources, so we must beware of archive
* specifications (i.e. things with left parentheses in them)
@@ -1644,9 +1604,8 @@ ParseDoDependencySourcesMundane(char *start, char *end,
*
* The sources are parsed in much the same way as the targets, except
* that they are expanded using the wildcarding scheme of the C-Shell,
- * and all instances of the resulting words in the list of all targets
- * are found. Each of the resulting nodes is then linked to each of the
- * targets as one of its children.
+ * and a target is created for each expanded word. Each of the resulting
+ * nodes is then linked to each of the targets as one of its children.
*
* Certain targets and sources such as .PHONY or .PRECIOUS are handled
* specially. These are the ones detailed by the specType variable.
@@ -1656,6 +1615,8 @@ ParseDoDependencySourcesMundane(char *start, char *end,
* Suff_IsTransform. If it is a transformation rule, its node is gotten
* from the suffix module via Suff_AddTransform rather than the standard
* Targ_FindNode in the target module.
+ *
+ * Upon return, the value of the line is unspecified.
*/
static void
ParseDoDependency(char *line)
@@ -1664,7 +1625,7 @@ ParseDoDependency(char *line)
GNodeType op; /* the operator on the line */
SearchPathList *paths; /* search paths to alter when parsing
* a list of .PATH targets */
- int tOp; /* operator from special target */
+ GNodeType tOp; /* operator from special target */
StringList *curTargs; /* target names to be found and added
* to the targets list */
char *lstart = line;
@@ -1677,7 +1638,7 @@ ParseDoDependency(char *line)
ParseSpecial specType = SP_NOT;
DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
- tOp = 0;
+ tOp = OP_NONE;
paths = NULL;
@@ -1690,9 +1651,8 @@ ParseDoDependency(char *line)
curTargs))
goto out;
- /*
- * Don't need the list of target names anymore...
- */
+ /* Don't need the list of target names anymore.
+ * The targets themselves are now in the global variable 'targets'. */
Lst_Free(curTargs);
curTargs = NULL;
@@ -1719,7 +1679,7 @@ ParseDoDependency(char *line)
* end of the string if not.
*/
pp_skip_whitespace(&cp);
- line = cp;
+ line = cp; /* XXX: 'line' is an inappropriate name */
/*
* Several special targets take different actions if present with no
@@ -1730,7 +1690,7 @@ ParseDoDependency(char *line)
* a .SILENT line creates silence when making all targets
* a .PATH removes all directories from the search path(s).
*/
- if (!*line) {
+ if (line[0] == '\0') {
ParseDoDependencySourcesEmpty(specType, paths);
} else if (specType == SP_MFLAGS) {
/*
@@ -1751,9 +1711,7 @@ ParseDoDependency(char *line)
*line = '\0';
}
- /*
- * NOW GO FOR THE SOURCES
- */
+ /* Now go for the sources. */
if (specType == SP_SUFFIXES || specType == SP_PATH ||
specType == SP_INCLUDES || specType == SP_LIBS ||
specType == SP_NULL || specType == SP_OBJDIR)
@@ -1849,12 +1807,9 @@ Parse_IsVar(const char *p, VarAssign *out_var)
{
VarAssignParsed pvar;
const char *firstSpace = NULL;
- char ch;
int level = 0;
- /* Skip to variable name */
- while (*p == ' ' || *p == '\t')
- p++;
+ cpp_skip_hspace(&p); /* Skip to variable name */
/* During parsing, the '+' of the '+=' operator is initially parsed
* as part of the variable name. It is later corrected, as is the ':sh'
@@ -1867,7 +1822,8 @@ Parse_IsVar(const char *p, VarAssign *out_var)
#endif
/* Scan for one of the assignment operators outside a variable expansion */
- while ((ch = *p++) != 0) {
+ while (*p != '\0') {
+ char ch = *p++;
if (ch == '(' || ch == '{') {
level++;
continue;
@@ -1887,7 +1843,7 @@ Parse_IsVar(const char *p, VarAssign *out_var)
ch = *p++;
#ifdef SUNSHCMD
- if (ch == ':' && strncmp(p, "sh", 2) == 0) {
+ if (ch == ':' && p[0] == 's' && p[1] == 'h') {
p += 2;
continue;
}
@@ -1917,7 +1873,7 @@ Parse_IsVar(const char *p, VarAssign *out_var)
static void
VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt)
{
- if (DEBUG(LINT)) {
+ if (opts.lint) {
if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) {
/* Check for syntax errors such as unclosed expressions or
* unknown modifiers. */
@@ -1936,20 +1892,18 @@ VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt,
{
const char *avalue = uvalue;
char *evalue;
- /*
- * Allow variables in the old value to be undefined, but leave their
- * expressions alone -- this is done by forcing oldVars to be false.
- * XXX: This can cause recursive variables, but that's not hard to do,
- * and this allows someone to do something like
- *
- * CFLAGS = $(.INCLUDES)
- * CFLAGS := -I.. $(CFLAGS)
- *
- * And not get an error.
- */
- Boolean oldOldVars = oldVars;
+ Boolean savedPreserveUndefined = preserveUndefined;
- oldVars = FALSE;
+ /* TODO: Can this assignment to preserveUndefined be moved further down
+ * to the actually interesting Var_Subst call, without affecting any
+ * edge cases?
+ *
+ * It might affect the implicit expansion of the variable name in the
+ * Var_Exists and Var_Set calls, even though it's unlikely that anyone
+ * cared about this edge case when adding this code. In addition,
+ * variable assignments should not refer to any undefined variables in
+ * the variable name. */
+ preserveUndefined = TRUE;
/*
* make sure that we set the variable the first time to nothing
@@ -1958,9 +1912,9 @@ VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt,
if (!Var_Exists(name, ctxt))
Var_Set(name, "", ctxt);
- (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue);
+ (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_KEEP_DOLLAR, &evalue);
/* TODO: handle errors */
- oldVars = oldOldVars;
+ preserveUndefined = savedPreserveUndefined;
avalue = evalue;
Var_Set(name, avalue, ctxt);
@@ -1979,7 +1933,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt,
cmd = uvalue;
if (strchr(cmd, '$') != NULL) {
char *ecmd;
- (void)Var_Subst(cmd, VAR_CMDLINE, VARE_UNDEFERR | VARE_WANTRES, &ecmd);
+ (void)Var_Subst(cmd, VAR_CMDLINE, VARE_WANTRES | VARE_UNDEFERR, &ecmd);
/* TODO: handle errors */
cmd = cmd_freeIt = ecmd;
}
@@ -2009,13 +1963,13 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue,
const char *avalue = uvalue;
void *avalue_freeIt = NULL;
- if (op == VAR_APPEND) {
+ if (op == VAR_APPEND)
Var_Append(name, uvalue, ctxt);
- } else if (op == VAR_SUBST) {
+ else if (op == VAR_SUBST)
VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt);
- } else if (op == VAR_SHELL) {
+ else if (op == VAR_SHELL)
VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt);
- } else {
+ else {
if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) {
*out_avalue_freeIt = NULL;
return FALSE;
@@ -2043,11 +1997,10 @@ VarAssignSpecial(const char *name, const char *avalue)
*/
Dir_InitCur(avalue);
Dir_SetPATH();
- } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) {
+ } else if (strcmp(name, MAKE_JOB_PREFIX) == 0)
Job_SetPrefix();
- } else if (strcmp(name, MAKE_EXPORTED) == 0) {
+ else if (strcmp(name, MAKE_EXPORTED) == 0)
Var_Export(avalue, FALSE);
- }
}
/* Perform the variable variable assignment in the given context. */
@@ -2067,36 +2020,41 @@ Parse_DoVar(VarAssign *var, GNode *ctxt)
}
-/*
- * ParseMaybeSubMake --
- * Scan the command string to see if it a possible submake node
- * Input:
- * cmd the command to scan
- * Results:
- * TRUE if the command is possibly a submake, FALSE if not.
- */
+/* See if the command possibly calls a sub-make by using the variable
+ * expressions ${.MAKE}, ${MAKE} or the plain word "make". */
static Boolean
-ParseMaybeSubMake(const char *cmd)
+MaybeSubMake(const char *cmd)
{
- size_t i;
- static struct {
- const char *name;
- size_t len;
- } vals[] = {
-#define MKV(A) { A, sizeof(A) - 1 }
- MKV("${MAKE}"),
- MKV("${.MAKE}"),
- MKV("$(MAKE)"),
- MKV("$(.MAKE)"),
- MKV("make"),
- };
- for (i = 0; i < sizeof vals / sizeof vals[0]; i++) {
- char *ptr;
- if ((ptr = strstr(cmd, vals[i].name)) == NULL)
+ const char *start;
+
+ for (start = cmd; *start != '\0'; start++) {
+ const char *p = start;
+ char endc;
+
+ /* XXX: What if progname != "make"? */
+ if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e')
+ if (start == cmd || !ch_isalnum(p[-1]))
+ if (!ch_isalnum(p[4]))
+ return TRUE;
+
+ if (*p != '$')
continue;
- if ((ptr == cmd || !ch_isalnum(ptr[-1]))
- && !ch_isalnum(ptr[vals[i].len]))
- return TRUE;
+ p++;
+
+ if (*p == '{')
+ endc = '}';
+ else if (*p == '(')
+ endc = ')';
+ else
+ continue;
+ p++;
+
+ if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */
+ p++;
+
+ if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E')
+ if (p[4] == endc)
+ return TRUE;
}
return FALSE;
}
@@ -2115,7 +2073,7 @@ ParseAddCmd(GNode *gn, char *cmd)
/* if target already supplied, ignore commands */
if (!(gn->type & OP_HAS_COMMANDS)) {
Lst_Append(gn->commands, cmd);
- if (ParseMaybeSubMake(cmd))
+ if (MaybeSubMake(cmd))
gn->type |= OP_SUBMAKE;
ParseMark(gn);
} else {
@@ -2145,49 +2103,46 @@ Parse_AddIncludeDir(const char *dir)
(void)Dir_AddDir(parseIncPath, dir);
}
-/* Push to another file.
+/* Handle one of the .[-ds]include directives by remembering the current file
+ * and pushing the included file on the stack. After the included file has
+ * finished, parsing continues with the including file; see Parse_SetInput
+ * and ParseEOF.
*
- * The input is the line minus the '.'. A file spec is a string enclosed in
- * <> or "". The <> file is looked for only in sysIncPath. The "" file is
- * first searched in the parsedir and then in the directories specified by
- * the -I command line options.
+ * System includes are looked up in sysIncPath, any other includes are looked
+ * up in the parsedir and then in the directories specified by the -I command
+ * line options.
*/
static void
-Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent)
+Parse_include_file(char *file, Boolean isSystem, Boolean depinc, Boolean silent)
{
struct loadedfile *lf;
char *fullname; /* full pathname of file */
char *newName;
- char *prefEnd, *incdir;
+ char *slash, *incdir;
int fd;
int i;
- /*
- * Now we know the file's name and its search path, we attempt to
- * find the durn thing. A return of NULL indicates the file don't
- * exist.
- */
fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
if (fullname == NULL && !isSystem) {
/*
- * Include files contained in double-quotes are first searched for
+ * Include files contained in double-quotes are first searched
* relative to the including file's location. We don't want to
* cd there, of course, so we just tack on the old file's
* leading path components and call Dir_FindFile to see if
- * we can locate the beast.
+ * we can locate the file.
*/
incdir = bmake_strdup(CurFile()->fname);
- prefEnd = strrchr(incdir, '/');
- if (prefEnd != NULL) {
- *prefEnd = '\0';
+ slash = strrchr(incdir, '/');
+ if (slash != NULL) {
+ *slash = '\0';
/* Now do lexical processing of leading "../" on the filename */
for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
- prefEnd = strrchr(incdir + 1, '/');
- if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0)
+ slash = strrchr(incdir + 1, '/');
+ if (slash == NULL || strcmp(slash, "/..") == 0)
break;
- *prefEnd = '\0';
+ *slash = '\0';
}
newName = str_concat3(incdir, "/", file + i);
fullname = Dir_FindFile(newName, parseIncPath);
@@ -2204,20 +2159,18 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent)
* then on the .PATH search path, if not found in a -I directory.
* If we have a suffix specific path we should use that.
*/
- char *suff;
+ const char *suff;
SearchPath *suffPath = NULL;
if ((suff = strrchr(file, '.'))) {
suffPath = Suff_GetPath(suff);
- if (suffPath != NULL) {
+ if (suffPath != NULL)
fullname = Dir_FindFile(file, suffPath);
- }
}
if (fullname == NULL) {
fullname = Dir_FindFile(file, parseIncPath);
- if (fullname == NULL) {
+ if (fullname == NULL)
fullname = Dir_FindFile(file, dirSearchPath);
- }
}
}
}
@@ -2261,12 +2214,11 @@ ParseDoInclude(char *line)
{
char endc; /* the character which ends the file spec */
char *cp; /* current position in file spec */
- int silent = *line != 'i';
+ Boolean silent = *line != 'i';
char *file = line + (silent ? 8 : 7);
/* Skip to delimiter character so we know where to look */
- while (*file == ' ' || *file == '\t')
- file++;
+ pp_skip_hspace(&file);
if (*file != '"' && *file != '<') {
Parse_Error(PARSE_FATAL,
@@ -2279,11 +2231,10 @@ ParseDoInclude(char *line)
* characters which bracket its name. Angle-brackets imply it's
* a system Makefile while double-quotes imply it's a user makefile
*/
- if (*file == '<') {
+ if (*file == '<')
endc = '>';
- } else {
+ else
endc = '"';
- }
/* Skip to matching delimiter */
for (cp = ++file; *cp && *cp != endc; cp++)
@@ -2291,15 +2242,15 @@ ParseDoInclude(char *line)
if (*cp != endc) {
Parse_Error(PARSE_FATAL,
- "Unclosed %cinclude filename. '%c' expected",
- '.', endc);
+ "Unclosed .include filename. '%c' expected", endc);
return;
}
+
*cp = '\0';
/*
- * Substitute for any variables in the file name before trying to
- * find the thing.
+ * Substitute for any variables in the filename before trying to
+ * find the file.
*/
(void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file);
/* TODO: handle errors */
@@ -2394,6 +2345,7 @@ StrContainsWord(const char *str, const char *word)
/* XXX: Searching through a set of words with this linear search is
* inefficient for variables that contain thousands of words. */
+/* XXX: The paths in this list don't seem to be normalized in any way. */
static Boolean
VarContainsWord(const char *varname, const char *word)
{
@@ -2405,7 +2357,10 @@ VarContainsWord(const char *varname, const char *word)
}
/* Track the makefiles we read - so makefiles can set dependencies on them.
- * Avoid adding anything more than once. */
+ * Avoid adding anything more than once.
+ *
+ * Time complexity: O(n) per call, in total O(n^2), where n is the number
+ * of makefiles that have been loaded. */
static void
ParseTrackInput(const char *name)
{
@@ -2414,7 +2369,7 @@ ParseTrackInput(const char *name)
}
-/* Start Parsing from the given source.
+/* Start parsing from the given source.
*
* The given file is added to the includes stack. */
void
@@ -2442,13 +2397,6 @@ Parse_SetInput(const char *name, int line, int fd,
return;
curFile = Vector_Push(&includes);
-
- /*
- * Once the previous state has been saved, we can get down to reading
- * the new file. We set up the name of the file to be the absolute
- * name of the include file so error messages refer to the right
- * place.
- */
curFile->fname = bmake_strdup(name);
curFile->fromForLoop = fromForLoop;
curFile->lineno = line;
@@ -2504,14 +2452,14 @@ IsSysVInclude(const char *line)
/* Avoid interpreting a dependency line as an include */
for (p = line; (p = strchr(p, ':')) != NULL;) {
- if (*++p == '\0') {
- /* end of line -> dependency */
+
+ /* end of line -> it's a dependency */
+ if (*++p == '\0')
return FALSE;
- }
- if (*p == ':' || ch_isspace(*p)) {
- /* :: operator or ': ' -> dependency */
+
+ /* '::' operator or ': ' -> it's a dependency */
+ if (*p == ':' || ch_isspace(*p))
return FALSE;
- }
}
return TRUE;
}
@@ -2521,8 +2469,8 @@ static void
ParseTraditionalInclude(char *line)
{
char *cp; /* current position in file spec */
- int done = 0;
- int silent = line[0] != 'i';
+ Boolean done = FALSE;
+ Boolean silent = line[0] != 'i';
char *file = line + (silent ? 8 : 7);
char *all_files;
@@ -2547,10 +2495,10 @@ ParseTraditionalInclude(char *line)
for (cp = file; *cp && !ch_isspace(*cp); cp++)
continue;
- if (*cp)
+ if (*cp != '\0')
*cp = '\0';
else
- done = 1;
+ done = TRUE;
Parse_include_file(file, FALSE, FALSE, silent);
}
@@ -2593,8 +2541,8 @@ ParseGmakeExport(char *line)
#endif
/* Called when EOF is reached in the current file. If we were reading an
- * include file, the includes stack is popped and things set up to go back
- * to reading the previous file at the previous location.
+ * include file or a .for loop, the includes stack is popped and things set
+ * up to go back to reading the previous file at the previous location.
*
* Results:
* TRUE to continue parsing, i.e. it had only reached the end of an
@@ -2614,12 +2562,10 @@ ParseEOF(void)
ptr = curFile->nextbuf(curFile->nextbuf_arg, &len);
curFile->buf_ptr = ptr;
curFile->buf_freeIt = ptr;
- curFile->buf_end = ptr + len;
+ curFile->buf_end = ptr + len; /* XXX: undefined behavior if ptr == NULL */
curFile->lineno = curFile->first_lineno;
- if (ptr != NULL) {
- /* Iterate again */
- return TRUE;
- }
+ if (ptr != NULL)
+ return TRUE; /* Iterate again */
/* Ensure the makefile (or loop) didn't have mismatched conditionals */
Cond_restore_depth(curFile->cond_depth);
@@ -2651,11 +2597,14 @@ ParseEOF(void)
return TRUE;
}
-#define PARSE_RAW 1
-#define PARSE_SKIP 2
+typedef enum GetLineMode {
+ PARSE_NORMAL,
+ PARSE_RAW,
+ PARSE_SKIP
+} GetLineMode;
static char *
-ParseGetLine(int flags)
+ParseGetLine(GetLineMode mode)
{
IFile *cf = CurFile();
char *ptr;
@@ -2678,11 +2627,11 @@ ParseGetLine(int flags)
/* XXX: can buf_end ever be null? */
if (cf->buf_end != NULL && ptr == cf->buf_end) {
/* end of buffer */
- ch = 0;
+ ch = '\0';
break;
}
ch = *ptr;
- if (ch == 0 || (ch == '\\' && ptr[1] == 0)) {
+ if (ch == '\0' || (ch == '\\' && ptr[1] == '\0')) {
/* XXX: can buf_end ever be null? */
if (cf->buf_end == NULL)
/* End of string (aka for loop) data */
@@ -2698,6 +2647,7 @@ ParseGetLine(int flags)
break;
}
}
+ /* XXX: Can cf->nextbuf ever be NULL? */
if (cf->nextbuf != NULL) {
/*
* End of this buffer; return EOF and outer logic
@@ -2738,7 +2688,7 @@ ParseGetLine(int flags)
/* Check we have a non-comment, non-blank line */
if (line_end == line || comment == line) {
- if (ch == 0)
+ if (ch == '\0')
/* At end of file */
return NULL;
/* Parse another line */
@@ -2746,14 +2696,14 @@ ParseGetLine(int flags)
}
/* We now have a line of data */
- *line_end = 0;
+ *line_end = '\0';
- if (flags & PARSE_RAW) {
+ if (mode == PARSE_RAW) {
/* Leave '\' (etc) in line buffer (eg 'for' lines) */
return line;
}
- if (flags & PARSE_SKIP) {
+ if (mode == PARSE_SKIP) {
/* Completely ignore non-directives */
if (line[0] != '.')
continue;
@@ -2765,7 +2715,7 @@ ParseGetLine(int flags)
/* Brutally ignore anything after a non-escaped '#' in non-commands */
if (comment != NULL && line[0] != '\t') {
line_end = comment;
- *line_end = 0;
+ *line_end = '\0';
}
/* If we didn't see a '\\' then the in-situ data is fine */
@@ -2778,13 +2728,13 @@ ParseGetLine(int flags)
for (; ; *tp++ = ch) {
ch = *ptr++;
if (ch != '\\') {
- if (ch == 0)
+ if (ch == '\0')
break;
continue;
}
ch = *ptr++;
- if (ch == 0) {
+ if (ch == '\0') {
/* Delete '\\' at end of buffer */
tp--;
break;
@@ -2802,9 +2752,8 @@ ParseGetLine(int flags)
continue;
}
- /* Escaped '\n' replace following whitespace with a single ' ' */
- while (ptr[0] == ' ' || ptr[0] == '\t')
- ptr++;
+ /* Escaped '\n' -- replace following whitespace with a single ' '. */
+ pp_skip_hspace(&ptr);
ch = ' ';
}
@@ -2812,17 +2761,14 @@ ParseGetLine(int flags)
while (tp > escaped && ch_isspace(tp[-1]))
tp--;
- *tp = 0;
+ *tp = '\0';
return line;
}
/* Read an entire line from the input file. Called only by Parse_File.
*
* Results:
- * A line without its newline.
- *
- * Side Effects:
- * Only those associated with reading a character
+ * A line without its newline and without any trailing whitespace.
*/
static char *
ParseReadLine(void)
@@ -2832,7 +2778,7 @@ ParseReadLine(void)
int rval;
for (;;) {
- line = ParseGetLine(0);
+ line = ParseGetLine(PARSE_NORMAL);
if (line == NULL)
return NULL;
@@ -2886,22 +2832,24 @@ ParseReadLine(void)
static void
FinishDependencyGroup(void)
{
- if (targets != NULL) {
- GNodeListNode *ln;
- for (ln = targets->first; ln != NULL; ln = ln->next) {
- GNode *gn = ln->datum;
+ GNodeListNode *ln;
- Suff_EndTransform(gn);
+ if (targets == NULL)
+ return;
- /* Mark the target as already having commands if it does, to
- * keep from having shell commands on multiple dependency lines. */
- if (!Lst_IsEmpty(gn->commands))
- gn->type |= OP_HAS_COMMANDS;
- }
+ for (ln = targets->first; ln != NULL; ln = ln->next) {
+ GNode *gn = ln->datum;
- Lst_Free(targets);
- targets = NULL;
+ Suff_EndTransform(gn);
+
+ /* Mark the target as already having commands if it does, to
+ * keep from having shell commands on multiple dependency lines. */
+ if (!Lst_IsEmpty(gn->commands))
+ gn->type |= OP_HAS_COMMANDS;
}
+
+ Lst_Free(targets);
+ targets = NULL;
}
/* Add the command to each target from the current dependency spec. */
@@ -2938,10 +2886,11 @@ ParseDirective(char *line)
if (*line == '.') {
/*
- * Lines that begin with the special character may be
- * include or undef directives.
- * On the other hand they can be suffix rules (.c.o: ...)
- * or just dependencies for filenames that start '.'.
+ * Lines that begin with '.' can be pretty much anything:
+ * - directives like '.include' or '.if',
+ * - suffix rules like '.c.o:',
+ * - dependencies for filenames that start with '.',
+ * - variable assignments like '.tmp=value'.
*/
cp = line + 1;
pp_skip_whitespace(&cp);
@@ -2983,12 +2932,13 @@ static Boolean
ParseVarassign(const char *line)
{
VarAssign var;
- if (Parse_IsVar(line, &var)) {
- FinishDependencyGroup();
- Parse_DoVar(&var, VAR_GLOBAL);
- return TRUE;
- }
- return FALSE;
+
+ if (!Parse_IsVar(line, &var))
+ return FALSE;
+
+ FinishDependencyGroup();
+ Parse_DoVar(&var, VAR_GLOBAL);
+ return TRUE;
}
static char *
@@ -3002,19 +2952,12 @@ FindSemicolon(char *p)
continue;
}
- if (*p == '$' && (p[1] == '(' || p[1] == '{')) {
+ if (*p == '$' && (p[1] == '(' || p[1] == '{'))
level++;
- continue;
- }
-
- if (level > 0 && (*p == ')' || *p == '}')) {
+ else if (level > 0 && (*p == ')' || *p == '}'))
level--;
- continue;
- }
-
- if (level == 0 && *p == ';') {
+ else if (level == 0 && *p == ';')
break;
- }
}
return p;
}
@@ -3055,7 +2998,7 @@ ParseDependency(char *line)
*
* Parsing the line first would also prevent that targets
* generated from variable expressions are interpreted as the
- * dependency operator, such as in "target${:U:} middle: source",
+ * dependency operator, such as in "target${:U\:} middle: source",
* in which the middle is interpreted as a source, not a target.
*/
@@ -3063,7 +3006,7 @@ ParseDependency(char *line)
* dependency lines.
*
* Ideally, only the right-hand side would allow undefined
- * variables since it is common to have no dependencies.
+ * variables since it is common to have optional dependencies.
* Having undefined variables on the left-hand side is more
* unusual though. Since both sides are expanded in a single
* pass, there is not much choice what to do here.
@@ -3073,7 +3016,7 @@ ParseDependency(char *line)
* Var_Parse does not print any parse errors in such a case.
* It simply returns the special empty string var_Error,
* which cannot be detected in the result of Var_Subst. */
- eflags = DEBUG(LINT) ? VARE_WANTRES : VARE_UNDEFERR | VARE_WANTRES;
+ eflags = opts.lint ? VARE_WANTRES : VARE_WANTRES | VARE_UNDEFERR;
(void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line);
/* TODO: handle errors */
@@ -3129,8 +3072,8 @@ ParseLine(char *line)
ParseDependency(line);
}
-/* Parse a top-level makefile into its component parts, incorporating them
- * into the global dependency graph.
+/* Parse a top-level makefile, incorporating its content into the global
+ * dependency graph.
*
* Input:
* name The name of the file being read
@@ -3145,7 +3088,6 @@ Parse_File(const char *name, int fd)
lf = loadfile(name, fd);
assert(targets == NULL);
- fatals = 0;
if (name == NULL)
name = "(stdin)";
@@ -3166,7 +3108,7 @@ Parse_File(const char *name, int fd)
FinishDependencyGroup();
- if (fatals) {
+ if (fatals != 0) {
(void)fflush(stdout);
(void)fprintf(stderr,
"%s: Fatal errors encountered -- cannot continue",
@@ -3206,19 +3148,9 @@ Parse_End(void)
}
-/*-
- *-----------------------------------------------------------------------
- * Parse_MainName --
- * Return a Lst of the main target to create for main()'s sake. If
- * no such target exists, we Punt with an obnoxious error message.
- *
- * Results:
- * A Lst of the single node to create.
- *
- * Side Effects:
- * None.
- *
- *-----------------------------------------------------------------------
+/*
+ * Return a list containing the single main target to create.
+ * If no such target exists, we Punt with an obnoxious error message.
*/
GNodeList *
Parse_MainName(void)
@@ -3227,15 +3159,17 @@ Parse_MainName(void)
mainList = Lst_New();
- if (mainNode == NULL) {
+ if (mainNode == NULL)
Punt("no target to make.");
- /*NOTREACHED*/
- } else if (mainNode->type & OP_DOUBLEDEP) {
+
+ if (mainNode->type & OP_DOUBLEDEP) {
Lst_Append(mainList, mainNode);
Lst_AppendAll(mainList, mainNode->cohorts);
} else
Lst_Append(mainList, mainNode);
+
Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
+
return mainList;
}
diff --git a/str.c b/str.c
index c453ce975e22..6633482772d3 100644
--- a/str.c
+++ b/str.c
@@ -1,4 +1,4 @@
-/* $NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $ */
+/* $NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $ */
/*-
* Copyright (c) 1988, 1989, 1990, 1993
@@ -71,7 +71,7 @@
#include "make.h"
/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */
-MAKE_RCSID("$NetBSD: str.c,v 1.70 2020/10/24 20:51:49 rillig Exp $");
+MAKE_RCSID("$NetBSD: str.c,v 1.74 2020/11/16 18:28:27 rillig Exp $");
/* Return the concatenation of s1 and s2, freshly allocated. */
char *
@@ -116,15 +116,13 @@ str_concat4(const char *s1, const char *s2, const char *s3, const char *s4)
}
/* Fracture a string into an array of words (as delineated by tabs or spaces)
- * taking quotation marks into account. Leading tabs/spaces are ignored.
+ * taking quotation marks into account.
*
* If expand is TRUE, quotes are removed and escape sequences such as \r, \t,
- * etc... are expanded. In this case, the return value is NULL on parse
- * errors.
+ * etc... are expanded. In this case, return NULL on parse errors.
*
- * Returns the fractured words, which must be freed later using Words_Free.
- * If expand was TRUE and there was a parse error, words is NULL, and in that
- * case, nothing needs to be freed.
+ * Returns the fractured words, which must be freed later using Words_Free,
+ * unless the returned Words.words was NULL.
*/
Words
Str_Words(const char *str, Boolean expand)
@@ -139,9 +137,8 @@ Str_Words(const char *str, Boolean expand)
char *word_end;
const char *str_p;
- /* skip leading space chars. */
- for (; *str == ' ' || *str == '\t'; ++str)
- continue;
+ /* XXX: why only hspace, not whitespace? */
+ cpp_skip_hspace(&str); /* skip leading space chars. */
/* words_buf holds the words, separated by '\0'. */
str_len = strlen(str);
@@ -239,7 +236,7 @@ Str_Words(const char *str, Boolean expand)
case '\n':
/* hmmm; fix it up as best we can */
ch = '\\';
- --str_p;
+ str_p--;
break;
case 'b':
ch = '\b';
@@ -264,21 +261,15 @@ Str_Words(const char *str, Boolean expand)
*word_end++ = ch;
}
done:
- words[words_len] = NULL;
+ words[words_len] = NULL; /* useful for argv */
return (Words){ words, words_len, words_buf };
}
/*
* Str_Match -- Test if a string matches a pattern like "*.[ch]".
+ * The following special characters are known *?\[] (as in fnmatch(3)).
*
- * XXX this function does not detect or report malformed patterns.
- *
- * Results:
- * Non-zero is returned if string matches the pattern, 0 otherwise. The
- * matching operation permits the following special characters in the
- * pattern: *?\[] (as in fnmatch(3)).
- *
- * Side effects: None.
+ * XXX: this function does not detect or report malformed patterns.
*/
Boolean
Str_Match(const char *str, const char *pat)
@@ -286,12 +277,12 @@ Str_Match(const char *str, const char *pat)
for (;;) {
/*
* See if we're at the end of both the pattern and the
- * string. If, we succeeded. If we're at the end of the
+ * string. If so, we succeeded. If we're at the end of the
* pattern but not at the end of the string, we failed.
*/
- if (*pat == 0)
- return *str == 0;
- if (*str == 0 && *pat != '*')
+ if (*pat == '\0')
+ return *str == '\0';
+ if (*str == '\0' && *pat != '*')
return FALSE;
/*
@@ -302,9 +293,9 @@ Str_Match(const char *str, const char *pat)
pat++;
while (*pat == '*')
pat++;
- if (*pat == 0)
+ if (*pat == '\0')
return TRUE;
- while (*str != 0) {
+ while (*str != '\0') {
if (Str_Match(str, pat))
return TRUE;
str++;
@@ -327,15 +318,18 @@ Str_Match(const char *str, const char *pat)
pat += neg ? 2 : 1;
for (;;) {
- if (*pat == ']' || *pat == 0) {
+ if (*pat == ']' || *pat == '\0') {
if (neg)
break;
return FALSE;
}
+ /* XXX: This naive comparison makes the parser
+ * for the pattern dependent on the actual of
+ * the string. This is unpredictable. */
if (*pat == *str)
break;
if (pat[1] == '-') {
- if (pat[2] == 0)
+ if (pat[2] == '\0')
return neg;
if (*pat <= *str && pat[2] >= *str)
break;
@@ -345,11 +339,11 @@ Str_Match(const char *str, const char *pat)
}
pat++;
}
- if (neg && *pat != ']' && *pat != 0)
+ if (neg && *pat != ']' && *pat != '\0')
return FALSE;
- while (*pat != ']' && *pat != 0)
+ while (*pat != ']' && *pat != '\0')
pat++;
- if (*pat == 0)
+ if (*pat == '\0')
pat--;
goto thisCharOK;
}
@@ -360,7 +354,7 @@ Str_Match(const char *str, const char *pat)
*/
if (*pat == '\\') {
pat++;
- if (*pat == 0)
+ if (*pat == '\0')
return FALSE;
}
diff --git a/suff.c b/suff.c
index 4ccf14a93ff7..f0be002cebe1 100644
--- a/suff.c
+++ b/suff.c
@@ -1,4 +1,4 @@
-/* $NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $ */
+/* $NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,36 +68,23 @@
* SUCH DAMAGE.
*/
-/*-
- * suff.c --
- * Functions to maintain suffix lists and find implicit dependents
- * using suffix transformation rules
+/*
+ * Maintain suffix lists and find implicit dependents using suffix
+ * transformation rules such as ".c.o".
*
* Interface:
- * Suff_Init Initialize all things to do with suffixes.
+ * Suff_Init Initialize the module.
*
- * Suff_End Clean up the module
+ * Suff_End Clean up the module.
*
- * Suff_DoPaths This function is used to make life easier
- * when searching for a file according to its
- * suffix. It takes the global search path,
- * as defined using the .PATH: target, and appends
- * its directories to the path of each of the
- * defined suffixes, as specified using
- * .PATH<suffix>: targets. In addition, all
- * directories given for suffixes labeled as
- * include files or libraries, using the .INCLUDES
- * or .LIBS targets, are played with using
- * Dir_MakeFlags to create the .INCLUDES and
- * .LIBS global variables.
+ * Suff_DoPaths Extend the search path of each suffix to include the
+ * default search path.
*
* Suff_ClearSuffixes
- * Clear out all the suffixes and defined
- * transformations.
+ * Clear out all the suffixes and transformations.
*
* Suff_IsTransform
- * Return TRUE if the passed string is the lhs
- * of a transformation rule.
+ * See if the passed string is a transformation rule.
*
* Suff_AddSuffix Add the passed string as another known suffix.
*
@@ -109,9 +96,7 @@
* Suff_AddLib Mark the given suffix as denoting a library.
*
* Suff_AddTransform
- * Add another transformation to the suffix
- * graph. Returns GNode suitable for framing, I
- * mean, tacking commands, attributes, etc. on.
+ * Add another transformation to the suffix graph.
*
* Suff_SetNull Define the suffix to consider the suffix of
* any file that doesn't have a known one.
@@ -129,14 +114,11 @@
#include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */
-MAKE_RCSID("$NetBSD: suff.c,v 1.230 2020/10/31 11:54:33 rillig Exp $");
+MAKE_RCSID("$NetBSD: suff.c,v 1.247 2020/11/16 23:27:41 rillig Exp $");
#define SUFF_DEBUG0(text) DEBUG0(SUFF, text)
#define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1)
#define SUFF_DEBUG2(fmt, arg1, arg2) DEBUG2(SUFF, fmt, arg1, arg2)
-#define SUFF_DEBUG3(fmt, arg1, arg2, arg3) DEBUG3(SUFF, fmt, arg1, arg2, arg3)
-#define SUFF_DEBUG4(fmt, arg1, arg2, arg3, arg4) \
- DEBUG4(SUFF, fmt, arg1, arg2, arg3, arg4)
typedef List SuffList;
typedef ListNode SuffListNode;
@@ -149,9 +131,11 @@ static SuffList *sufflist; /* List of suffixes */
static SuffList *suffClean; /* List of suffixes to be cleaned */
#endif
static SrcList *srclist; /* List of sources */
-static GNodeList *transforms; /* List of transformation rules */
-static int sNum = 0; /* Counter for assigning suffix numbers */
+/* List of transformation rules, such as ".c.o" */
+static GNodeList *transforms;
+
+static int sNum = 0; /* Counter for assigning suffix numbers */
typedef enum SuffFlags {
SUFF_INCLUDE = 0x01, /* One which is #include'd */
@@ -165,21 +149,29 @@ ENUM_FLAGS_RTTI_3(SuffFlags,
typedef List SuffListList;
-/*
- * Structure describing an individual suffix.
- */
typedef struct Suff {
- char *name; /* The suffix itself, such as ".c" */
- size_t nameLen; /* Length of the name, to avoid strlen calls */
- SuffFlags flags; /* Type of suffix */
- SearchPath *searchPath; /* The path along which files of this suffix
- * may be found */
- int sNum; /* The suffix number */
- int refCount; /* Reference count of list membership
- * and several other places */
- SuffList *parents; /* Suffixes we have a transformation to */
- SuffList *children; /* Suffixes we have a transformation from */
- SuffListList *ref; /* Lists in which this suffix is referenced */
+ /* The suffix itself, such as ".c" */
+ char *name;
+ /* Length of the name, to avoid strlen calls */
+ size_t nameLen;
+ /* Type of suffix */
+ SuffFlags flags;
+ /* The path along which files of this suffix may be found */
+ SearchPath *searchPath;
+ /* The suffix number; TODO: document the purpose of this number */
+ int sNum;
+ /* Reference count of list membership and several other places */
+ int refCount;
+ /* Suffixes we have a transformation to */
+ SuffList *parents;
+ /* Suffixes we have a transformation from */
+ SuffList *children;
+
+ /* Lists in which this suffix is referenced.
+ * XXX: These lists are used nowhere, they are just appended to, for no
+ * apparent reason. They do have the side effect of increasing refCount
+ * though. */
+ SuffListList *ref;
} Suff;
/*
@@ -191,37 +183,26 @@ typedef struct Src {
Suff *suff; /* The suffix on the file */
struct Src *parent; /* The Src for which this is a source */
GNode *node; /* The node describing the file */
- int children; /* Count of existing children (so we don't free
+ int numChildren; /* Count of existing children (so we don't free
* this thing too early or never nuke it) */
#ifdef DEBUG_SRC
SrcList *childrenList;
#endif
} Src;
-static Suff *suffNull; /* The NULL suffix for this run */
-static Suff *emptySuff; /* The empty suffix required for POSIX
- * single-suffix transformation rules */
+/* TODO: Document the difference between suffNull and emptySuff. */
+/* The NULL suffix for this run */
+static Suff *suffNull;
+/* The empty suffix required for POSIX single-suffix transformation rules */
+static Suff *emptySuff;
static void SuffFindDeps(GNode *, SrcList *);
static void SuffExpandWildcards(GNodeListNode *, GNode *);
- /*************** Lst Predicates ****************/
-/*-
- *-----------------------------------------------------------------------
- * SuffStrIsPrefix --
- * See if pref is a prefix of str.
- *
- * Input:
- * pref possible prefix
- * str string to check
- *
- * Results:
- * NULL if it ain't, pointer to character in str after prefix if so
- *
- * Side Effects:
- * None
- *-----------------------------------------------------------------------
+/*
+ * See if pref is a prefix of str.
+ * Return NULL if it ain't, pointer to character in str after prefix if so.
*/
static const char *
SuffStrIsPrefix(const char *pref, const char *str)
@@ -231,18 +212,12 @@ SuffStrIsPrefix(const char *pref, const char *str)
str++;
}
- return *pref ? NULL : str;
+ return *pref != '\0' ? NULL : str;
}
-/* See if suff is a suffix of str.
- *
- * Input:
- * s possible suffix
- * nameLen length of the string to examine
- * nameEnd end of the string to examine
- *
- * Results:
- * NULL if it ain't, pointer to the start of suffix in str if it is.
+/*
+ * See if suff is a suffix of name.
+ * Return NULL if it ain't, pointer to the start of suffix in name if it is.
*/
static const char *
SuffSuffGetSuffix(const Suff *s, size_t nameLen, const char *nameEnd)
@@ -302,10 +277,8 @@ FindTransformByName(const char *name)
return NULL;
}
- /*********** Maintenance Functions ************/
-
static void
-SuffUnRef(SuffList *list, Suff *suff)
+SuffList_Unref(SuffList *list, Suff *suff)
{
SuffListNode *ln = Lst_FindDatum(list, suff);
if (ln != NULL) {
@@ -318,45 +291,46 @@ SuffUnRef(SuffList *list, Suff *suff)
static void
SuffFree(void *sp)
{
- Suff *s = sp;
+ Suff *suff = sp;
- if (s == suffNull)
+ if (suff == suffNull)
suffNull = NULL;
- if (s == emptySuff)
+ if (suff == emptySuff)
emptySuff = NULL;
#if 0
/* We don't delete suffixes in order, so we cannot use this */
- if (s->refCount)
- Punt("Internal error deleting suffix `%s' with refcount = %d", s->name,
- s->refCount);
+ if (suff->refCount != 0)
+ Punt("Internal error deleting suffix `%s' with refcount = %d",
+ suff->name, suff->refCount);
#endif
- Lst_Free(s->ref);
- Lst_Free(s->children);
- Lst_Free(s->parents);
- Lst_Destroy(s->searchPath, Dir_Destroy);
+ Lst_Free(suff->ref);
+ Lst_Free(suff->children);
+ Lst_Free(suff->parents);
+ Lst_Destroy(suff->searchPath, Dir_Destroy);
- free(s->name);
- free(s);
+ free(suff->name);
+ free(suff);
}
/* Remove the suffix from the list, and free if it is otherwise unused. */
static void
-SuffRemove(SuffList *list, Suff *suff)
+SuffList_Remove(SuffList *list, Suff *suff)
{
- SuffUnRef(list, suff);
+ SuffList_Unref(list, suff);
if (suff->refCount == 0) {
- SuffUnRef(sufflist, suff);
+ /* XXX: can lead to suff->refCount == -1 */
+ SuffList_Unref(sufflist, suff);
SuffFree(suff);
}
}
/* Insert the suffix into the list, keeping the list ordered by suffix
- * numbers. */
+ * number. */
static void
-SuffInsert(SuffList *list, Suff *suff)
+SuffList_Insert(SuffList *list, Suff *suff)
{
SuffListNode *ln;
Suff *listSuff = NULL;
@@ -374,8 +348,8 @@ SuffInsert(SuffList *list, Suff *suff)
suff->refCount++;
Lst_Append(suff->ref, list);
} else if (listSuff->sNum != suff->sNum) {
- SUFF_DEBUG4("inserting \"%s\" (%d) before \"%s\" (%d)\n",
- suff->name, suff->sNum, listSuff->name, listSuff->sNum);
+ DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n",
+ suff->name, suff->sNum, listSuff->name, listSuff->sNum);
Lst_InsertBefore(list, ln, suff);
suff->refCount++;
Lst_Append(suff->ref, list);
@@ -384,29 +358,38 @@ SuffInsert(SuffList *list, Suff *suff)
}
}
+static void
+SuffRelate(Suff *srcSuff, Suff *targSuff)
+{
+ SuffList_Insert(targSuff->children, srcSuff);
+ SuffList_Insert(srcSuff->parents, targSuff);
+}
+
static Suff *
SuffNew(const char *name)
{
- Suff *s = bmake_malloc(sizeof(Suff));
-
- s->name = bmake_strdup(name);
- s->nameLen = strlen(s->name);
- s->searchPath = Lst_New();
- s->children = Lst_New();
- s->parents = Lst_New();
- s->ref = Lst_New();
- s->sNum = sNum++;
- s->flags = 0;
- s->refCount = 1;
-
- return s;
+ Suff *suff = bmake_malloc(sizeof *suff);
+
+ suff->name = bmake_strdup(name);
+ suff->nameLen = strlen(suff->name);
+ suff->searchPath = Lst_New();
+ suff->children = Lst_New();
+ suff->parents = Lst_New();
+ suff->ref = Lst_New();
+ suff->sNum = sNum++;
+ suff->flags = 0;
+ suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */
+
+ return suff;
}
-/* This is gross. Nuke the list of suffixes but keep all transformation
- * rules around. The transformation graph is destroyed in this process, but
- * we leave the list of rules so when a new graph is formed the rules will
- * remain. This function is called from the parse module when a .SUFFIXES:\n
- * line is encountered. */
+/*
+ * Nuke the list of suffixes but keep all transformation rules around. The
+ * transformation graph is destroyed in this process, but we leave the list
+ * of rules so when a new graph is formed, the rules will remain. This
+ * function is called when a line '.SUFFIXES:' with an empty suffixes list is
+ * encountered in a makefile.
+ */
void
Suff_ClearSuffixes(void)
{
@@ -477,7 +460,8 @@ SuffParseTransform(const char *str, Suff **out_src, Suff **out_targ)
}
/* Return TRUE if the given string is a transformation rule, that is, a
- * concatenation of two known suffixes. */
+ * concatenation of two known suffixes such as ".c.o" or a single suffix
+ * such as ".o". */
Boolean
Suff_IsTransform(const char *str)
{
@@ -500,18 +484,16 @@ Suff_IsTransform(const char *str)
GNode *
Suff_AddTransform(const char *name)
{
- GNode *gn; /* GNode of transformation rule */
- Suff *s, /* source suffix */
- *t; /* target suffix */
- Boolean ok;
+ Suff *srcSuff;
+ Suff *targSuff;
- gn = FindTransformByName(name);
+ GNode *gn = FindTransformByName(name);
if (gn == NULL) {
/*
* Make a new graph node for the transformation. It will be filled in
* by the Parse module.
*/
- gn = Targ_NewGN(name);
+ gn = GNode_New(name);
Lst_Append(transforms, gn);
} else {
/*
@@ -528,17 +510,18 @@ Suff_AddTransform(const char *name)
gn->type = OP_TRANSFORM;
- ok = SuffParseTransform(name, &s, &t);
- assert(ok);
- (void)ok;
+ {
+ Boolean ok = SuffParseTransform(name, &srcSuff, &targSuff);
+ assert(ok);
+ (void)ok;
+ }
/*
* link the two together in the proper relationship and order
*/
SUFF_DEBUG2("defining transformation from `%s' to `%s'\n",
- s->name, t->name);
- SuffInsert(t->children, s);
- SuffInsert(s->parents, t);
+ srcSuff->name, targSuff->name);
+ SuffRelate(srcSuff, targSuff);
return gn;
}
@@ -557,40 +540,29 @@ Suff_EndTransform(GNode *gn)
{
if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts))
gn = gn->cohorts->last->datum;
+
if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
Lst_IsEmpty(gn->children))
{
- Suff *s, *t;
+ Suff *srcSuff, *targSuff;
/*
* SuffParseTransform() may fail for special rules which are not
* actual transformation rules. (e.g. .DEFAULT)
*/
- if (SuffParseTransform(gn->name, &s, &t)) {
- SuffList *p;
-
- SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n",
- s->name, t->name);
+ if (SuffParseTransform(gn->name, &srcSuff, &targSuff)) {
/*
- * Store s->parents because s could be deleted in SuffRemove
+ * Remember parents since srcSuff could be deleted in
+ * SuffList_Remove
*/
- p = s->parents;
+ SuffList *srcSuffParents = srcSuff->parents;
- /*
- * Remove the source from the target's children list. We check for a
- * nil return to handle a beanhead saying something like
- * .c.o .c.o:
- *
- * We'll be called twice when the next target is seen, but .c and .o
- * are only linked once...
- */
- SuffRemove(t->children, s);
+ SUFF_DEBUG2("deleting transformation from `%s' to `%s'\n",
+ srcSuff->name, targSuff->name);
- /*
- * Remove the target from the source's parents list
- */
- SuffRemove(p, t);
+ SuffList_Remove(targSuff->children, srcSuff);
+ SuffList_Remove(srcSuffParents, targSuff);
}
} else if (gn->type & OP_TRANSFORM) {
SUFF_DEBUG1("transformation %s complete\n", gn->name);
@@ -625,8 +597,7 @@ SuffRebuildGraph(GNode *transform, Suff *suff)
Suff *to = FindSuffByName(toName);
if (to != NULL) {
/* Link in and return, since it can't be anything else. */
- SuffInsert(to->children, suff);
- SuffInsert(suff->parents, to);
+ SuffRelate(suff, to);
return;
}
}
@@ -637,12 +608,8 @@ SuffRebuildGraph(GNode *transform, Suff *suff)
toName = SuffSuffGetSuffix(suff, nameLen, name + nameLen);
if (toName != NULL) {
Suff *from = FindSuffByNameLen(name, (size_t)(toName - name));
-
- if (from != NULL) {
- /* establish the proper relationship */
- SuffInsert(suff->children, from);
- SuffInsert(from->parents, suff);
- }
+ if (from != NULL)
+ SuffRelate(from, suff);
}
}
@@ -659,7 +626,7 @@ SuffRebuildGraph(GNode *transform, Suff *suff)
static Boolean
SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r)
{
- Suff *s, *t;
+ Suff *srcSuff, *targSuff;
char *ptr;
if (*inout_main == NULL && *gs_r && !(target->type & OP_NOTARGET)) {
@@ -675,7 +642,7 @@ SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r)
ptr == target->name)
return FALSE;
- if (SuffParseTransform(target->name, &s, &t)) {
+ if (SuffParseTransform(target->name, &srcSuff, &targSuff)) {
if (*inout_main == target) {
*gs_r = TRUE;
*inout_main = NULL;
@@ -688,9 +655,8 @@ SuffScanTargets(GNode *target, GNode **inout_main, Suff *gs_s, Boolean *gs_r)
* link the two together in the proper relationship and order
*/
SUFF_DEBUG2("defining transformation from `%s' to `%s'\n",
- s->name, t->name);
- SuffInsert(t->children, s);
- SuffInsert(s->parents, t);
+ srcSuff->name, targSuff->name);
+ SuffRelate(srcSuff, targSuff);
}
return FALSE;
}
@@ -753,14 +719,18 @@ Suff_GetPath(const char *sname)
return s != NULL ? s->searchPath : NULL;
}
-/* Extend the search paths for all suffixes to include the default search
- * path.
+/*
+ * Extend the search paths for all suffixes to include the default search
+ * path (dirSearchPath).
+ *
+ * The default search path can be defined using the special target '.PATH'.
+ * The search path of each suffix can be defined using the special target
+ * '.PATH<suffix>'.
*
- * The searchPath field of all the suffixes is extended by the directories
- * in dirSearchPath. If paths were specified for the ".h" suffix, the
- * directories are stuffed into a global variable called ".INCLUDES" with
- * each directory preceded by a -I. The same is done for the ".a" suffix,
- * except the variable is called ".LIBS" and the flag is -L.
+ * If paths were specified for the ".h" suffix, the directories are stuffed
+ * into a global variable called ".INCLUDES" with each directory preceded by
+ * '-I'. The same is done for the ".a" suffix, except the variable is called
+ * ".LIBS" and the flag is '-L'.
*/
void
Suff_DoPaths(void)
@@ -777,14 +747,12 @@ Suff_DoPaths(void)
Suff *s = ln->datum;
if (!Lst_IsEmpty(s->searchPath)) {
#ifdef INCLUDES
- if (s->flags & SUFF_INCLUDE) {
+ if (s->flags & SUFF_INCLUDE)
Dir_Concat(inIncludes, s->searchPath);
- }
#endif
#ifdef LIBRARIES
- if (s->flags & SUFF_LIBRARY) {
+ if (s->flags & SUFF_LIBRARY)
Dir_Concat(inLibs, s->searchPath);
- }
#endif
Dir_Concat(s->searchPath, dirSearchPath);
} else {
@@ -857,7 +825,7 @@ SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn)
src->suff = suff;
src->parent = parent;
src->node = gn;
- src->children = 0;
+ src->numChildren = 0;
#ifdef DEBUG_SRC
src->childrenList = Lst_New();
#endif
@@ -867,11 +835,11 @@ SrcNew(char *name, char *pref, Suff *suff, Src *parent, GNode *gn)
static void
SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName,
- const char *debug_tag)
+ const char *debug_tag MAKE_ATTR_UNUSED)
{
Src *s2 = SrcNew(srcName, targ->pref, suff, targ, NULL);
suff->refCount++;
- targ->children++;
+ targ->numChildren++;
Lst_Append(srcList, s2);
#ifdef DEBUG_SRC
Lst_Append(targ->childrenList, s2);
@@ -883,7 +851,7 @@ SuffAddSrc(Suff *suff, SrcList *srcList, Src *targ, char *srcName,
/* Add a suffix as a Src structure to the given list with its parent
* being the given Src structure. If the suffix is the null suffix,
- * the prefix is used unaltered as the file name in the Src structure.
+ * the prefix is used unaltered as the filename in the Src structure.
*
* Input:
* suff suffix for which to create a Src structure
@@ -904,19 +872,14 @@ SuffAddSources(Suff *suff, SrcList *srcList, Src *targ)
SuffAddSrc(suff, srcList, targ, str_concat2(targ->pref, suff->name), "2");
}
-/* Add all the children of targ as Src structures to the given list.
- *
- * Input:
- * l list to which to add the new level
- * targ Src structure to use as the parent
- */
+/* Add all the children of targ to the list. */
static void
-SuffAddLevel(SrcList *l, Src *targ)
+SuffAddLevel(SrcList *srcs, Src *targ)
{
SrcListNode *ln;
for (ln = targ->suff->children->first; ln != NULL; ln = ln->next) {
Suff *childSuff = ln->datum;
- SuffAddSources(childSuff, l, targ);
+ SuffAddSources(childSuff, srcs, targ);
}
}
@@ -933,34 +896,34 @@ SuffRemoveSrc(SrcList *l)
#endif
for (ln = l->first; ln != NULL; ln = ln->next) {
- Src *s = ln->datum;
+ Src *src = ln->datum;
- if (s->children == 0) {
- free(s->file);
- if (s->parent == NULL)
- free(s->pref);
+ if (src->numChildren == 0) {
+ free(src->file);
+ if (src->parent == NULL)
+ free(src->pref);
else {
#ifdef DEBUG_SRC
- SrcListNode *ln2 = Lst_FindDatum(s->parent->childrenList, s);
+ SrcListNode *ln2 = Lst_FindDatum(src->parent->childrenList, src);
if (ln2 != NULL)
- Lst_Remove(s->parent->childrenList, ln2);
+ Lst_Remove(src->parent->childrenList, ln2);
#endif
- s->parent->children--;
+ src->parent->numChildren--;
}
#ifdef DEBUG_SRC
debug_printf("free: list %p src %p children %d\n",
- l, s, s->children);
- Lst_Free(s->childrenList);
+ l, src, src->children);
+ Lst_Free(src->childrenList);
#endif
Lst_Remove(l, ln);
- free(s);
+ free(src);
return TRUE;
}
#ifdef DEBUG_SRC
else {
debug_printf("keep: list %p src %p children %d:",
- l, s, s->children);
- SrcList_PrintAddrs(s->childrenList);
+ l, src, src->children);
+ SrcList_PrintAddrs(src->childrenList);
}
#endif
}
@@ -968,14 +931,7 @@ SuffRemoveSrc(SrcList *l)
return FALSE;
}
-/* Find the first existing file/target in the list srcs.
- *
- * Input:
- * srcs list of Src structures to search through
- *
- * Results:
- * The lowest structure in the chain of transformations, or NULL.
- */
+/* Find the first existing file/target in srcs. */
static Src *
SuffFindThem(SrcList *srcs, SrcList *slst)
{
@@ -1068,10 +1024,7 @@ SuffFindCmds(Src *targ, SrcList *slst)
}
if (strncmp(cp, targ->pref, prefLen) != 0)
continue;
- /*
- * The node matches the prefix ok, see if it has a known
- * suffix.
- */
+ /* The node matches the prefix ok, see if it has a known suffix. */
suff = FindSuffByName(cp + prefLen);
if (suff == NULL)
continue;
@@ -1100,7 +1053,7 @@ SuffFindCmds(Src *targ, SrcList *slst)
*/
ret = SrcNew(bmake_strdup(sgn->name), targ->pref, suff, targ, sgn);
suff->refCount++;
- targ->children++;
+ targ->numChildren++;
#ifdef DEBUG_SRC
debug_printf("3 add targ %p ret %p\n", targ, ret);
Lst_Append(targ->childrenList, ret);
@@ -1147,7 +1100,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
}
SUFF_DEBUG1("Expanding \"%s\"...", cgn->name);
- (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR|VARE_WANTRES, &cp);
+ (void)Var_Subst(cgn->name, pgn, VARE_WANTRES | VARE_UNDEFERR, &cp);
/* TODO: handle errors */
{
@@ -1166,15 +1119,15 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
/*
* Break the result into a vector of strings whose nodes
* we can find, then add those nodes to the members list.
- * Unfortunately, we can't use brk_string b/c it
+ * Unfortunately, we can't use Str_Words because it
* doesn't understand about variable specifications with
* spaces in them...
*/
char *start;
char *initcp = cp; /* For freeing... */
- for (start = cp; *start == ' ' || *start == '\t'; start++)
- continue;
+ start = cp;
+ pp_skip_hspace(&start);
cp = start;
while (*cp != '\0') {
if (*cp == ' ' || *cp == '\t') {
@@ -1185,9 +1138,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
*cp++ = '\0';
gn = Targ_GetNode(start);
Lst_Append(members, gn);
- while (*cp == ' ' || *cp == '\t') {
- cp++;
- }
+ pp_skip_hspace(&cp);
start = cp; /* Continue at the next non-space. */
} else if (*cp == '$') {
/*
@@ -1200,7 +1151,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
/* XXX: Why VARE_WANTRES when the result is not used? */
(void)Var_Parse(&nested_p, pgn,
- VARE_UNDEFERR|VARE_WANTRES,
+ VARE_WANTRES | VARE_UNDEFERR,
&junk, &freeIt);
/* TODO: handle errors */
if (junk == var_Error) {
@@ -1213,7 +1164,7 @@ SuffExpandChildren(GNodeListNode *cln, GNode *pgn)
}
free(freeIt);
- } else if (*cp == '\\' && cp[1] != '\0') {
+ } else if (cp[0] == '\\' && cp[1] != '\0') {
/*
* Escaped something -- skip over it
*/
@@ -1277,7 +1228,7 @@ static void
SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
{
GNode *cgn = cln->datum;
- StringList *explist;
+ StringList *expansions;
if (!Dir_HasWildcards(cgn->name))
return;
@@ -1285,15 +1236,15 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
/*
* Expand the word along the chosen path
*/
- explist = Lst_New();
- Dir_Expand(cgn->name, Suff_FindPath(cgn), explist);
+ expansions = Lst_New();
+ Dir_Expand(cgn->name, Suff_FindPath(cgn), expansions);
- while (!Lst_IsEmpty(explist)) {
+ while (!Lst_IsEmpty(expansions)) {
GNode *gn;
/*
* Fetch next expansion off the list and find its GNode
*/
- char *cp = Lst_Dequeue(explist);
+ char *cp = Lst_Dequeue(expansions);
SUFF_DEBUG1("%s...", cp);
gn = Targ_GetNode(cp);
@@ -1304,7 +1255,7 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
pgn->unmade++;
}
- Lst_Free(explist);
+ Lst_Free(expansions);
SUFF_DEBUG0("\n");
@@ -1319,7 +1270,7 @@ SuffExpandWildcards(GNodeListNode *cln, GNode *pgn)
/* Find a path along which to expand the node.
*
- * If the word has a known suffix, use that path.
+ * If the node has a known suffix, use that path.
* If it has no known suffix, use the default system search path.
*
* Input:
@@ -1359,77 +1310,61 @@ Suff_FindPath(GNode* gn)
/* Apply a transformation rule, given the source and target nodes and
* suffixes.
*
- * Input:
- * tGn Target node
- * sGn Source node
- * t Target suffix
- * s Source suffix
+ * The source and target are linked and the commands from the transformation
+ * are added to the target node's commands list. The target also inherits all
+ * the sources for the transformation rule.
*
* Results:
* TRUE if successful, FALSE if not.
- *
- * Side Effects:
- * The source and target are linked and the commands from the
- * transformation are added to the target node's commands list.
- * All attributes but OP_DEPMASK and OP_TRANSFORM are applied
- * to the target. The target also inherits all the sources for
- * the transformation rule.
*/
static Boolean
-SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s)
+SuffApplyTransform(GNode *tgn, GNode *sgn, Suff *tsuff, Suff *ssuff)
{
- GNodeListNode *ln, *nln; /* General node */
+ GNodeListNode *ln;
char *tname; /* Name of transformation rule */
GNode *gn; /* Node for same */
/*
* Form the proper links between the target and source.
*/
- Lst_Append(tGn->children, sGn);
- Lst_Append(sGn->parents, tGn);
- tGn->unmade++;
+ Lst_Append(tgn->children, sgn);
+ Lst_Append(sgn->parents, tgn);
+ tgn->unmade++;
/*
* Locate the transformation rule itself
*/
- tname = str_concat2(s->name, t->name);
+ tname = str_concat2(ssuff->name, tsuff->name);
gn = FindTransformByName(tname);
free(tname);
if (gn == NULL) {
- /*
- * Not really such a transformation rule (can happen when we're
- * called to link an OP_MEMBER and OP_ARCHV node), so return
- * FALSE.
- */
+ /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */
return FALSE;
}
- SUFF_DEBUG3("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name);
+ DEBUG3(SUFF,"\tapplying %s -> %s to \"%s\"\n",
+ ssuff->name, tsuff->name, tgn->name);
- /*
- * Record last child for expansion purposes
- */
- ln = tGn->children->last;
+ /* Record last child; Make_HandleUse may add child nodes. */
+ ln = tgn->children->last;
- /*
- * Pass the buck to Make_HandleUse to apply the rule
- */
- (void)Make_HandleUse(gn, tGn);
+ /* Apply the rule. */
+ Make_HandleUse(gn, tgn);
- /*
- * Deal with wildcards and variables in any acquired sources
- */
- for (ln = ln != NULL ? ln->next : NULL; ln != NULL; ln = nln) {
- nln = ln->next;
- SuffExpandChildren(ln, tGn);
+ /* Deal with wildcards and variables in any acquired sources. */
+ ln = ln != NULL ? ln->next : NULL;
+ while (ln != NULL) {
+ GNodeListNode *nln = ln->next;
+ SuffExpandChildren(ln, tgn);
+ ln = nln;
}
/*
- * Keep track of another parent to which this beast is transformed so
+ * Keep track of another parent to which this node is transformed so
* the .IMPSRC variable can be set correctly for the parent.
*/
- Lst_Append(sGn->implicitParents, tGn);
+ Lst_Append(sgn->implicitParents, tgn);
return TRUE;
}
@@ -1496,10 +1431,7 @@ SuffFindArchiveDeps(GNode *gn, SrcList *slst)
Var_Set(TARGET, GNode_VarTarget(mem), gn);
ms = mem->suffix;
- if (ms == NULL) {
- /*
- * Didn't know what it was -- use .NULL suffix if not in make mode
- */
+ if (ms == NULL) { /* Didn't know what it was. */
SUFF_DEBUG0("using null suffix\n");
ms = suffNull;
}
@@ -1555,16 +1487,16 @@ SuffFindArchiveDeps(GNode *gn, SrcList *slst)
* Replace the opening and closing parens now we've no need of the separate
* pieces.
*/
- *eoarch = '('; *eoname = ')';
+ *eoarch = '(';
+ *eoname = ')';
/*
* Pretend gn appeared to the left of a dependency operator so
* the user needn't provide a transformation from the member to the
* archive.
*/
- if (!GNode_IsTarget(gn)) {
+ if (!GNode_IsTarget(gn))
gn->type |= OP_DEPENDS;
- }
/*
* Flag the member as such so we remember to look in the archive for
@@ -1766,11 +1698,10 @@ SuffFindNormalDeps(GNode *gn, SrcList *slst)
* No known transformations -- use the first suffix found
* for setting the local variables.
*/
- if (!Lst_IsEmpty(targs)) {
+ if (targs->first != NULL)
targ = targs->first->datum;
- } else {
+ else
targ = NULL;
- }
} else {
/*
* Work up the transformation path to find the suffix of the
@@ -1783,7 +1714,7 @@ SuffFindNormalDeps(GNode *gn, SrcList *slst)
Var_Set(TARGET, GNode_Path(gn), gn);
- pref = (targ != NULL) ? targ->pref : gn->name;
+ pref = targ != NULL ? targ->pref : gn->name;
Var_Set(PREFIX, pref, gn);
/*
@@ -1810,9 +1741,8 @@ sfnd_abort:
* If the suffix indicates that the target is a library, mark that in
* the node's type field.
*/
- if (targ->suff->flags & SUFF_LIBRARY) {
+ if (targ->suff->flags & SUFF_LIBRARY)
gn->type |= OP_LIB;
- }
/*
* Check for overriding transformation rule implied by sources
@@ -1825,10 +1755,9 @@ sfnd_abort:
* Free up all the Src structures in the transformation path
* up to, but not including, the parent node.
*/
- while (bottom && bottom->parent != NULL) {
- if (Lst_FindDatum(slst, bottom) == NULL) {
+ while (bottom != NULL && bottom->parent != NULL) {
+ if (Lst_FindDatum(slst, bottom) == NULL)
Lst_Append(slst, bottom);
- }
bottom = bottom->parent;
}
bottom = src;
@@ -1854,9 +1783,8 @@ sfnd_abort:
* transformation rule. Also, the unmade field of gn is incremented.
* Etc.
*/
- if (bottom->node == NULL) {
+ if (bottom->node == NULL)
bottom->node = Targ_GetNode(bottom->file);
- }
for (src = bottom; src->parent != NULL; src = src->parent) {
targ = src->parent;
@@ -1866,9 +1794,8 @@ sfnd_abort:
src->node->suffix = src->suff;
src->node->suffix->refCount++;
- if (targ->node == NULL) {
+ if (targ->node == NULL)
targ->node = Targ_GetNode(targ->file);
- }
SuffApplyTransform(targ->node, src->node,
targ->suff, src->suff);
@@ -1880,18 +1807,15 @@ sfnd_abort:
* filesystem for their implicit source when it's already
* known). Note that the node can't have any sources that
* need expanding, since SuffFindThem will stop on an existing
- * node, so all we need to do is set the standard and System V
- * variables.
+ * node, so all we need to do is set the standard variables.
*/
targ->node->type |= OP_DEPS_FOUND;
-
Var_Set(PREFIX, targ->pref, targ->node);
-
Var_Set(TARGET, targ->node->name, targ->node);
}
}
- if (gn->suffix)
+ if (gn->suffix != NULL)
gn->suffix->refCount--;
gn->suffix = src->suff;
gn->suffix->refCount++;
@@ -1901,9 +1825,8 @@ sfnd_abort:
* two lists.
*/
sfnd_return:
- if (bottom)
- if (Lst_FindDatum(slst, bottom) == NULL)
- Lst_Append(slst, bottom);
+ if (bottom != NULL && Lst_FindDatum(slst, bottom) == NULL)
+ Lst_Append(slst, bottom);
while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
continue;
@@ -1913,7 +1836,7 @@ sfnd_return:
}
-/* Find implicit sources for the target described by the graph node.
+/* Find implicit sources for the target.
*
* Nodes are added to the graph below the passed-in node. The nodes are
* marked to have their IMPSRC variable filled in. The PREFIX variable is set
@@ -1995,21 +1918,20 @@ SuffFindDeps(GNode *gn, SrcList *slst)
void
Suff_SetNull(const char *name)
{
- Suff *s = FindSuffByName(name);
- if (s == NULL) {
+ Suff *suff = FindSuffByName(name);
+ if (suff == NULL) {
Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.",
name);
return;
}
- if (suffNull != NULL) {
+ if (suffNull != NULL)
suffNull->flags &= ~(unsigned)SUFF_NULL;
- }
- s->flags |= SUFF_NULL;
+ suff->flags |= SUFF_NULL;
/*
* XXX: Here's where the transformation mangling would take place
*/
- suffNull = s;
+ suffNull = suff;
}
/* Initialize the suffixes module. */
@@ -2047,8 +1969,6 @@ Suff_End(void)
}
-/********************* DEBUGGING FUNCTIONS **********************/
-
static void
PrintSuffNames(const char *prefix, SuffList *suffs)
{
@@ -2063,23 +1983,24 @@ PrintSuffNames(const char *prefix, SuffList *suffs)
}
static void
-PrintSuff(Suff *s)
+PrintSuff(Suff *suff)
{
- debug_printf("# \"%s\" (num %d, ref %d)", s->name, s->sNum, s->refCount);
- if (s->flags != 0) {
+ debug_printf("# \"%s\" (num %d, ref %d)",
+ suff->name, suff->sNum, suff->refCount);
+ if (suff->flags != 0) {
char flags_buf[SuffFlags_ToStringSize];
debug_printf(" (%s)",
Enum_FlagsToString(flags_buf, sizeof flags_buf,
- s->flags, SuffFlags_ToStringSpecs));
+ suff->flags, SuffFlags_ToStringSpecs));
}
debug_printf("\n");
- PrintSuffNames("To", s->parents);
- PrintSuffNames("From", s->children);
+ PrintSuffNames("To", suff->parents);
+ PrintSuffNames("From", suff->children);
debug_printf("#\tSearch Path: ");
- Dir_PrintPath(s->searchPath);
+ Dir_PrintPath(suff->searchPath);
debug_printf("\n");
}
diff --git a/targ.c b/targ.c
index 30a3de2d86aa..43489c9d922b 100644
--- a/targ.c
+++ b/targ.c
@@ -1,4 +1,4 @@
-/* $NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $ */
+/* $NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -68,19 +68,17 @@
* SUCH DAMAGE.
*/
-/*-
- * targ.c --
- * Functions for maintaining the Lst allTargets. Target nodes are
- * kept in two structures: a Lst and a hash table.
+/*
+ * Maintaining the targets and sources, which are both implemented as GNode.
*
* Interface:
- * Targ_Init Initialization procedure.
+ * Targ_Init Initialize the module.
*
- * Targ_End Clean up the module
+ * Targ_End Clean up the module.
*
* Targ_List Return the list of all targets so far.
*
- * Targ_NewGN Create a new GNode for the passed target
+ * GNode_New Create a new GNode for the passed target
* (string). The node is *not* placed in the
* hash table, though all its fields are
* initialized.
@@ -121,23 +119,26 @@
#include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: targ.c,v 1.126 2020/10/30 07:19:30 rillig Exp $");
+MAKE_RCSID("$NetBSD: targ.c,v 1.135 2020/11/16 22:28:44 rillig Exp $");
-static GNodeList *allTargets; /* the list of all targets found so far */
-#ifdef CLEANUP
-static GNodeList *allGNs; /* List of all the GNodes */
-#endif
-static HashTable targets; /* a hash table of same */
+/* All target nodes found so far, but not the source nodes. */
+static GNodeList *allTargets;
+static HashTable allTargetsByName;
#ifdef CLEANUP
-static void TargFreeGN(void *);
+static GNodeList *allNodes;
+
+static void GNode_Free(void *);
#endif
void
Targ_Init(void)
{
allTargets = Lst_New();
- HashTable_Init(&targets);
+ HashTable_Init(&allTargetsByName);
+#ifdef CLEANUP
+ allNodes = Lst_New();
+#endif
}
void
@@ -146,56 +147,67 @@ Targ_End(void)
Targ_Stats();
#ifdef CLEANUP
Lst_Free(allTargets);
- if (allGNs != NULL)
- Lst_Destroy(allGNs, TargFreeGN);
- HashTable_Done(&targets);
+ HashTable_Done(&allTargetsByName);
+ Lst_Destroy(allNodes, GNode_Free);
#endif
}
void
Targ_Stats(void)
{
- HashTable_DebugStats(&targets, "targets");
+ HashTable_DebugStats(&allTargetsByName, "targets");
}
-/* Return the list of all targets. */
+/*
+ * Return the list of all targets, which are all nodes that appear on the
+ * left-hand side of a dependency declaration such as "target: source".
+ * The returned list does not contain pure sources.
+ */
GNodeList *
Targ_List(void)
{
return allTargets;
}
-/* Create and initialize a new graph node. The gnode is added to the list of
- * all gnodes.
+/* Create a new graph node, but don't register it anywhere.
*
- * Input:
- * name the name of the node, such as "clean", "src.c", ".END"
+ * Graph nodes that appear on the left-hand side of a dependency line such
+ * as "target: source" are called targets. XXX: In some cases (like the
+ * .ALLTARGETS variable), all nodes are called targets as well, even if they
+ * never appear on the left-hand side. This is a mistake.
+ *
+ * Typical names for graph nodes are:
+ * "src.c" (an ordinary file)
+ * "clean" (a .PHONY target)
+ * ".END" (a special hook target)
+ * "-lm" (a library)
+ * "libc.a(isspace.o)" (an archive member)
*/
GNode *
-Targ_NewGN(const char *name)
+GNode_New(const char *name)
{
GNode *gn;
- gn = bmake_malloc(sizeof(GNode));
+ gn = bmake_malloc(sizeof *gn);
gn->name = bmake_strdup(name);
gn->uname = NULL;
gn->path = NULL;
gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : 0;
- gn->unmade = 0;
- gn->unmade_cohorts = 0;
- gn->cohort_num[0] = '\0';
- gn->centurion = NULL;
- gn->made = UNMADE;
gn->flags = 0;
- gn->checked_seqno = 0;
+ gn->made = UNMADE;
+ gn->unmade = 0;
gn->mtime = 0;
gn->youngestChild = NULL;
gn->implicitParents = Lst_New();
- gn->cohorts = Lst_New();
gn->parents = Lst_New();
gn->children = Lst_New();
gn->order_pred = Lst_New();
gn->order_succ = Lst_New();
+ gn->cohorts = Lst_New();
+ gn->cohort_num[0] = '\0';
+ gn->unmade_cohorts = 0;
+ gn->centurion = NULL;
+ gn->checked_seqno = 0;
HashTable_Init(&gn->context);
gn->commands = Lst_New();
gn->suffix = NULL;
@@ -203,9 +215,7 @@ Targ_NewGN(const char *name)
gn->lineno = 0;
#ifdef CLEANUP
- if (allGNs == NULL)
- allGNs = Lst_New();
- Lst_Append(allGNs, gn);
+ Lst_Append(allNodes, gn);
#endif
return gn;
@@ -213,24 +223,29 @@ Targ_NewGN(const char *name)
#ifdef CLEANUP
static void
-TargFreeGN(void *gnp)
+GNode_Free(void *gnp)
{
GNode *gn = gnp;
free(gn->name);
free(gn->uname);
free(gn->path);
-
- Lst_Free(gn->implicitParents);
- Lst_Free(gn->cohorts);
- Lst_Free(gn->parents);
- Lst_Free(gn->children);
- Lst_Free(gn->order_succ);
- Lst_Free(gn->order_pred);
- HashTable_Done(&gn->context);
- Lst_Free(gn->commands);
-
- /* XXX: does gn->suffix need to be freed? It is reference-counted. */
+ /* gn->youngestChild is not owned by this node. */
+ Lst_Free(gn->implicitParents); /* ... but not the nodes themselves, */
+ Lst_Free(gn->parents); /* as they are not owned by this node. */
+ Lst_Free(gn->children); /* likewise */
+ Lst_Free(gn->order_pred); /* likewise */
+ Lst_Free(gn->order_succ); /* likewise */
+ Lst_Free(gn->cohorts); /* likewise */
+ HashTable_Done(&gn->context); /* ... but not the variables themselves,
+ * even though they are owned by this node.
+ * XXX: they should probably be freed. */
+ Lst_Free(gn->commands); /* ... but not the commands themselves,
+ * as they may be shared with other nodes. */
+ /* gn->suffix is not owned by this node. */
+ /* XXX: gn->suffix should be unreferenced here. This requires a thorough
+ * check that the reference counting is done correctly in all places,
+ * otherwise a suffix might be freed too early. */
free(gn);
}
@@ -240,7 +255,7 @@ TargFreeGN(void *gnp)
GNode *
Targ_FindNode(const char *name)
{
- return HashTable_FindValue(&targets, name);
+ return HashTable_FindValue(&allTargetsByName, name);
}
/* Get the existing global node, or create it. */
@@ -248,7 +263,7 @@ GNode *
Targ_GetNode(const char *name)
{
Boolean isNew;
- HashEntry *he = HashTable_CreateEntry(&targets, name, &isNew);
+ HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew);
if (!isNew)
return HashEntry_Get(he);
@@ -259,14 +274,16 @@ Targ_GetNode(const char *name)
}
}
-/* Create a node, register it in .ALLTARGETS but don't store it in the
+/*
+ * Create a node, register it in .ALLTARGETS but don't store it in the
* table of global nodes. This means it cannot be found by name.
*
- * This is used for internal nodes, such as cohorts or .WAIT nodes. */
+ * This is used for internal nodes, such as cohorts or .WAIT nodes.
+ */
GNode *
Targ_NewInternalNode(const char *name)
{
- GNode *gn = Targ_NewGN(name);
+ GNode *gn = GNode_New(name);
Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
Lst_Append(allTargets, gn);
if (doing_depend)
@@ -274,8 +291,10 @@ Targ_NewInternalNode(const char *name)
return gn;
}
-/* Return the .END node, which contains the commands to be executed when
- * everything else is done. */
+/*
+ * Return the .END node, which contains the commands to be run when
+ * everything else has been made.
+ */
GNode *Targ_GetEndNode(void)
{
/* Save the node locally to avoid having to search for it all the time. */
@@ -303,31 +322,33 @@ Targ_FindList(StringList *names)
/* Return true if should ignore errors when creating gn. */
Boolean
-Targ_Ignore(GNode *gn)
+Targ_Ignore(const GNode *gn)
{
return opts.ignoreErrors || gn->type & OP_IGNORE;
}
/* Return true if be silent when creating gn. */
Boolean
-Targ_Silent(GNode *gn)
+Targ_Silent(const GNode *gn)
{
return opts.beSilent || gn->type & OP_SILENT;
}
/* See if the given target is precious. */
Boolean
-Targ_Precious(GNode *gn)
+Targ_Precious(const GNode *gn)
{
+ /* XXX: Why are '::' targets precious? */
return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
}
-/******************* DEBUG INFO PRINTING ****************/
-
-static GNode *mainTarg; /* the main target, as set by Targ_SetMain */
+/*
+ * The main target to be made; only for debugging output.
+ * See mainNode in parse.c for the definitive source.
+ */
+static GNode *mainTarg;
-/* Set our idea of the main target we'll be creating. Used for debugging
- * output. */
+/* Remember the main target to make; only used for debugging. */
void
Targ_SetMain(GNode *gn)
{
@@ -459,12 +480,12 @@ Targ_PrintNode(GNode *gn, int pass)
debug_printf("# *** MAIN TARGET ***\n");
}
if (pass >= 2) {
- if (gn->unmade) {
+ if (gn->unmade > 0) {
debug_printf("# %d unmade children\n", gn->unmade);
} else {
debug_printf("# No unmade children\n");
}
- if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
+ if (!(gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
if (gn->mtime != 0) {
debug_printf("# last modified %s: %s\n",
Targ_FmtTime(gn->mtime),
@@ -532,20 +553,27 @@ Targ_PrintGraph(int pass)
{
debug_printf("#*** Input graph:\n");
Targ_PrintNodes(allTargets, pass);
- debug_printf("\n\n");
- debug_printf("#\n# Files that are only sources:\n");
+ debug_printf("\n");
+ debug_printf("\n");
+
+ debug_printf("#\n");
+ debug_printf("# Files that are only sources:\n");
PrintOnlySources();
+
debug_printf("#*** Global Variables:\n");
Var_Dump(VAR_GLOBAL);
+
debug_printf("#*** Command-line Variables:\n");
Var_Dump(VAR_CMDLINE);
+
debug_printf("\n");
Dir_PrintDirectories();
debug_printf("\n");
+
Suff_PrintAll();
}
-/* Propagate some type information to cohort nodes (those from the ::
+/* Propagate some type information to cohort nodes (those from the '::'
* dependency operator).
*
* Should be called after the makefiles are parsed but before any action is
diff --git a/unit-tests/Makefile b/unit-tests/Makefile
index 0940d55671ee..cca63155e868 100644
--- a/unit-tests/Makefile
+++ b/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.107 2020/11/02 00:40:25 sjg Exp $
+# $Id: Makefile,v 1.115 2020/11/18 04:01:07 sjg Exp $
#
-# $NetBSD: Makefile,v 1.181 2020/11/01 19:02:22 rillig Exp $
+# $NetBSD: Makefile,v 1.206 2020/11/18 01:12:00 sjg Exp $
#
# Unit tests for make(1)
#
@@ -36,8 +36,11 @@
# src/tests/usr.bin/make/t_make.sh as well.
#TESTS+= archive
TESTS+= archive-suffix
+TESTS+= cmd-errors
+TESTS+= cmd-errors-lint
TESTS+= cmd-interrupt
TESTS+= cmdline
+TESTS+= cmdline-undefined
TESTS+= comment
TESTS+= cond-cmp-numeric
TESTS+= cond-cmp-numeric-eq
@@ -58,8 +61,10 @@ TESTS+= cond-func-target
TESTS+= cond-late
TESTS+= cond-op
TESTS+= cond-op-and
+TESTS+= cond-op-and-lint
TESTS+= cond-op-not
TESTS+= cond-op-or
+TESTS+= cond-op-or-lint
TESTS+= cond-op-parentheses
TESTS+= cond-short
TESTS+= cond-token-number
@@ -144,6 +149,7 @@ TESTS+= directive-for
TESTS+= directive-for-generating-endif
TESTS+= directive-hyphen-include
TESTS+= directive-if
+TESTS+= directive-if-nested
TESTS+= directive-ifdef
TESTS+= directive-ifmake
TESTS+= directive-ifndef
@@ -156,7 +162,6 @@ TESTS+= directive-undef
TESTS+= directive-unexport
TESTS+= directive-unexport-env
TESTS+= directive-warning
-TESTS+= directives
TESTS+= dollar
TESTS+= doterror
TESTS+= dotwait
@@ -169,9 +174,11 @@ TESTS+= export-env
TESTS+= export-variants
TESTS+= forloop
TESTS+= forsubst
+TESTS+= gnode-submake
TESTS+= hanoi-include
TESTS+= impsrc
TESTS+= include-main
+TESTS+= job-flags
#TESTS+= job-output-long-lines
TESTS+= lint
TESTS+= make-exported
@@ -180,6 +187,7 @@ TESTS+= modmatch
TESTS+= modmisc
TESTS+= modts
TESTS+= modword
+TESTS+= objdir-writable
TESTS+= opt
TESTS+= opt-backwards
TESTS+= opt-chdir
@@ -223,6 +231,7 @@ TESTS+= opt-query
TESTS+= opt-raw
TESTS+= opt-silent
TESTS+= opt-touch
+TESTS+= opt-touch-jobs
TESTS+= opt-tracefile
TESTS+= opt-var-expanded
TESTS+= opt-var-literal
@@ -248,7 +257,9 @@ TESTS+= sh-multi-line
TESTS+= sh-single-line
TESTS+= shell-csh
TESTS+= shell-custom
+.if exists(/bin/ksh)
TESTS+= shell-ksh
+.endif
TESTS+= shell-sh
TESTS+= suff-add-later
TESTS+= suff-clear-regular
@@ -256,6 +267,7 @@ TESTS+= suff-clear-single
TESTS+= suff-lookup
TESTS+= suff-main
TESTS+= suff-rebuild
+TESTS+= suff-self
TESTS+= suff-transform-endless
TESTS+= suff-transform-expand
TESTS+= suff-transform-select
@@ -366,38 +378,54 @@ TESTS+= varname-makeflags
TESTS+= varname-pwd
TESTS+= varname-vpath
TESTS+= varparse-dynamic
+TESTS+= varparse-errors
TESTS+= varparse-mod
TESTS+= varparse-undef-partial
TESTS+= varquote
-TESTS+= varshell
+# Ideas for more tests:
+# char-0020-space.mk
+# char-005C-backslash.mk
+# escape-cond-str.mk
+# escape-cond-func-arg.mk
+# escape-cond-func-arg.mk
+# escape-varmod.mk
+# escape-varmod-define.mk
+# escape-varmod-match.mk
+# escape-varname.mk
+# escape-varassign-varname.mk
+# escape-varassign-varname-cmdline.mk
+# escape-varassign-value.mk
+# escape-varassign-value-cmdline.mk
+# escape-dependency-source.mk
+# escape-dependency-target.mk
+# escape-for-varname.mk
+# escape-for-item.mk
+# posix-*.mk (see posix.mk and posix1.mk)
+
+.if ${.OBJDIR} != ${.CURDIR}
+RO_OBJDIR:= ${.OBJDIR}/roobj
+.else
+RO_OBJDIR:= ${TMPDIR:U/tmp}/roobj
+.endif
# Additional environment variables for some of the tests.
# The base environment is -i PATH="$PATH".
+ENV.depsrc-optional+= TZ=UTC
ENV.envfirst= FROM_ENV=value-from-env
+ENV.objdir-writable+= RO_OBJDIR=${RO_OBJDIR}
ENV.varmisc= FROM_ENV=env
ENV.varmisc+= FROM_ENV_BEFORE=env
ENV.varmisc+= FROM_ENV_AFTER=env
ENV.varmod-localtime+= TZ=Europe/Berlin
+ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# Override make flags for some of the tests; default is -k.
# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of
# settings FLAGS.test=-dv here, since that is closer to the test code.
FLAGS.cond-func-make= via-cmdline
FLAGS.directive-ifmake= first second
-FLAGS.doterror= # none
-FLAGS.envfirst= -e
-FLAGS.export= # none
-FLAGS.opt-ignore= -i
-FLAGS.opt-keep-going= -k
-FLAGS.opt-no-action= -n
-FLAGS.opt-query= -q
-FLAGS.opt-var-expanded= -v VAR -v VALUE
-FLAGS.opt-var-literal= -V VAR -V VALUE
-FLAGS.opt-warnings-as-errors= -W
-FLAGS.order= -j1
-FLAGS.recursive= -dL
-FLAGS.sh-leading-plus= -n
-FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmline-plain'
+FLAGS.doterror= # none, especially not -k
+FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain'
# Some tests need extra postprocessing.
SED_CMDS.export= \
@@ -406,6 +434,9 @@ SED_CMDS.export= \
.for t in export-all export-env
SED_CMDS.$t= ${SED_CMDS.export}
.endfor
+SED_CMDS.directive-export-gmake= \
+ ${:D dash is a pain } \
+ -e /non-zero/d
SED_CMDS.job-output-long-lines= \
${:D Job separators on their own line are ok. } \
-e '/^--- job-[ab] ---$$/d' \
@@ -417,6 +448,7 @@ SED_CMDS.job-output-long-lines= \
${:D marker should always be at the beginning of the line. } \
-e '/^aa*--- job-b ---$$/d' \
-e '/^bb*--- job-a ---$$/d'
+SED_CMDS.objdir-writable= -e 's,${RO_OBJDIR},OBJDIR/roobj,g'
SED_CMDS.opt-debug-graph1= \
-e 's,${.CURDIR},CURDIR,'
SED_CMDS.opt-debug-graph1+= \
@@ -428,11 +460,14 @@ SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,'
# The "-q" may be there or not, see jobs.c, variable shells.
SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: sh\) -q,\1,'
+SED_CMDS.var-op-shell+= -e 's,^${.SHELL:T}: ,,'
+SED_CMDS.var-op-shell+= -e '/command/{ s,^[1-9]: ,,;s,No such.*,not found,; }'
+SED_CMDS.vardebug= \
+ ${:D canonicalize .SHELL } \
+ -e 's,${.SHELL},</path/to/shell>,'
SED_CMDS.varmod-subst-regex+= \
-e 's,\(Regex compilation error:\).*,\1 (details omitted),'
SED_CMDS.varmod-edge+= -e 's, line [0-9]*:, line omitted:,'
-SED_CMDS.varshell+= -e 's,^${.SHELL:T}: ,,'
-SED_CMDS.varshell+= -e '/command/s,No such.*,not found,'
SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g'
@@ -442,7 +477,7 @@ SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g'
# Some tests need an additional round of postprocessing.
POSTPROC.deptgt-suffixes= \
${TOOL_SED} -n -e '/^\#\*\*\* Suffixes/,/^\#\*/p'
-POSTPROC.varname= ${TOOL_SED} -n -e '/^MAGIC/p' -e '/^ORDER_/p'
+POSTPROC.gnode-submake= awk '/Input graph/, /^$$/'
POSTPROC.varname-empty= ${TOOL_SED} -n -e '/^Var_Set/p' -e '/^out:/p'
# Some tests reuse other tests, which makes them unnecessarily fragile.
@@ -519,6 +554,8 @@ MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc
# always pretend .MAKE was called 'make'
_SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,'
_SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,'
+_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,'
+_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
# replace anything after 'stopped in' with unit-tests
_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
# strip ${.CURDIR}/ from the output
diff --git a/unit-tests/archive-suffix.mk b/unit-tests/archive-suffix.mk
index 9f7fa219c667..a216fd2d3c6a 100755
--- a/unit-tests/archive-suffix.mk
+++ b/unit-tests/archive-suffix.mk
@@ -1,11 +1,11 @@
-# $NetBSD: archive-suffix.mk,v 1.1 2020/08/29 14:47:26 rillig Exp $
+# $NetBSD: archive-suffix.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
#
# Between 2020-08-23 and 2020-08-30, the below code produced an assertion
-# failure in Var_Set_with_flags, triggered by Compat_Make, when setting the
+# failure in Var_SetWithFlags, triggered by Compat_Make, when setting the
# .IMPSRC of an archive node to its .TARGET.
#
# The code assumed that the .TARGET variable of every node would be set, but
-# but that is not guaranteed.
+# that is not guaranteed.
#
# Between 2016-03-15 and 2016-03-16 the behavior of the below code changed.
# Until 2016-03-15, it remade the target, starting with 2016-03-16 it says
diff --git a/unit-tests/archive.mk b/unit-tests/archive.mk
index 2f91005f988c..f8815cf40a40 100644
--- a/unit-tests/archive.mk
+++ b/unit-tests/archive.mk
@@ -1,18 +1,15 @@
-# $NetBSD: archive.mk,v 1.10 2020/10/09 06:44:42 rillig Exp $
+# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $
#
# Very basic demonstration of handling archives, based on the description
# in PSD.doc/tutorial.ms.
#
# This test aims at covering the code, not at being an introduction to
-# archive handling. That's why it is more complicated and detailed than
-# strictly necessary.
+# archive handling. That's why it deviates from the tutorial style of
+# several other tests.
ARCHIVE= libprog.a
FILES= archive.mk modmisc.mk varmisc.mk
-MAKE_CMD= ${.MAKE} -f ${MAKEFILE}
-RUN?= @set -eu;
-
all:
.if ${.PARSEDIR:tA} != ${.CURDIR:tA}
@cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR}
@@ -20,13 +17,13 @@ all:
# The following targets create and remove files. The filesystem cache in
# dir.c would probably not handle this correctly, therefore each of the
# targets is run in its separate sub-make.
- ${RUN} ${MAKE_CMD} remove-archive
- ${RUN} ${MAKE_CMD} create-archive
- ${RUN} ${MAKE_CMD} list-archive
- ${RUN} ${MAKE_CMD} list-archive-wildcard
- ${RUN} ${MAKE_CMD} depend-on-existing-member
- ${RUN} ${MAKE_CMD} depend-on-nonexistent-member
- ${RUN} ${MAKE_CMD} remove-archive
+ @${MAKE} -f ${MAKEFILE} remove-archive
+ @${MAKE} -f ${MAKEFILE} create-archive
+ @${MAKE} -f ${MAKEFILE} list-archive
+ @${MAKE} -f ${MAKEFILE} list-archive-wildcard
+ @${MAKE} -f ${MAKEFILE} depend-on-existing-member
+ @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member
+ @${MAKE} -f ${MAKEFILE} remove-archive
create-archive: ${ARCHIVE} pre post
@@ -43,15 +40,16 @@ list-archive: ${ARCHIVE} pre post
# XXX: I had expected that this dependency would select all *.mk files from
# the archive. Instead, the globbing is done in the current directory.
+#
# To prevent an overly long file list, the pattern is restricted to [at]*.mk.
list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post
- ${RUN} printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
+ @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@}
depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post
- ${RUN} echo $@
+ @echo $@
depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post
- ${RUN} echo $@
+ @echo $@
remove-archive: pre post
rm -f ${ARCHIVE}
diff --git a/unit-tests/cmd-errors-lint.exp b/unit-tests/cmd-errors-lint.exp
new file mode 100644
index 000000000000..09924c538de0
--- /dev/null
+++ b/unit-tests/cmd-errors-lint.exp
@@ -0,0 +1,9 @@
+: undefined
+make: Unclosed variable "UNCLOSED"
+: unclosed-variable
+make: Unclosed variable expression (expecting '}') for "UNCLOSED"
+: unclosed-modifier
+make: Unknown modifier 'Z'
+: unknown-modifier
+: end
+exit status 2
diff --git a/unit-tests/cmd-errors-lint.mk b/unit-tests/cmd-errors-lint.mk
new file mode 100644
index 000000000000..371e12af0f4f
--- /dev/null
+++ b/unit-tests/cmd-errors-lint.mk
@@ -0,0 +1,32 @@
+# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $
+#
+# Demonstrate how errors in variable expansions affect whether the commands
+# are actually executed.
+
+.MAKEFLAGS: -dL
+
+all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+
+# Undefined variables are not an error. They expand to empty strings.
+undefined:
+ : $@ ${UNDEFINED}
+
+# XXX: As of 2020-11-01, this obvious syntax error is not detected.
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-variable:
+ : $@ ${UNCLOSED
+
+# XXX: As of 2020-11-01, this obvious syntax error is not detected.
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-modifier:
+ : $@ ${UNCLOSED:
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unknown-modifier:
+ : $@ ${UNKNOWN:Z}
+
+end:
+ : $@
diff --git a/unit-tests/cmd-errors.exp b/unit-tests/cmd-errors.exp
new file mode 100644
index 000000000000..6d9c6bb7f890
--- /dev/null
+++ b/unit-tests/cmd-errors.exp
@@ -0,0 +1,9 @@
+: undefined eol
+make: Unclosed variable "UNCLOSED"
+: unclosed-variable
+make: Unclosed variable expression (expecting '}') for "UNCLOSED"
+: unclosed-modifier
+make: Unknown modifier 'Z'
+: unknown-modifier eol
+: end eol
+exit status 0
diff --git a/unit-tests/cmd-errors.mk b/unit-tests/cmd-errors.mk
new file mode 100644
index 000000000000..5ad4be311873
--- /dev/null
+++ b/unit-tests/cmd-errors.mk
@@ -0,0 +1,30 @@
+# $NetBSD: cmd-errors.mk,v 1.3 2020/11/09 23:36:34 rillig Exp $
+#
+# Demonstrate how errors in variable expansions affect whether the commands
+# are actually executed.
+
+all: undefined unclosed-variable unclosed-modifier unknown-modifier end
+
+# Undefined variables are not an error. They expand to empty strings.
+undefined:
+ : $@ ${UNDEFINED} eol
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-variable:
+ : $@ ${UNCLOSED
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unclosed-modifier:
+ : $@ ${UNCLOSED:
+
+# XXX: As of 2020-11-01, this command is executed even though it contains
+# parse errors.
+unknown-modifier:
+ : $@ ${UNKNOWN:Z} eol
+
+end:
+ : $@ eol
+
+# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0.
diff --git a/unit-tests/cmd-interrupt.mk b/unit-tests/cmd-interrupt.mk
index 033f3307bd2e..fa0d85fc9063 100755
--- a/unit-tests/cmd-interrupt.mk
+++ b/unit-tests/cmd-interrupt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cmd-interrupt.mk,v 1.2 2020/08/28 18:16:22 rillig Exp $
+# $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
#
# Tests for interrupting a command.
#
@@ -22,7 +22,7 @@ all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-af
clean-before clean-after: .PHONY
@rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious
-interrupt-ordinary: .PHONY
+interrupt-ordinary:
@${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true
# The ././ is necessary to work around the file cache.
@echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok }
diff --git a/unit-tests/cmdline-undefined.exp b/unit-tests/cmdline-undefined.exp
new file mode 100644
index 000000000000..977ceee6dbf5
--- /dev/null
+++ b/unit-tests/cmdline-undefined.exp
@@ -0,0 +1,17 @@
+The = assignment operator
+make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
+make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
+make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
+make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
+make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
+make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
+
+The := assignment operator
+make: "cmdline-undefined.mk" line 29: From the command line: Undefined is .
+make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is .
+make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is .
+make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined.
+make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined.
+make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined.
+
+exit status 0
diff --git a/unit-tests/cmdline-undefined.mk b/unit-tests/cmdline-undefined.mk
new file mode 100644
index 000000000000..5a3375cbbfb8
--- /dev/null
+++ b/unit-tests/cmdline-undefined.mk
@@ -0,0 +1,40 @@
+# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $
+#
+# Tests for undefined variable expressions in the command line.
+
+all:
+ # When the command line is parsed, variable assignments using the
+ # '=' assignment operator do get their variable name expanded
+ # (which probably occurs rarely in practice, if at all), but their
+ # variable value is not expanded, as usual.
+ #
+ @echo 'The = assignment operator'
+ @${.MAKE} -f ${MAKEFILE} print-undefined \
+ CMDLINE='Undefined is $${UNDEFINED}.'
+ @echo
+
+ # The interesting case is using the ':=' assignment operator, which
+ # expands its right-hand side. But only those variables that are
+ # defined.
+ @echo 'The := assignment operator'
+ @${.MAKE} -f ${MAKEFILE} print-undefined \
+ CMDLINE:='Undefined is $${UNDEFINED}.'
+ @echo
+
+.if make(print-undefined)
+
+.MAKEFLAGS: MAKEFLAGS_ASSIGN='Undefined is $${UNDEFINED}.'
+.MAKEFLAGS: MAKEFLAGS_SUBST:='Undefined is $${UNDEFINED}.'
+
+.info From the command line: ${CMDLINE}
+.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN}
+.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST}
+
+UNDEFINED?= now defined
+
+.info From the command line: ${CMDLINE}
+.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN}
+.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST}
+
+print-undefined:
+.endif
diff --git a/unit-tests/cmdline.mk b/unit-tests/cmdline.mk
index c12c31220cb5..cd88cead4558 100644
--- a/unit-tests/cmdline.mk
+++ b/unit-tests/cmdline.mk
@@ -1,8 +1,7 @@
-# $NetBSD: cmdline.mk,v 1.1 2020/07/28 22:44:44 rillig Exp $
+# $NetBSD: cmdline.mk,v 1.2 2020/11/15 14:07:53 rillig Exp $
#
# Tests for command line parsing and related special variables.
-RUN?= @set -eu;
TMPBASE?= /tmp
SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID
SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID
@@ -14,14 +13,14 @@ all: prepare-dirs
all: makeobjdir-direct makeobjdir-indirect
prepare-dirs:
- ${RUN} rm -rf ${DIR2} ${DIR12}
- ${RUN} mkdir -p ${DIR2} ${DIR12}
+ @rm -rf ${DIR2} ${DIR12}
+ @mkdir -p ${DIR2} ${DIR12}
# The .OBJDIR can be set via the MAKEOBJDIR command line variable.
# It must be a command line variable; an environment variable would not work.
makeobjdir-direct:
@echo $@:
- ${RUN} ${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir
+ @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir
# The .OBJDIR can be set via the MAKEOBJDIR command line variable,
# and that variable could even contain the usual modifiers.
@@ -31,7 +30,7 @@ makeobjdir-direct:
# see MAKE_CMD.
makeobjdir-indirect:
@echo $@:
- ${RUN} ${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
+ @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir
show-objdir:
@echo $@: ${.OBJDIR:Q}
diff --git a/unit-tests/comment.mk b/unit-tests/comment.mk
index 1cdcfcdd86ba..d4fb041104a7 100644
--- a/unit-tests/comment.mk
+++ b/unit-tests/comment.mk
@@ -1,4 +1,4 @@
-# $NetBSD: comment.mk,v 1.2 2020/09/07 19:17:36 rillig Exp $
+# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
#
# Demonstrate how comments are written in makefiles.
@@ -12,7 +12,7 @@ that \
goes \
on and on.
- # Comments can be indented, but that is rather unusual.
+ # Comments can be indented with spaces, but that is rather unusual.
# Comments can be indented with a tab.
# These are not shell commands, they are just makefile comments.
@@ -21,6 +21,8 @@ on and on.
.endif # And after the closing directive.
VAR= # This comment makes the variable value empty.
+ # ParseGetLine removes any whitespace before the
+ # comment.
.if ${VAR} != ""
. error
.endif
@@ -35,7 +37,9 @@ VAR= value
. error
.endif
-# This is NOT an escaped comment due to the double backslashes \\
+# This comment ends with 2 backslashes. An even number of backslashes does
+# not count as a line continuation, therefore the variable assignment that
+# follows is actively interpreted. \\
VAR= not part of the comment
.if ${VAR} != "not part of the comment"
. error
@@ -55,7 +59,7 @@ WORDS= ${VAR:[#]} [#
. error
.endif
-# An odd number of comment signs makes a line continuation, \\\
+# An odd number of backslashes makes a line continuation, \\\
no matter if it is 3 or 5 \\\\\
or 9 backslashes. \\\\\\\\\
This is the last line of the comment.
diff --git a/unit-tests/cond-cmp-numeric-eq.exp b/unit-tests/cond-cmp-numeric-eq.exp
index 1f12e858a66c..64e383ef32a2 100644
--- a/unit-tests/cond-cmp-numeric-eq.exp
+++ b/unit-tests/cond-cmp-numeric-eq.exp
@@ -1,6 +1,6 @@
-make: "cond-cmp-numeric-eq.mk" line 54: warning: Unknown operator
-make: "cond-cmp-numeric-eq.mk" line 54: Malformed conditional (!(12345 = 12345))
-make: "cond-cmp-numeric-eq.mk" line 61: Malformed conditional (!(12345 === 12345))
+make: "cond-cmp-numeric-eq.mk" line 67: warning: Unknown operator
+make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345))
+make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345))
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-cmp-numeric-eq.mk b/unit-tests/cond-cmp-numeric-eq.mk
index 0e77d364ac94..c6b39876e75e 100755
--- a/unit-tests/cond-cmp-numeric-eq.mk
+++ b/unit-tests/cond-cmp-numeric-eq.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric-eq.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $
#
# Tests for numeric comparisons with the == operator in .if conditions.
@@ -49,6 +49,19 @@
. error
.endif
+# Because an IEEE 754 double can only hold integers with a mantissa of 53
+# bits, these two numbers are considered the same. The 993 is rounded down
+# to the 992.
+.if 9007199254740993 == 9007199254740992
+.else
+. error
+.endif
+# The 995 is rounded up, the 997 is rounded down.
+.if 9007199254740995 == 9007199254740997
+.else
+. error Probably a misconfiguration in the floating point environment, \
+ or maybe a machine without IEEE 754 floating point support.
+.endif
# There is no = operator for numbers.
.if !(12345 = 12345)
diff --git a/unit-tests/cond-cmp-numeric.exp b/unit-tests/cond-cmp-numeric.exp
index 67d882e0c628..578d53228f6e 100644
--- a/unit-tests/cond-cmp-numeric.exp
+++ b/unit-tests/cond-cmp-numeric.exp
@@ -6,6 +6,10 @@ make: "cond-cmp-numeric.mk" line 16: warning: String comparison operator must be
make: "cond-cmp-numeric.mk" line 16: Malformed conditional (${:UNaN} > NaN)
CondParser_Eval: !(${:UNaN} == NaN)
lhs = "NaN", rhs = "NaN", op = ==
+CondParser_Eval: 123 ! 123
+lhs = 123.000000, rhs = 123.000000, op = !
+make: "cond-cmp-numeric.mk" line 34: warning: Unknown operator
+make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-cmp-numeric.mk b/unit-tests/cond-cmp-numeric.mk
index 67358ddaf86b..b1ec3e719d47 100644
--- a/unit-tests/cond-cmp-numeric.mk
+++ b/unit-tests/cond-cmp-numeric.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric.mk,v 1.3 2020/09/12 18:01:51 rillig Exp $
+# $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $
#
# Tests for numeric comparisons in .if conditions.
@@ -25,5 +25,17 @@
. error
.endif
+# The parsing code in CondParser_Comparison only performs a light check on
+# whether the operator is valid, leaving the rest of the work to the
+# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this
+# parse error is properly reported.
+#
+# XXX: The warning message does not mention the actual operator.
+.if 123 ! 123
+. error
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-cmp-string.exp b/unit-tests/cond-cmp-string.exp
index 735b7cda4430..6e6c218267bc 100644
--- a/unit-tests/cond-cmp-string.exp
+++ b/unit-tests/cond-cmp-string.exp
@@ -1,8 +1,8 @@
make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str)
-make: "cond-cmp-string.mk" line 37: Malformed conditional ("string" != "str""ing")
-make: "cond-cmp-string.mk" line 42: warning: String comparison operator must be either == or !=
-make: "cond-cmp-string.mk" line 42: Malformed conditional (!("value" = "value"))
-make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" === "value"))
+make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing")
+make: "cond-cmp-string.mk" line 49: warning: String comparison operator must be either == or !=
+make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value"))
+make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value"))
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-cmp-string.mk b/unit-tests/cond-cmp-string.mk
index 6af457925e97..6b5cba83bef7 100644
--- a/unit-tests/cond-cmp-string.mk
+++ b/unit-tests/cond-cmp-string.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-string.mk,v 1.11 2020/10/30 14:53:31 rillig Exp $
+# $NetBSD: cond-cmp-string.mk,v 1.13 2020/11/15 14:07:53 rillig Exp $
#
# Tests for string comparisons in .if conditions.
@@ -19,9 +19,14 @@
. error
.endif
-# The left-hand side of the comparison requires a defined variable.
-# The variable named "" is not defined, but applying the :U modifier to it
-# makes it "kind of defined" (see VAR_KEEP). Therefore it is ok here.
+# The left-hand side of the comparison requires that any variable expression
+# is defined.
+#
+# The variable named "" is never defined, nevertheless it can be used as a
+# starting point for variable expressions. Applying the :U modifier to such
+# an undefined expression turns it into a defined expression.
+#
+# See ApplyModifier_Defined and VEF_DEF.
.if ${:Ustr} != "str"
. error
.endif
@@ -33,9 +38,11 @@
.endif
# It is not possible to concatenate two string literals to form a single
-# string.
+# string. In C, Python and the shell this is possible, but not in make.
.if "string" != "str""ing"
. error
+.else
+. error
.endif
# There is no = operator for strings.
@@ -88,3 +95,16 @@
.if ${:U word } != " ${:Uword} "
. error
.endif
+
+# If at least one side of the comparison is a string literal, the string
+# comparison is performed.
+.if 12345 != "12345"
+. error
+.endif
+
+# If at least one side of the comparison is a string literal, the string
+# comparison is performed. The ".0" in the left-hand side makes the two
+# sides of the equation unequal.
+.if 12345.0 == "12345"
+. error
+.endif
diff --git a/unit-tests/cond-cmp-unary.exp b/unit-tests/cond-cmp-unary.exp
index 39a9383953dd..89f90dc1651f 100755
--- a/unit-tests/cond-cmp-unary.exp
+++ b/unit-tests/cond-cmp-unary.exp
@@ -1 +1,2 @@
+make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty.
exit status 0
diff --git a/unit-tests/cond-cmp-unary.mk b/unit-tests/cond-cmp-unary.mk
index 88ce79bf1a99..168de0f30e3f 100755
--- a/unit-tests/cond-cmp-unary.mk
+++ b/unit-tests/cond-cmp-unary.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-unary.mk,v 1.1 2020/09/14 06:22:59 rillig Exp $
+# $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 rillig Exp $
#
# Tests for unary comparisons in .if conditions, that is, comparisons with
# a single operand. If the operand is a number, it is compared to zero,
@@ -25,6 +25,9 @@
.endif
# The empty string may come from a variable expression.
+#
+# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in
+# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber.
.if ${:U}
. error
.endif
@@ -40,4 +43,16 @@
. error
.endif
+# A string of whitespace should evaluate to false.
+#
+# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip
+# whitespace before testing for the end. This was probably an oversight in
+# a commit from 1992-04-15 saying "A variable is empty when it just contains
+# spaces".
+.if ${:U }
+. info This is only reached because of a bug in EvalNotEmpty.
+.else
+. error
+.endif
+
all: # nothing
diff --git a/unit-tests/cond-func-commands.mk b/unit-tests/cond-func-commands.mk
index c6e1724c72f3..e127a8ebdc03 100644
--- a/unit-tests/cond-func-commands.mk
+++ b/unit-tests/cond-func-commands.mk
@@ -1,10 +1,11 @@
-# $NetBSD: cond-func-commands.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $
#
# Tests for the commands() function in .if conditions.
.MAIN: all
-# The target "target" does not exist yet, therefore it cannot have commands.
+# At this point, the target 'target' does not exist yet, therefore it cannot
+# have commands. Sounds obvious, but good to know that it is really so.
.if commands(target)
. error
.endif
diff --git a/unit-tests/cond-func-defined.exp b/unit-tests/cond-func-defined.exp
index 70c6342a02c3..caf66e39938f 100644
--- a/unit-tests/cond-func-defined.exp
+++ b/unit-tests/cond-func-defined.exp
@@ -1,5 +1,10 @@
make: "cond-func-defined.mk" line 23: warning: Missing closing parenthesis for defined()
make: "cond-func-defined.mk" line 23: Malformed conditional (!defined(A B))
+make: "cond-func-defined.mk" line 33: warning: Missing closing parenthesis for defined()
+make: "cond-func-defined.mk" line 33: Malformed conditional (defined(DEF)
+make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are
+make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable
+make: "cond-func-defined.mk" line 47: involved, even if it feels like it.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-func-defined.mk b/unit-tests/cond-func-defined.mk
index ec4feae05839..2aa49ccbf147 100644
--- a/unit-tests/cond-func-defined.mk
+++ b/unit-tests/cond-func-defined.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-defined.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $
#
# Tests for the defined() function in .if conditions.
@@ -29,5 +29,24 @@ ${:UA B}= variable name with spaces
. error
.endif
+# Parse error: missing closing parenthesis; see ParseFuncArg.
+.if defined(DEF
+. error
+.else
+. error
+.endif
+
+# Variables from .for loops are not defined.
+# See directive-for.mk for more details.
+.for var in value
+. if defined(var)
+. error
+. else
+. info In .for loops, variable expressions for the loop variables are
+. info substituted at evaluation time. There is no actual variable
+. info involved, even if it feels like it.
+. endif
+.endfor
+
all:
@:;
diff --git a/unit-tests/cond-func-empty.exp b/unit-tests/cond-func-empty.exp
index 39a9383953dd..77a4edd47f49 100644
--- a/unit-tests/cond-func-empty.exp
+++ b/unit-tests/cond-func-empty.exp
@@ -1 +1,5 @@
-exit status 0
+make: "cond-func-empty.mk" line 152: Unclosed variable "WORD"
+make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD)
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk
index f93b45895e6e..f43d99bf92c5 100644
--- a/unit-tests/cond-func-empty.mk
+++ b/unit-tests/cond-func-empty.mk
@@ -1,11 +1,10 @@
-# $NetBSD: cond-func-empty.mk,v 1.8 2020/09/23 08:11:28 rillig Exp $
+# $NetBSD: cond-func-empty.mk,v 1.10 2020/11/15 14:07:53 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness.
#
# Note that the argument in the parentheses is indeed a variable name,
-# optionally followed by variable modifiers. This is like the defined()
-# function.
+# optionally followed by variable modifiers.
#
.undef UNDEF
@@ -25,13 +24,15 @@ WORD= word
. error
.endif
-# The :S modifier replaces the empty value with an actual word, and
-# after that the expression is no longer empty. Because the variable
-# was undefined in the first place, the expression has the flag VAR_JUNK
-# but not VAR_KEEP, therefore it is still considered undefined.
-# Only very few variable modifiers turn an undefined variable expression
-# into a defined variable expression. The :U and :D modifiers belong to
-# that group, but :S doesn't (see VAR_KEEP).
+# The :S modifier replaces the empty value with an actual word. The
+# expression is now no longer empty, but it is still possible to see whether
+# the expression was based on an undefined variable. The expression has the
+# flag VEF_UNDEF.
+#
+# The expression does not have the flag VEF_DEF though, therefore it is still
+# considered undefined. Yes, indeed, undefined but not empty. There are a
+# few variable modifiers that turn an undefined expression into a defined
+# expression, among them :U and :D, but not :S.
#
# XXX: This is hard to explain to someone who doesn't know these
# implementation details.
@@ -49,13 +50,14 @@ WORD= word
.endif
# And now to the surprising part. Applying the following :S modifier to the
-# undefined variable makes it non-empty, but the marker VAR_JUNK is preserved
-# nevertheless. The :U modifier that follows only looks at VAR_JUNK to decide
-# whether the variable is defined or not. This kind of makes sense since the
-# :U modifier tests the _variable_, not the _expression_.
+# undefined expression makes it non-empty, but the marker VEF_UNDEF is
+# preserved nevertheless. The :U modifier that follows only looks at the
+# VEF_UNDEF flag to decide whether the variable is defined or not. This kind
+# of makes sense since the :U modifier tests the _variable_, not the
+# _expression_.
#
-# But since the variable was undefined to begin with, the fallback value is
-# used in this expression.
+# But since the variable was undefined to begin with, the fallback value from
+# the :U modifier is used in this expression.
#
.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback"
. error
@@ -128,7 +130,7 @@ ${:U }= space
# If everything goes well, the argument expands to "WORD", and that variable
# is defined at the beginning of this file. The surrounding 'W' and 'D'
# ensure that the parser in ParseEmptyArg has the correct position, both
-# before and after the call to Var_ParsePP.
+# before and after the call to Var_Parse.
.if empty(W${:UOR}D)
. error
.endif
@@ -146,5 +148,12 @@ ${:U WORD }= variable name with spaces
. error
.endif
+# Parse error: missing closing parenthesis.
+.if empty(WORD
+. error
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-func.exp b/unit-tests/cond-func.exp
index 0069ed75726c..73b6273d0b09 100644
--- a/unit-tests/cond-func.exp
+++ b/unit-tests/cond-func.exp
@@ -1,9 +1,15 @@
-make: "cond-func.mk" line 29: warning: Missing closing parenthesis for defined()
-make: "cond-func.mk" line 29: Malformed conditional (!defined(A B))
-make: "cond-func.mk" line 44: warning: Missing closing parenthesis for defined()
-make: "cond-func.mk" line 44: Malformed conditional (!defined(A&B))
-make: "cond-func.mk" line 47: warning: Missing closing parenthesis for defined()
-make: "cond-func.mk" line 47: Malformed conditional (!defined(A|B))
+make: "cond-func.mk" line 36: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 36: Malformed conditional (!defined(A B))
+make: "cond-func.mk" line 51: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 51: Malformed conditional (!defined(A&B))
+make: "cond-func.mk" line 54: warning: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 54: Malformed conditional (!defined(A|B))
+make: "cond-func.mk" line 94: The empty variable is never defined.
+make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...).
+make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...).
+make: "cond-func.mk" line 119: Symbols may start with a function name.
+make: "cond-func.mk" line 124: Symbols may start with a function name.
+make: "cond-func.mk" line 130: Malformed conditional (defined()
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-func.mk b/unit-tests/cond-func.mk
index 8e7362182429..4ff43b72ef88 100644
--- a/unit-tests/cond-func.mk
+++ b/unit-tests/cond-func.mk
@@ -1,15 +1,22 @@
-# $NetBSD: cond-func.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $
#
# Tests for those parts of the functions in .if conditions that are common
# among several functions.
#
# The below test uses the function defined(...) since it has no side-effects,
-# the other functions (except empty(...)) would work equally well.
+# the other functions (except empty(...)) would work equally well. The
+# function empty is special because it uses a different parsing algorithm for
+# its argument.
DEF= defined
${:UA B}= variable name with spaces
${:UVAR(value)}= variable name with parentheses
-${:UVAR{value}}= variable name with braces
+${:UVAR{value}}= variable name with balanced braces
+
+# Really strange variable names must be given indirectly via another variable,
+# so that no unbalanced braces appear in the top-level expression.
+VARNAME_UNBALANCED_BRACES= VAR{{{value
+${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
.if !defined(DEF)
. error
@@ -59,6 +66,12 @@ ${:UVAR{value}}= variable name with braces
. error
.endif
+# Braces do not have any special meaning when parsing arguments.
+# They don't need to be balanced.
+.if !defined(VAR{{{value)
+. error
+.endif
+
# There may be spaces around the operators and parentheses, and even
# inside the parentheses. The spaces inside the parentheses are not
# allowed for the empty() function (see cond-func-empty.mk), therefore
@@ -67,5 +80,58 @@ ${:UVAR{value}}= variable name with braces
. error
.endif
+# The following condition is interpreted as defined(A) && defined(B).
+# In lack of a function call expression, each kind of .if directive has a
+# default function that is called when a bare word is parsed. For the plain
+# .if directive, this function is defined(); see "struct If ifs" in cond.c.
+.if A&B
+. error
+.endif
+
+.if defined()
+. error
+.else
+. info The empty variable is never defined.
+.endif
+
+# The plain word 'defined' is interpreted as '!empty(defined)'.
+# That variable is not defined (yet).
+.if defined
+. error
+.else
+. info A plain function name is parsed as !empty(...).
+.endif
+
+# If a variable named 'defined' is actually defined and not empty, the plain
+# symbol 'defined' evaluates to true.
+defined= non-empty
+.if defined
+. info A plain function name is parsed as !empty(...).
+.else
+. error
+.endif
+
+# A plain symbol name may start with one of the function names, in this case
+# 'defined'.
+.if defined-var
+. error
+.else
+. info Symbols may start with a function name.
+.endif
+
+defined-var= non-empty
+.if defined-var
+. info Symbols may start with a function name.
+.else
+. error
+.endif
+
+# Missing closing parenthesis when parsing the function argument.
+.if defined(
+. error
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-late.mk b/unit-tests/cond-late.mk
index 397f5febd480..4df3df2cf1d4 100644
--- a/unit-tests/cond-late.mk
+++ b/unit-tests/cond-late.mk
@@ -1,7 +1,9 @@
-# $NetBSD: cond-late.mk,v 1.2 2020/07/25 20:37:46 rillig Exp $
+# $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
#
# Using the :? modifier, variable expressions can contain conditional
-# expressions that are evaluated late. Any variables appearing in these
+# expressions that are evaluated late, at expansion time.
+#
+# Any variables appearing in these
# conditions are expanded before parsing the condition. This is
# different from many other places.
#
@@ -11,15 +13,15 @@
# They should also not contain operators like == or <, since these are
# actually interpreted as these operators. This is demonstrated below.
#
-# If the order of evaluation were to change to first parse the condition
-# and then expand the variables, the output would change from the
-# current "yes no" to "yes yes", since both variables are non-empty.
all: cond-literal
COND.true= "yes" == "yes"
COND.false= "yes" != "yes"
+# If the order of evaluation were to change to first parse the condition
+# and then expand the variables, the output would change from the
+# current "yes no" to "yes yes", since both variables are non-empty.
cond-literal:
@echo ${ ${COND.true} :?yes:no}
@echo ${ ${COND.false} :?yes:no}
diff --git a/unit-tests/cond-op-and-lint.exp b/unit-tests/cond-op-and-lint.exp
new file mode 100644
index 000000000000..8817fd0d658b
--- /dev/null
+++ b/unit-tests/cond-op-and-lint.exp
@@ -0,0 +1,4 @@
+make: "cond-op-and-lint.mk" line 9: Unknown operator '&'
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/cond-op-and-lint.mk b/unit-tests/cond-op-and-lint.mk
new file mode 100644
index 000000000000..6262339016f5
--- /dev/null
+++ b/unit-tests/cond-op-and-lint.mk
@@ -0,0 +1,13 @@
+# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
+#
+# Tests for the && operator in .if conditions, in lint mode.
+
+.MAKEFLAGS: -dL
+
+# The '&' operator is not allowed in lint mode.
+# It is not used in practice anyway.
+.if 0 & 0
+. error
+.else
+. error
+.endif
diff --git a/unit-tests/cond-op-not.exp b/unit-tests/cond-op-not.exp
index 39a9383953dd..37f57b7fdfa7 100644
--- a/unit-tests/cond-op-not.exp
+++ b/unit-tests/cond-op-not.exp
@@ -1 +1,6 @@
+make: "cond-op-not.mk" line 29: Not empty evaluates to true.
+make: "cond-op-not.mk" line 37: Not space evaluates to false.
+make: "cond-op-not.mk" line 41: Not 0 evaluates to true.
+make: "cond-op-not.mk" line 49: Not 1 evaluates to false.
+make: "cond-op-not.mk" line 55: Not word evaluates to false.
exit status 0
diff --git a/unit-tests/cond-op-not.mk b/unit-tests/cond-op-not.mk
index d929318785a5..388c62d8898f 100644
--- a/unit-tests/cond-op-not.mk
+++ b/unit-tests/cond-op-not.mk
@@ -1,6 +1,6 @@
-# $NetBSD: cond-op-not.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-op-not.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $
#
-# Tests for the ! operator in .if conditions.
+# Tests for the ! operator in .if conditions, which negates its argument.
# The exclamation mark negates its operand.
.if !1
@@ -18,5 +18,42 @@
. error
.endif
+# The operator '==' binds more tightly than '!'.
+# This is unusual since most other programming languages define the precedence
+# to be the other way round.
+.if !${:Uexpression} == "expression"
+. error
+.endif
+
+.if !${:U}
+. info Not empty evaluates to true.
+.else
+. info Not empty evaluates to false.
+.endif
+
+.if !${:U }
+. info Not space evaluates to true.
+.else
+. info Not space evaluates to false.
+.endif
+
+.if !${:U0}
+. info Not 0 evaluates to true.
+.else
+. info Not 0 evaluates to false.
+.endif
+
+.if !${:U1}
+. info Not 1 evaluates to true.
+.else
+. info Not 1 evaluates to false.
+.endif
+
+.if !${:Uword}
+. info Not word evaluates to true.
+.else
+. info Not word evaluates to false.
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-op-or-lint.exp b/unit-tests/cond-op-or-lint.exp
new file mode 100644
index 000000000000..8abae99b6c4c
--- /dev/null
+++ b/unit-tests/cond-op-or-lint.exp
@@ -0,0 +1,4 @@
+make: "cond-op-or-lint.mk" line 9: Unknown operator '|'
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/cond-op-or-lint.mk b/unit-tests/cond-op-or-lint.mk
new file mode 100644
index 000000000000..aa29e9a6c2f2
--- /dev/null
+++ b/unit-tests/cond-op-or-lint.mk
@@ -0,0 +1,13 @@
+# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $
+#
+# Tests for the || operator in .if conditions, in lint mode.
+
+.MAKEFLAGS: -dL
+
+# The '|' operator is not allowed in lint mode.
+# It is not used in practice anyway.
+.if 0 | 0
+. error
+.else
+. error
+.endif
diff --git a/unit-tests/cond-op-parentheses.exp b/unit-tests/cond-op-parentheses.exp
index 39a9383953dd..a0fa137af4ca 100644
--- a/unit-tests/cond-op-parentheses.exp
+++ b/unit-tests/cond-op-parentheses.exp
@@ -1 +1,2 @@
+make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112.
exit status 0
diff --git a/unit-tests/cond-op-parentheses.mk b/unit-tests/cond-op-parentheses.mk
index 6c48d83dd2be..39ebc1607455 100644
--- a/unit-tests/cond-op-parentheses.mk
+++ b/unit-tests/cond-op-parentheses.mk
@@ -1,8 +1,19 @@
-# $NetBSD: cond-op-parentheses.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: cond-op-parentheses.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
#
# Tests for parentheses in .if conditions.
# TODO: Implementation
+# Test for deeply nested conditions.
+.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
+ (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
+ 1 \
+ )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \
+ ))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+. info Parentheses can be nested at least to depth 112.
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-op.exp b/unit-tests/cond-op.exp
index fd5bc20f673c..1a66e5ffeabd 100644
--- a/unit-tests/cond-op.exp
+++ b/unit-tests/cond-op.exp
@@ -1,7 +1,16 @@
-make: "cond-op.mk" line 45: Malformed conditional ("!word" == !word)
-make: "cond-op.mk" line 70: Malformed conditional (0 ${ERR::=evaluated})
-make: "cond-op.mk" line 74: warning: After detecting a parse error, the rest is evaluated.
-make: "cond-op.mk" line 78: Parsing continues until here.
+make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word)
+make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated})
+make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated.
+make: "cond-op.mk" line 83: Parsing continues until here.
+make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C)
+make: "cond-op.mk" line 93: 0 0 0 => 0 0 0
+make: "cond-op.mk" line 93: 0 0 1 => 0 0 0
+make: "cond-op.mk" line 93: 0 1 0 => 0 0 0
+make: "cond-op.mk" line 93: 0 1 1 => 1 1 1
+make: "cond-op.mk" line 93: 1 0 0 => 0 1 1
+make: "cond-op.mk" line 93: 1 0 1 => 1 1 1
+make: "cond-op.mk" line 93: 1 1 0 => 0 1 1
+make: "cond-op.mk" line 93: 1 1 1 => 1 1 1
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-op.mk b/unit-tests/cond-op.mk
index 71c4f7b66441..170355f6c0ad 100644
--- a/unit-tests/cond-op.mk
+++ b/unit-tests/cond-op.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op.mk,v 1.8 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-op.mk,v 1.10 2020/11/15 14:58:14 rillig Exp $
#
# Tests for operators like &&, ||, ! in .if conditions.
#
@@ -9,8 +9,8 @@
# cond-op-parentheses.mk
# In make, && binds more tightly than ||, like in C.
-# If make had the same precedence for both && and ||, the result would be
-# different.
+# If make had the same precedence for both && and ||, like in the shell,
+# the result would be different.
# If || were to bind more tightly than &&, the result would be different
# as well.
.if !(1 || 1 && 0)
@@ -18,13 +18,17 @@
.endif
# If make were to interpret the && and || operators like the shell, the
-# implicit binding would be this:
+# previous condition would be interpreted as:
.if (1 || 1) && 0
. error
.endif
# The precedence of the ! operator is different from C though. It has a
-# lower precedence than the comparison operators.
+# lower precedence than the comparison operators. Negating a condition
+# does not need parentheses.
+#
+# This kind of condition looks so unfamiliar that it doesn't occur in
+# practice.
.if !"word" == "word"
. error
.endif
@@ -36,7 +40,8 @@
# TODO: Demonstrate that the precedence of the ! and == operators actually
# makes a difference. There is a simple example for sure, I just cannot
-# wrap my head around it.
+# wrap my head around it right now. See the truth table generator below
+# for an example that doesn't require much thought.
# This condition is malformed because the '!' on the right-hand side must not
# appear unquoted. If any, it must be enclosed in quotes.
@@ -71,11 +76,27 @@
. error
.endif
.if ${ERR:Uundefined} == evaluated
-. warning After detecting a parse error, the rest is evaluated.
+. info After detecting a parse error, the rest is evaluated.
.endif
# Just in case that parsing should ever stop on the first error.
.info Parsing continues until here.
+# Demonstration that '&&' has higher precedence than '||'.
+.info A B C => (A || B) && C A || B && C A || (B && C)
+.for a in 0 1
+. for b in 0 1
+. for c in 0 1
+. for r1 in ${ ($a || $b) && $c :?1:0}
+. for r2 in ${ $a || $b && $c :?1:0}
+. for r3 in ${ $a || ($b && $c) :?1:0}
+. info $a $b $c => ${r1} ${r2} ${r3}
+. endfor
+. endfor
+. endfor
+. endfor
+. endfor
+.endfor
+
all:
@:;
diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk
index 9e3b0575eb78..077684be33fc 100644
--- a/unit-tests/cond-short.mk
+++ b/unit-tests/cond-short.mk
@@ -1,11 +1,14 @@
-# $NetBSD: cond-short.mk,v 1.11 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: cond-short.mk,v 1.12 2020/11/15 14:58:14 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
+# This is called 'short-circuit evaluation' and is the usual evaluation
+# mode in most programming languages. A notable exception is Ada, which
+# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'.
#
# Between 2015-10-11 and 2020-06-28, the right-hand side of an && or ||
# operator was always evaluated, which was wrong.
-#
+# TODO: Had the evaluation been correct at some time before 2015-11-12?
# The && operator.
@@ -113,6 +116,9 @@ VAR= # empty again, for the following tests
# make sure these do not cause complaint
#.MAKEFLAGS: -dc
+# TODO: Rewrite this whole section and check all the conditions and variables.
+# Several of the assumptions are probably wrong here.
+# TODO: replace 'x=' with '.info' or '.error'.
V42= 42
iV1= ${V42}
iV2= ${V66}
@@ -167,5 +173,16 @@ x= Fail
.endif
x!= echo '0 || ${iV2:U2} < ${V42}: $x' >&2; echo
+# TODO: Has this always worked? There may have been a time, maybe around
+# 2000, when make would complain about the "Malformed conditional" because
+# UNDEF is not defined.
+.if defined(UNDEF) && ${UNDEF} != "undefined"
+. error
+.endif
+
+# TODO: Test each modifier to make sure it is skipped when it is irrelevant
+# for the result. Since this test is already quite long, do that in another
+# test.
+
all:
@:;:
diff --git a/unit-tests/cond-token-number.exp b/unit-tests/cond-token-number.exp
index ea1f92797ff4..b5bfaf95d575 100644
--- a/unit-tests/cond-token-number.exp
+++ b/unit-tests/cond-token-number.exp
@@ -1,8 +1,8 @@
-make: "cond-token-number.mk" line 13: Malformed conditional (-0)
-make: "cond-token-number.mk" line 21: Malformed conditional (+0)
-make: "cond-token-number.mk" line 29: Malformed conditional (!-1)
-make: "cond-token-number.mk" line 37: Malformed conditional (!+1)
-make: "cond-token-number.mk" line 54: End of the tests.
+make: "cond-token-number.mk" line 15: Malformed conditional (-0)
+make: "cond-token-number.mk" line 25: Malformed conditional (+0)
+make: "cond-token-number.mk" line 35: Malformed conditional (!-1)
+make: "cond-token-number.mk" line 45: Malformed conditional (!+1)
+make: "cond-token-number.mk" line 80: End of the tests.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-token-number.mk b/unit-tests/cond-token-number.mk
index 4171a07d56c6..93e2646a60eb 100644
--- a/unit-tests/cond-token-number.mk
+++ b/unit-tests/cond-token-number.mk
@@ -1,6 +1,8 @@
-# $NetBSD: cond-token-number.mk,v 1.3 2020/09/14 06:22:59 rillig Exp $
+# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
#
# Tests for number tokens in .if conditions.
+#
+# TODO: Add introduction.
.if 0
. error
@@ -12,6 +14,8 @@
# See the ch_isdigit call in CondParser_String.
.if -0
. error
+.else
+. error
.endif
# Even though +0 is a number and would be accepted by strtod, it is not
@@ -20,6 +24,8 @@
# See the ch_isdigit call in CondParser_String.
.if +0
. error
+.else
+. error
.endif
# Even though -1 is a number and would be accepted by strtod, it is not
@@ -28,6 +34,8 @@
# See the ch_isdigit call in CondParser_String.
.if !-1
. error
+.else
+. error
.endif
# Even though +1 is a number and would be accepted by strtod, it is not
@@ -36,6 +44,8 @@
# See the ch_isdigit call in CondParser_String.
.if !+1
. error
+.else
+. error
.endif
# When the number comes from a variable expression though, it may be signed.
@@ -50,6 +60,22 @@
. error
.endif
+# Hexadecimal numbers are accepted.
+.if 0x0
+. error
+.endif
+.if 0x1
+.else
+. error
+.endif
+
+# This is not a hexadecimal number, even though it has an x.
+# It is interpreted as a string instead, effectively meaning defined(3x4).
+.if 3x4
+.else
+. error
+.endif
+
# Ensure that parsing continues until here.
.info End of the tests.
diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk
index ba9934f5b882..a5ffa37a5c84 100644
--- a/unit-tests/cond-token-plain.mk
+++ b/unit-tests/cond-token-plain.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-token-plain.mk,v 1.4 2020/09/12 17:47:24 rillig Exp $
+# $NetBSD: cond-token-plain.mk,v 1.6 2020/11/15 14:58:14 rillig Exp $
#
# Tests for plain tokens (that is, string literals without quotes)
# in .if conditions.
@@ -14,7 +14,7 @@
# parser gets to see it.
#
# XXX: The error message is missing for this malformed condition.
-# The right-hand side of the comparison is just a '"'.
+# The right-hand side of the comparison is just a '"', before unescaping.
.if ${:U} != "#hash"
. error
.endif
@@ -31,16 +31,19 @@
# comment handling anymore. The comments are supposed to be stripped off
# in a very early parsing phase.
#
+# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the
+# original problems. This workaround is probably not needed anymore.
+#
# XXX: Missing error message for the malformed condition. The right-hand
-# side is double-quotes, backslash, backslash.
-# XXX: It is unexpected that the right-hand side evaluates to a single
-# backslash.
+# side before unescaping is double-quotes, backslash, backslash.
.if ${:U\\} != "\\#hash"
. error
.endif
# The right-hand side of a comparison is not parsed as a token, therefore
# the code from CondParser_Token does not apply to it.
+# TODO: Explain the consequences.
+# TODO: Does this mean that more syntactic variants are allowed here?
.if ${:U\#hash} != \#hash
. error
.endif
diff --git a/unit-tests/cond-token-string.exp b/unit-tests/cond-token-string.exp
index 39a9383953dd..5df4cc675bb1 100644
--- a/unit-tests/cond-token-string.exp
+++ b/unit-tests/cond-token-string.exp
@@ -1 +1,8 @@
-exit status 0
+make: Unknown modifier 'Z'
+make: "cond-token-string.mk" line 9: Malformed conditional ("" != "${:Uvalue:Z}")
+make: "cond-token-string.mk" line 18: xvalue is not defined.
+make: "cond-token-string.mk" line 24: Malformed conditional (x${:Uvalue} == "")
+make: "cond-token-string.mk" line 33: Expected.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/cond-token-string.mk b/unit-tests/cond-token-string.mk
index 1a8019754824..7e110806d408 100644
--- a/unit-tests/cond-token-string.mk
+++ b/unit-tests/cond-token-string.mk
@@ -1,8 +1,39 @@
-# $NetBSD: cond-token-string.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: cond-token-string.mk,v 1.3 2020/11/10 22:23:37 rillig Exp $
#
# Tests for quoted and unquoted string literals in .if conditions.
# TODO: Implementation
+# Cover the code in CondParser_String that frees the memory after parsing
+# a variable expression based on an undefined variable.
+.if "" != "${:Uvalue:Z}"
+. error
+.else
+. error
+.endif
+
+.if x${:Uvalue}
+. error
+.else
+. info xvalue is not defined.
+.endif
+
+# The 'x' produces a "Malformed conditional" since the left-hand side of a
+# comparison in an .if directive must be either a variable expression, a
+# quoted string literal or a number that starts with a digit.
+.if x${:Uvalue} == ""
+. error
+.else
+. error
+.endif
+
+# In plain words, a '\' can be used to escape any character, just as in
+# double-quoted string literals. See CondParser_String.
+.if \x${:Uvalue} == "xvalue"
+. info Expected.
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-token-var.exp b/unit-tests/cond-token-var.exp
index eb71a43c55f3..fcd92d12a3da 100644
--- a/unit-tests/cond-token-var.exp
+++ b/unit-tests/cond-token-var.exp
@@ -1,7 +1,7 @@
-make: "cond-token-var.mk" line 9: ok
-make: "cond-token-var.mk" line 15: Malformed conditional (${UNDEF} == ${DEF})
-make: "cond-token-var.mk" line 20: Malformed conditional (${DEF} == ${UNDEF})
-make: "cond-token-var.mk" line 29: Malformed conditional (${UNDEF})
+make: "cond-token-var.mk" line 20: ok
+make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF})
+make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF})
+make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-token-var.mk b/unit-tests/cond-token-var.mk
index 6e55bd2d552d..30eba87ad4d2 100644
--- a/unit-tests/cond-token-var.mk
+++ b/unit-tests/cond-token-var.mk
@@ -1,6 +1,17 @@
-# $NetBSD: cond-token-var.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
#
-# Tests for variables in .if conditions.
+# Tests for variable expressions in .if conditions.
+#
+# Note the fine distinction between a variable and a variable expression.
+# A variable has a name and a value. To access the value, one writes a
+# variable expression of the form ${VAR}. This is a simple variable
+# expression. Variable expressions can get more complicated by adding
+# variable modifiers such as in ${VAR:Mpattern}.
+#
+# XXX: Strictly speaking, variable modifiers should be called expression
+# modifiers instead since they only modify the expression, not the variable.
+# Well, except for the assignment modifiers, these do indeed change the value
+# of the variable.
DEF= defined
@@ -12,11 +23,13 @@ DEF= defined
.endif
# A variable that appears on the left-hand side must be defined.
+# The following line thus generates a parse error.
.if ${UNDEF} == ${DEF}
. error
.endif
# A variable that appears on the right-hand side must be defined.
+# The following line thus generates a parse error.
.if ${DEF} == ${UNDEF}
. error
.endif
@@ -25,10 +38,11 @@ DEF= defined
.if ${DEF}
.endif
-# An undefined variable generates a warning.
+# An undefined variable on its own generates a parse error.
.if ${UNDEF}
.endif
-# The :U modifier turns an undefined variable into an ordinary expression.
+# The :U modifier turns an undefined expression into a defined expression.
+# Since the expression is defined now, it doesn't generate any parse error.
.if ${UNDEF:U}
.endif
diff --git a/unit-tests/cond-undef-lint.exp b/unit-tests/cond-undef-lint.exp
index 365edae61275..2c4feb0376ff 100755
--- a/unit-tests/cond-undef-lint.exp
+++ b/unit-tests/cond-undef-lint.exp
@@ -1,7 +1,7 @@
make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined
make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined
make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined
-make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined
+make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-undef-lint.mk b/unit-tests/cond-undef-lint.mk
index 334ceb594054..9dfd1bd53252 100755
--- a/unit-tests/cond-undef-lint.mk
+++ b/unit-tests/cond-undef-lint.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-undef-lint.mk,v 1.2 2020/09/14 07:13:29 rillig Exp $
+# $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
#
# Tests for defined and undefined variables in .if conditions, in lint mode.
#
@@ -42,6 +42,10 @@ DEF= defined
.endif
# The variable VAR.defined is not defined and thus generates an error message.
+#
+# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least
+# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect
+# expression, as in the next example?
.if ${VAR.${DEF}}
. error
.else
diff --git a/unit-tests/cond1.exp b/unit-tests/cond1.exp
index 38d616babdfb..b9db035833be 100644
--- a/unit-tests/cond1.exp
+++ b/unit-tests/cond1.exp
@@ -1,5 +1,5 @@
-make: "cond1.mk" line 75: warning: extra else
-make: "cond1.mk" line 85: warning: extra else
+make: "cond1.mk" line 80: warning: extra else
+make: "cond1.mk" line 90: warning: extra else
2 is prime
A='other' B='unknown' C='clever' o='no,no'
Passed:
diff --git a/unit-tests/cond1.mk b/unit-tests/cond1.mk
index eedff8c1530b..53908c2dacf1 100644
--- a/unit-tests/cond1.mk
+++ b/unit-tests/cond1.mk
@@ -1,4 +1,9 @@
-# $NetBSD: cond1.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $
+# $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $
+
+# TODO: Convert these tests into tutorial form.
+# TODO: Split these tests by topic.
+# TODO: Use better variable names and expression values that actually express
+# the intended behavior. uname(1) has nothing to do with conditions.
# hard code these!
TEST_UNAME_S= NetBSD
diff --git a/unit-tests/dep-double-colon.mk b/unit-tests/dep-double-colon.mk
index 67a28a4315cd..70fddbcf21a4 100644
--- a/unit-tests/dep-double-colon.mk
+++ b/unit-tests/dep-double-colon.mk
@@ -1,6 +1,9 @@
-# $NetBSD: dep-double-colon.mk,v 1.4 2020/09/26 15:41:53 rillig Exp $
+# $NetBSD: dep-double-colon.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the :: operator in dependency declarations.
+# Tests for the '::' operator in dependency declarations, which allows
+# several dependency groups for a single node, each having its own attributes
+# and dependencies. In the code, the additional dependency groups are called
+# cohorts.
all::
@echo 'command 1a'
diff --git a/unit-tests/dep-exclam.mk b/unit-tests/dep-exclam.mk
index 2779a66ea6e3..5f6b72da3fe0 100644
--- a/unit-tests/dep-exclam.mk
+++ b/unit-tests/dep-exclam.mk
@@ -1,6 +1,11 @@
-# $NetBSD: dep-exclam.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: dep-exclam.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the ! operator in dependency declarations.
+# Tests for the ! operator in dependency declarations, which always re-creates
+# the target, whether or not it is out of date.
+#
+# TODO: Is this related to OP_PHONY?
+# TODO: Is this related to OP_EXEC?
+# TODO: Is this related to OP_MAKE?
# TODO: Implementation
diff --git a/unit-tests/depsrc-ignore.mk b/unit-tests/depsrc-ignore.mk
index 1be3eabe8806..aea7d24941ab 100644
--- a/unit-tests/depsrc-ignore.mk
+++ b/unit-tests/depsrc-ignore.mk
@@ -1,11 +1,10 @@
-# $NetBSD: depsrc-ignore.mk,v 1.4 2020/08/29 16:13:27 rillig Exp $
+# $NetBSD: depsrc-ignore.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the special source .IGNORE in dependency declarations,
# which ignores any command failures for that target.
#
-# Even though ignore-errors fails, the all target is still made.
-# Since the all target is not marked with .IGNORE, it stops at the
-# first failing command.
+# Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is
+# not marked with .IGNORE, it stops at the first failing command.
#
# XXX: The ordering of the messages in the output is confusing.
# The "ignored" comes much too late to be related to the "false
@@ -24,8 +23,8 @@
# This is what actually happens, as of 2020-08-29. To verify it, set the
# following breakpoints in CompatRunCommand:
#
-# * the "!silent" line, to see all commands.
-# * the "fflush" line, to see stdout being flushed.
+# * the "!silent" line, to see all commands
+# * the "fflush" line, to see stdout being flushed
# * the "status = WEXITSTATUS" line
# * the "(continuing)" line
# * the "(ignored)" line
diff --git a/unit-tests/depsrc-make.mk b/unit-tests/depsrc-make.mk
index f7b5a9cca8ff..8069007e6e11 100644
--- a/unit-tests/depsrc-make.mk
+++ b/unit-tests/depsrc-make.mk
@@ -1,9 +1,11 @@
-# $NetBSD: depsrc-make.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $
+# $NetBSD: depsrc-make.mk,v 1.4 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the special source .MAKE in dependency declarations, which
# executes the commands of the target even if the -n or -t command line
# options are given.
+# TODO: Add a test for the -t command line option.
+
.MAKEFLAGS: -n
all: this-is-made
diff --git a/unit-tests/depsrc-optional.exp b/unit-tests/depsrc-optional.exp
index 05f2ac6624c4..fce85b3cb38e 100644
--- a/unit-tests/depsrc-optional.exp
+++ b/unit-tests/depsrc-optional.exp
@@ -1,2 +1,20 @@
-`all' is up to date.
+Make_ExpandUse: examine all
+ExamineLater: need to examine "important"
+Make_ExpandUse: examine important
+ExamineLater: need to examine "optional"
+ExamineLater: need to examine "optional-cohort"
+Make_ExpandUse: examine optional
+Make_ExpandUse: examine optional-cohort
+Examining optional...non-existent...up-to-date.
+Examining optional-cohort...non-existent...:: operator and no sources...out-of-date.
+: A leaf node using '::' is considered out-of-date.
+ recheck(optional-cohort): update time from 0:00:00 Jan 01, 1970 to now
+Examining important...non-existent...modified before source "optional-cohort"...out-of-date.
+: important is made.
+ recheck(important): update time from 0:00:00 Jan 01, 1970 to now
+Examining all...non-existent...modified before source "important"...out-of-date.
+: all is made.
+ recheck(all): update time from 0:00:00 Jan 01, 1970 to now
+Examining .END...non-existent...non-existent and no sources...out-of-date.
+ recheck(.END): update time from 0:00:00 Jan 01, 1970 to now
exit status 0
diff --git a/unit-tests/depsrc-optional.mk b/unit-tests/depsrc-optional.mk
index 75ae38bf3194..f12eeca2b3cb 100644
--- a/unit-tests/depsrc-optional.mk
+++ b/unit-tests/depsrc-optional.mk
@@ -1,18 +1,21 @@
-# $NetBSD: depsrc-optional.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $
+# $NetBSD: depsrc-optional.mk,v 1.5 2020/11/08 10:33:47 rillig Exp $
#
# Tests for the special source .OPTIONAL in dependency declarations,
# which ignores the target if make cannot find out how to create it.
#
# TODO: Describe practical use cases for this feature.
-# TODO: Explain why the commands for "important" are not executed.
-# I had thought that only the "optional" commands were skipped.
-
all: important
: ${.TARGET} is made.
-important: optional
+important: optional optional-cohort
: ${.TARGET} is made.
optional: .OPTIONAL
- : This is not executed.
+ : An optional leaf node is not executed.
+
+# See IsOODateRegular.
+optional-cohort:: .OPTIONAL
+ : A leaf node using '::' is considered out-of-date.
+
+.MAKEFLAGS: -dm
diff --git a/unit-tests/depsrc-precious.mk b/unit-tests/depsrc-precious.mk
index 699b83d767b1..e8522a300790 100644
--- a/unit-tests/depsrc-precious.mk
+++ b/unit-tests/depsrc-precious.mk
@@ -1,6 +1,14 @@
-# $NetBSD: depsrc-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: depsrc-precious.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the special source .PRECIOUS in dependency declarations.
+# Tests for the special source .PRECIOUS in dependency declarations, which
+# is only relevant if the commands for the target fail or are interrupted.
+# In such a case, the target file is usually removed, to avoid having
+# half-finished files with a timestamp suggesting the file were up-to-date.
+#
+# For targets marked with .PRECIOUS, the target file is not removed.
+# The author of the makefile is then responsible for avoiding the above
+# situation, in which the target would be wrongly considered up-to-date,
+# just because its timestamp says so.
# TODO: Implementation
diff --git a/unit-tests/depsrc-usebefore.mk b/unit-tests/depsrc-usebefore.mk
index c6be2bae0a9c..001cfb0d71c1 100644
--- a/unit-tests/depsrc-usebefore.mk
+++ b/unit-tests/depsrc-usebefore.mk
@@ -1,7 +1,11 @@
-# $NetBSD: depsrc-usebefore.mk,v 1.5 2020/08/22 11:53:18 rillig Exp $
+# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the special source .USEBEFORE in dependency declarations,
# which allows to prepend common commands to other targets.
+#
+# See also:
+# .USE
+# depsrc-use.mk
all: action directly
diff --git a/unit-tests/depsrc.mk b/unit-tests/depsrc.mk
index d461e1111d0f..15b27286de22 100644
--- a/unit-tests/depsrc.mk
+++ b/unit-tests/depsrc.mk
@@ -1,9 +1,11 @@
-# $NetBSD: depsrc.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: depsrc.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
# Tests for special sources (those starting with a dot, followed by
# uppercase letters) in dependency declarations, such as .PHONY.
# TODO: Implementation
+# TODO: Test 'target: ${:U.SILENT}'
+
all:
@:;
diff --git a/unit-tests/deptgt-begin.exp b/unit-tests/deptgt-begin.exp
index 527020f6818d..abc80afe9964 100644
--- a/unit-tests/deptgt-begin.exp
+++ b/unit-tests/deptgt-begin.exp
@@ -1,4 +1,7 @@
+make: "deptgt-begin.mk" line 17: warning: duplicate script for target ".BEGIN" ignored
+make: "deptgt-begin.mk" line 8: warning: using previous script for ".BEGIN" defined here
: parse time
+: Making before-begin before .BEGIN.
: .BEGIN
: all
exit status 0
diff --git a/unit-tests/deptgt-begin.mk b/unit-tests/deptgt-begin.mk
index c6ca2f4aa3c7..b71d78f371ed 100644
--- a/unit-tests/deptgt-begin.mk
+++ b/unit-tests/deptgt-begin.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-begin.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $
+# $NetBSD: deptgt-begin.mk,v 1.5 2020/11/15 22:28:08 rillig Exp $
#
# Tests for the special target .BEGIN in dependency declarations,
# which is a container for commands that are run before any other
@@ -7,6 +7,40 @@
.BEGIN:
: $@
+# To register a custom action to be run at the beginning, the simplest way is
+# to directly place some commands on the '.BEGIN' target. This doesn't scale
+# though, since the ':' dependency operator prevents that any other place may
+# add its commands after this.
+#
+# There are several ways to resolve this situation, which are detailed below.
+.BEGIN:
+ : Making another $@.
+
+# One way to run commands at the beginning is to define a custom target and
+# make the .BEGIN depend on that target. This way, the commands from the
+# custom target are run even before the .BEGIN target.
+.BEGIN: before-begin
+before-begin: .PHONY .NOTMAIN
+ : Making $@ before .BEGIN.
+
+# Another way is to define a custom target and make that a .USE dependency.
+# For the .BEGIN target, .USE dependencies do not work though, since in
+# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the
+# .BEGIN target has been run, which is too late.
+.BEGIN: use
+use: .USE .NOTMAIN
+ : Making $@ from a .USE dependency.
+
+# Same as with .USE, but run the commands before the main commands from the
+# .BEGIN target.
+#
+# For the .BEGIN target, .USEBEFORE dependencies do not work though, since in
+# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the
+# .BEGIN target has been run, which is too late.
+.BEGIN: use-before
+use-before: .USEBEFORE .NOTMAIN
+ : Making $@ from a .USEBEFORE dependency.
+
all:
: $@
diff --git a/unit-tests/deptgt-error.mk b/unit-tests/deptgt-error.mk
index 07bc1b5d3408..5d515b95afc3 100644
--- a/unit-tests/deptgt-error.mk
+++ b/unit-tests/deptgt-error.mk
@@ -1,6 +1,7 @@
-# $NetBSD: deptgt-error.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the special target .ERROR in dependency declarations.
+# Tests for the special target .ERROR in dependency declarations, which
+# collects commands that are run when another target fails.
# TODO: Implementation
diff --git a/unit-tests/deptgt-ignore.mk b/unit-tests/deptgt-ignore.mk
index 6ace0841f28b..49c14d2cfd43 100644
--- a/unit-tests/deptgt-ignore.mk
+++ b/unit-tests/deptgt-ignore.mk
@@ -1,6 +1,7 @@
-# $NetBSD: deptgt-ignore.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the special target .IGNORE in dependency declarations.
+# 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.
# TODO: Implementation
diff --git a/unit-tests/deptgt-interrupt.mk b/unit-tests/deptgt-interrupt.mk
index d555f864563e..d94009a52e05 100644
--- a/unit-tests/deptgt-interrupt.mk
+++ b/unit-tests/deptgt-interrupt.mk
@@ -1,6 +1,8 @@
-# $NetBSD: deptgt-interrupt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the special target .INTERRUPT in dependency declarations.
+# 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
diff --git a/unit-tests/deptgt-main.mk b/unit-tests/deptgt-main.mk
index cf1b1b4fd340..84d05dc25ed6 100644
--- a/unit-tests/deptgt-main.mk
+++ b/unit-tests/deptgt-main.mk
@@ -1,6 +1,8 @@
-# $NetBSD: deptgt-main.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
-# Tests for the special target .MAIN in dependency declarations.
+# 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
diff --git a/unit-tests/deptgt-makeflags.exp b/unit-tests/deptgt-makeflags.exp
index a226cafc9ba1..7eb54eba7f30 100644
--- a/unit-tests/deptgt-makeflags.exp
+++ b/unit-tests/deptgt-makeflags.exp
@@ -6,4 +6,5 @@ Var_Parse: ${DOLLAR} != "\$\$" with VARE_UNDEFERR|VARE_WANTRES
lhs = "$$", rhs = "$$", op = !=
Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d
Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0
+make: Unterminated quoted string [make VAR=initial UNBALANCED=']
exit status 0
diff --git a/unit-tests/deptgt-makeflags.mk b/unit-tests/deptgt-makeflags.mk
index 70a9cd0b590b..0a0f410e14c4 100644
--- a/unit-tests/deptgt-makeflags.mk
+++ b/unit-tests/deptgt-makeflags.mk
@@ -1,30 +1,35 @@
-# $NetBSD: deptgt-makeflags.mk,v 1.4 2020/10/23 14:48:49 rillig Exp $
+# $NetBSD: deptgt-makeflags.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the special target .MAKEFLAGS in dependency declarations,
# which adds command line options later, at parse time.
+#
+# In these unit tests, it is often used to temporarily toggle the debug log
+# during parsing.
# The -D option sets a variable in the "Global" scope and thus can be
# undefined later.
.MAKEFLAGS: -D VAR
-
.if ${VAR} != 1
. error
.endif
+# Variables that are set via the -D command line option are normal global
+# variables and can thus be undefined later.
.undef VAR
-
.if defined(VAR)
. error
.endif
+# The -D command line option can define a variable again, after it has been
+# undefined.
.MAKEFLAGS: -D VAR
-
.if ${VAR} != 1
. error
.endif
+# The "dependency" for .MAKEFLAGS is split into words, interpreting the usual
+# quotes and escape sequences from the backslash.
.MAKEFLAGS: VAR="value"' with'\ spaces
-
.if ${VAR} != "value with spaces"
. error
.endif
@@ -32,7 +37,6 @@
# Variables set on the command line as VAR=value are placed in the
# "Command" scope and thus cannot be undefined.
.undef VAR
-
.if ${VAR} != "value with spaces"
. error
.endif
@@ -47,5 +51,43 @@
.endif
.MAKEFLAGS: -d0
+# An empty command line is skipped.
+.MAKEFLAGS: # none
+
+# Escape sequences like \n are interpreted.
+# The following line looks as if it assigned a newline to nl, but it doesn't.
+# Instead, the \n ends up as a line that is then interpreted as a variable
+# assignment. At that point, the line is simply "nl=\n", and the \n is
+# skipped since it is whitespace (see Parse_IsVar).
+.MAKEFLAGS: nl="\n"
+.if ${nl} != ""
+. error
+.endif
+
+# Next try at defining another newline variable. Since whitespace around the
+# variable value is trimmed, two empty variable expressions surround the
+# literal newline now. This prevents the newline from being skipped during
+# parsing. The ':=' assignment operator expands the empty variable
+# expressions, leaving only the newline as the variable value.
+#
+# This is one of the very few ways (maybe even the only one) to inject literal
+# newlines into a line that is being parsed. This may confuse the parser.
+# For example, in cond.c the parser only expects horizontal whitespace (' '
+# and '\t'), but no newlines.
+#.MAKEFLAGS: -dcpv
+.MAKEFLAGS: nl:="$${:U}\n$${:U}"
+.if ${nl} != ${.newline}
+. error
+.endif
+#.MAKEFLAGS: -d0
+
+# Unbalanced quotes produce an error message. If they occur anywhere in the
+# command line, the whole command line is skipped.
+.MAKEFLAGS: VAR=previous
+.MAKEFLAGS: VAR=initial UNBALANCED='
+.if ${VAR} != "previous"
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/deptgt-silent.exp b/unit-tests/deptgt-silent.exp
index 6cab88f53144..c9b89da0ae5e 100644
--- a/unit-tests/deptgt-silent.exp
+++ b/unit-tests/deptgt-silent.exp
@@ -1,3 +1,5 @@
+echo 'This is a loud command.'
+This is a loud command.
This is not echoed because of the @.
This is not echoed because of the .SILENT.
exit status 0
diff --git a/unit-tests/deptgt-silent.mk b/unit-tests/deptgt-silent.mk
index 64d54c00e632..335a1e897295 100644
--- a/unit-tests/deptgt-silent.mk
+++ b/unit-tests/deptgt-silent.mk
@@ -1,10 +1,15 @@
-# $NetBSD: deptgt-silent.mk,v 1.3 2020/09/10 21:40:50 rillig Exp $
+# $NetBSD: deptgt-silent.mk,v 1.4 2020/11/15 20:49:20 rillig Exp $
#
# Tests for the special target .SILENT in dependency declarations.
.SILENT: all
-all:
+all: loud
@echo 'This is not echoed because of the @.'
# Without the .SILENT, the following command would be echoed.
echo 'This is not echoed because of the .SILENT.'
+
+# Demonstrate that the .SILENT only applies to the particular target.
+# This is unlike .DELETE_ON_ERROR, which is a global setting.
+loud: .PHONY
+ echo 'This is a loud command.'
diff --git a/unit-tests/deptgt.exp b/unit-tests/deptgt.exp
index 2b899313b938..fee0563f5b37 100644
--- a/unit-tests/deptgt.exp
+++ b/unit-tests/deptgt.exp
@@ -1,5 +1,13 @@
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'
+ParseDoDependency(: empty-source)
+ParseReadLine (35): ' : command for empty targets list'
+ParseReadLine (36): ': empty-source'
+ParseDoDependency(: empty-source)
+ParseReadLine (37): ' : command for empty targets list'
+ParseReadLine (38): '.MAKEFLAGS: -d0'
+ParseDoDependency(.MAKEFLAGS: -d0)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/deptgt.mk b/unit-tests/deptgt.mk
index 517ed5aa2b75..83f81b6f58ed 100644
--- a/unit-tests/deptgt.mk
+++ b/unit-tests/deptgt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt.mk,v 1.8 2020/10/18 13:02:10 rillig Exp $
+# $NetBSD: deptgt.mk,v 1.9 2020/11/15 11:57:00 rillig Exp $
#
# Tests for special targets like .BEGIN or .SUFFIXES in dependency
# declarations.
@@ -27,5 +27,15 @@ target1 target2: sources # targets := [target1, target2]
VAR=value # targets := NULL
: command3 # parse error, since targets == NULL
+# In a dependency declaration, the list of targets can be empty.
+# It doesn't matter whether the empty string is generated by a variable
+# expression or whether it is just omitted.
+.MAKEFLAGS: -dp
+${:U}: empty-source
+ : command for empty targets list
+: empty-source
+ : command for empty targets list
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/dir.mk b/unit-tests/dir.mk
index 24ce47d959ed..55e22d88b803 100644
--- a/unit-tests/dir.mk
+++ b/unit-tests/dir.mk
@@ -1,4 +1,4 @@
-# $NetBSD: dir.mk,v 1.7 2020/10/31 21:30:03 rillig Exp $
+# $NetBSD: dir.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $
#
# Tests for dir.c.
@@ -64,7 +64,8 @@ fetch fetch-post extract extract-post:
: $@
# The expansions may have duplicates.
-# These are merged together because of the dependency line.
+# When the source of the dependency line is expanded later, each of the
+# expanded words will be the same.
all: dup-{1,1,1,1,1,1,1}
dup-1:
diff --git a/unit-tests/directive-elif.exp b/unit-tests/directive-elif.exp
index 39a9383953dd..6219b4896795 100644
--- a/unit-tests/directive-elif.exp
+++ b/unit-tests/directive-elif.exp
@@ -1 +1,17 @@
-exit status 0
+make: "directive-elif.mk" line 7: begin .elif misspellings tests, part 1
+make: "directive-elif.mk" line 9: 1-then
+make: "directive-elif.mk" line 18: begin .elif misspellings tests, part 2
+make: "directive-elif.mk" line 29: begin .elif misspellings tests, part 3
+make: "directive-elif.mk" line 41: which branch is taken on misspelling after false?
+make: "directive-elif.mk" line 49: else
+make: "directive-elif.mk" line 52: which branch is taken on misspelling after true?
+make: "directive-elif.mk" line 54: 1-then
+make: "directive-elif.mk" line 55: Unknown directive "elsif"
+make: "directive-elif.mk" line 56: 1-elsif
+make: "directive-elif.mk" line 57: Unknown directive "elsif"
+make: "directive-elif.mk" line 58: 2-elsif
+make: "directive-elif.mk" line 64: if-less elif
+make: "directive-elif.mk" line 69: warning: extra elif
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-elif.mk b/unit-tests/directive-elif.mk
index fc8bb7e7d197..f9a43abfffc1 100644
--- a/unit-tests/directive-elif.mk
+++ b/unit-tests/directive-elif.mk
@@ -1,8 +1,72 @@
-# $NetBSD: directive-elif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-elif.mk,v 1.6 2020/11/12 19:46:36 rillig Exp $
#
# Tests for the .elif directive.
# TODO: Implementation
+.info begin .elif misspellings tests, part 1
+.if 1
+. info 1-then
+.elif 1 # ok
+. info 1-elif
+.elsif 1 # oops: misspelled
+. info 1-elsif
+.elseif 1 # oops: misspelled
+. info 1-elseif
+.endif
+
+.info begin .elif misspellings tests, part 2
+.if 0
+. info 0-then
+.elif 0 # ok
+. info 0-elif
+.elsif 0 # oops: misspelled
+. info 0-elsif
+.elseif 0 # oops: misspelled
+. info 0-elseif
+.endif
+
+.info begin .elif misspellings tests, part 3
+.if 0
+. info 0-then
+.elsif 0 # oops: misspelled
+. info 0-elsif
+.endif
+.if 0
+. info 0-then
+.elseif 0 # oops: misspelled
+. info 0-elseif
+.endif
+
+.info which branch is taken on misspelling after false?
+.if 0
+. info 0-then
+.elsif 1
+. info 1-elsif
+.elsif 2
+. info 2-elsif
+.else
+. info else
+.endif
+
+.info which branch is taken on misspelling after true?
+.if 1
+. info 1-then
+.elsif 1
+. info 1-elsif
+.elsif 2
+. info 2-elsif
+.else
+. info else
+.endif
+
+# Expect: "if-less elif"
+.elif 0
+
+.if 1
+.else
+# Expect: "warning: if-less elif"
+.elif
+.endif
+
all:
- @:;
diff --git a/unit-tests/directive-else.exp b/unit-tests/directive-else.exp
index 387577099b81..ca60595745a9 100644
--- a/unit-tests/directive-else.exp
+++ b/unit-tests/directive-else.exp
@@ -1,8 +1,11 @@
-make: "directive-else.mk" line 10: ok
-make: "directive-else.mk" line 14: ok
-make: "directive-else.mk" line 20: if-less else
-make: "directive-else.mk" line 26: ok
-make: "directive-else.mk" line 27: warning: extra else
+make: "directive-else.mk" line 11: The .else directive does not take arguments.
+make: "directive-else.mk" line 12: ok
+make: "directive-else.mk" line 16: ok
+make: "directive-else.mk" line 17: The .else directive does not take arguments.
+make: "directive-else.mk" line 22: if-less else
+make: "directive-else.mk" line 28: ok
+make: "directive-else.mk" line 29: warning: extra else
+make: "directive-else.mk" line 42: The .else directive does not take arguments.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-else.mk b/unit-tests/directive-else.mk
index 8fbbb5189ad5..fdd94007a1fa 100644
--- a/unit-tests/directive-else.mk
+++ b/unit-tests/directive-else.mk
@@ -1,7 +1,9 @@
-# $NetBSD: directive-else.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: directive-else.mk,v 1.6 2020/11/13 09:01:59 rillig Exp $
#
# Tests for the .else directive.
+.MAKEFLAGS: -dL # To enable the check for ".else <cond>"
+
# The .else directive does not take any arguments.
# As of 2020-08-29, make doesn't warn about this.
.if 0
@@ -28,5 +30,17 @@
. info After an extra .else, everything is skipped.
.endif
+# An .else may have a comment. This comment does not count as an argument,
+# therefore no parse error.
+.if 0
+.else # comment
+.endif
+
+# A variable expression does count as an argument, even if it is empty.
+# XXX: This should be a parse error.
+.if 0
+.else ${:U}
+.endif
+
all:
@:;
diff --git a/unit-tests/directive-endif.mk b/unit-tests/directive-endif.mk
index 12b608ffbeee..b0b531af2f06 100644
--- a/unit-tests/directive-endif.mk
+++ b/unit-tests/directive-endif.mk
@@ -1,8 +1,27 @@
-# $NetBSD: directive-endif.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-endif.mk,v 1.3 2020/11/12 22:40:11 rillig Exp $
#
# Tests for the .endif directive.
+#
+# See also:
+# Cond_EvalLine
# TODO: Implementation
+.MAKEFLAGS: -dL
+
+# Error: .endif does not take arguments
+# XXX: Missing error message
+.if 0
+.endif 0
+
+# Error: .endif does not take arguments
+# XXX: Missing error message
+.if 1
+.endif 1
+
+# Comments are allowed after an '.endif'.
+.if 2
+.endif # comment
+
all:
@:;
diff --git a/unit-tests/directive-export-env.mk b/unit-tests/directive-export-env.mk
index 49d1edb9f6fe..82b5e8087c6d 100644
--- a/unit-tests/directive-export-env.mk
+++ b/unit-tests/directive-export-env.mk
@@ -1,8 +1,12 @@
-# $NetBSD: directive-export-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-export-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .export-env directive.
# TODO: Implementation
+.export-en # oops: misspelled
+.export-env
+.export-environment # oops: misspelled
+
all:
@:;
diff --git a/unit-tests/directive-export-gmake.mk b/unit-tests/directive-export-gmake.mk
index 51de8fe55f64..d94cd9debf64 100644
--- a/unit-tests/directive-export-gmake.mk
+++ b/unit-tests/directive-export-gmake.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export-gmake.mk,v 1.2 2020/10/19 18:59:53 rillig Exp $
+# $NetBSD: directive-export-gmake.mk,v 1.3 2020/11/17 20:16:44 rillig Exp $
#
# Tests for the export directive (without leading dot), as in GNU make.
@@ -39,8 +39,8 @@ export VAR= leading spaces
# may be silently discarded. One such shell is dash, which is the default
# shell on Ubuntu and Debian.
export VAR =trailing space in varname
-.if ${:!env | grep trailing!} != "VAR =trailing space in varname"
-. if ${:!env | grep trailing!} != "" # for dash
+.if ${:!env | grep trailing || true!} != "VAR =trailing space in varname"
+. if ${:!env | grep trailing || true!} != "" # for dash
. error
. endif
.endif
diff --git a/unit-tests/directive-export-literal.mk b/unit-tests/directive-export-literal.mk
index 817c836fd6c4..51e5b522a3b9 100644
--- a/unit-tests/directive-export-literal.mk
+++ b/unit-tests/directive-export-literal.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export-literal.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
+# $NetBSD: directive-export-literal.mk,v 1.6 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .export-literal directive, which exports a variable value
# without expanding it.
@@ -7,5 +7,9 @@ UT_VAR= value with ${UNEXPANDED} expression
.export-literal UT_VAR
+.export-litera # oops: misspelled
+.export-literal # oops: missing argument
+.export-literally # oops: misspelled
+
all:
@echo "$$UT_VAR"
diff --git a/unit-tests/directive-export.exp b/unit-tests/directive-export.exp
index 39a9383953dd..bd828b63c10c 100644
--- a/unit-tests/directive-export.exp
+++ b/unit-tests/directive-export.exp
@@ -1 +1,4 @@
-exit status 0
+make: "directive-export.mk" line 25: Unknown directive "expor"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk
index 6fbf8f09d4dc..bae50aecbdaf 100644
--- a/unit-tests/directive-export.mk
+++ b/unit-tests/directive-export.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-export.mk,v 1.3 2020/10/29 17:27:12 rillig Exp $
+# $NetBSD: directive-export.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .export directive.
@@ -21,5 +21,11 @@ VAR= value $$ ${INDIRECT}
. error
.endif
+# Tests for parsing the .export directive.
+.expor # misspelled
+.export # oops: missing argument
+.export VARNAME
+.exporting works # oops: misspelled
+
all:
@:;
diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp
index 88ab45529e3f..af610cc34edd 100755
--- a/unit-tests/directive-for.exp
+++ b/unit-tests/directive-for.exp
@@ -1,19 +1,19 @@
-make: "directive-for.mk" line 100: outer
-make: "directive-for.mk" line 125: a:\ a:\file.txt
-make: "directive-for.mk" line 125: d:\\
-make: "directive-for.mk" line 125: d:\\file.txt
-make: "directive-for.mk" line 132: ( ( (
-make: "directive-for.mk" line 132: [ [ [
-make: "directive-for.mk" line 132: { { {
-make: "directive-for.mk" line 132: ) ) )
-make: "directive-for.mk" line 132: ] ] ]
-make: "directive-for.mk" line 132: } } }
-make: "directive-for.mk" line 132: (()) (()) (())
-make: "directive-for.mk" line 132: [[]] [[]] [[]]
-make: "directive-for.mk" line 132: {{}} {{}} {{}}
-make: "directive-for.mk" line 132: )( )( )(
-make: "directive-for.mk" line 132: ][ ][ ][
-make: "directive-for.mk" line 132: }{ }{ }{
-make: "directive-for.mk" line 140: outer value value
-make: "directive-for.mk" line 140: outer "quoted" \"quoted\"
+make: "directive-for.mk" line 108: outer
+make: "directive-for.mk" line 133: a:\ a:\file.txt
+make: "directive-for.mk" line 133: d:\\
+make: "directive-for.mk" line 133: d:\\file.txt
+make: "directive-for.mk" line 140: ( ( (
+make: "directive-for.mk" line 140: [ [ [
+make: "directive-for.mk" line 140: { { {
+make: "directive-for.mk" line 140: ) ) )
+make: "directive-for.mk" line 140: ] ] ]
+make: "directive-for.mk" line 140: } } }
+make: "directive-for.mk" line 140: (()) (()) (())
+make: "directive-for.mk" line 140: [[]] [[]] [[]]
+make: "directive-for.mk" line 140: {{}} {{}} {{}}
+make: "directive-for.mk" line 140: )( )( )(
+make: "directive-for.mk" line 140: ][ ][ ][
+make: "directive-for.mk" line 140: }{ }{ }{
+make: "directive-for.mk" line 148: outer value value
+make: "directive-for.mk" line 148: outer "quoted" \"quoted\"
exit status 0
diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk
index e0987d331a14..93f0a14f5892 100755
--- a/unit-tests/directive-for.mk
+++ b/unit-tests/directive-for.mk
@@ -1,6 +1,13 @@
-# $NetBSD: directive-for.mk,v 1.8 2020/10/25 15:49:03 rillig Exp $
+# $NetBSD: directive-for.mk,v 1.9 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the .for directive.
+#
+# TODO: Describe naming conventions for the loop variables.
+# .for f in values
+# .for file in values
+# .for _FILE_ in values
+# .for .FILE. in values
+# .for _f_ in values
# Using the .for loop, lists of values can be produced.
# In simple cases, the :@var@${var}@ variable modifier can be used to
@@ -15,6 +22,7 @@ NUMBERS+= ${num}
.endif
# The .for loop also works for multiple iteration variables.
+# This is something that the variable modifier :@ cannot do.
.for name value in VARNAME value NAME2 value2
${name}= ${value}
.endfor
@@ -26,7 +34,7 @@ ${name}= ${value}
# just like the :M or :S variable modifiers.
#
# Until 2012-06-03, it had split the items exactly at whitespace, without
-# taking the quotes into account.
+# taking the quotes into account. This had resulted in 10 words.
#
.undef WORDS
.for var in one t\ w\ o "three three" 'four four' `five six`
diff --git a/unit-tests/directive-if-nested.exp b/unit-tests/directive-if-nested.exp
new file mode 100644
index 000000000000..1a9ae02f07b2
--- /dev/null
+++ b/unit-tests/directive-if-nested.exp
@@ -0,0 +1,2 @@
+make: "directive-if-nested.inc" line 1001: deeply nested .if directives
+exit status 0
diff --git a/unit-tests/directive-if-nested.mk b/unit-tests/directive-if-nested.mk
new file mode 100644
index 000000000000..19c8e9452660
--- /dev/null
+++ b/unit-tests/directive-if-nested.mk
@@ -0,0 +1,25 @@
+# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $
+#
+# Tests for deeply nested .if directives. By default, memory for 128 nested
+# .if directives is pre-allocated, any deeper nesting is reallocated.
+#
+# See also:
+# Cond_EvalLine
+
+GEN= directive-if-nested.inc
+
+all: set-up test tear-down
+
+set-up: .PHONY
+ @{ printf '.if %s\n' ${:U:range=1000}; \
+ printf '.info deeply nested .if directives\n'; \
+ printf '.endif # %s\n' ${:U:range=1000}; \
+ printf '\n'; \
+ printf 'all:\n'; \
+ } > ${GEN}
+
+test: .PHONY
+ @${MAKE} -f ${GEN}
+
+tear-down: .PHONY
+ @rm -f ${GEN}
diff --git a/unit-tests/directive-if.exp b/unit-tests/directive-if.exp
index 39a9383953dd..21a33fe4cfd6 100644
--- a/unit-tests/directive-if.exp
+++ b/unit-tests/directive-if.exp
@@ -1 +1,15 @@
-exit status 0
+make: "directive-if.mk" line 13: 0 evaluates to false.
+make: "directive-if.mk" line 17: 1 evaluates to true.
+make: "directive-if.mk" line 40: Unknown directive "ifx"
+make: "directive-if.mk" line 41: Unknown directive "error"
+make: "directive-if.mk" line 42: if-less else
+make: "directive-if.mk" line 43: Unknown directive "error"
+make: "directive-if.mk" line 44: if-less endif
+make: "directive-if.mk" line 47: Malformed conditional ()
+make: "directive-if.mk" line 57: Quotes in plain words are probably a mistake.
+make: "directive-if.mk" line 66: Don't do this, always put a space after a directive.
+make: "directive-if.mk" line 70: Don't do this, always put a space after a directive.
+make: "directive-if.mk" line 76: Don't do this, always put a space around comparison operators.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-if.mk b/unit-tests/directive-if.mk
index 72d7d0e2d920..3b1d13c7a0c0 100644
--- a/unit-tests/directive-if.mk
+++ b/unit-tests/directive-if.mk
@@ -1,8 +1,81 @@
-# $NetBSD: directive-if.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-if.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the .if directive.
+#
+# See also:
+# cond-*.mk
# TODO: Implementation
+.if 0
+. error
+.else
+. info 0 evaluates to false.
+.endif
+
+.if 1
+. info 1 evaluates to true.
+.else
+. error
+.endif
+
+# There is no '.ifx'.
+#
+# The commit from 2005-05-01 intended to detect this situation, but it failed
+# to do this since the call to is_token had its arguments switched. They were
+# expected as (str, token, token_len) but were actually passed as (token, str,
+# token_len). This made is_token return true even if the directive was
+# directly followed by alphanumerical characters, which was wrong. The
+# typical cases produced an error message such as "Malformed conditional
+# (x 123)", while the intended error message was "Unknown directive".
+#
+# Back at that time, the commits only modified the main code but did not add
+# the corresponding unit tests. This allowed the bug to hide for more than
+# 15 years.
+#
+# Since 2020-11-10, the correct error message is produced. The '.ifx' is no
+# longer interpreted as a variant of '.if', therefore the '.error' and '.else'
+# are interpreted as ordinary directives, producing the error messages
+# "if-less else" and "if-less endif".
+.ifx 123
+. error
+.else
+. error
+.endif
+
+# Missing condition.
+.if
+. error
+.else
+. error
+.endif
+
+# A plain word must not start with a '"'. It may contain a embedded quotes
+# though, which are kept. The quotes need not be balanced. The next space
+# ends the word, and the remaining " || 1" is parsed as "or true".
+.if ${:Uplain"""""} == plain""""" || 1
+. info Quotes in plain words are probably a mistake.
+# XXX: Accepting quotes in plain words is probably a mistake as well.
+.else
+. error
+.endif
+
+.if0
+. error
+.else
+. info Don't do this, always put a space after a directive.
+.endif
+
+.if${:U-3}
+. info Don't do this, always put a space after a directive.
+.else
+. error
+.endif
+
+.if${:U-3}>-4
+. info Don't do this, always put a space around comparison operators.
+.else
+. error
+.endif
+
all:
- @:;
diff --git a/unit-tests/directive-ifdef.exp b/unit-tests/directive-ifdef.exp
index 39a9383953dd..b8453ecc4124 100644
--- a/unit-tests/directive-ifdef.exp
+++ b/unit-tests/directive-ifdef.exp
@@ -1 +1,2 @@
+make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible.
exit status 0
diff --git a/unit-tests/directive-ifdef.mk b/unit-tests/directive-ifdef.mk
index 64f073fb5ae1..516e095c464f 100644
--- a/unit-tests/directive-ifdef.mk
+++ b/unit-tests/directive-ifdef.mk
@@ -1,8 +1,18 @@
-# $NetBSD: directive-ifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-ifdef.mk,v 1.3 2020/11/08 22:38:28 rillig Exp $
#
# Tests for the .ifdef directive.
# TODO: Implementation
+DEFINED= defined
+
+# It looks redundant to have a call to defined() in an .ifdef, but it's
+# possible. The .ifdef only affects plain symbols, not function calls.
+.ifdef defined(DEFINED)
+. info Function calls in .ifdef are possible.
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/directive-ifmake.exp b/unit-tests/directive-ifmake.exp
index 5fefeb252b48..fd4bcae151a0 100644
--- a/unit-tests/directive-ifmake.exp
+++ b/unit-tests/directive-ifmake.exp
@@ -1,9 +1,10 @@
-make: "directive-ifmake.mk" line 8: ok: positive condition works
-make: "directive-ifmake.mk" line 19: ok: negation works
-make: "directive-ifmake.mk" line 25: ok: double negation works
-make: "directive-ifmake.mk" line 32: ok: both mentioned
-make: "directive-ifmake.mk" line 39: ok: only those mentioned
-make: "directive-ifmake.mk" line 49: Targets can even be added at parse time.
+make: "directive-ifmake.mk" line 13: ok: positive condition works
+make: "directive-ifmake.mk" line 24: ok: negation works
+make: "directive-ifmake.mk" line 33: ok: double negation works
+make: "directive-ifmake.mk" line 40: ok: both mentioned
+make: "directive-ifmake.mk" line 47: ok: only those mentioned
+make: "directive-ifmake.mk" line 57: Targets can even be added at parse time.
+make: "directive-ifmake.mk" line 75: ok
: first
: second
: late-target
diff --git a/unit-tests/directive-ifmake.mk b/unit-tests/directive-ifmake.mk
index 20329bc5ce25..4d49add72626 100644
--- a/unit-tests/directive-ifmake.mk
+++ b/unit-tests/directive-ifmake.mk
@@ -1,44 +1,52 @@
-# $NetBSD: directive-ifmake.mk,v 1.4 2020/08/30 14:25:45 rillig Exp $
+# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the .ifmake directive, which provides a shortcut for asking
# whether a certain target is requested to be made from the command line.
+#
+# TODO: Describe why the shortcut may be useful (if it's useful at all),
+# instead of sticking to the simple '.if' only.
+
+# The targets 'first' and 'second' are passed in on the command line.
# This is the most basic form.
.ifmake first
-.info ok: positive condition works
+. info ok: positive condition works
.else
-.warning positive condition fails
+. warning positive condition fails
.endif
# The not operator works as expected.
# An alternative interpretation were that this condition is asking whether
# the target "!first" was requested. To distinguish this, see the next test.
.ifmake !first
-.warning unexpected
+. warning unexpected
.else
-.info ok: negation works
+. info ok: negation works
.endif
# See if the exclamation mark really means "not", or if it is just part of
-# the target name.
+# the target name. Since it means 'not', the two exclamation marks are
+# effectively ignored, and 'first' is indeed a requested target. If the
+# exclamation mark were part of the name instead, the name would be '!!first',
+# and such a target was not requested to be made.
.ifmake !!first
-.info ok: double negation works
+. info ok: double negation works
.else
-.warning double negation fails
+. warning double negation fails
.endif
# Multiple targets can be combined using the && and || operators.
.ifmake first && second
-.info ok: both mentioned
+. info ok: both mentioned
.else
-.warning && does not work as expected
+. warning && does not work as expected
.endif
# Negation also works in complex conditions.
.ifmake first && !unmentioned
-.info ok: only those mentioned
+. info ok: only those mentioned
.else
-.warning && with ! does not work as expected
+. warning && with ! does not work as expected
.endif
# Using the .MAKEFLAGS special dependency target, arbitrary command
@@ -46,10 +54,29 @@
# possible to extend the targets to be made.
.MAKEFLAGS: late-target
.ifmake late-target
-.info Targets can even be added at parse time.
+. info Targets can even be added at parse time.
+.else
+. info No, targets cannot be added at parse time anymore.
+.endif
+
+# Numbers are interpreted as numbers, no matter whether the directive is
+# a plain .if or an .ifmake.
+.ifmake 0
+. error
+.endif
+.ifmake 1
.else
-.info No, targets cannot be added at parse time anymore.
+. error
.endif
+# A condition that consists of a variable expression only (without any
+# comparison operator) can be used with .if and the other .ifxxx directives.
+.ifmake ${:Ufirst}
+. info ok
+.else
+. error
+.endif
+
+
first second unmentioned late-target:
: $@
diff --git a/unit-tests/directive-include.exp b/unit-tests/directive-include.exp
index 3a75ce38728b..af56eefb2b88 100755
--- a/unit-tests/directive-include.exp
+++ b/unit-tests/directive-include.exp
@@ -2,4 +2,7 @@ CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null"
lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = !=
CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null"
lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = !=
-exit status 0
+make: "directive-include.mk" line 25: Could not find nonexistent.mk
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk
index d71b27ef2c63..120706cef8d7 100755
--- a/unit-tests/directive-include.mk
+++ b/unit-tests/directive-include.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-include.mk,v 1.3 2020/10/31 23:01:23 rillig Exp $
+# $NetBSD: directive-include.mk,v 1.4 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .include directive, which includes another file.
@@ -22,5 +22,10 @@
. error
.endif
+.include "nonexistent.mk"
+.include "/dev/null" # size 0
+# including a directory technically succeeds, but shouldn't.
+#.include "." # directory
+
all:
@:;
diff --git a/unit-tests/directive-info.exp b/unit-tests/directive-info.exp
index 39a9383953dd..971f417b706a 100644
--- a/unit-tests/directive-info.exp
+++ b/unit-tests/directive-info.exp
@@ -1 +1,14 @@
-exit status 0
+make: "directive-info.mk" line 7: begin .info tests
+make: "directive-info.mk" line 8: Unknown directive "inf"
+make: "directive-info.mk" line 9: Unknown directive "info"
+make: "directive-info.mk" line 10: message
+make: "directive-info.mk" line 11: indented message
+make: "directive-info.mk" line 12: Unknown directive "information"
+make: "directive-info.mk" line 13: message
+make: "directive-info.mk" line 18: Unknown directive "info"
+make: "directive-info.mk" line 19: Unknown directive "info"
+make: "directive-info.mk" line 22: Unknown directive "info-message"
+make: "directive-info.mk" line 23: no-target: no-source
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-info.mk b/unit-tests/directive-info.mk
index 3eb972ad7a0e..bbfc80ea0c9a 100644
--- a/unit-tests/directive-info.mk
+++ b/unit-tests/directive-info.mk
@@ -1,8 +1,27 @@
-# $NetBSD: directive-info.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-info.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $
#
# Tests for the .info directive.
# TODO: Implementation
+.info begin .info tests
+.inf # misspelled
+.info # oops: message should be "missing parameter"
+.info message
+.info indented message
+.information
+.information message # oops: misspelled
+.info.man: # not a message, but possibly a suffix rule
+
+# Even if lines would have trailing whitespace, this would be trimmed by
+# ParseGetLine.
+.info
+.info # comment
+
+.info: message # This is a dependency declaration.
+.info-message # This is an unknown directive.
+.info no-target: no-source # This is a .info directive, not a dependency.
+# See directive.mk for more tests of this kind.
+
all:
@:;
diff --git a/unit-tests/directive-sinclude.mk b/unit-tests/directive-sinclude.mk
index b1faa7d41e65..1932e7b3ba13 100755
--- a/unit-tests/directive-sinclude.mk
+++ b/unit-tests/directive-sinclude.mk
@@ -1,7 +1,11 @@
-# $NetBSD: directive-sinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $
+# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the .sinclude 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
diff --git a/unit-tests/directive-undef.exp b/unit-tests/directive-undef.exp
index 39a9383953dd..303d5a3e2a27 100644
--- a/unit-tests/directive-undef.exp
+++ b/unit-tests/directive-undef.exp
@@ -1 +1,4 @@
-exit status 0
+make: "directive-undef.mk" line 16: Unknown directive "unde"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-undef.mk b/unit-tests/directive-undef.mk
index aac110437686..c72513a1bf5a 100644
--- a/unit-tests/directive-undef.mk
+++ b/unit-tests/directive-undef.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-undef.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: directive-undef.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .undef directive.
@@ -13,5 +13,9 @@
. warning $1$2$3
.endif
+.unde # misspelled
+.undef # oops: missing argument
+.undefined # oops: misspelled
+
all:
@:;
diff --git a/unit-tests/directive-unexport-env.mk b/unit-tests/directive-unexport-env.mk
index 34d867d706ef..637286af0d6b 100644
--- a/unit-tests/directive-unexport-env.mk
+++ b/unit-tests/directive-unexport-env.mk
@@ -1,8 +1,12 @@
-# $NetBSD: directive-unexport-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-unexport-env.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .unexport-env directive.
# TODO: Implementation
+.unexport-en # oops: misspelled
+.unexport-env # ok
+.unexport-environment # oops: misspelled
+
all:
@:;
diff --git a/unit-tests/directive-unexport.exp b/unit-tests/directive-unexport.exp
index 263a2fedcc0b..72b24e7344fc 100644
--- a/unit-tests/directive-unexport.exp
+++ b/unit-tests/directive-unexport.exp
@@ -2,4 +2,7 @@ make: "directive-unexport.mk" line 14: UT_A=a UT_B=b UT_C=c
make: "directive-unexport.mk" line 15: UT_A UT_B UT_C
make: "directive-unexport.mk" line 23: UT_A=a UT_B=b UT_C=c
make: "directive-unexport.mk" line 24:
-exit status 0
+make: "directive-unexport.mk" line 26: Unknown directive "unexpor"
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-unexport.mk b/unit-tests/directive-unexport.mk
index b44932904805..3ba4a1b1f307 100644
--- a/unit-tests/directive-unexport.mk
+++ b/unit-tests/directive-unexport.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-unexport.mk,v 1.4 2020/10/30 23:54:42 sjg Exp $
+# $NetBSD: directive-unexport.mk,v 1.5 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .unexport directive.
@@ -23,5 +23,9 @@ UT_C= c
.info ${:!env|sort|grep '^UT_'!}
.info ${.MAKE.EXPORTED}
+.unexpor # misspelled
+.unexport # oops: missing argument
+.unexporting works # oops: misspelled
+
all:
@:;
diff --git a/unit-tests/directive-warning.exp b/unit-tests/directive-warning.exp
index 39a9383953dd..630285fd3612 100644
--- a/unit-tests/directive-warning.exp
+++ b/unit-tests/directive-warning.exp
@@ -1 +1,11 @@
-exit status 0
+make: "directive-warning.mk" line 7: Unknown directive "warn"
+make: "directive-warning.mk" line 8: Unknown directive "warn"
+make: "directive-warning.mk" line 9: Unknown directive "warnin"
+make: "directive-warning.mk" line 10: Unknown directive "warnin"
+make: "directive-warning.mk" line 11: Unknown directive "warning"
+make: "directive-warning.mk" line 12: warning: message
+make: "directive-warning.mk" line 13: Unknown directive "warnings"
+make: "directive-warning.mk" line 14: warning: messages
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-warning.mk b/unit-tests/directive-warning.mk
index e1e636e3ec9f..75560aa9e4df 100644
--- a/unit-tests/directive-warning.mk
+++ b/unit-tests/directive-warning.mk
@@ -1,8 +1,17 @@
-# $NetBSD: directive-warning.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-warning.mk,v 1.3 2020/11/03 17:17:31 rillig Exp $
#
# Tests for the .warning directive.
# TODO: Implementation
+.warn # misspelled
+.warn message # misspelled
+.warnin # misspelled
+.warnin message # misspelled
+.warning # oops: should be "missing argument"
+.warning message # ok
+.warnings # misspelled
+.warnings messages # oops
+
all:
@:;
diff --git a/unit-tests/directive.exp b/unit-tests/directive.exp
index 39a9383953dd..b93d768169ab 100644
--- a/unit-tests/directive.exp
+++ b/unit-tests/directive.exp
@@ -1 +1,12 @@
-exit status 0
+make: "directive.mk" line 9: Unknown directive "indented"
+make: "directive.mk" line 10: Unknown directive "indented"
+make: "directive.mk" line 11: Unknown directive "indented"
+make: "directive.mk" line 15: Unknown directive "info"
+Global:.info =
+Global:.info = value
+make: "directive.mk" line 26: := value
+Global:.MAKEFLAGS = -r -k -d v -d
+Global:.MAKEFLAGS = -r -k -d v -d 0
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive.mk b/unit-tests/directive.mk
index 8d01a49a34cf..d463ce4f009a 100644
--- a/unit-tests/directive.mk
+++ b/unit-tests/directive.mk
@@ -1,8 +1,35 @@
-# $NetBSD: directive.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $
#
# Tests for the preprocessing directives, such as .if or .info.
# TODO: Implementation
+# Unknown directives are correctly named in the error messages,
+# even if they are indented.
+.indented none
+. indented 2 spaces
+. indented tab
+
+# Directives must be written directly, not indirectly via variable
+# expressions.
+.${:Uinfo} directives cannot be indirect
+
+# There is no directive called '.target', therefore this is parsed as a
+# dependency declaration with 2 targets and 1 source.
+.target target: source
+
+# This looks ambiguous. It could be either an .info message or a variable
+# assignment. It is a variable assignment.
+.MAKEFLAGS: -dv
+.info:= value
+.info?= value # This is a variable assignment as well.
+.info := value # The space after the '.info' makes this
+ # a directive.
+.MAKEFLAGS: -d0
+
+# This is a dependency since directives must be given directly.
+# Not even the space after the '.info' can change anything about this.
+.${:Uinfo} : source
+
all:
@:;
diff --git a/unit-tests/directives.exp b/unit-tests/directives.exp
deleted file mode 100644
index 15dd9aa9e962..000000000000
--- a/unit-tests/directives.exp
+++ /dev/null
@@ -1,42 +0,0 @@
-make: "directives.mk" line 10: begin .export tests
-make: "directives.mk" line 11: Unknown directive "expor"
-make: "directives.mk" line 20: begin .export-env tests
-make: "directives.mk" line 30: begin .export-literal tests
-make: "directives.mk" line 40: begin .info tests
-make: "directives.mk" line 41: Unknown directive "inf"
-make: "directives.mk" line 42: Unknown directive "info"
-make: "directives.mk" line 43: message
-make: "directives.mk" line 44: indented message
-make: "directives.mk" line 45: Unknown directive "information"
-make: "directives.mk" line 46: message
-make: "directives.mk" line 50: begin .undef tests
-make: "directives.mk" line 51: Unknown directive "unde"
-make: "directives.mk" line 60: begin .unexport tests
-make: "directives.mk" line 61: Unknown directive "unexpor"
-make: "directives.mk" line 70: begin .unexport-env tests
-make: "directives.mk" line 80: begin .warning tests
-make: "directives.mk" line 81: Unknown directive "warn"
-make: "directives.mk" line 82: Unknown directive "warnin"
-make: "directives.mk" line 83: Unknown directive "warning"
-make: "directives.mk" line 84: warning: message
-make: "directives.mk" line 85: Unknown directive "warnings"
-make: "directives.mk" line 86: warning: messages
-make: "directives.mk" line 90: begin .elif misspellings tests, part 1
-make: "directives.mk" line 100: begin .elif misspellings tests, part 2
-make: "directives.mk" line 110: begin .elif misspellings tests, part 3
-make: "directives.mk" line 120: which branch is taken on misspelling after false?
-make: "directives.mk" line 127: else taken
-make: "directives.mk" line 130: which branch is taken on misspelling after true?
-make: "directives.mk" line 132: Unknown directive "elsif"
-make: "directives.mk" line 133: 1 taken
-make: "directives.mk" line 134: Unknown directive "elsif"
-make: "directives.mk" line 135: 2 taken
-make: "directives.mk" line 140: Unknown directive "indented"
-make: "directives.mk" line 141: Unknown directive "indented"
-make: "directives.mk" line 142: Unknown directive "indented"
-make: "directives.mk" line 143: Unknown directive "info"
-make: "directives.mk" line 150: Could not find nonexistent.mk
-make: "directives.mk" line 160: end of the tests
-make: Fatal errors encountered -- cannot continue
-make: stopped in unit-tests
-exit status 1
diff --git a/unit-tests/directives.mk b/unit-tests/directives.mk
deleted file mode 100644
index 436a5d35e4ec..000000000000
--- a/unit-tests/directives.mk
+++ /dev/null
@@ -1,163 +0,0 @@
-# $NetBSD: directives.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $
-#
-# Tests for parsing directives, in the same order as in the manual page.
-#
-# Each test group has 10 lines, to keep the line numbers in directives.exp
-# stable.
-#
-# no tests for .error since it exits immediately, see ParseMessage.
-
-.info begin .export tests
-.expor # misspelled
-.export # oops: missing argument
-.export VARNAME
-.exporting works # oops: misspelled
-
-
-
-
-
-.info begin .export-env tests
-.export-en # oops: misspelled
-.export-env
-.export-environment # oops: misspelled
-
-
-
-
-
-
-.info begin .export-literal tests
-.export-litera # oops: misspelled
-.export-literal # oops: missing argument
-.export-literal VARNAME
-.export-literally # oops: misspelled
-
-
-
-
-
-.info begin .info tests
-.inf # misspelled
-.info # oops: message should be "missing parameter"
-.info message
-.info indented message
-.information
-.information message # oops: misspelled
-.info.man: # not a message, but a suffix rule
-
-
-.info begin .undef tests
-.unde # misspelled
-.undef # oops: missing argument
-.undefined # oops: misspelled
-.undef VARNAME
-
-
-
-
-
-.info begin .unexport tests
-.unexpor # misspelled
-.unexport # oops: missing argument
-.unexport VARNAME # ok
-.unexporting works # oops: misspelled
-
-
-
-
-
-.info begin .unexport-env tests
-.unexport-en # misspelled
-.unexport-env # ok
-.unexport-environment # oops: misspelled
-
-
-
-
-
-
-.info begin .warning tests
-.warn # misspelled
-.warnin # misspelled
-.warning # oops: should be "missing argument"
-.warning message # ok
-.warnings # misspelled
-.warnings messages # oops
-
-
-
-.info begin .elif misspellings tests, part 1
-.if 1
-.elif 1 # ok
-.elsif 1 # oops: misspelled
-.elseif 1 # oops: misspelled
-.endif
-
-
-
-
-.info begin .elif misspellings tests, part 2
-.if 0
-.elif 0 # ok
-.elsif 0 # oops: misspelled
-.elseif 0 # oops: misspelled
-.endif
-
-
-
-
-.info begin .elif misspellings tests, part 3
-.if 0
-.elsif 0 # oops: misspelled
-.endif
-.if 0
-.elseif 0 # oops: misspelled
-.endif
-
-
-
-.info which branch is taken on misspelling after false?
-.if 0
-.elsif 1
-. info 1 taken
-.elsif 2
-. info 2 taken
-.else
-. info else taken
-.endif
-
-.info which branch is taken on misspelling after true?
-.if 1
-.elsif 1
-. info 1 taken
-.elsif 2
-. info 2 taken
-.else
-. info else taken
-.endif
-
-.indented none
-. indented 2 spaces
-. indented tab
-.${:Uinfo} directives cannot be indirect
-
-
-
-
-
-
-.include "nonexistent.mk"
-.include "/dev/null" # size 0
-# including a directory technically succeeds, but shouldn't.
-#.include "." # directory
-
-
-
-
-
-
-.info end of the tests
-
-all:
- @:
diff --git a/unit-tests/dollar.exp b/unit-tests/dollar.exp
index 496adc02f157..9b8c9d638a87 100644
--- a/unit-tests/dollar.exp
+++ b/unit-tests/dollar.exp
@@ -1,7 +1,7 @@
Printing dollar from literals and variables
-To survive the parser, a dollar character must be doubled.
+To survive the parser, a dollar sign must be doubled.
1 dollar literal => <single-quote-var-value>
1 dollar literal eol => <>
2 dollar literal => <$>
diff --git a/unit-tests/dollar.mk b/unit-tests/dollar.mk
index b7f7d9fa7c9e..8a06c5bf722b 100644
--- a/unit-tests/dollar.mk
+++ b/unit-tests/dollar.mk
@@ -1,6 +1,6 @@
-# $NetBSD: dollar.mk,v 1.3 2020/05/17 09:37:48 rillig Exp $
+# $NetBSD: dollar.mk,v 1.4 2020/11/03 18:21:36 rillig Exp $
#
-# Test the various places where a dollar character can appear and
+# Test the various places where a dollar sign can appear and
# see what happens. There are lots of surprises here.
#
@@ -30,7 +30,7 @@ ${:U'}= single-quote-var-value'
all:
$H 'Printing dollar from literals and variables'
- $C 'To survive the parser, a dollar character must be doubled.'
+ $C 'To survive the parser, a dollar sign must be doubled.'
$T '1 dollar literal' '$'
$T '1 dollar literal eol' ''$
$T '2 dollar literal' '$$'
diff --git a/unit-tests/envfirst.mk b/unit-tests/envfirst.mk
index fedcc0a75056..b5cfc4f5c578 100644
--- a/unit-tests/envfirst.mk
+++ b/unit-tests/envfirst.mk
@@ -1,7 +1,9 @@
-# $NetBSD: envfirst.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: envfirst.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# The -e option makes environment variables stronger than global variables.
+.MAKEFLAGS: -e
+
.if ${FROM_ENV} != value-from-env
. error ${FROM_ENV}
.endif
diff --git a/unit-tests/error.exp b/unit-tests/error.exp
index 8d0ebef89e8e..3adc099a4625 100644
--- a/unit-tests/error.exp
+++ b/unit-tests/error.exp
@@ -1,6 +1,6 @@
-make: "error.mk" line 3: just FYI
-make: "error.mk" line 4: warning: this could be serious
-make: "error.mk" line 5: this is fatal
+make: "error.mk" line 6: just FYI
+make: "error.mk" line 7: warning: this could be serious
+make: "error.mk" line 8: this is fatal
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/error.mk b/unit-tests/error.mk
index 6e2654dbea72..0029b3bc6aa9 100644
--- a/unit-tests/error.mk
+++ b/unit-tests/error.mk
@@ -1,10 +1,12 @@
-# $NetBSD: error.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $
+# $NetBSD: error.mk,v 1.3 2020/11/03 17:38:45 rillig Exp $
+#
+# Demonstrate that the .error directive exits immediately, without
+# continuing parsing until the end of the file.
.info just FYI
.warning this could be serious
.error this is fatal
+.info this is not reached because of the .error above
all:
-
-.info.html:
- @echo this should be ignored
+ : this is not reached because of the .error
diff --git a/unit-tests/escape.mk b/unit-tests/escape.mk
index 264b124bf1ad..8bdd3ad2ab49 100644
--- a/unit-tests/escape.mk
+++ b/unit-tests/escape.mk
@@ -1,4 +1,4 @@
-# $NetBSD: escape.mk,v 1.13 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig Exp $
#
# Test backslash escaping.
@@ -187,7 +187,7 @@ var-1bsnl-space: .PHONY __printvars \
# Backslash-newline in a command is retained.
#
# The "#" in "# second line without space" makes it a comment instead
-# of a syntax error if the preceding line is parsed incorretly.
+# of a syntax error if the preceding line is parsed incorrectly.
# The ":" in "third line':" makes it look like the start of a
# target instead of a syntax error if the first line is parsed incorrectly.
#
@@ -220,7 +220,7 @@ cmd-1bsnl-eof:
# XXX: This may differ from POSIX, but matches gmake.
#
# When make passes two backslashes to the shell, the shell will pass one
-# backslash to the echo commant.
+# backslash to the echo command.
#
all: cmd-2bsnl
cmd-2bsnl: .PHONY
diff --git a/unit-tests/forloop.exp b/unit-tests/forloop.exp
index 63b67c15bcc6..422711b41247 100644
--- a/unit-tests/forloop.exp
+++ b/unit-tests/forloop.exp
@@ -1,17 +1,17 @@
-x=one
-x="two and three"
-x=four
-x="five"
-x=-I/this
-x=-I"This or that"
-x=-Ithat
-x="-DTHIS=\"this and that\""
-cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
-newline-item=(a)
-a=one b="two and three"
-a=four b="five"
-a=ONE b="TWO AND THREE"
-a=FOUR b="FIVE"
+make: "forloop.mk" line 14: x=one
+make: "forloop.mk" line 14: x="two and three"
+make: "forloop.mk" line 14: x=four
+make: "forloop.mk" line 14: x="five"
+make: "forloop.mk" line 20: x=-I/this
+make: "forloop.mk" line 20: x=-I"This or that"
+make: "forloop.mk" line 20: x=-Ithat
+make: "forloop.mk" line 20: x="-DTHIS=\"this and that\""
+make: "forloop.mk" line 27: cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
+make: "forloop.mk" line 41: newline-item=(a)
+make: "forloop.mk" line 47: a=one b="two and three"
+make: "forloop.mk" line 47: a=four b="five"
+make: "forloop.mk" line 47: a=ONE b="TWO AND THREE"
+make: "forloop.mk" line 47: a=FOUR b="FIVE"
We expect an error next:
make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables
make: Fatal errors encountered -- cannot continue
diff --git a/unit-tests/forloop.mk b/unit-tests/forloop.mk
index 0162ffa27a07..cef05cbe4c61 100644
--- a/unit-tests/forloop.mk
+++ b/unit-tests/forloop.mk
@@ -1,4 +1,4 @@
-# $NetBSD: forloop.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: forloop.mk,v 1.7 2020/11/03 17:37:57 rillig Exp $
all: for-loop
@@ -11,40 +11,40 @@ XTRA_LIST= xtra
.else
. for x in ${LIST}
-X!= echo 'x=$x' >&2; echo
+. info x=$x
. endfor
CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\""
cfl=
. for x in ${CFL}
-X!= echo 'x=$x' >&2; echo
+. info x=$x
. if empty(cfl)
cfl= $x
. else
cfl+= $x
. endif
. endfor
-X!= echo 'cfl=${cfl}' >&2; echo
+. info cfl=${cfl}
. if ${cfl} != ${CFL}
-. error ${.newline}'${cfl}' != ${.newline}'${CFL}'
+. error ${.newline}${cfl} != ${.newline}${CFL}
. endif
. for a b in ${EMPTY}
-X!= echo 'a=$a b=$b' >&2; echo
+. info a=$a b=$b
. endfor
# Since at least 1993, iteration stops at the first newline.
# Back then, the .newline variable didn't exist, therefore it was unlikely
# that a newline ever occurred.
. for var in a${.newline}b${.newline}c
-X!= echo 'newline-item=('${var:Q}')' 1>&2; echo
+. info newline-item=(${var})
. endfor
.endif # for-fail
.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST}
-X!= echo 'a=$a b=$b' >&2; echo
+. info a=$a b=$b
.endfor
for-loop:
diff --git a/unit-tests/forsubst.mk b/unit-tests/forsubst.mk
index 79af3d9a45fb..9f293ab7f94e 100644
--- a/unit-tests/forsubst.mk
+++ b/unit-tests/forsubst.mk
@@ -1,4 +1,16 @@
-# $NetBSD: forsubst.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $
+# $NetBSD: forsubst.mk,v 1.3 2020/11/03 17:59:27 rillig Exp $
+#
+# The parser used to break dependency lines at ';' without regard for
+# substitution patterns. Back then, the first ';' was interpreted as the
+# separator between the dependency and its commands. This (perhaps coupled
+# with the new handling of .for variables in ${:U<value>...) caused
+# interesting results for lines like:
+#
+# .for file in ${LIST}
+# for-subst: ${file:S;^;${here}/;g}
+# .endfor
+#
+# See the commit to unit-tests/forsubst (without the .mk) from 2009-10-07.
all: for-subst
diff --git a/unit-tests/gnode-submake.exp b/unit-tests/gnode-submake.exp
new file mode 100644
index 000000000000..dbce13efaefa
--- /dev/null
+++ b/unit-tests/gnode-submake.exp
@@ -0,0 +1,11 @@
+#*** Input graph:
+# all, made UNMADE, type OP_DEPENDS, flags none
+# makeinfo, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
+# make-index, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
+# braces-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
+# braces-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
+# braces-no-dot-modifier, made UNMADE, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
+# parentheses-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
+# parentheses-no-dot, made UNMADE, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none
+
+exit status 0
diff --git a/unit-tests/gnode-submake.mk b/unit-tests/gnode-submake.mk
new file mode 100644
index 000000000000..40ff20276df9
--- /dev/null
+++ b/unit-tests/gnode-submake.mk
@@ -0,0 +1,42 @@
+# $NetBSD: gnode-submake.mk,v 1.1 2020/11/07 23:25:06 rillig Exp $
+#
+# Test whether OP_SUBMAKE is determined correctly. If it is, this node's
+# shell commands are connected to the make process via pipes, to coordinate
+# the number of running jobs.
+#
+# Determining whether a node is a sub-make node happens when the node is
+# parsed. This information is only used in parallel mode, but the result
+# from parsing is available in compat mode as well.
+
+.MAKEFLAGS: -n -dg1
+
+all: makeinfo make-index
+all: braces-dot braces-no-dot
+all: braces-no-dot-modifier
+all: parentheses-dot parentheses-no-dot
+
+makeinfo:
+ # The command contains the substring "make", but not as a whole word.
+ : makeinfo submake
+
+make-index:
+ # The command contains the word "make", therefore it is considered a
+ # possible sub-make. It isn't really, but that doesn't hurt.
+ : make-index
+
+braces-dot:
+ : ${.MAKE}
+
+braces-no-dot:
+ : ${MAKE}
+
+braces-no-dot-modifier:
+ # The command refers to MAKE, but not in its pure form. Therefore it
+ # is not considered a sub-make.
+ : ${MAKE:T}
+
+parentheses-dot:
+ : $(.MAKE)
+
+parentheses-no-dot:
+ : $(MAKE)
diff --git a/unit-tests/include-sub.mk b/unit-tests/include-sub.mk
index aeb7c3a69082..0b8dc77398ab 100644
--- a/unit-tests/include-sub.mk
+++ b/unit-tests/include-sub.mk
@@ -1,4 +1,4 @@
-# $NetBSD: include-sub.mk,v 1.6 2020/10/25 12:08:53 rillig Exp $
+# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $
.if ${.INCLUDEDFROMFILE} == "include-main.mk"
. info sub-before-ok
@@ -20,7 +20,7 @@
# To see the variable 'includes' in action:
#
# Breakpoints:
-# Parse_File at "PtrVector_Push(&includes, curFile)"
+# Parse_File at "Vector_Push(&includes)"
# ParseMessage at entry
# Watches:
# ((const IFile *[10])(*includes.items))
diff --git a/unit-tests/job-flags.exp b/unit-tests/job-flags.exp
new file mode 100644
index 000000000000..b0c81d8f7094
--- /dev/null
+++ b/unit-tests/job-flags.exp
@@ -0,0 +1,12 @@
+.BEGIN
+silent
+ignore
+true in ignore
+false in ignore
+*** [ignore] Error code 1 (ignored)
+false without indentation
+false space
+false tab
+*** [ignore-cmds] Error code 1 (ignored)
+.END
+exit status 0
diff --git a/unit-tests/job-flags.mk b/unit-tests/job-flags.mk
new file mode 100644
index 000000000000..d4c3b5d43643
--- /dev/null
+++ b/unit-tests/job-flags.mk
@@ -0,0 +1,32 @@
+# $NetBSD: job-flags.mk,v 1.2 2020/11/14 13:17:47 rillig Exp $
+#
+# Tests for Job.flags, which are controlled by special source dependencies
+# like .SILENT or .IGNORE, as well as the command line options -s or -i.
+
+.MAKEFLAGS: -j1
+
+all: silent .WAIT ignore .WAIT ignore-cmds
+
+.BEGIN:
+ @echo $@
+
+silent: .SILENT .PHONY
+ echo $@
+
+ignore: .IGNORE .PHONY
+ @echo $@
+ true in $@
+ false in $@
+ @echo 'Still there in $@'
+
+ignore-cmds: .PHONY
+ # This node is not marked .IGNORE; individual commands can be switched
+ # to ignore mode by prefixing them with a '-'.
+ -false without indentation
+ # This also works if the '-' is indented by a space or a tab.
+ # Leading whitespace is stripped off by ParseLine_ShellCommand.
+ -false space
+ -false tab
+
+.END:
+ @echo $@
diff --git a/unit-tests/moderrs.mk b/unit-tests/moderrs.mk
index 77ba39a3d57d..8fdcb496ee29 100644
--- a/unit-tests/moderrs.mk
+++ b/unit-tests/moderrs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: moderrs.mk,v 1.24 2020/11/01 14:36:25 rillig Exp $
+# $NetBSD: moderrs.mk,v 1.25 2020/11/15 20:20:58 rillig Exp $
#
# various modifier error tests
@@ -123,7 +123,7 @@ mod-regex-delimiter: print-header print-footer
# always set; some may be missing. Warn about these.
#
# Since there is no way to turn off this warning, the combination of
-# alternative matches and capturing groups is not widely used.
+# alternative matches and capturing groups is seldom used, if at all.
#
# A newly added modifier 'U' such as in :C,(a.)|(b.),\1\2,U might be added
# for treating undefined capturing groups as empty, but that would create a
diff --git a/unit-tests/modmisc.mk b/unit-tests/modmisc.mk
index f57e679cd4da..64a84ce0dadd 100644
--- a/unit-tests/modmisc.mk
+++ b/unit-tests/modmisc.mk
@@ -1,4 +1,4 @@
-# $NetBSD: modmisc.mk,v 1.49 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: modmisc.mk,v 1.51 2020/11/15 20:20:58 rillig Exp $
#
# miscellaneous modifier tests
@@ -48,9 +48,9 @@ emptyvar:
@echo @:${:@var@${var}@}
# The :U modifier turns even the "" variable into something that has a value.
-# The resulting variable is empty, but is still considered to contain a
-# single empty word. This word can be accessed by the :S and :C modifiers,
-# but not by the :@ modifier since it explicitly skips empty words.
+# The value of the resulting expression is empty, but is still considered to
+# contain a single empty word. This word can be accessed by the :S and :C
+# modifiers, but not by the :@ modifier since it explicitly skips empty words.
undefvar:
@echo S:${:U:S,^$,empty,}
@echo C:${:U:C,^$,empty,}
@@ -60,12 +60,12 @@ undefvar:
mod-quote:
@echo $@: new${.newline:Q}${.newline:Q}line
-# Cover the bmake_realloc in brk_string.
+# Cover the bmake_realloc in Str_Words.
mod-break-many-words:
@echo $@: ${UNDEF:U:range=500:[#]}
# To apply a modifier indirectly via another variable, the whole
-# modifier must be put into a single variable.
+# modifier must be put into a single variable expression.
.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
. warning unexpected
.endif
diff --git a/unit-tests/modts.mk b/unit-tests/modts.mk
index 67ba7eb97078..4776c5818ea5 100644
--- a/unit-tests/modts.mk
+++ b/unit-tests/modts.mk
@@ -1,7 +1,6 @@
-# $NetBSD: modts.mk,v 1.7 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $
-LIST= one two three
-LIST+= four five six
+LIST= one two three four five six
FU_mod-ts= a / b / cool
diff --git a/unit-tests/modword.mk b/unit-tests/modword.mk
index 43edf6a32b77..383c9dca975b 100644
--- a/unit-tests/modword.mk
+++ b/unit-tests/modword.mk
@@ -1,6 +1,7 @@
-# $NetBSD: modword.mk,v 1.4 2020/11/01 13:55:31 rillig Exp $
+# $NetBSD: modword.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
#
# Test behaviour of new :[] modifier
+# TODO: When was this modifier new?
all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw
diff --git a/unit-tests/objdir-writable.exp b/unit-tests/objdir-writable.exp
new file mode 100644
index 000000000000..f15cf914a7f5
--- /dev/null
+++ b/unit-tests/objdir-writable.exp
@@ -0,0 +1,5 @@
+make warning: OBJDIR/roobj: Permission denied.
+/tmp
+OBJDIR/roobj
+OBJDIR/roobj
+exit status 0
diff --git a/unit-tests/objdir-writable.mk b/unit-tests/objdir-writable.mk
new file mode 100644
index 000000000000..9fc1c69afb56
--- /dev/null
+++ b/unit-tests/objdir-writable.mk
@@ -0,0 +1,31 @@
+# $NetBSD: objdir-writable.mk,v 1.4 2020/11/14 07:36:00 sjg Exp $
+
+# test checking for writable objdir
+
+RO_OBJDIR?= ${TMPDIR:U/tmp}/roobj
+
+.if make(do-objdir)
+# this should succeed
+.OBJDIR: ${RO_OBJDIR}
+
+do-objdir:
+.else
+all: no-objdir ro-objdir explicit-objdir
+
+# make it now
+x!= echo; mkdir -p ${RO_OBJDIR}; chmod 555 ${RO_OBJDIR}
+
+.END: rm-objdir
+rm-objdir:
+ @rmdir ${RO_OBJDIR}
+
+no-objdir:
+ @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR
+
+ro-objdir:
+ @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no
+
+explicit-objdir:
+ @MAKEOBJDIR=/tmp ${.MAKE} -r -f ${MAKEFILE:tA} -C /tmp do-objdir -V .OBJDIR
+.endif
+
diff --git a/unit-tests/opt-chdir.exp b/unit-tests/opt-chdir.exp
index 39a9383953dd..d20f9eb2f07b 100644
--- a/unit-tests/opt-chdir.exp
+++ b/unit-tests/opt-chdir.exp
@@ -1 +1,6 @@
+make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long
+*** Error code 1 (ignored)
+cwd: /
+make: chdir /nonexistent: No such file or directory
+*** Error code 1 (ignored)
exit status 0
diff --git a/unit-tests/opt-chdir.mk b/unit-tests/opt-chdir.mk
index 8735fddbef9e..20241f02740e 100644
--- a/unit-tests/opt-chdir.mk
+++ b/unit-tests/opt-chdir.mk
@@ -1,8 +1,27 @@
-# $NetBSD: opt-chdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $
#
-# Tests for the -C command line option.
+# Tests for the -C command line option, which changes the directory at the
+# beginning.
+#
+# This option has been available since 2009-08-27.
+
+.MAKEFLAGS: -d0 # switch stdout to line-buffered
+
+all: chdir-filename-too-long
+all: chdir-root
+all: chdir-nonexistent
+
+# Try to overflow the internal buffer for .CURDIR, which is curdir.
+chdir-filename-too-long: .PHONY .IGNORE
+ # 5000 slashes, separated by dots: /./././.../././
+ @${MAKE} -C ${:U:range=5000:@@/@:ts.}
-# TODO: Implementation
+# Changing to another directory is possible via the command line.
+# In this test, it is the root directory since almost any other directory
+# is not guaranteed to exist on every platform.
+chdir-root: .PHONY .IGNORE
+ @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -C / -V 'cwd: $${.CURDIR}'
-all:
- @:;
+# Trying to change to a nonexistent directory exits immediately.
+chdir-nonexistent: .PHONY .IGNORE
+ @${MAKE} -C /nonexistent
diff --git a/unit-tests/opt-debug-jobs.exp b/unit-tests/opt-debug-jobs.exp
index 1214dce781b5..0431867756a1 100644
--- a/unit-tests/opt-debug-jobs.exp
+++ b/unit-tests/opt-debug-jobs.exp
@@ -10,7 +10,9 @@ echo ": variable"
echo ": 'single' and \"double\" quotes"
{ : 'single' and "double" quotes
} || exit $?
-Running all locally
+{ sleep 1
+} || exit $?
+Running all
Command: sh
JobExec(all): pid <pid> added to jobs table
job table @ job started
diff --git a/unit-tests/opt-debug-jobs.mk b/unit-tests/opt-debug-jobs.mk
index fb65d4f3356c..f3732df7e25d 100644
--- a/unit-tests/opt-debug-jobs.mk
+++ b/unit-tests/opt-debug-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-jobs.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
+# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $
#
# Tests for the -dj command line option, which adds debug logging about
# running jobs in multiple shells.
@@ -24,3 +24,10 @@ all:
# This allows to copy and paste the whole command, without having
# to unescape anything.
: 'single' and "double" quotes
+
+ # Avoid a race condition in the debug output. Without sleeping,
+ # it is not guaranteed that the two lines "exited/stopped" and
+ # "JobFinish" are output earlier than the stdout of the actual shell
+ # commands. The '@' prefix avoids that this final command gets into
+ # another race condition with the "exited/stopped" line.
+ @sleep 1
diff --git a/unit-tests/opt-ignore.mk b/unit-tests/opt-ignore.mk
index ff11c1e15c31..f776e8a27229 100644
--- a/unit-tests/opt-ignore.mk
+++ b/unit-tests/opt-ignore.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-ignore.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $
+# $NetBSD: opt-ignore.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -i command line option, which ignores the exit status of the
# shell commands, and just continues with the next command, even from the same
@@ -11,6 +11,7 @@
# failed?
.MAKEFLAGS: -d0 # switch stdout to being line-buffered
+.MAKEFLAGS: -i
all: dependency other
diff --git a/unit-tests/opt-keep-going.mk b/unit-tests/opt-keep-going.mk
index ec4adfa00e62..72f605246712 100644
--- a/unit-tests/opt-keep-going.mk
+++ b/unit-tests/opt-keep-going.mk
@@ -1,10 +1,11 @@
-# $NetBSD: opt-keep-going.mk,v 1.4 2020/10/18 18:12:42 rillig Exp $
+# $NetBSD: opt-keep-going.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -k command line option, which stops building a target as soon
# as an error is detected, but continues building the other, independent
# targets, as far as possible.
.MAKEFLAGS: -d0 # switch stdout to being line-buffered
+.MAKEFLAGS: -k
all: dependency other
diff --git a/unit-tests/opt-no-action.mk b/unit-tests/opt-no-action.mk
index 32b3b1564acb..91c05b2dc00f 100644
--- a/unit-tests/opt-no-action.mk
+++ b/unit-tests/opt-no-action.mk
@@ -1,9 +1,11 @@
-# $NetBSD: opt-no-action.mk,v 1.3 2020/08/19 05:25:26 rillig Exp $
+# $NetBSD: opt-no-action.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -n command line option, which runs almost no commands.
# It just outputs them, to be inspected by human readers.
# Only commands that are in a .MAKE target or prefixed by '+' are run.
+.MAKEFLAGS: -n
+
# This command cannot be prevented from being run since it is used at parse
# time, and any later variable assignments may depend on its result.
!= echo 'command during parsing' 1>&2; echo
diff --git a/unit-tests/opt-query.mk b/unit-tests/opt-query.mk
index 04e605991140..0a7d5219a8fe 100644
--- a/unit-tests/opt-query.mk
+++ b/unit-tests/opt-query.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-query.mk,v 1.3 2020/08/19 05:13:18 rillig Exp $
+# $NetBSD: opt-query.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -q command line option.
#
@@ -6,6 +6,8 @@
# None of the commands in the targets are run, not even those that are
# prefixed with '+'.
+.MAKEFLAGS: -q
+
# This command cannot be prevented from being run since it is used at parse
# time, and any later variable assignments may depend on its result.
!= echo 'command during parsing' 1>&2; echo
diff --git a/unit-tests/opt-touch-jobs.exp b/unit-tests/opt-touch-jobs.exp
new file mode 100644
index 000000000000..0cfde5135198
--- /dev/null
+++ b/unit-tests/opt-touch-jobs.exp
@@ -0,0 +1,4 @@
+: Making opt-touch-make.
+`opt-touch-join' is up to date.
+`opt-touch-use' is up to date.
+exit status 0
diff --git a/unit-tests/opt-touch-jobs.mk b/unit-tests/opt-touch-jobs.mk
new file mode 100644
index 000000000000..4402d76e9578
--- /dev/null
+++ b/unit-tests/opt-touch-jobs.mk
@@ -0,0 +1,30 @@
+# $NetBSD: opt-touch-jobs.mk,v 1.1 2020/11/14 15:35:20 rillig Exp $
+#
+# Tests for the -t command line option in jobs mode.
+
+.MAKEFLAGS: -j1
+.MAKEFLAGS: -t
+.MAKEFLAGS: opt-touch-phony
+.MAKEFLAGS: opt-touch-join
+.MAKEFLAGS: opt-touch-use
+.MAKEFLAGS: opt-touch-make
+
+opt-touch-phony: .PHONY
+ : Making $@.
+
+opt-touch-join: .JOIN
+ : Making $@.
+
+opt-touch-use: .USE
+ : Making use of $@.
+
+# Even though it is listed last, in the output it appears first.
+# This is because it is the only node that actually needs to be run.
+# The "is up to date" of the other nodes happens after all jobs have
+# finished, by Make_Run > MakePrintStatusList > MakePrintStatus.
+opt-touch-make: .MAKE
+ : Making $@.
+
+.END:
+ @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \
+ [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; }
diff --git a/unit-tests/opt-touch.exp b/unit-tests/opt-touch.exp
index 39a9383953dd..c9e6c7890f42 100644
--- a/unit-tests/opt-touch.exp
+++ b/unit-tests/opt-touch.exp
@@ -1 +1,4 @@
+`opt-touch-join' is up to date.
+`opt-touch-use' is up to date.
+: Making opt-touch-make.
exit status 0
diff --git a/unit-tests/opt-touch.mk b/unit-tests/opt-touch.mk
index 5093c5cad6ac..defb7c59e0ad 100644
--- a/unit-tests/opt-touch.mk
+++ b/unit-tests/opt-touch.mk
@@ -1,8 +1,21 @@
-# $NetBSD: opt-touch.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-touch.mk,v 1.4 2020/11/14 14:13:09 rillig Exp $
#
# Tests for the -t command line option.
-# TODO: Implementation
+.MAKEFLAGS: -t opt-touch-phony opt-touch-join opt-touch-use opt-touch-make
-all:
- @:;
+opt-touch-phony: .PHONY
+ : Making $@.
+
+opt-touch-join: .JOIN
+ : Making $@.
+
+opt-touch-use: .USE
+ : Making use of $@.
+
+opt-touch-make: .MAKE
+ : Making $@.
+
+.END:
+ @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \
+ [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; }
diff --git a/unit-tests/opt-var-expanded.mk b/unit-tests/opt-var-expanded.mk
index 0b4088a82082..f7203374b8cc 100644
--- a/unit-tests/opt-var-expanded.mk
+++ b/unit-tests/opt-var-expanded.mk
@@ -1,6 +1,8 @@
-# $NetBSD: opt-var-expanded.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+# $NetBSD: opt-var-expanded.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -v command line option.
+.MAKEFLAGS: -v VAR -v VALUE
+
VAR= other ${VALUE} $$$$
VALUE= value
diff --git a/unit-tests/opt-var-literal.mk b/unit-tests/opt-var-literal.mk
index a819e7537105..d236e389d259 100644
--- a/unit-tests/opt-var-literal.mk
+++ b/unit-tests/opt-var-literal.mk
@@ -1,6 +1,8 @@
-# $NetBSD: opt-var-literal.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+# $NetBSD: opt-var-literal.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -V command line option.
+.MAKEFLAGS: -V VAR -V VALUE
+
VAR= other ${VALUE} $$$$
VALUE= value
diff --git a/unit-tests/opt-warnings-as-errors.exp b/unit-tests/opt-warnings-as-errors.exp
index bd54bb673f08..278c9469607f 100644
--- a/unit-tests/opt-warnings-as-errors.exp
+++ b/unit-tests/opt-warnings-as-errors.exp
@@ -1,6 +1,6 @@
-make: "opt-warnings-as-errors.mk" line 5: warning: message 1
+make: "opt-warnings-as-errors.mk" line 7: warning: message 1
make: parsing warnings being treated as errors
-make: "opt-warnings-as-errors.mk" line 6: warning: message 2
+make: "opt-warnings-as-errors.mk" line 8: warning: message 2
parsing continues
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/opt-warnings-as-errors.mk b/unit-tests/opt-warnings-as-errors.mk
index 905753410db0..2302dd4794b5 100644
--- a/unit-tests/opt-warnings-as-errors.mk
+++ b/unit-tests/opt-warnings-as-errors.mk
@@ -1,7 +1,9 @@
-# $NetBSD: opt-warnings-as-errors.mk,v 1.3 2020/08/23 14:28:04 rillig Exp $
+# $NetBSD: opt-warnings-as-errors.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for the -W command line option, which turns warnings into errors.
+.MAKEFLAGS: -W
+
.warning message 1
.warning message 2
diff --git a/unit-tests/opt.exp b/unit-tests/opt.exp
index 39a9383953dd..11344ae0c359 100644
--- a/unit-tests/opt.exp
+++ b/unit-tests/opt.exp
@@ -1 +1,22 @@
+make -r -f /dev/null -V MAKEFLAGS
+ -r -k -d 0
+
+make -:
+usage: make [-BeikNnqrSstWwX]
+ [-C directory] [-D variable] [-d flags] [-f makefile]
+ [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]
+ [-V variable] [-v variable] [variable=value] [target ...]
+*** Error code 2 (ignored)
+
+make -r -f /dev/null -- -VAR=value -f /dev/null
+make: don't know how to make -f (continuing)
+`/dev/null' is up to date.
+
+make -?
+usage: make [-BeikNnqrSstWwX]
+ [-C directory] [-D variable] [-d flags] [-f makefile]
+ [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]
+ [-V variable] [-v variable] [variable=value] [target ...]
+*** Error code 2 (ignored)
+
exit status 0
diff --git a/unit-tests/opt.mk b/unit-tests/opt.mk
index eae430965df7..0931a66d3d15 100644
--- a/unit-tests/opt.mk
+++ b/unit-tests/opt.mk
@@ -1,8 +1,28 @@
-# $NetBSD: opt.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt.mk,v 1.6 2020/11/18 01:06:59 sjg Exp $
#
# Tests for the command line options.
-# TODO: Implementation
+.MAKEFLAGS: -d0 # make stdout line-buffered
-all:
- @:;
+all: .IGNORE
+ # The options from the top-level make are passed to the sub-makes via
+ # the environment variable MAKEFLAGS. This is where the " -r -k -d 0"
+ # comes from. See MainParseArg.
+ ${MAKE} -r -f /dev/null -V MAKEFLAGS
+ @echo
+
+ # Just to see how the custom argument parsing code reacts to a syntax
+ # error. The colon is used in the options string, marking an option
+ # that takes arguments. It is not an option by itself, though.
+ ${MAKE} -:
+ @echo
+
+ # See whether a '--' stops handling of command line options, like in
+ # standard getopt programs. Yes, it does, and it treats the
+ # second '-f' as a target to be created.
+ ${MAKE} -r -f /dev/null -- -VAR=value -f /dev/null
+ @echo
+
+ # This is the normal way to print the usage of a command.
+ ${MAKE} -?
+ @echo
diff --git a/unit-tests/order.mk b/unit-tests/order.mk
index f90b627d9e5f..9b94016c1841 100644
--- a/unit-tests/order.mk
+++ b/unit-tests/order.mk
@@ -1,10 +1,12 @@
-# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $
+# $NetBSD: order.mk,v 1.2 2020/11/09 20:50:56 rillig Exp $
# Test that .ORDER is handled correctly.
# The explicit dependency the.o: the.h will make us examine the.h
# the .ORDER will prevent us building it immediately,
# we should then examine the.c rather than stop.
+.MAKEFLAGS: -j1
+
all: the.o
.ORDER: the.c the.h
diff --git a/unit-tests/recursive.exp b/unit-tests/recursive.exp
index bb5db75a474c..36cd1c989532 100644
--- a/unit-tests/recursive.exp
+++ b/unit-tests/recursive.exp
@@ -1,5 +1,5 @@
-make: "recursive.mk" line 34: Unclosed variable "MISSING_PAREN"
-make: "recursive.mk" line 35: Unclosed variable "MISSING_BRACE"
+make: "recursive.mk" line 36: Unclosed variable "MISSING_PAREN"
+make: "recursive.mk" line 37: Unclosed variable "MISSING_BRACE"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk
index 6e5d8a2ca8b5..73a8409fe030 100644
--- a/unit-tests/recursive.mk
+++ b/unit-tests/recursive.mk
@@ -1,4 +1,4 @@
-# $NetBSD: recursive.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: recursive.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# In -dL mode, a variable may get expanded before it makes sense.
# This would stop make from doing anything since the "recursive" error
@@ -11,6 +11,8 @@
# Seen in pkgsrc/x11/libXfixes, and probably many more package that use
# GNU Automake.
+.MAKEFLAGS: -dL
+
AM_V_lt= ${am__v_lt_${V}}
am__v_lt_= ${am__v_lt_${AM_DEFAULT_VERBOSITY}}
am__v_lt_0= --silent
diff --git a/unit-tests/sh-leading-at.exp b/unit-tests/sh-leading-at.exp
index 5ffa84690a40..8347fda085f7 100644
--- a/unit-tests/sh-leading-at.exp
+++ b/unit-tests/sh-leading-at.exp
@@ -2,4 +2,5 @@ ok
space after @
echo 'echoed'
echoed
+3
exit status 0
diff --git a/unit-tests/sh-leading-at.mk b/unit-tests/sh-leading-at.mk
index 19a6e59e4e6a..9f98005ec088 100644
--- a/unit-tests/sh-leading-at.mk
+++ b/unit-tests/sh-leading-at.mk
@@ -1,10 +1,18 @@
-# $NetBSD: sh-leading-at.mk,v 1.3 2020/08/22 09:16:08 rillig Exp $
+# $NetBSD: sh-leading-at.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
#
# Tests for shell commands preceded by an '@', to suppress printing
# the command to stdout.
+#
+# See also:
+# .SILENT
+# depsrc-silent.mk
+# opt-silent.mk
all:
@
@echo 'ok'
@ echo 'space after @'
echo 'echoed'
+ # The leading '@' can be repeated.
+ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+ @@@echo '3'
diff --git a/unit-tests/sh-leading-hyphen.mk b/unit-tests/sh-leading-hyphen.mk
index 94be43495afb..d760abb9afdd 100644
--- a/unit-tests/sh-leading-hyphen.mk
+++ b/unit-tests/sh-leading-hyphen.mk
@@ -1,7 +1,12 @@
-# $NetBSD: sh-leading-hyphen.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: sh-leading-hyphen.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
# Tests for shell commands preceded by a '-', to ignore the exit status of
# the command line.
+#
+# See also:
+# .IGNORE
+# depsrc-ignore.mk
+# opt-ignore.mk
# TODO: Implementation
diff --git a/unit-tests/sh-leading-plus.mk b/unit-tests/sh-leading-plus.mk
index 75279d7d57fd..ff57b4a38a7d 100644
--- a/unit-tests/sh-leading-plus.mk
+++ b/unit-tests/sh-leading-plus.mk
@@ -1,8 +1,10 @@
-# $NetBSD: sh-leading-plus.mk,v 1.3 2020/08/23 14:46:33 rillig Exp $
+# $NetBSD: sh-leading-plus.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $
#
# Tests for shell commands preceded by a '+', to run them even if
# the command line option -n is given.
+.MAKEFLAGS: -n
+
all:
@echo 'this command is not run'
@+echo 'this command is run'
diff --git a/unit-tests/sh-meta-chars.mk b/unit-tests/sh-meta-chars.mk
index 126ca2ceb118..a029c73a855c 100644
--- a/unit-tests/sh-meta-chars.mk
+++ b/unit-tests/sh-meta-chars.mk
@@ -1,9 +1,13 @@
-# $NetBSD: sh-meta-chars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: sh-meta-chars.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
#
# Tests for running shell commands that contain meta-characters.
#
# These meta-characters decide whether the command is run by the shell
-# or executed directly via execv. See Cmd_Exec for details.
+# or executed directly via execv, but only in compatibility mode, not
+# in jobs mode, and only if MAKE_NATIVE is defined during compilation.
+#
+# See also:
+# Compat_RunCommand, useShell
# TODO: Implementation
diff --git a/unit-tests/suff-self.exp b/unit-tests/suff-self.exp
new file mode 100644
index 000000000000..4e70762209a2
--- /dev/null
+++ b/unit-tests/suff-self.exp
@@ -0,0 +1,3 @@
+make: Graph cycles through suff-self.suff
+`all' not remade because of errors.
+exit status 0
diff --git a/unit-tests/suff-self.mk b/unit-tests/suff-self.mk
new file mode 100644
index 000000000000..8874cc5157e8
--- /dev/null
+++ b/unit-tests/suff-self.mk
@@ -0,0 +1,11 @@
+# $NetBSD: suff-self.mk,v 1.1 2020/11/16 15:12:16 rillig Exp $
+#
+# See what happens if someone defines a self-referencing suffix
+# transformation rule.
+
+.SUFFIXES: .suff
+
+.suff.suff:
+ : Making ${.TARGET} out of ${.IMPSRC}.
+
+all: suff-self.suff
diff --git a/unit-tests/use-inference.mk b/unit-tests/use-inference.mk
index b0e5017bc6fb..cde3c772edaa 100644
--- a/unit-tests/use-inference.mk
+++ b/unit-tests/use-inference.mk
@@ -1,4 +1,4 @@
-# $NetBSD: use-inference.mk,v 1.1 2020/08/09 16:32:28 rillig Exp $
+# $NetBSD: use-inference.mk,v 1.2 2020/11/05 00:41:04 rillig Exp $
#
# Demonstrate that .USE rules do not have an effect on inference rules.
# At least not in the special case where the inference rule does not
@@ -33,3 +33,6 @@ use-inference.from: # assume it exists
# This is strange since make definitely knows about the .from.to suffix
# inference rule. But it seems to ignore it, maybe because it doesn't
# have any associated commands.
+
+# XXX: Despite the error message "don't know how to make", the exit status
+# is 0. This is inconsistent.
diff --git a/unit-tests/var-class-local.exp b/unit-tests/var-class-local.exp
index f1595c6810fb..db85b47cae06 100644
--- a/unit-tests/var-class-local.exp
+++ b/unit-tests/var-class-local.exp
@@ -1,2 +1,5 @@
+: Making var-class-local.c out of nothing.
+: Making var-class-local.o from var-class-local.c.
+: Making basename "var-class-local.o" in "." from "var-class-local.c" in ".".
: all overwritten
exit status 0
diff --git a/unit-tests/var-class-local.mk b/unit-tests/var-class-local.mk
index 68696ea854af..f9d56e539ff0 100644
--- a/unit-tests/var-class-local.mk
+++ b/unit-tests/var-class-local.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-class-local.mk,v 1.4 2020/10/25 09:46:25 rillig Exp $
+# $NetBSD: var-class-local.mk,v 1.5 2020/11/05 18:08:39 rillig Exp $
#
# Tests for target-local variables, such as ${.TARGET} or $@.
@@ -25,6 +25,20 @@
.endif
all:
+
+.SUFFIXES: .c .o
+
+var-class-local.c:
+ : Making ${.TARGET} out of nothing.
+
+.c.o:
+ : Making ${.TARGET} from ${.IMPSRC}.
+
+ # The local variables @F, @D, <F, <D are legacy forms.
+ # See the manual page for details.
+ : Making basename "${@F}" in "${@D}" from "${<F}" in "${<D}".
+
+all: var-class-local.o
# The ::= modifier overwrites the .TARGET variable in the node
# 'all', not in the global scope. This can be seen with the -dv
# option, looking for "all:@ = overwritten".
diff --git a/unit-tests/var-op-assign.exp b/unit-tests/var-op-assign.exp
index 0e9e2d211a5f..0562ffcda733 100644
--- a/unit-tests/var-op-assign.exp
+++ b/unit-tests/var-op-assign.exp
@@ -1,6 +1,6 @@
this will be evaluated later
-make: "var-op-assign.mk" line 52: Need an operator
-make: "var-op-assign.mk" line 86: Parsing still continues until here.
+make: "var-op-assign.mk" line 59: Need an operator
+make: "var-op-assign.mk" line 93: Parsing still continues until here.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/var-op-assign.mk b/unit-tests/var-op-assign.mk
index c988d4141b83..3bcc3de0ba0e 100644
--- a/unit-tests/var-op-assign.mk
+++ b/unit-tests/var-op-assign.mk
@@ -1,11 +1,12 @@
-# $NetBSD: var-op-assign.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: var-op-assign.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the = variable assignment operator, which overwrites an existing
# variable or creates it.
# This is a simple variable assignment.
# To the left of the assignment operator '=' there is the variable name,
-# and to the right is the variable value.
+# and to the right is the variable value. The variable value is stored as-is,
+# it is not expanded in any way.
#
VAR= value
@@ -36,9 +37,15 @@ VAR= new value and \# some $$ special characters # comment
# The variable value may contain references to other variables.
# In this example, the reference is to the variable with the empty name,
-# which always expands to an empty string. This alone would not produce
-# any side-effects, therefore the variable has a :!...! modifier that
-# executes a shell command.
+# which is never defined.
+#
+# This alone would not produce any side-effects, therefore the variable has
+# a :!...! modifier that executes a shell command. The :!...! modifier turns
+# an undefined expression into a defined one, see ApplyModifier_ShellCommand,
+# the call to ApplyModifiersState_Define.
+#
+# Since the right-hand side of a '=' assignment is not expanded at the time
+# when the variable is defined, the first command is not run at all.
VAR= ${:! echo 'not yet evaluated' 1>&2 !}
VAR= ${:! echo 'this will be evaluated later' 1>&2 !}
@@ -48,7 +55,7 @@ VAR= ${:! echo 'this will be evaluated later' 1>&2 !}
.endif
# In a variable assignment, the variable name must consist of a single word.
-#
+# The following line therefore generates a parse error.
VARIABLE NAME= variable value
# But if the whitespace appears inside parentheses or braces, everything is
diff --git a/unit-tests/var-op-expand.exp b/unit-tests/var-op-expand.exp
index 39a9383953dd..8ccbbd5ae92d 100644
--- a/unit-tests/var-op-expand.exp
+++ b/unit-tests/var-op-expand.exp
@@ -1 +1,10 @@
+Var_Parse: ${UNDEF} with VARE_WANTRES
+Global:VAR_ASSIGN_ = undef value
+Var_Parse: ${UNDEF} with VARE_WANTRES
+Var_Parse: ${UNDEF} with VARE_WANTRES
+Global:VAR_SUBST_${UNDEF} =
+Var_Parse: ${UNDEF} with VARE_WANTRES
+Global:VAR_SUBST_ = undef value
+Global:.MAKEFLAGS = -r -k -d v -d
+Global:.MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk
index 07c5fb647759..0b5ddbbc0386 100644
--- a/unit-tests/var-op-expand.mk
+++ b/unit-tests/var-op-expand.mk
@@ -1,9 +1,27 @@
-# $NetBSD: var-op-expand.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: var-op-expand.mk,v 1.4 2020/11/08 14:00:52 rillig Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
# TODO: Implementation
+# XXX: edge case: When a variable name refers to an undefined variable, the
+# behavior differs between the '=' and the ':=' assignment operators.
+# This bug exists since var.c 1.42 from 2000-05-11.
+#
+# The '=' operator expands the undefined variable to an empty string, thus
+# assigning to VAR_ASSIGN_. In the name of variables to be set, it should
+# really be forbidden to refer to undefined variables.
+#
+# The ':=' operator expands the variable name twice. In one of these
+# expansions, the undefined variable expression is preserved (controlled by
+# preserveUndefined in VarAssign_EvalSubst), in the other expansion it expands
+# to an empty string. This way, 2 variables are created using a single
+# variable assignment. It's magic. :-/
+.MAKEFLAGS: -dv
+VAR_ASSIGN_${UNDEF}= undef value
+VAR_SUBST_${UNDEF}:= undef value
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/var-op-shell.exp b/unit-tests/var-op-shell.exp
index 39a9383953dd..caea85ab5daa 100644
--- a/unit-tests/var-op-shell.exp
+++ b/unit-tests/var-op-shell.exp
@@ -1 +1,7 @@
+make: "var-op-shell.mk" line 28: warning: "echo "failed"; false" returned non-zero status
+make: "var-op-shell.mk" line 34: warning: "false" returned non-zero status
+make: "var-op-shell.mk" line 59: warning: "kill -14 $$" exited on a signal
+/bin/no/such/command: not found
+make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status
+stderr
exit status 0
diff --git a/unit-tests/var-op-shell.mk b/unit-tests/var-op-shell.mk
index 83580a89e6c2..7b52513e1131 100644
--- a/unit-tests/var-op-shell.mk
+++ b/unit-tests/var-op-shell.mk
@@ -1,9 +1,84 @@
-# $NetBSD: var-op-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: var-op-shell.mk,v 1.3 2020/11/09 20:39:46 rillig Exp $
#
# Tests for the != variable assignment operator, which runs its right-hand
# side through the shell.
-# TODO: Implementation
+# The variable OUTPUT gets the output from running the shell command.
+OUTPUT!= echo "success"'ful'
+.if ${OUTPUT} != "successful"
+. error
+.endif
+
+# Since 2014-08-20, the output of the shell command may be empty.
+#
+# On 1996-05-29, when the '!=' assignment operator and Cmd_Exec were added,
+# an empty output produced the error message "Couldn't read shell's output
+# for \"%s\"".
+#
+# The error message is still there but reserved for technical errors.
+# It may be possible to trigger the error message by killing the shell after
+# reading part of its output.
+OUTPUT!= true
+.if ${OUTPUT} != ""
+. error
+.endif
+
+# The output of a shell command that failed is processed nevertheless.
+# TODO: Make this an error in lint mode.
+OUTPUT!= echo "failed"; false
+.if ${OUTPUT} != "failed"
+. error
+.endif
+
+# A command with empty output may fail as well.
+OUTPUT!= false
+.if ${OUTPUT} != ""
+. error
+.endif
+
+# In the output of the command, each newline is replaced with a space.
+# Except for the very last one, which is discarded.
+OUTPUT!= echo "line 1"; echo "line 2"
+.if ${OUTPUT} != "line 1 line 2"
+. error
+.endif
+
+# A failing command in the middle results in the exit status 0, which in the
+# end means that the whole sequence of commands succeeded.
+OUTPUT!= echo "before"; false; echo "after"
+.if ${OUTPUT} != "before after"
+. error
+.endif
+
+# NB: The signal number must be numeric since some shells (which ones?) don't
+# accept symbolic signal names. 14 is typically SIGALRM.
+#
+# XXX: The number of the signal is not mentioned in the warning since that
+# would have been difficult to implement; currently the errfmt is a format
+# string containing a single %s conversion.
+OUTPUT!= kill -14 $$$$
+.if ${OUTPUT} != ""
+. error
+.endif
+
+# A nonexistent command produces a non-zero exit status.
+OUTPUT!= /bin/no/such/command
+.if ${OUTPUT} != ""
+. error
+.endif
+
+# The output from the shell's stderr is not captured, it just passes through.
+OUTPUT!= echo "stdout"; echo "stderr" 1>&2
+.if ${OUTPUT} != "stdout"
+. error
+.endif
+
+# The 8 dollar signs end up as 4 dollar signs when expanded. The shell sees
+# the command "echo '$$$$'". The 4 dollar signs are stored in OUTPUT, and
+# when that variable is expanded, they expand to 2 dollar signs.
+OUTPUT!= echo '$$$$$$$$'
+.if ${OUTPUT} != "\$\$"
+. error
+.endif
all:
- @:;
diff --git a/unit-tests/var-op-sunsh.mk b/unit-tests/var-op-sunsh.mk
index efef19bf1567..0e16b2b42d34 100644
--- a/unit-tests/var-op-sunsh.mk
+++ b/unit-tests/var-op-sunsh.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-op-sunsh.mk,v 1.5 2020/10/04 08:32:52 rillig Exp $
+# $NetBSD: var-op-sunsh.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the :sh= variable assignment operator, which runs its right-hand
# side through the shell. It is a seldom-used alternative to the !=
@@ -118,5 +118,7 @@ VAR :sh += echo two
. error ${VAR}
.endif
+# TODO: test VAR:sh!=command
+
all:
@:;
diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp
index 474a46ee1b5c..06c8b590e1b1 100644
--- a/unit-tests/vardebug.exp
+++ b/unit-tests/vardebug.exp
@@ -77,7 +77,7 @@ make: "vardebug.mk" line 44: Malformed conditional (${:Uvariable:unknown})
Var_Parse: ${UNDEFINED} with VARE_UNDEFERR|VARE_WANTRES
make: "vardebug.mk" line 53: Malformed conditional (${UNDEFINED})
Global:delete .SHELL (not found)
-Command:.SHELL = /bin/sh
+Command:.SHELL = </path/to/shell>
Command:.SHELL = overwritten ignored (read-only)
Global:.MAKEFLAGS = -r -k -d v -d
Global:.MAKEFLAGS = -r -k -d v -d 0
diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk
index e2d53129a932..aced1a0554d1 100644
--- a/unit-tests/varmisc.mk
+++ b/unit-tests/varmisc.mk
@@ -1,5 +1,5 @@
-# $Id: varmisc.mk,v 1.20 2020/10/26 17:43:57 sjg Exp $
-# $NetBSD: varmisc.mk,v 1.26 2020/10/24 08:50:17 rillig Exp $
+# $Id: varmisc.mk,v 1.21 2020/11/11 23:08:50 sjg Exp $
+# $NetBSD: varmisc.mk,v 1.28 2020/11/07 00:07:02 rillig Exp $
#
# Miscellaneous variable tests.
@@ -88,7 +88,7 @@ VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif
-# begin .MAKE.SAVE_DOLLARS; see Var_Set_with_flags and s2Boolean.
+# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean.
SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off
SD_4_DOLLARS= $$$$
diff --git a/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp
index 39a9383953dd..7f61cc426305 100644
--- a/unit-tests/varmod-defined.exp
+++ b/unit-tests/varmod-defined.exp
@@ -1 +1,23 @@
+Global:8_DOLLARS = $$$$$$$$
+Global:VAR =
+Var_Parse: ${8_DOLLARS} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Global:VAR = $$$$$$$$
+Var_Parse: ${VAR:D${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Applying ${VAR:D...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Var_Parse: ${8_DOLLARS}} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Result of ${VAR:D${8_DOLLARS}} is "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Global:VAR = $$$$$$$$
+Var_Parse: ${VAR:@var@${8_DOLLARS}@} with VARE_WANTRES|VARE_KEEP_DOLLAR
+Applying ${VAR:@...} to "$$$$$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Modifier part: "var"
+Modifier part: "${8_DOLLARS}"
+ModifyWords: split "$$$$$$$$" into 1 words
+Global:var = $$$$$$$$
+Var_Parse: ${8_DOLLARS} with VARE_WANTRES
+ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$"
+Global:delete var
+Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (VARE_WANTRES|VARE_KEEP_DOLLAR, none, none)
+Global:VAR = $$$$
+Global:.MAKEFLAGS = -r -k -d v -d
+Global:.MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/unit-tests/varmod-defined.mk b/unit-tests/varmod-defined.mk
index a722ebf666e6..59b9d79d754b 100644
--- a/unit-tests/varmod-defined.mk
+++ b/unit-tests/varmod-defined.mk
@@ -1,8 +1,10 @@
-# $NetBSD: varmod-defined.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-defined.mk,v 1.9 2020/11/12 00:40:55 rillig Exp $
#
# Tests for the :D variable modifier, which returns the given string
# if the variable is defined. It is closely related to the :U modifier.
+.MAKE.SAVE_DOLLARS= yes
+
DEF= defined
.undef UNDEF
@@ -85,5 +87,19 @@ DEF= defined
# TODO: Add more tests for parsing the plain text part, to cover each branch
# of ApplyModifier_Defined.
+# The :D and :U modifiers behave differently from the :@var@ modifier in
+# that they preserve dollars in a ':=' assignment. This is because
+# ApplyModifier_Defined passes the eflags unmodified to Var_Parse, unlike
+# ApplyModifier_Loop, which uses ParseModifierPart, which in turn removes
+# VARE_KEEP_DOLLAR from eflags.
+#
+# XXX: This inconsistency is documented nowhere.
+.MAKEFLAGS: -dv
+8_DOLLARS= $$$$$$$$
+VAR:= ${8_DOLLARS}
+VAR:= ${VAR:D${8_DOLLARS}}
+VAR:= ${VAR:@var@${8_DOLLARS}@}
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/varmod-exclam-shell.mk b/unit-tests/varmod-exclam-shell.mk
index eaad8275805f..14b3d2510b72 100644
--- a/unit-tests/varmod-exclam-shell.mk
+++ b/unit-tests/varmod-exclam-shell.mk
@@ -1,27 +1,36 @@
-# $NetBSD: varmod-exclam-shell.mk,v 1.3 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-exclam-shell.mk,v 1.4 2020/11/03 18:42:33 rillig Exp $
#
-# Tests for the :!cmd! variable modifier.
+# Tests for the :!cmd! variable modifier, which evaluates the modifier
+# argument, independent of the value or the name of the original variable.
.if ${:!echo hello | tr 'l' 'l'!} != "hello"
-. warning unexpected
+. error
.endif
# The output is truncated at the first null byte.
# Cmd_Exec returns only a string pointer without length information.
+# Truncating the output is not necessarily intended but may also be a side
+# effect from the implementation. Having null bytes in the output of a
+# shell command is so unusual that it doesn't matter in practice.
.if ${:!echo hello | tr 'l' '\0'!} != "he"
-. warning unexpected
+. error
.endif
+# The newline at the end of the output is stripped.
.if ${:!echo!} != ""
-. warning A newline at the end of the output must be stripped.
+. error
.endif
+# Only the final newline of the output is stripped. All other newlines are
+# converted to spaces.
.if ${:!echo;echo!} != " "
-. warning Only a single newline at the end of the output is stripped.
+. error
.endif
+# Each newline in the output is converted to a space, except for the newline
+# at the end of the output, which is stripped.
.if ${:!echo;echo;echo;echo!} != " "
-. warning Other newlines in the output are converted to spaces.
+. error
.endif
all:
diff --git a/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp
index ac1527a11c9e..75518c08117f 100644
--- a/unit-tests/varmod-ifelse.exp
+++ b/unit-tests/varmod-ifelse.exp
@@ -3,6 +3,14 @@ make: "varmod-ifelse.mk" line 27: Malformed conditional (${${:Uvariable expressi
make: Bad conditional expression ` == ""' in == ""?bad-assign:bad-assign
make: Bad conditional expression ` == ""' in == ""?bad-cond:bad-cond
make: "varmod-ifelse.mk" line 44: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond})
+make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no
+make: "varmod-ifelse.mk" line 66: Malformed conditional (${1 == == 2:?yes:no} != "")
+CondParser_Eval: "${1 == == 2:?yes:no}" != ""
+CondParser_Eval: 1 == == 2
+lhs = 1.000000, rhs = 0.000000, op = ==
+make: Bad conditional expression `1 == == 2' in 1 == == 2?yes:no
+lhs = "", rhs = "", op = !=
+make: "varmod-ifelse.mk" line 92: warning: Oops, the parse error should have been propagated.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk
index 8bd67195282c..ea94dc875e4d 100644
--- a/unit-tests/varmod-ifelse.mk
+++ b/unit-tests/varmod-ifelse.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-ifelse.mk,v 1.5 2020/10/23 14:24:51 rillig Exp $
+# $NetBSD: varmod-ifelse.mk,v 1.6 2020/11/12 00:29:55 rillig Exp $
#
# Tests for the ${cond:?then:else} variable modifier, which evaluates either
# the then-expression or the else-expression, depending on the condition.
@@ -57,5 +57,41 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign}
. error
.endif
+# This line generates 2 error messages. The first comes from evaluating the
+# malformed conditional "1 == == 2", which is reported as "Bad conditional
+# expression" by ApplyModifier_IfElse. The variable expression containing that
+# conditional therefore returns a parse error from Var_Parse, and this parse
+# error propagates to CondEvalExpression, where the "Malformed conditional"
+# comes from.
+.if ${1 == == 2:?yes:no} != ""
+. error
+.else
+. error
+.endif
+
+# If the "Bad conditional expression" appears in a quoted string literal, the
+# error message "Malformed conditional" is not printed, leaving only the "Bad
+# conditional expression".
+#
+# XXX: The left-hand side is enclosed in quotes. This results in Var_Parse
+# being called without VARE_UNDEFERR being set. When ApplyModifier_IfElse
+# returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the
+# value of the variable expression is still undefined. CondParser_String is
+# then supposed to do proper error handling, but since varUndefined is local
+# to var.c, it cannot distinguish this return value from an ordinary empty
+# string. The left-hand side of the comparison is therefore just an empty
+# string, which is obviously equal to the empty string on the right-hand side.
+#
+# XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The
+# condition should be detected as being malformed before any comparison is
+# done since there is no well-formed comparison in the condition at all.
+.MAKEFLAGS: -dc
+.if "${1 == == 2:?yes:no}" != ""
+. error
+.else
+. warning Oops, the parse error should have been propagated.
+.endif
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp
index 497de68df82c..66cfd6f51e16 100644
--- a/unit-tests/varmod-loop.exp
+++ b/unit-tests/varmod-loop.exp
@@ -1,3 +1,11 @@
+ParseReadLine (117): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$'
+CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
+lhs = "$$$$ $$$$ $$$$", rhs = "$$$$ $$$$ $$$$", op = !=
+ParseReadLine (122): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}'
+CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
+lhs = "$$ $$$$ $$$$", rhs = "$$ $$$$ $$$$", op = !=
+ParseReadLine (147): '.MAKEFLAGS: -d0'
+ParseDoDependency(.MAKEFLAGS: -d0)
:+one+ +two+ +three+:
:x1y x2y x3y:
:x1y x2y x3y:
diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk
index a3fbbfceae8d..654c449d7bfa 100644
--- a/unit-tests/varmod-loop.mk
+++ b/unit-tests/varmod-loop.mk
@@ -1,7 +1,9 @@
-# $NetBSD: varmod-loop.mk,v 1.5 2020/10/31 12:34:03 rillig Exp $
+# $NetBSD: varmod-loop.mk,v 1.8 2020/11/12 00:40:55 rillig Exp $
#
# Tests for the :@var@...${var}...@ variable modifier.
+.MAKE.SAVE_DOLLARS= yes
+
all: mod-loop-varname
all: mod-loop-resolve
all: mod-loop-varname-dollar
@@ -57,7 +59,7 @@ mod-loop-varname-dollar:
@echo $@:${1 2 3:L:@v$$@($v)@:Q}.
@echo $@:${1 2 3:L:@v$$$@($v)@:Q}.
-# Demonstrate that it is possible to generate dollar characters using the
+# Demonstrate that it is possible to generate dollar signs using the
# :@ modifier.
#
# These are edge cases that could have resulted in a parse error as well
@@ -100,3 +102,46 @@ mod-loop-dollar:
.if defined(var)
. error
.endif
+
+# Assignment using the ':=' operator, combined with the :@var@ modifier
+#
+8_DOLLARS= $$$$$$$$
+# This string literal is written with 8 dollars, and this is saved as the
+# variable value. But as soon as this value is evaluated, it goes through
+# Var_Subst, which replaces each '$$' with a single '$'. This could be
+# prevented by VARE_KEEP_DOLLAR, but that flag is usually removed before
+# expanding subexpressions. See ApplyModifier_Loop and ParseModifierPart
+# for examples.
+#
+.MAKEFLAGS: -dcp
+USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
+.if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
+. error
+.endif
+#
+SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
+# The ':=' assignment operator evaluates the variable value using the flag
+# VARE_KEEP_DOLLAR, which means that some dollar signs are preserved, but not
+# all. The dollar signs in the top-level expression and in the indirect
+# ${8_DOLLARS} are preserved.
+#
+# The variable modifier :@var@ does not preserve the dollar signs though, no
+# matter in which context it is evaluated. What happens in detail is:
+# First, the modifier part "${8_DOLLARS}" is parsed without expanding it.
+# Next, each word of the value is expanded on its own, and at this moment
+# in ApplyModifier_Loop, the VARE_KEEP_DOLLAR flag is not passed down to
+# ModifyWords, resulting in "$$$$" for the first word of USE_8_DOLLARS.
+#
+# The remaining words of USE_8_DOLLARS are not affected by any variable
+# modifier and are thus expanded with the flag VARE_KEEP_DOLLAR in action.
+# The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value
+# "$$$$ $$$$$$$$ $$$$$$$$".
+#
+# The variable expression in the condition then expands this raw stored value
+# once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no
+# longer take place since they had only been active during the evaluation of
+# the variable assignment.
+.if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
+. error
+.endif
+.MAKEFLAGS: -d0
diff --git a/unit-tests/varmod-match.mk b/unit-tests/varmod-match.mk
index 5e16a9cc8bf1..9b56fb451eda 100644
--- a/unit-tests/varmod-match.mk
+++ b/unit-tests/varmod-match.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-match.mk,v 1.5 2020/09/13 05:36:26 rillig Exp $
+# $NetBSD: varmod-match.mk,v 1.6 2020/11/15 18:33:41 rillig Exp $
#
# Tests for the :M variable modifier, which filters words that match the
# given pattern.
@@ -51,5 +51,10 @@ ${:U*}= asterisk
. error
.endif
+# TODO: ${VAR:M(((}}}}
+# TODO: ${VAR:M{{{)))}
+# TODO: ${VAR:M${UNBALANCED}}
+# TODO: ${VAR:M${:U(((\}\}\}}}
+
all:
@:;
diff --git a/unit-tests/varmod-order-shuffle.mk b/unit-tests/varmod-order-shuffle.mk
index 4ad057e0a810..185141b6c4a5 100644
--- a/unit-tests/varmod-order-shuffle.mk
+++ b/unit-tests/varmod-order-shuffle.mk
@@ -1,8 +1,10 @@
-# $NetBSD: varmod-order-shuffle.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-order-shuffle.mk,v 1.6 2020/11/09 20:16:33 rillig Exp $
#
# Tests for the :Ox variable modifier, which returns the words of the
# variable, shuffled.
#
+# The variable modifier :Ox is available since 2005-06-01.
+#
# As of 2020-08-16, make uses random(3) seeded by the current time in seconds.
# This makes the random numbers completely predictable since there is no other
# part of make that uses random numbers.
diff --git a/unit-tests/varmod-shell.exp b/unit-tests/varmod-shell.exp
index 39a9383953dd..9aef0c9e5acc 100644
--- a/unit-tests/varmod-shell.exp
+++ b/unit-tests/varmod-shell.exp
@@ -1 +1,3 @@
+make: "echo word; false" returned non-zero status
+make: "echo word; false" returned non-zero status
exit status 0
diff --git a/unit-tests/varmod-shell.mk b/unit-tests/varmod-shell.mk
index 052968004f1b..db82e302f2a8 100644
--- a/unit-tests/varmod-shell.mk
+++ b/unit-tests/varmod-shell.mk
@@ -1,9 +1,35 @@
-# $NetBSD: varmod-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-shell.mk,v 1.5 2020/11/17 20:11:02 rillig Exp $
#
# Tests for the :sh variable modifier, which runs the shell command
# given by the variable value and returns its output.
+#
+# This modifier has been added on 2000-04-29.
+#
+# See also:
+# ApplyModifier_ShellCommand
# TODO: Implementation
+# The command to be run is enclosed between exclamation marks.
+# The previous value of the expression is irrelevant for this modifier.
+# The :!cmd! modifier turns an undefined expression into a defined one.
+.if ${:!echo word!} != "word"
+. error
+.endif
+
+# If the command exits with non-zero, an error message is printed.
+# XXX: Processing continues as usual though.
+#
+# Between 2000-04-29 and 2020-11-17, the error message mentioned the previous
+# value of the expression (which is usually an empty string) instead of the
+# command that was executed. It's strange that such a simple bug could
+# survive such a long time.
+.if ${:!echo word; false!} != "word"
+. error
+.endif
+.if ${:Uprevious value:!echo word; false!} != "word"
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/varmod-subst.exp b/unit-tests/varmod-subst.exp
index e752fb8058a8..3122c17b1ed3 100644
--- a/unit-tests/varmod-subst.exp
+++ b/unit-tests/varmod-subst.exp
@@ -9,28 +9,39 @@ mod-subst-delimiter:
1 two 3 horizontal tabulator
1 two 3 space
1 two 3 exclamation mark
-1 two 3 double quotes
-1 two 3 hash
-1 two 3 dollar
-1 two 3 percent
+1 two 3 quotation mark
+1 two 3 number sign
+1 two 3 dollar sign
+1 two 3 percent sign
+1 two 3 ampersand
1 two 3 apostrophe
-1 two 3 opening parenthesis
-1 two 3 closing parenthesis
+1 two 3 left parenthesis
+1 two 3 right parenthesis
+1 two 3 asterisk
+1 two 3 plus sign
+1 two 3 comma
+1 two 3 hyphen-minus
+1 two 3 full stop
+1 two 3 solidus
1 two 3 digit
1 two 3 colon
-1 two 3 less than sign
-1 two 3 equal sign
-1 two 3 greater than sign
+1 two 3 semicolon
+1 two 3 less-than sign
+1 two 3 equals sign
+1 two 3 greater-than sign
1 two 3 question mark
-1 two 3 at
-1 two 3 letter
-1 two 3 opening bracket
-1 two 3 backslash
-1 two 3 closing bracket
-1 two 3 caret
-1 two 3 opening brace
+1 two 3 commercial at
+1 two 3 capital letter
+1 two 3 left square bracket
+1 two 3 reverse solidus
+1 two 3 right square bracket
+1 two 3 circumflex accent
+1 two 3 low line
+1 two 3 grave accent
+1 two 3 small letter
+1 two 3 left curly bracket
1 two 3 vertical line
-1 two 3 closing brace
+1 two 3 right curly bracket
1 two 3 tilde
mod-subst-chain:
A B c.
diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk
index 5fc657ea10ee..3c3ee673c07a 100644
--- a/unit-tests/varmod-subst.mk
+++ b/unit-tests/varmod-subst.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-subst.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-subst.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the :S,from,to, variable modifier.
@@ -8,55 +8,72 @@ all: mod-subst-chain
all: mod-subst-dollar
WORDS= sequences of letters
+
.if ${WORDS:S,,,} != ${WORDS}
. warning The empty pattern matches something.
.endif
+
.if ${WORDS:S,e,*,1} != "s*quences of letters"
. warning The :S modifier flag '1' is not applied exactly once.
.endif
+
.if ${WORDS:S,f,*,1} != "sequences o* letters"
. warning The :S modifier flag '1' is only applied to the first word,\
not to the first occurrence.
.endif
+
.if ${WORDS:S,e,*,} != "s*quences of l*tters"
. warning The :S modifier does not replace every first match per word.
.endif
+
.if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs"
. warning The :S modifier flag 'g' does not replace every occurrence.
.endif
+
.if ${WORDS:S,^sequ,occurr,} != "occurrences of letters"
. warning The :S modifier fails for a short match anchored at the start.
.endif
+
.if ${WORDS:S,^of,with,} != "sequences with letters"
. warning The :S modifier fails for an exact match anchored at the start.
.endif
+
.if ${WORDS:S,^office,does not match,} != ${WORDS}
. warning The :S modifier matches a too long pattern anchored at the start.
.endif
+
.if ${WORDS:S,f$,r,} != "sequences or letters"
. warning The :S modifier fails for a short match anchored at the end.
.endif
+
.if ${WORDS:S,s$,,} != "sequence of letter"
. warning The :S modifier fails to replace one occurrence per word.
.endif
+
.if ${WORDS:S,of$,,} != "sequences letters"
. warning The :S modifier fails for an exact match anchored at the end.
.endif
+
.if ${WORDS:S,eof$,,} != ${WORDS}
. warning The :S modifier matches a too long pattern anchored at the end.
.endif
+
.if ${WORDS:S,^of$,,} != "sequences letters"
. warning The :S modifier does not match a word anchored at both ends.
.endif
+
.if ${WORDS:S,^o$,,} != ${WORDS}
. warning The :S modifier matches a prefix anchored at both ends.
.endif
+
.if ${WORDS:S,^f$,,} != ${WORDS}
. warning The :S modifier matches a suffix anchored at both ends.
.endif
+
.if ${WORDS:S,^eof$,,} != ${WORDS}
. warning The :S modifier matches a too long prefix anchored at both ends.
.endif
+
.if ${WORDS:S,^office$,,} != ${WORDS}
. warning The :S modifier matches a too long suffix anchored at both ends.
.endif
@@ -78,30 +95,41 @@ mod-subst-delimiter:
@echo ${:U1 2 3:S 2 two :Q} horizontal tabulator
@echo ${:U1 2 3:S 2 two :Q} space
@echo ${:U1 2 3:S!2!two!:Q} exclamation mark
- @echo ${:U1 2 3:S"2"two":Q} double quotes
+ @echo ${:U1 2 3:S"2"two":Q} quotation mark
# In shell command lines, the hash does not need to be escaped.
# It needs to be escaped in variable assignment lines though.
- @echo ${:U1 2 3:S#2#two#:Q} hash
- @echo ${:U1 2 3:S$2$two$:Q} dollar
- @echo ${:U1 2 3:S%2%two%:Q} percent
+ @echo ${:U1 2 3:S#2#two#:Q} number sign
+ @echo ${:U1 2 3:S$2$two$:Q} dollar sign
+ @echo ${:U1 2 3:S%2%two%:Q} percent sign
+ @echo ${:U1 2 3:S&2&two&:Q} ampersand
@echo ${:U1 2 3:S'2'two':Q} apostrophe
- @echo ${:U1 2 3:S(2(two(:Q} opening parenthesis
- @echo ${:U1 2 3:S)2)two):Q} closing parenthesis
+ @echo ${:U1 2 3:S(2(two(:Q} left parenthesis
+ @echo ${:U1 2 3:S)2)two):Q} right parenthesis
+ @echo ${:U1 2 3:S*2*two*:Q} asterisk
+ @echo ${:U1 2 3:S+2+two+:Q} plus sign
+ @echo ${:U1 2 3:S,2,two,:Q} comma
+ @echo ${:U1 2 3:S-2-two-:Q} hyphen-minus
+ @echo ${:U1 2 3:S.2.two.:Q} full stop
+ @echo ${:U1 2 3:S/2/two/:Q} solidus
@echo ${:U1 2 3:S121two1:Q} digit
@echo ${:U1 2 3:S:2:two::Q} colon
- @echo ${:U1 2 3:S<2<two<:Q} less than sign
- @echo ${:U1 2 3:S=2=two=:Q} equal sign
- @echo ${:U1 2 3:S>2>two>:Q} greater than sign
+ @echo ${:U1 2 3:S;2;two;:Q} semicolon
+ @echo ${:U1 2 3:S<2<two<:Q} less-than sign
+ @echo ${:U1 2 3:S=2=two=:Q} equals sign
+ @echo ${:U1 2 3:S>2>two>:Q} greater-than sign
@echo ${:U1 2 3:S?2?two?:Q} question mark
- @echo ${:U1 2 3:S@2@two@:Q} at
- @echo ${:U1 2 3:Sa2atwoa:Q} letter
- @echo ${:U1 2 3:S[2[two[:Q} opening bracket
- @echo ${:U1 2 3:S\2\two\:Q} backslash
- @echo ${:U1 2 3:S]2]two]:Q} closing bracket
- @echo ${:U1 2 3:S^2^two^:Q} caret
- @echo ${:U1 2 3:S{2{two{:Q} opening brace
+ @echo ${:U1 2 3:S@2@two@:Q} commercial at
+ @echo ${:U1 2 3:SA2AtwoA:Q} capital letter
+ @echo ${:U1 2 3:S[2[two[:Q} left square bracket
+ @echo ${:U1 2 3:S\2\two\:Q} reverse solidus
+ @echo ${:U1 2 3:S]2]two]:Q} right square bracket
+ @echo ${:U1 2 3:S^2^two^:Q} circumflex accent
+ @echo ${:U1 2 3:S_2_two_:Q} low line
+ @echo ${:U1 2 3:S`2`two`:Q} grave accent
+ @echo ${:U1 2 3:Sa2atwoa:Q} small letter
+ @echo ${:U1 2 3:S{2{two{:Q} left curly bracket
@echo ${:U1 2 3:S|2|two|:Q} vertical line
- @echo ${:U1 2 3:S}2}two}:Q} closing brace
+ @echo ${:U1 2 3:S}2}two}:Q} right curly bracket
@echo ${:U1 2 3:S~2~two~:Q} tilde
# The :S and :C modifiers can be chained without a separating ':'.
@@ -121,7 +149,7 @@ mod-subst-chain:
# modifiers with the matching modifiers.
@echo ${:Uvalue:S,a,x,i}.
-# No matter how many dollar characters there are, they all get merged
+# No matter how many dollar signs there are, they all get merged
# into a single dollar by the :S modifier.
#
# As of 2020-08-09, this is because ParseModifierPart sees a '$' and
@@ -145,7 +173,7 @@ mod-subst-dollar:
@echo $@:${:U40:S,^,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$,:Q}:
# This generates no dollar at all:
@echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}:
-# Here is an alternative way to generate dollar characters.
+# Here is an alternative way to generate dollar signs.
# It's unexpectedly complicated though.
@echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}:
# In modifiers, dollars are escaped using the backslash, not using another
diff --git a/unit-tests/varmod-to-abs.exp b/unit-tests/varmod-to-abs.exp
index 39a9383953dd..426b4d39744f 100644
--- a/unit-tests/varmod-to-abs.exp
+++ b/unit-tests/varmod-to-abs.exp
@@ -1 +1,5 @@
+make: "varmod-to-abs.mk" line 18: does-not-exist.c
+make: "varmod-to-abs.mk" line 19: does-not-exist.c
+cached_realpath: varmod-to-abs.mk -> varmod-to-abs.mk
+make: "varmod-to-abs.mk" line 23: varmod-to-abs.mk
exit status 0
diff --git a/unit-tests/varmod-to-abs.mk b/unit-tests/varmod-to-abs.mk
index 7a74e89088e5..7f23318487e3 100644
--- a/unit-tests/varmod-to-abs.mk
+++ b/unit-tests/varmod-to-abs.mk
@@ -1,9 +1,28 @@
-# $NetBSD: varmod-to-abs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-to-abs.mk,v 1.5 2020/11/15 05:48:17 rillig Exp $
#
# Tests for the :tA variable modifier, which returns the absolute path for
# each of the words in the variable value.
# TODO: Implementation
+# Between 2016-06-03 and 2020-11-14, it was possible to trick the :tA modifier
+# into resolving completely unrelated absolute paths by defining a global
+# variable with the same name as the path that is to be resolved. There were
+# a few restrictions though: The "redirected" path had to start with a slash,
+# and it had to exist (see ModifyWord_Realpath).
+#
+# This unintended behavior was caused by cached_realpath using a GNode for
+# keeping the cache, just like the GNode for global variables.
+.MAKEFLAGS: -dd
+does-not-exist.c= /dev/null
+.info ${does-not-exist.c:L:tA}
+.info ${does-not-exist.c:L:tA}
+
+# The output of the following line is modified by the global _SED_CMDS in
+# unit-tests/Makefile. See the .rawout file for the truth.
+.info ${MAKEFILE:tA}
+
+.MAKEFLAGS: -d0
+
all:
@:;
diff --git a/unit-tests/varmod-to-lower.mk b/unit-tests/varmod-to-lower.mk
index 6ab4af740fae..19d3406054b7 100644
--- a/unit-tests/varmod-to-lower.mk
+++ b/unit-tests/varmod-to-lower.mk
@@ -1,7 +1,9 @@
-# $NetBSD: varmod-to-lower.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-to-lower.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the :tl variable modifier, which returns the words in the
# variable value, converted to lowercase.
+#
+# TODO: What about non-ASCII characters? ISO-8859-1, UTF-8?
.if ${:UUPPER:tl} != "upper"
. error
diff --git a/unit-tests/varmod-to-separator.mk b/unit-tests/varmod-to-separator.mk
index 89aa3d978bee..08c6126ecc68 100644
--- a/unit-tests/varmod-to-separator.mk
+++ b/unit-tests/varmod-to-separator.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-to-separator.mk,v 1.6 2020/11/01 14:36:25 rillig Exp $
+# $NetBSD: varmod-to-separator.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the :ts variable modifier, which joins the words of the variable
# using an arbitrary character as word separator.
@@ -166,4 +166,10 @@ WORDS= one two three four five six
. info This line is not reached.
.endif
+# TODO: This modifier used to accept decimal numbers as well, in the form
+# ':ts\120'. When has this been changed to octal, and what happens now
+# for ':ts\90' ('Z' in decimal ASCII, undefined in octal)?
+
+# TODO: :ts\x1F600
+
all:
diff --git a/unit-tests/varmod-undefined.mk b/unit-tests/varmod-undefined.mk
index 1beaa763716b..e06fc73244ab 100644
--- a/unit-tests/varmod-undefined.mk
+++ b/unit-tests/varmod-undefined.mk
@@ -1,9 +1,10 @@
-# $NetBSD: varmod-undefined.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-undefined.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
#
# Tests for the :U variable modifier, which returns the given string
# if the variable is undefined.
#
# See also:
+# directive-for.mk
# varmod-defined.mk
# The pattern ${:Uword} is heavily used when expanding .for loops.
diff --git a/unit-tests/varmod.exp b/unit-tests/varmod.exp
index e4257bb0b596..a80979e1410d 100644
--- a/unit-tests/varmod.exp
+++ b/unit-tests/varmod.exp
@@ -1,6 +1,8 @@
make: "varmod.mk" line 42: To escape a dollar, use \$, not $$, at "$$:L} != """
make: "varmod.mk" line 42: Invalid variable name ':', at "$:L} != """
make: "varmod.mk" line 47: Dollar followed by nothing
+make: "varmod.mk" line 56: Missing delimiter ':' after modifier "P"
+make: "varmod.mk" line 57: Unknown directive "error"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk
index 0a2a4c52c29b..b496bdd206a2 100644
--- a/unit-tests/varmod.mk
+++ b/unit-tests/varmod.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod.mk,v 1.3 2020/09/13 07:42:20 rillig Exp $
+# $NetBSD: varmod.mk,v 1.4 2020/11/02 17:30:22 rillig Exp $
#
# Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback.
@@ -48,4 +48,13 @@ DOLLAR2= ${:U\$}
. error
.endif
+# The variable modifier :P does not fall back to the SysV modifier.
+# Therefore the modifier :P=RE generates a parse error.
+# XXX: The .error should not be reached since the variable expression is
+# malformed.
+VAR= STOP
+.if ${VAR:P=RE} != "STORE"
+. error
+.endif
+
all: # nothing
diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp
index 704d4863d4e9..f219515444cf 100755
--- a/unit-tests/varname-dot-shell.exp
+++ b/unit-tests/varname-dot-shell.exp
@@ -1,6 +1,6 @@
ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}'
Global:ORIG_SHELL =
-Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_ASSIGN
+Var_Parse: ${.SHELL} with VARE_WANTRES|VARE_KEEP_DOLLAR
Global:delete .SHELL (not found)
Command:.SHELL = (details omitted)
Global:ORIG_SHELL = (details omitted)
diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp
index ba465cc3eff2..24c596e1c568 100644
--- a/unit-tests/varname-empty.exp
+++ b/unit-tests/varname-empty.exp
@@ -1,5 +1,5 @@
Var_Set("${:U}", "cmdline-u", ...) name expands to empty string - ignored
-Var_Set("", "cmline-plain", ...) name expands to empty string - ignored
+Var_Set("", "cmdline-plain", ...) name expands to empty string - ignored
Var_Set("", "default", ...) name expands to empty string - ignored
Var_Set("", "assigned", ...) name expands to empty string - ignored
Var_Set("", "appended", ...) name expands to empty string - ignored
diff --git a/unit-tests/varname-makefile.exp b/unit-tests/varname-makefile.exp
index 39a9383953dd..67919c400193 100755
--- a/unit-tests/varname-makefile.exp
+++ b/unit-tests/varname-makefile.exp
@@ -1 +1,2 @@
+: In the end, MAKEFILE is /dev/null.
exit status 0
diff --git a/unit-tests/varname-makefile.mk b/unit-tests/varname-makefile.mk
index 785fe9301df2..641c53de9cf8 100755
--- a/unit-tests/varname-makefile.mk
+++ b/unit-tests/varname-makefile.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-makefile.mk,v 1.2 2020/09/05 06:25:38 rillig Exp $
+# $NetBSD: varname-makefile.mk,v 1.3 2020/11/09 22:36:44 rillig Exp $
#
# Tests for the special MAKEFILE variable, which contains the current
# makefile from the -f command line option.
@@ -41,4 +41,9 @@ MAKEFILE= overwritten
.endif
all:
- @:;
+ # MAKEFILE is the file that appeared last in the command line.
+ : In the end, MAKEFILE is ${MAKEFILE}.
+
+# Additional makefiles can be added while reading a makefile. They will be
+# read in order.
+.MAKEFLAGS: -f /dev/null
diff --git a/unit-tests/varname-vpath.exp b/unit-tests/varname-vpath.exp
index 39a9383953dd..bf7a3036e99d 100644
--- a/unit-tests/varname-vpath.exp
+++ b/unit-tests/varname-vpath.exp
@@ -1 +1,12 @@
+CondParser_Eval: !defined(TEST_MAIN)
+CondParser_Eval: exists(file-in-subdirectory)
+exists(file-in-subdirectory) result is ""
+CondParser_Eval: exists(file2-in-subdirectory)
+exists(file2-in-subdirectory) result is ""
+CondParser_Eval: exists(file-in-subdirectory)
+exists(file-in-subdirectory) result is "varname-vpath.dir/file-in-subdirectory"
+: yes 1
+CondParser_Eval: exists(file2-in-subdirectory)
+exists(file2-in-subdirectory) result is "varname-vpath.dir2/file2-in-subdirectory"
+: yes 2
exit status 0
diff --git a/unit-tests/varname-vpath.mk b/unit-tests/varname-vpath.mk
index 8924647a5072..65fe9945c202 100644
--- a/unit-tests/varname-vpath.mk
+++ b/unit-tests/varname-vpath.mk
@@ -1,8 +1,42 @@
-# $NetBSD: varname-vpath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-vpath.mk,v 1.3 2020/11/10 00:19:19 rillig Exp $
#
-# Tests for the special VPATH variable.
+# Tests for the special VPATH variable, which is an obsolete way of
+# specifying a colon-separated search path. This search path is not active
+# when the makefiles are read, but only later when the shell commands are run.
+#
+# Instead of the VPATH, better use the -I option or the special target .PATH.
+
+.if !defined(TEST_MAIN)
+
+all: .SILENT
+ rm -rf varname-vpath.dir
+ mkdir varname-vpath.dir
+ touch varname-vpath.dir/file-in-subdirectory
+ rm -rf varname-vpath.dir2
+ mkdir varname-vpath.dir2
+ touch varname-vpath.dir2/file2-in-subdirectory
+
+ TEST_MAIN=yes VPATH=varname-vpath.dir:varname-vpath.dir2 \
+ ${MAKE} -f ${MAKEFILE} -dc
-# TODO: Implementation
+ rm -r varname-vpath.dir
+ rm -r varname-vpath.dir2
+
+.else
+
+# The VPATH variable does not take effect at parse time.
+# It is evaluated only once, between reading the makefiles and making the
+# targets. Therefore it could also be an ordinary variable, it doesn't need
+# to be an environment variable or a command line variable.
+. if exists(file-in-subdirectory)
+. error
+. endif
+. if exists(file2-in-subdirectory)
+. error
+. endif
all:
- @:;
+ : ${exists(file-in-subdirectory):L:?yes 1:no 1}
+ : ${exists(file2-in-subdirectory):L:?yes 2:no 2}
+
+.endif
diff --git a/unit-tests/varname.exp b/unit-tests/varname.exp
index 39a9383953dd..93962d7fb7b7 100644
--- a/unit-tests/varname.exp
+++ b/unit-tests/varname.exp
@@ -1 +1,24 @@
-exit status 0
+Global:VAR{{{}}} = 3 braces
+Var_Parse: ${VAR{{{}}}}" != "3 braces" with VARE_WANTRES
+Global:VARNAME = VAR(((
+Var_Parse: ${VARNAME} with VARE_WANTRES
+Global:VAR((( = 3 open parentheses
+Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" with VARE_WANTRES
+Var_Parse: ${:UVAR(((}= try1 with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF)
+Result of ${:UVAR(((} is "VAR(((" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
+Global:.ALLTARGETS = VAR(((=)
+make: "varname.mk" line 30: No closing parenthesis in archive specification
+make: "varname.mk" line 30: Error in archive specification: "VAR"
+Var_Parse: ${:UVAR\(\(\(}= try2 with VARE_UNDEFERR|VARE_WANTRES
+Applying ${:U...} to "" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF)
+Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (VARE_UNDEFERR|VARE_WANTRES, none, VEF_UNDEF|VEF_DEF)
+Global:.ALLTARGETS = VAR(((=) VAR\(\(\(=
+make: "varname.mk" line 35: Need an operator
+Var_Parse: ${VARNAME} with VARE_WANTRES
+Global:VAR((( = try3
+Global:.MAKEFLAGS = -r -k -d v -d
+Global:.MAKEFLAGS = -r -k -d v -d 0
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varname.mk b/unit-tests/varname.mk
index e9fbc89873ca..f586c7602cb7 100644
--- a/unit-tests/varname.mk
+++ b/unit-tests/varname.mk
@@ -1,8 +1,44 @@
-# $NetBSD: varname.mk,v 1.4 2020/10/18 08:47:54 rillig Exp $
+# $NetBSD: varname.mk,v 1.8 2020/11/02 22:59:48 rillig Exp $
#
# Tests for special variables, such as .MAKE or .PARSEDIR.
+# And for variable names in general.
-# TODO: Implementation
+.MAKEFLAGS: -dv
+
+# In variable names, braces are allowed, but they must be balanced.
+# Parentheses and braces may be mixed.
+VAR{{{}}}= 3 braces
+.if "${VAR{{{}}}}" != "3 braces"
+. error
+.endif
+
+# In variable expressions, the parser works differently. It doesn't treat
+# braces and parentheses equally, therefore the first closing brace already
+# marks the end of the variable name.
+VARNAME= VAR(((
+${VARNAME}= 3 open parentheses
+.if "${VAR(((}}}}" != "3 open parentheses}}}"
+. error
+.endif
+
+# In the above test, the variable name is constructed indirectly. Neither
+# of the following expressions produces the intended effect.
+#
+# This is not a variable assignment since the parentheses and braces are not
+# balanced. At the end of the line, there are still 3 levels open, which
+# means the variable name is not finished.
+${:UVAR(((}= try1
+# On the left-hand side of a variable assignments, the backslash is not parsed
+# as an escape character, therefore the parentheses still count to the nesting
+# level, which at the end of the line is still 3. Therefore this is not a
+# variable assignment as well.
+${:UVAR\(\(\(}= try2
+# To assign to a variable with an arbitrary name, the variable name has to
+# come from an external source, not the text that is parsed in the assignment
+# itself. This is exactly the reason why further above, the indirect
+# ${VARNAME} works, while all other attempts fail.
+${VARNAME}= try3
+
+.MAKEFLAGS: -d0
all:
- @:;
diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/unit-tests/varparse-errors.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk
new file mode 100644
index 000000000000..42f5b65a728e
--- /dev/null
+++ b/unit-tests/varparse-errors.mk
@@ -0,0 +1,35 @@
+# $NetBSD: varparse-errors.mk,v 1.1 2020/11/08 16:44:47 rillig Exp $
+
+# Tests for parsing and evaluating all kinds of variable expressions.
+#
+# This is the basis for redesigning the error handling in Var_Parse and
+# Var_Subst, collecting typical and not so typical use cases.
+#
+# See also:
+# VarParseResult
+# Var_Parse
+# Var_Subst
+
+PLAIN= plain value
+
+LITERAL_DOLLAR= To get a dollar, double $$ it.
+
+INDIRECT= An ${:Uindirect} value.
+
+REF_UNDEF= A reference to an ${UNDEF}undefined variable.
+
+ERR_UNCLOSED= An ${UNCLOSED variable expression.
+
+ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier.
+
+ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}.
+
+# In a conditional, a variable expression that is not enclosed in quotes is
+# expanded using the flags VARE_UNDEFERR and VARE_WANTRES.
+# The variable itself must be defined.
+# It may refer to undefined variables though.
+.if ${REF_UNDEF} != "A reference to an undefined variable."
+. error
+.endif
+
+all:
diff --git a/unit-tests/varparse-undef-partial.mk b/unit-tests/varparse-undef-partial.mk
index 4851b6d9d567..27f44d79b31a 100644
--- a/unit-tests/varparse-undef-partial.mk
+++ b/unit-tests/varparse-undef-partial.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varparse-undef-partial.mk,v 1.2 2020/09/27 09:53:41 rillig Exp $
+# $NetBSD: varparse-undef-partial.mk,v 1.3 2020/11/04 05:10:01 rillig Exp $
# When an undefined variable is expanded in a ':=' assignment, only the
# initial '$' of the variable expression is skipped by the parser, while
@@ -9,7 +9,7 @@ LIST= ${DEF} ${UNDEF} ${VAR.${PARAM}} end
DEF= defined
PARAM= :Q
-# The expression ${VAR.{PARAM}} refers to the variable named "VAR.:Q",
+# The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q",
# with the ":Q" being part of the name. This variable is not defined,
# therefore the initial '$' of that whole expression is skipped by the
# parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest
@@ -28,15 +28,15 @@ VAR.= var-dot without parameter
${:UVAR.\:Q}= var-dot with parameter :Q
# At this point, the variable "VAR." is defined, therefore the expression
-# ${VAR.:Q} is expanded as usual.
+# ${VAR.:Q} is expanded, consisting of the variable name "VAR." and the
+# modifier ":Q".
.if ${EVAL} != "defined var-dot\\ without\\ parameter end"
. error ${EVAL}
.endif
# In contrast to the previous line, evaluating the original LIST again now
-# produces a different result since the ":Q" has already been inserted
-# literally into the expression. The variable named "VAR.:Q" is defined,
-# therefore it is resolved as usual. The ":Q" is interpreted as part of the
+# produces a different result since the variable named "VAR.:Q" is now
+# defined. It is expanded as usual, interpreting the ":Q" as part of the
# variable name, as would be expected from reading the variable expression.
EVAL:= ${LIST}
.if ${EVAL} != "defined var-dot with parameter :Q end"
diff --git a/unit-tests/varshell.exp b/unit-tests/varshell.exp
deleted file mode 100644
index 54df3527ed1c..000000000000
--- a/unit-tests/varshell.exp
+++ /dev/null
@@ -1,10 +0,0 @@
-make: "varshell.mk" line 6: warning: "/bin/no/such/command 2> /dev/null" returned non-zero status
-make: "varshell.mk" line 9: warning: "false" returned non-zero status
-make: "varshell.mk" line 10: warning: "echo "output before the error"; false" returned non-zero status
-EXEC_FAILED=''
-TERMINATED_BY_SIGNAL=''
-ERROR_NO_OUTPUT=''
-ERROR_WITH_OUTPUT='output before the error'
-NO_ERROR_NO_OUTPUT=''
-NO_ERROR_WITH_OUTPUT='this is good'
-exit status 0
diff --git a/unit-tests/varshell.mk b/unit-tests/varshell.mk
deleted file mode 100644
index 113c265cce3c..000000000000
--- a/unit-tests/varshell.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-# $Id: varshell.mk,v 1.6 2020/10/26 17:55:23 sjg Exp $
-# $NetBSD: varshell.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $
-#
-# Test VAR != shell command
-
-EXEC_FAILED!= /bin/no/such/command 2> /dev/null
-# SunOS cannot handle this one
-#TERMINATED_BY_SIGNAL!= kill -14 $$$$
-ERROR_NO_OUTPUT!= false
-ERROR_WITH_OUTPUT!= echo "output before the error"; false
-NO_ERROR_NO_OUTPUT!= true
-NO_ERROR_WITH_OUTPUT!= echo "this is good"
-
-allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \
- NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT
-
-all:
-.for v in ${allvars}
- @echo ${v}=\'${${v}}\'
-.endfor
diff --git a/util.c b/util.c
index 039d9b7b9761..a5d867d9df91 100644
--- a/util.c
+++ b/util.c
@@ -1,9 +1,9 @@
-/* $NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $ */
+/* $NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $ */
/*
* Missing stuff from OS's
*
- * $Id: util.c,v 1.39 2020/10/10 19:42:02 sjg Exp $
+ * $Id: util.c,v 1.41 2020/11/18 03:58:32 sjg Exp $
*/
#include <sys/param.h>
@@ -13,7 +13,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: util.c,v 1.64 2020/10/06 21:51:33 rillig Exp $");
+MAKE_RCSID("$NetBSD: util.c,v 1.68 2020/11/16 18:29:49 rillig Exp $");
#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
extern int errno, sys_nerr;
@@ -24,10 +24,9 @@ strerror(int e)
{
static char buf[100];
if (e < 0 || e >= sys_nerr) {
- snprintf(buf, sizeof(buf), "Unknown error %d", e);
+ snprintf(buf, sizeof buf, "Unknown error %d", e);
return buf;
- }
- else
+ } else
return sys_errlist[e];
}
#endif
@@ -96,7 +95,7 @@ setenv(const char *name, const char *value, int rewrite)
}
if (*value == '=') /* no `=' in value */
- ++value;
+ value++;
l_value = strlen(value);
/* find if already exists */
@@ -291,8 +290,7 @@ getwd(char *pathname)
for (d = readdir(dp); d != NULL; d = readdir(dp))
if (d->d_fileno == st_cur.st_ino)
break;
- }
- else {
+ } else {
/*
* Parent has a different device. This is a mount point so we
* need to stat every member
@@ -384,14 +382,14 @@ vsnprintf(char *s, size_t n, const char *fmt, va_list args)
* We cast to void * to make everyone happy.
*/
fakebuf._ptr = (void *)s;
- fakebuf._cnt = n-1;
+ fakebuf._cnt = n - 1;
fakebuf._file = -1;
_doprnt(fmt, args, &fakebuf);
fakebuf._cnt++;
putc('\0', &fakebuf);
- if (fakebuf._cnt<0)
+ if (fakebuf._cnt < 0)
fakebuf._cnt = 0;
- return n-fakebuf._cnt-1;
+ return n - fakebuf._cnt - 1;
#else
#ifndef _PATH_DEVNULL
# define _PATH_DEVNULL "/dev/null"
diff --git a/var.c b/var.c
index 3f10b44a8ccc..80b8d95d82ef 100644
--- a/var.c
+++ b/var.c
@@ -1,4 +1,4 @@
-/* $NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $ */
+/* $NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -138,7 +138,7 @@
#include "metachar.h"
/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.689 2020/11/17 20:11:02 rillig Exp $");
#define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
#define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
@@ -146,7 +146,7 @@ MAKE_RCSID("$NetBSD: var.c,v 1.641 2020/11/01 23:17:40 rillig Exp $");
#define VAR_DEBUG4(fmt, arg1, arg2, arg3, arg4) DEBUG4(VAR, fmt, arg1, arg2, arg3, arg4)
ENUM_FLAGS_RTTI_3(VarEvalFlags,
- VARE_UNDEFERR, VARE_WANTRES, VARE_ASSIGN);
+ VARE_UNDEFERR, VARE_WANTRES, VARE_KEEP_DOLLAR);
/*
* This lets us tell if we have replaced the original environ
@@ -165,11 +165,6 @@ char var_Error[] = "";
* be deferred until it is defined in an actual target. */
static char varUndefined[] = "";
-/* Special return value for Var_Parse, just to avoid allocating empty strings.
- * In contrast to var_Error and varUndefined, this is not an error marker but
- * just an ordinary successful return value. */
-static char emptyString[] = "";
-
/*
* Traditionally this make consumed $$ during := like any other expansion.
* Other make's do not, and this make follows straight since 2016-01-09.
@@ -274,6 +269,7 @@ typedef enum VarExportedMode {
static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE;
typedef enum VarExportFlags {
+ VAR_EXPORT_NORMAL = 0,
/*
* We pass this to Var_Export when doing the initial export
* or after updating an exported var.
@@ -300,7 +296,7 @@ VarNew(const char *name, void *name_freeIt, const char *value, VarFlags flags)
Var *var = bmake_malloc(sizeof *var);
var->name = name;
var->name_freeIt = name_freeIt;
- Buf_Init(&var->val, value_len + 1);
+ Buf_InitSize(&var->val, value_len + 1);
Buf_AddBytes(&var->val, value, value_len);
var->flags = flags;
return var;
@@ -447,7 +443,7 @@ VarFreeEnv(Var *v, Boolean freeValue)
/* Add a new variable of the given name and value to the given context.
* The name and val arguments are duplicated so they may safely be freed. */
static void
-VarAdd(const char *name, const char *val, GNode *ctxt, VarSet_Flags flags)
+VarAdd(const char *name, const char *val, GNode *ctxt, VarSetFlags flags)
{
HashEntry *he = HashTable_CreateEntry(&ctxt->context, name, NULL);
Var *v = VarNew(he->key /* aliased */, NULL, val,
@@ -531,7 +527,7 @@ Var_Export1(const char *name, VarExportFlags flags)
if (!MayExport(name))
return FALSE;
- v = VarFind(name, VAR_GLOBAL, 0);
+ v = VarFind(name, VAR_GLOBAL, FALSE);
if (v == NULL)
return FALSE;
@@ -597,7 +593,7 @@ Var_ExportVars(void)
* children see a correctly incremented value.
*/
char tmp[BUFSIZ];
- snprintf(tmp, sizeof(tmp), "%d", makelevel + 1);
+ snprintf(tmp, sizeof tmp, "%d", makelevel + 1);
setenv(MAKE_LEVEL_ENV, tmp, 1);
if (var_exportedVars == VAR_EXPORTED_NONE)
@@ -610,7 +606,7 @@ Var_ExportVars(void)
HashIter_Init(&hi, &VAR_GLOBAL->context);
while (HashIter_Next(&hi) != NULL) {
Var *var = hi.entry->value;
- Var_Export1(var->name, 0);
+ Var_Export1(var->name, VAR_EXPORT_NORMAL);
}
return;
}
@@ -622,7 +618,7 @@ Var_ExportVars(void)
size_t i;
for (i = 0; i < words.len; i++)
- Var_Export1(words.words[i], 0);
+ Var_Export1(words.words[i], VAR_EXPORT_NORMAL);
Words_Free(words);
}
free(val);
@@ -741,7 +737,7 @@ Var_UnExport(const char *str)
Words words = Str_Words(varnames, FALSE);
for (i = 0; i < words.len; i++) {
const char *varname = words.words[i];
- v = VarFind(varname, VAR_GLOBAL, 0);
+ v = VarFind(varname, VAR_GLOBAL, FALSE);
if (v == NULL) {
VAR_DEBUG1("Not unexporting \"%s\" (not found)\n", varname);
continue;
@@ -780,8 +776,8 @@ Var_UnExport(const char *str)
/* See Var_Set for documentation. */
void
-Var_Set_with_flags(const char *name, const char *val, GNode *ctxt,
- VarSet_Flags flags)
+Var_SetWithFlags(const char *name, const char *val, GNode *ctxt,
+ VarSetFlags flags)
{
const char *unexpanded_name = name;
char *name_freeIt = NULL;
@@ -804,7 +800,7 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt,
}
if (ctxt == VAR_GLOBAL) {
- v = VarFind(name, VAR_CMDLINE, 0);
+ v = VarFind(name, VAR_CMDLINE, FALSE);
if (v != NULL) {
if (v->flags & VAR_FROM_CMD) {
VAR_DEBUG3("%s:%s = %s ignored!\n", ctxt->name, name, val);
@@ -819,9 +815,9 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt,
* here will override anything in a lower context, so there's not much
* point in searching them all just to save a bit of memory...
*/
- v = VarFind(name, ctxt, 0);
+ v = VarFind(name, ctxt, FALSE);
if (v == NULL) {
- if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT)) {
+ if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) {
/*
* This var would normally prevent the same name being added
* to VAR_GLOBAL, so delete it from there if needed.
@@ -850,9 +846,9 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt,
* to the environment (as per POSIX standard)
* Other than internals.
*/
- if (ctxt == VAR_CMDLINE && !(flags & VAR_NO_EXPORT) && name[0] != '.') {
+ if (ctxt == VAR_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && name[0] != '.') {
if (v == NULL)
- v = VarFind(name, ctxt, 0); /* we just added it */
+ v = VarFind(name, ctxt, FALSE); /* we just added it */
v->flags |= VAR_FROM_CMD;
/*
@@ -867,7 +863,7 @@ Var_Set_with_flags(const char *name, const char *val, GNode *ctxt,
Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
}
if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0)
- save_dollars = s2Boolean(val, save_dollars);
+ save_dollars = ParseBoolean(val, save_dollars);
out:
free(name_freeIt);
@@ -902,7 +898,7 @@ out:
void
Var_Set(const char *name, const char *val, GNode *ctxt)
{
- Var_Set_with_flags(name, val, ctxt, 0);
+ Var_SetWithFlags(name, val, ctxt, VAR_SET_NONE);
}
/*-
@@ -965,8 +961,6 @@ Var_Append(const char *name, const char *val, GNode *ctxt)
ctxt->name, name, Buf_GetAll(&v->val, NULL));
if (v->flags & VAR_FROM_ENV) {
- HashEntry *h;
-
/*
* If the original variable came from the environment, we
* have to install it in the global context (we could place
@@ -974,8 +968,9 @@ Var_Append(const char *name, const char *val, GNode *ctxt)
* export other variables...)
*/
v->flags &= ~(unsigned)VAR_FROM_ENV;
- h = HashTable_CreateEntry(&ctxt->context, name, NULL);
- HashEntry_Set(h, v);
+ /* This is the only place where a variable is created whose
+ * v->name is not the same as ctxt->context->key. */
+ HashTable_Set(&ctxt->context, name, v);
}
}
free(name_freeIt);
@@ -1061,7 +1056,7 @@ typedef struct SepBuf {
static void
SepBuf_Init(SepBuf *buf, char sep)
{
- Buf_Init(&buf->buf, 32 /* bytes */);
+ Buf_InitSize(&buf->buf, 32);
buf->needSep = FALSE;
buf->sep = sep;
}
@@ -1354,9 +1349,9 @@ nosub:
#ifndef NO_REGEX
/* Print the error caused by a regcomp or regexec call. */
static void
-VarREError(int reerr, regex_t *pat, const char *str)
+VarREError(int reerr, const regex_t *pat, const char *str)
{
- size_t errlen = regerror(reerr, pat, 0, 0);
+ size_t errlen = regerror(reerr, pat, NULL, 0);
char *errbuf = bmake_malloc(errlen);
regerror(reerr, pat, errbuf, errlen);
Error("%s: %s", str, errbuf);
@@ -1470,7 +1465,7 @@ ModifyWord_Loop(const char *word, SepBuf *buf, void *data)
return;
args = data;
- Var_Set_with_flags(args->tvar, word, args->ctx, VAR_NO_EXPORT);
+ Var_SetWithFlags(args->tvar, word, args->ctx, VAR_SET_NO_EXPORT);
(void)Var_Subst(args->str, args->ctx, args->eflags, &s);
/* TODO: handle errors */
@@ -1501,7 +1496,7 @@ VarSelectWords(char sep, Boolean oneBigWord, const char *str, int first,
if (oneBigWord) {
/* fake what Str_Words() would do if there were only one word */
words.len = 1;
- words.words = bmake_malloc((words.len + 1) * sizeof(char *));
+ words.words = bmake_malloc((words.len + 1) * sizeof(words.words[0]));
words.freeIt = bmake_strdup(str);
words.words[0] = words.freeIt;
words.words[1] = NULL;
@@ -1609,7 +1604,7 @@ Words_JoinFree(Words words)
Buffer buf;
size_t i;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
for (i = 0; i < words.len; i++) {
if (i != 0)
@@ -1646,7 +1641,7 @@ static char *
VarQuote(const char *str, Boolean quoteDollar)
{
Buffer buf;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
for (; *str != '\0'; str++) {
if (*str == '\n') {
@@ -1733,19 +1728,24 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim)
{
char buf[BUFSIZ];
- if (!tim)
+ if (tim == 0)
time(&tim);
- if (!*fmt)
+ if (*fmt == '\0')
fmt = "%c";
- strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&tim) : localtime(&tim));
+ strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim));
- buf[sizeof(buf) - 1] = '\0';
+ buf[sizeof buf - 1] = '\0';
return bmake_strdup(buf);
}
-/* The ApplyModifier functions all work in the same way. They get the
- * current parsing position (pp) and parse the modifier from there. The
- * modifier typically lasts until the next ':', or a closing '}' or ')'
+/*
+ * The ApplyModifier functions take an expression that is being evaluated.
+ * Their task is to apply a single modifier to the expression.
+ * To do this, they parse the modifier and its parameters from pp and apply
+ * the parsed modifier to the current value of the expression, generating a
+ * new value from it.
+ *
+ * The modifier typically lasts until the next ':', or a closing '}' or ')'
* (taken from st->endc), or the end of the string (parse error).
*
* The high-level behavior of these functions is:
@@ -1758,7 +1758,11 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim)
*
* If parsing succeeds, the parsing position *pp is updated to point to the
* first character following the modifier, which typically is either ':' or
- * st->endc.
+ * st->endc. The modifier doesn't have to check for this delimiter character,
+ * this is done by ApplyModifiers.
+ *
+ * XXX: As of 2020-11-15, some modifiers such as :S, :C, :P, :L do not
+ * need to be followed by a ':' or endc; this was an unintended mistake.
*
* If parsing fails because of a missing delimiter (as in the :S, :C or :@
* modifiers), return AMR_CLEANUP.
@@ -1767,8 +1771,8 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim)
* try the SysV modifier ${VAR:from=to} as fallback. This should only be
* done as long as there have been no side effects from evaluating nested
* variables, to avoid evaluating them more than once. In this case, the
- * parsing position must not be updated. (XXX: Why not? The original parsing
- * position is well-known in ApplyModifiers.)
+ * parsing position may or may not be updated. (XXX: Why not? The original
+ * parsing position is well-known in ApplyModifiers.)
*
* If parsing fails and the SysV modifier ${VAR:from=to} should not be used
* as a fallback, either issue an error message using Error or Parse_Error
@@ -1786,7 +1790,7 @@ VarStrftime(const char *fmt, Boolean zulu, time_t tim)
* during parsing though.
*
* Evaluating the modifier usually takes the current value of the variable
- * expression from st->val, or the variable name from st->v->name and stores
+ * expression from st->val, or the variable name from st->var->name and stores
* the result in st->newVal.
*
* If evaluating fails (as of 2020-08-23), an error message is printed using
@@ -1819,7 +1823,7 @@ ENUM_FLAGS_RTTI_2(VarExprFlags,
typedef struct ApplyModifiersState {
const char startc; /* '\0' or '{' or '(' */
const char endc; /* '\0' or '}' or ')' */
- Var * const v;
+ Var * const var;
GNode * const ctxt;
const VarEvalFlags eflags;
@@ -1896,7 +1900,7 @@ ParseModifierPart(
Buffer buf;
const char *p;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
/*
* Skim through until the matching delimiter is found; pick up variable
@@ -1934,7 +1938,7 @@ ParseModifierPart(
const char *nested_p = p;
const char *nested_val;
void *nested_val_freeIt;
- VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_ASSIGN;
+ VarEvalFlags nested_eflags = eflags & ~(unsigned)VARE_KEEP_DOLLAR;
(void)Var_Parse(&nested_p, st->ctxt, nested_eflags,
&nested_val, &nested_val_freeIt);
@@ -1982,7 +1986,8 @@ ParseModifierPart(
if (*p != delim) {
*pp = p;
- Error("Unfinished modifier for %s ('%c' missing)", st->v->name, delim);
+ Error("Unfinished modifier for %s ('%c' missing)",
+ st->var->name, delim);
*out_part = NULL;
return VPR_PARSE_MSG;
}
@@ -1997,7 +2002,7 @@ ParseModifierPart(
}
/* Test whether mod starts with modname, followed by a delimiter. */
-static Boolean
+MAKE_INLINE Boolean
ModMatch(const char *mod, const char *modname, char endc)
{
size_t n = strlen(modname);
@@ -2006,7 +2011,7 @@ ModMatch(const char *mod, const char *modname, char endc)
}
/* Test whether mod starts with modname, followed by a delimiter or '='. */
-static inline Boolean
+MAKE_INLINE Boolean
ModMatchEq(const char *mod, const char *modname, char endc)
{
size_t n = strlen(modname);
@@ -2080,35 +2085,35 @@ ApplyModifier_Loop(const char **pp, ApplyModifiersState *st)
{
struct ModifyWord_LoopArgs args;
char prev_sep;
- VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES;
VarParseResult res;
args.ctx = st->ctxt;
(*pp)++; /* Skip the first '@' */
- res = ParseModifierPart(pp, '@', eflags, st,
+ res = ParseModifierPart(pp, '@', VARE_NONE, st,
&args.tvar, NULL, NULL, NULL);
if (res != VPR_OK)
return AMR_CLEANUP;
- if (DEBUG(LINT) && strchr(args.tvar, '$') != NULL) {
+ if (opts.lint && strchr(args.tvar, '$') != NULL) {
Parse_Error(PARSE_FATAL,
"In the :@ modifier of \"%s\", the variable name \"%s\" "
"must not contain a dollar.",
- st->v->name, args.tvar);
+ st->var->name, args.tvar);
return AMR_CLEANUP;
}
- res = ParseModifierPart(pp, '@', eflags, st,
+ res = ParseModifierPart(pp, '@', VARE_NONE, st,
&args.str, NULL, NULL, NULL);
if (res != VPR_OK)
return AMR_CLEANUP;
- args.eflags = st->eflags & (VARE_UNDEFERR | VARE_WANTRES);
+ args.eflags = st->eflags & ~(unsigned)VARE_KEEP_DOLLAR;
prev_sep = st->sep;
st->sep = ' '; /* XXX: should be st->sep for consistency */
st->newVal = ModifyWords(st->val, ModifyWord_Loop, &args,
st->oneBigWord, st->sep);
st->sep = prev_sep;
+ /* XXX: Consider restoring the previous variable instead of deleting. */
Var_Delete(args.tvar, st->ctxt);
free(args.tvar);
free(args.str);
@@ -2122,16 +2127,19 @@ ApplyModifier_Defined(const char **pp, ApplyModifiersState *st)
Buffer buf;
const char *p;
- VarEvalFlags eflags = st->eflags & ~(unsigned)VARE_WANTRES;
- if (st->eflags & VARE_WANTRES) {
+ VarEvalFlags eflags = VARE_NONE;
+ if (st->eflags & VARE_WANTRES)
if ((**pp == 'D') == !(st->exprFlags & VEF_UNDEF))
- eflags |= VARE_WANTRES;
- }
+ eflags = st->eflags;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
p = *pp + 1;
while (*p != st->endc && *p != ':' && *p != '\0') {
+ /* XXX: This code is similar to the one in Var_Parse.
+ * See if the code can be merged.
+ * See also ApplyModifier_Match. */
+
/* Escaped delimiter or other special character */
if (*p == '\\') {
char c = p[1];
@@ -2177,7 +2185,7 @@ static ApplyModifierResult
ApplyModifier_Literal(const char **pp, ApplyModifiersState *st)
{
ApplyModifiersState_Define(st);
- st->newVal = bmake_strdup(st->v->name);
+ st->newVal = bmake_strdup(st->var->name);
(*pp)++;
return AMR_OK;
}
@@ -2272,17 +2280,17 @@ ApplyModifier_Path(const char **pp, ApplyModifiersState *st)
ApplyModifiersState_Define(st);
- gn = Targ_FindNode(st->v->name);
+ gn = Targ_FindNode(st->var->name);
if (gn == NULL || gn->type & OP_NOPATH) {
path = NULL;
} else if (gn->path != NULL) {
path = bmake_strdup(gn->path);
} else {
SearchPath *searchPath = Suff_FindPath(gn);
- path = Dir_FindFile(st->v->name, searchPath);
+ path = Dir_FindFile(st->var->name, searchPath);
}
if (path == NULL)
- path = bmake_strdup(st->v->name);
+ path = bmake_strdup(st->var->name);
st->newVal = path;
(*pp)++;
@@ -2307,11 +2315,10 @@ ApplyModifier_ShellCommand(const char **pp, ApplyModifiersState *st)
if (st->eflags & VARE_WANTRES)
st->newVal = Cmd_Exec(cmd, &errfmt);
else
- st->newVal = emptyString;
- free(cmd);
-
+ st->newVal = bmake_strdup("");
if (errfmt != NULL)
- Error(errfmt, st->val); /* XXX: why still return AMR_OK? */
+ Error(errfmt, cmd); /* XXX: why still return AMR_OK? */
+ free(cmd);
ApplyModifiersState_Define(st);
return AMR_OK;
@@ -2348,7 +2355,7 @@ ApplyModifier_Range(const char **pp, ApplyModifiersState *st)
Words_Free(words);
}
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
for (i = 0; i < n; i++) {
if (i != 0)
@@ -2374,8 +2381,11 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st)
/*
* In the loop below, ignore ':' unless we are at (or back to) the
* original brace level.
- * XXX This will likely not work right if $() and ${} are intermixed.
+ * XXX: This will likely not work right if $() and ${} are intermixed.
*/
+ /* XXX: This code is similar to the one in Var_Parse.
+ * See if the code can be merged.
+ * See also ApplyModifier_Defined. */
int nest = 0;
const char *p;
for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) {
@@ -2428,7 +2438,8 @@ ApplyModifier_Match(const char **pp, ApplyModifiersState *st)
free(old_pattern);
}
- VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n", st->v->name, st->val, pattern);
+ VAR_DEBUG3("Pattern[%s] for [%s] is [%s]\n",
+ st->var->name, st->val, pattern);
callback = mod[0] == 'M' ? ModifyWord_Match : ModifyWord_NoMatch;
st->newVal = ModifyWords(st->val, callback, pattern,
@@ -2754,7 +2765,7 @@ ApplyModifier_Words(const char **pp, ApplyModifiersState *st)
size_t ac = words.len;
Words_Free(words);
- Buf_Init(&buf, 4); /* 3 digits + '\0' is usually enough */
+ Buf_InitSize(&buf, 4); /* 3 digits + '\0' is usually enough */
Buf_AddInt(&buf, (int)ac);
st->newVal = Buf_Destroy(&buf, FALSE);
}
@@ -2842,7 +2853,7 @@ ApplyModifier_Order(const char **pp, ApplyModifiersState *st)
if (mod[1] == st->endc || mod[1] == ':') {
/* :O sorts ascending */
- qsort(words.words, words.len, sizeof(char *), str_cmp_asc);
+ qsort(words.words, words.len, sizeof words.words[0], str_cmp_asc);
} else if ((mod[1] == 'r' || mod[1] == 'x') &&
(mod[2] == st->endc || mod[2] == ':')) {
@@ -2850,7 +2861,7 @@ ApplyModifier_Order(const char **pp, ApplyModifiersState *st)
if (mod[1] == 'r') {
/* :Or sorts descending */
- qsort(words.words, words.len, sizeof(char *), str_cmp_desc);
+ qsort(words.words, words.len, sizeof words.words[0], str_cmp_desc);
} else {
/* :Ox shuffles
@@ -2885,16 +2896,16 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st)
VarParseResult res;
Boolean value = FALSE;
- VarEvalFlags then_eflags = st->eflags & ~(unsigned)VARE_WANTRES;
- VarEvalFlags else_eflags = st->eflags & ~(unsigned)VARE_WANTRES;
+ VarEvalFlags then_eflags = VARE_NONE;
+ VarEvalFlags else_eflags = VARE_NONE;
int cond_rc = COND_PARSE; /* anything other than COND_INVALID */
if (st->eflags & VARE_WANTRES) {
- cond_rc = Cond_EvalCondition(st->v->name, &value);
+ cond_rc = Cond_EvalCondition(st->var->name, &value);
if (cond_rc != COND_INVALID && value)
- then_eflags |= VARE_WANTRES;
+ then_eflags = st->eflags;
if (cond_rc != COND_INVALID && !value)
- else_eflags |= VARE_WANTRES;
+ else_eflags = st->eflags;
}
(*pp)++; /* skip past the '?' */
@@ -2911,7 +2922,7 @@ ApplyModifier_IfElse(const char **pp, ApplyModifiersState *st)
(*pp)--;
if (cond_rc == COND_INVALID) {
Error("Bad conditional expression `%s' in %s?%s:%s",
- st->v->name, st->v->name, then_expr, else_expr);
+ st->var->name, st->var->name, then_expr, else_expr);
return AMR_CLEANUP;
}
@@ -2965,14 +2976,14 @@ ApplyModifier_Assign(const char **pp, ApplyModifiersState *st)
return AMR_UNKNOWN; /* "::<unrecognised>" */
ok:
- if (st->v->name[0] == '\0') {
+ if (st->var->name[0] == '\0') {
*pp = mod + 1;
return AMR_BAD;
}
v_ctxt = st->ctxt; /* context where v belongs */
if (!(st->exprFlags & VEF_UNDEF) && st->ctxt != VAR_GLOBAL) {
- Var *gv = VarFind(st->v->name, st->ctxt, 0);
+ Var *gv = VarFind(st->var->name, st->ctxt, FALSE);
if (gv == NULL)
v_ctxt = VAR_GLOBAL;
else
@@ -3000,7 +3011,7 @@ ok:
if (st->eflags & VARE_WANTRES) {
switch (op[0]) {
case '+':
- Var_Append(st->v->name, val, v_ctxt);
+ Var_Append(st->var->name, val, v_ctxt);
break;
case '!': {
const char *errfmt;
@@ -3008,7 +3019,7 @@ ok:
if (errfmt)
Error(errfmt, val);
else
- Var_Set(st->v->name, cmd_output, v_ctxt);
+ Var_Set(st->var->name, cmd_output, v_ctxt);
free(cmd_output);
break;
}
@@ -3017,12 +3028,12 @@ ok:
break;
/* FALLTHROUGH */
default:
- Var_Set(st->v->name, val, v_ctxt);
+ Var_Set(st->var->name, val, v_ctxt);
break;
}
}
free(val);
- st->newVal = emptyString;
+ st->newVal = bmake_strdup("");
return AMR_OK;
}
@@ -3146,7 +3157,7 @@ ApplyModifier_SunShell(const char **pp, ApplyModifiersState *st)
if (errfmt)
Error(errfmt, st->val);
} else
- st->newVal = emptyString;
+ st->newVal = bmake_strdup("");
*pp = p + 2;
return AMR_OK;
} else
@@ -3166,11 +3177,11 @@ LogBeforeApply(const ApplyModifiersState *st, const char *mod, const char endc)
/* At this point, only the first character of the modifier can
* be used since the end of the modifier is not yet known. */
debug_printf("Applying ${%s:%c%s} to \"%s\" (%s, %s, %s)\n",
- st->v->name, mod[0], is_single_char ? "" : "...", st->val,
+ st->var->name, mod[0], is_single_char ? "" : "...", st->val,
Enum_FlagsToString(eflags_str, sizeof eflags_str,
st->eflags, VarEvalFlags_ToStringSpecs),
Enum_FlagsToString(vflags_str, sizeof vflags_str,
- st->v->flags, VarFlags_ToStringSpecs),
+ st->var->flags, VarFlags_ToStringSpecs),
Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
st->exprFlags,
VarExprFlags_ToStringSpecs));
@@ -3186,11 +3197,11 @@ LogAfterApply(ApplyModifiersState *st, const char *p, const char *mod)
const char *newVal = st->newVal == var_Error ? "error" : st->newVal;
debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s, %s)\n",
- st->v->name, (int)(p - mod), mod, quot, newVal, quot,
+ st->var->name, (int)(p - mod), mod, quot, newVal, quot,
Enum_FlagsToString(eflags_str, sizeof eflags_str,
st->eflags, VarEvalFlags_ToStringSpecs),
Enum_FlagsToString(vflags_str, sizeof vflags_str,
- st->v->flags, VarFlags_ToStringSpecs),
+ st->var->flags, VarFlags_ToStringSpecs),
Enum_FlagsToString(exprflags_str, sizeof exprflags_str,
st->exprFlags,
VarExprFlags_ToStringSpecs));
@@ -3272,20 +3283,18 @@ typedef enum ApplyModifiersIndirectResult {
} ApplyModifiersIndirectResult;
/* While expanding a variable expression, expand and apply indirect
- * modifiers. */
+ * modifiers such as in ${VAR:${M_indirect}}. */
static ApplyModifiersIndirectResult
ApplyModifiersIndirect(
ApplyModifiersState *const st,
- const char **inout_p,
- void **const out_freeIt
+ const char **const inout_p,
+ void **const inout_freeIt
) {
const char *p = *inout_p;
- const char *nested_p = p;
- void *freeIt;
- const char *rval;
- char c;
+ const char *mods;
+ void *mods_freeIt;
- (void)Var_Parse(&nested_p, st->ctxt, st->eflags, &rval, &freeIt);
+ (void)Var_Parse(&p, st->ctxt, st->eflags, &mods, &mods_freeIt);
/* TODO: handle errors */
/*
@@ -3293,44 +3302,41 @@ ApplyModifiersIndirect(
* interested. This means the expression ${VAR:${M_1}${M_2}}
* is not accepted, but ${VAR:${M_1}:${M_2}} is.
*/
- if (rval[0] != '\0' &&
- (c = *nested_p) != '\0' && c != ':' && c != st->endc) {
- if (DEBUG(LINT))
+ if (mods[0] != '\0' && *p != '\0' && *p != ':' && *p != st->endc) {
+ if (opts.lint)
Parse_Error(PARSE_FATAL,
"Missing delimiter ':' after indirect modifier \"%.*s\"",
- (int)(nested_p - p), p);
+ (int)(p - *inout_p), *inout_p);
- free(freeIt);
+ free(mods_freeIt);
/* XXX: apply_mods doesn't sound like "not interested". */
- /* XXX: Why is the indirect modifier parsed again by
+ /* XXX: Why is the indirect modifier parsed once more by
* apply_mods? If any, p should be advanced to nested_p. */
return AMIR_APPLY_MODS;
}
VAR_DEBUG3("Indirect modifier \"%s\" from \"%.*s\"\n",
- rval, (int)(size_t)(nested_p - p), p);
-
- p = nested_p;
-
- if (rval[0] != '\0') {
- const char *rval_pp = rval;
- st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->v,
- &st->exprFlags, st->ctxt, st->eflags, out_freeIt);
- if (st->val == var_Error
- || (st->val == varUndefined && !(st->eflags & VARE_UNDEFERR))
- || *rval_pp != '\0') {
- free(freeIt);
+ mods, (int)(p - *inout_p), *inout_p);
+
+ if (mods[0] != '\0') {
+ const char *rval_pp = mods;
+ st->val = ApplyModifiers(&rval_pp, st->val, '\0', '\0', st->var,
+ &st->exprFlags, st->ctxt, st->eflags,
+ inout_freeIt);
+ if (st->val == var_Error || st->val == varUndefined ||
+ *rval_pp != '\0') {
+ free(mods_freeIt);
*inout_p = p;
return AMIR_OUT; /* error already reported */
}
}
- free(freeIt);
+ free(mods_freeIt);
if (*p == ':')
p++;
else if (*p == '\0' && st->endc != '\0') {
Error("Unclosed variable specification after complex "
- "modifier (expecting '%c') for %s", st->endc, st->v->name);
+ "modifier (expecting '%c') for %s", st->endc, st->var->name);
*inout_p = p;
return AMIR_OUT;
}
@@ -3342,15 +3348,15 @@ ApplyModifiersIndirect(
/* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */
static char *
ApplyModifiers(
- const char **pp, /* the parsing position, updated upon return */
+ const char **const pp, /* the parsing position, updated upon return */
char *const val, /* the current value of the expression */
char const startc, /* '(' or '{', or '\0' for indirect modifiers */
char const endc, /* ')' or '}', or '\0' for indirect modifiers */
- Var * const v,
- VarExprFlags *exprFlags,
- GNode * const ctxt, /* for looking up and modifying variables */
+ Var *const v,
+ VarExprFlags *const exprFlags,
+ GNode *const ctxt, /* for looking up and modifying variables */
VarEvalFlags const eflags,
- void ** const out_freeIt /* free this after using the return value */
+ void **const inout_freeIt /* free this after using the return value */
) {
ApplyModifiersState st = {
startc, endc, v, ctxt, eflags,
@@ -3369,11 +3375,18 @@ ApplyModifiers(
assert(val != NULL);
p = *pp;
+
+ if (*p == '\0' && endc != '\0') {
+ Error("Unclosed variable expression (expecting '%c') for \"%s\"",
+ st.endc, st.var->name);
+ goto cleanup;
+ }
+
while (*p != '\0' && *p != endc) {
if (*p == '$') {
ApplyModifiersIndirectResult amir;
- amir = ApplyModifiersIndirect(&st, &p, out_freeIt);
+ amir = ApplyModifiersIndirect(&st, &p, inout_freeIt);
if (amir == AMIR_CONTINUE)
continue;
if (amir == AMIR_OUT)
@@ -3396,6 +3409,9 @@ ApplyModifiers(
if (res == AMR_UNKNOWN) {
Error("Unknown modifier '%c'", *mod);
+ /* Guess the end of the current modifier.
+ * XXX: Skipping the rest of the modifier hides errors and leads
+ * to wrong results. Parsing should rather stop here. */
for (p++; *p != ':' && *p != st.endc && *p != '\0'; p++)
continue;
st.newVal = var_Error;
@@ -3409,26 +3425,25 @@ ApplyModifiers(
LogAfterApply(&st, p, mod);
if (st.newVal != st.val) {
- if (*out_freeIt) {
+ if (*inout_freeIt != NULL) {
free(st.val);
- *out_freeIt = NULL;
+ *inout_freeIt = NULL;
}
st.val = st.newVal;
- if (st.val != var_Error && st.val != varUndefined &&
- st.val != emptyString) {
- *out_freeIt = st.val;
- }
+ if (st.val != var_Error && st.val != varUndefined)
+ *inout_freeIt = st.val;
}
if (*p == '\0' && st.endc != '\0') {
Error("Unclosed variable specification (expecting '%c') "
"for \"%s\" (value \"%s\") modifier %c",
- st.endc, st.v->name, st.val, *mod);
+ st.endc, st.var->name, st.val, *mod);
} else if (*p == ':') {
p++;
- } else if (DEBUG(LINT) && *p != '\0' && *p != endc) {
+ } else if (opts.lint && *p != '\0' && *p != endc) {
Parse_Error(PARSE_FATAL,
"Missing delimiter ':' after modifier \"%.*s\"",
(int)(p - mod), mod);
+ /* TODO: propagate parse error to the enclosing expression */
}
}
out:
@@ -3438,13 +3453,14 @@ out:
return st.val;
bad_modifier:
+ /* XXX: The modifier end is only guessed. */
Error("Bad modifier `:%.*s' for %s",
- (int)strcspn(mod, ":)}"), mod, st.v->name);
+ (int)strcspn(mod, ":)}"), mod, st.var->name);
cleanup:
*pp = p;
- free(*out_freeIt);
- *out_freeIt = NULL;
+ free(*inout_freeIt);
+ *inout_freeIt = NULL;
*exprFlags = st.exprFlags;
return var_Error;
}
@@ -3513,7 +3529,7 @@ ParseVarname(const char **pp, char startc, char endc,
const char *p = *pp;
int depth = 1;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
while (*p != '\0') {
/* Track depth so we can spot parse errors. */
@@ -3528,12 +3544,12 @@ ParseVarname(const char **pp, char startc, char endc,
/* A variable inside a variable, expand. */
if (*p == '$') {
- void *freeIt;
- const char *rval;
- (void)Var_Parse(&p, ctxt, eflags, &rval, &freeIt);
+ const char *nested_val;
+ void *nested_val_freeIt;
+ (void)Var_Parse(&p, ctxt, eflags, &nested_val, &nested_val_freeIt);
/* TODO: handle errors */
- Buf_AddStr(&buf, rval);
- free(freeIt);
+ Buf_AddStr(&buf, nested_val);
+ free(nested_val_freeIt);
} else {
Buf_AddByte(&buf, *p);
p++;
@@ -3544,7 +3560,7 @@ ParseVarname(const char **pp, char startc, char endc,
return Buf_Destroy(&buf, FALSE);
}
-static Boolean
+static VarParseResult
ValidShortVarname(char varname, const char *start)
{
switch (varname) {
@@ -3555,11 +3571,11 @@ ValidShortVarname(char varname, const char *start)
case '$':
break; /* and continue below */
default:
- return TRUE;
+ return VPR_OK;
}
- if (!DEBUG(LINT))
- return FALSE;
+ if (!opts.lint)
+ return VPR_PARSE_SILENT;
if (varname == '$')
Parse_Error(PARSE_FATAL,
@@ -3570,23 +3586,20 @@ ValidShortVarname(char varname, const char *start)
Parse_Error(PARSE_FATAL,
"Invalid variable name '%c', at \"%s\"", varname, start);
- return FALSE;
+ return VPR_PARSE_MSG;
}
/* Parse a single-character variable name such as $V or $@.
* Return whether to continue parsing. */
static Boolean
-ParseVarnameShort(
- char startc,
- const char **pp,
- GNode *ctxt,
- VarEvalFlags eflags,
- VarParseResult *out_FALSE_res,
- const char **out_FALSE_val,
- Var **out_TRUE_var
-) {
+ParseVarnameShort(char startc, const char **pp, GNode *ctxt,
+ VarEvalFlags eflags,
+ VarParseResult *out_FALSE_res, const char **out_FALSE_val,
+ Var **out_TRUE_var)
+{
char name[2];
Var *v;
+ VarParseResult vpr;
/*
* If it's not bounded by braces of some sort, life is much simpler.
@@ -3594,10 +3607,11 @@ ParseVarnameShort(
* value if it exists.
*/
- if (!ValidShortVarname(startc, *pp)) {
+ vpr = ValidShortVarname(startc, *pp);
+ if (vpr != VPR_OK) {
(*pp)++;
*out_FALSE_val = var_Error;
- *out_FALSE_res = VPR_PARSE_MSG;
+ *out_FALSE_res = vpr;
return FALSE;
}
@@ -3608,7 +3622,7 @@ ParseVarnameShort(
*pp += 2;
*out_FALSE_val = UndefinedShortVarValue(startc, ctxt, eflags);
- if (DEBUG(LINT) && *out_FALSE_val == var_Error) {
+ if (opts.lint && *out_FALSE_val == var_Error) {
Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name);
*out_FALSE_res = VPR_UNDEF_MSG;
return FALSE;
@@ -3621,20 +3635,83 @@ ParseVarnameShort(
return TRUE;
}
+/* Find variables like @F or <D. */
+static Var *
+FindLocalLegacyVar(const char *varname, size_t namelen, GNode *ctxt,
+ const char **out_extraModifiers)
+{
+ /* Only resolve these variables if ctxt is a "real" target. */
+ if (ctxt == VAR_CMDLINE || ctxt == VAR_GLOBAL)
+ return NULL;
+
+ if (namelen != 2)
+ return NULL;
+ if (varname[1] != 'F' && varname[1] != 'D')
+ return NULL;
+ if (strchr("@%?*!<>", varname[0]) == NULL)
+ return NULL;
+
+ {
+ char name[] = { varname[0], '\0' };
+ Var *v = VarFind(name, ctxt, FALSE);
+
+ if (v != NULL) {
+ if (varname[1] == 'D') {
+ *out_extraModifiers = "H:";
+ } else { /* F */
+ *out_extraModifiers = "T:";
+ }
+ }
+ return v;
+ }
+}
+
+static VarParseResult
+EvalUndefined(Boolean dynamic, const char *start, const char *p, char *varname,
+ VarEvalFlags eflags,
+ void **out_freeIt, const char **out_val)
+{
+ if (dynamic) {
+ char *pstr = bmake_strsedup(start, p);
+ free(varname);
+ *out_freeIt = pstr;
+ *out_val = pstr;
+ return VPR_OK;
+ }
+
+ if ((eflags & VARE_UNDEFERR) && opts.lint) {
+ Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", varname);
+ free(varname);
+ *out_val = var_Error;
+ return VPR_UNDEF_MSG;
+ }
+
+ if (eflags & VARE_UNDEFERR) {
+ free(varname);
+ *out_val = var_Error;
+ return VPR_UNDEF_SILENT;
+ }
+
+ free(varname);
+ *out_val = varUndefined;
+ return VPR_OK;
+}
+
/* Parse a long variable name enclosed in braces or parentheses such as $(VAR)
* or ${VAR}, up to the closing brace or parenthesis, or in the case of
* ${VAR:Modifiers}, up to the ':' that starts the modifiers.
* Return whether to continue parsing. */
static Boolean
ParseVarnameLong(
- const char **pp,
+ const char *p,
char startc,
GNode *ctxt,
VarEvalFlags eflags,
+ const char **out_FALSE_pp,
VarParseResult *out_FALSE_res,
const char **out_FALSE_val,
- void **out_FALSE_freePtr,
+ void **out_FALSE_freeIt,
char *out_TRUE_endc,
const char **out_TRUE_p,
@@ -3650,10 +3727,10 @@ ParseVarnameLong(
Boolean haveModifier;
Boolean dynamic = FALSE;
- const char *const start = *pp;
+ const char *const start = p;
char endc = startc == '(' ? ')' : '}';
- const char *p = start + 2;
+ p += 2; /* skip "${" or "$(" or "y(" */
varname = ParseVarname(&p, startc, endc, ctxt, eflags, &namelen);
if (*p == ':') {
@@ -3662,8 +3739,8 @@ ParseVarnameLong(
haveModifier = FALSE;
} else {
Parse_Error(PARSE_FATAL, "Unclosed variable \"%s\"", varname);
- *pp = p;
free(varname);
+ *out_FALSE_pp = p;
*out_FALSE_val = var_Error;
*out_FALSE_res = VPR_PARSE_MSG;
return FALSE;
@@ -3674,28 +3751,8 @@ ParseVarnameLong(
/* At this point, p points just after the variable name,
* either at ':' or at endc. */
- /*
- * Check also for bogus D and F forms of local variables since we're
- * in a local context and the name is the right length.
- */
- if (v == NULL && ctxt != VAR_CMDLINE && ctxt != VAR_GLOBAL &&
- namelen == 2 && (varname[1] == 'F' || varname[1] == 'D') &&
- strchr("@%?*!<>", varname[0]) != NULL)
- {
- /*
- * Well, it's local -- go look for it.
- */
- char name[] = { varname[0], '\0' };
- v = VarFind(name, ctxt, 0);
-
- if (v != NULL) {
- if (varname[1] == 'D') {
- *out_TRUE_extraModifiers = "H:";
- } else { /* F */
- *out_TRUE_extraModifiers = "T:";
- }
- }
- }
+ if (v == NULL)
+ v = FindLocalLegacyVar(varname, namelen, ctxt, out_TRUE_extraModifiers);
if (v == NULL) {
/* Defer expansion of dynamic variables if they appear in non-local
@@ -3705,37 +3762,9 @@ ParseVarnameLong(
if (!haveModifier) {
p++; /* skip endc */
- *pp = p;
- if (dynamic) {
- char *pstr = bmake_strsedup(start, p);
- free(varname);
- *out_FALSE_res = VPR_OK;
- *out_FALSE_freePtr = pstr;
- *out_FALSE_val = pstr;
- return FALSE;
- }
-
- if ((eflags & VARE_UNDEFERR) && (eflags & VARE_WANTRES) &&
- DEBUG(LINT))
- {
- Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined",
- varname);
- free(varname);
- *out_FALSE_res = VPR_UNDEF_MSG;
- *out_FALSE_val = var_Error;
- return FALSE;
- }
-
- if (eflags & VARE_UNDEFERR) {
- free(varname);
- *out_FALSE_res = VPR_UNDEF_SILENT;
- *out_FALSE_val = var_Error;
- return FALSE;
- }
-
- free(varname);
- *out_FALSE_res = VPR_OK;
- *out_FALSE_val = varUndefined;
+ *out_FALSE_pp = p;
+ *out_FALSE_res = EvalUndefined(dynamic, start, p, varname, eflags,
+ out_FALSE_freeIt, out_FALSE_val);
return FALSE;
}
@@ -3763,56 +3792,51 @@ ParseVarnameLong(
return TRUE;
}
-/*-
- *-----------------------------------------------------------------------
- * Var_Parse --
- * Given the start of a variable expression (such as $v, $(VAR),
- * ${VAR:Mpattern}), extract the variable name, possibly some
- * modifiers and find its value by applying the modifiers to the
- * original value.
- *
- * When parsing a condition in ParseEmptyArg, pp may also point to
- * the "y" of "empty(VARNAME:Modifiers)", which is syntactically
- * identical.
+/*
+ * Given the start of a variable expression (such as $v, $(VAR),
+ * ${VAR:Mpattern}), extract the variable name and value, and the modifiers,
+ * if any. While doing that, apply the modifiers to the value of the
+ * expression, forming its final value. A few of the modifiers such as :!cmd!
+ * or ::= have side effects.
*
* Input:
- * str The string to parse
- * ctxt The context for the variable
- * flags Select the exact details of parsing
- * out_val_freeIt Must be freed by the caller after using out_val
- *
- * Results:
- * Returns the value of the variable expression, never NULL.
- * Returns var_Error if there was a parse error and VARE_UNDEFERR was
- * set.
- * Returns varUndefined if there was an undefined variable and
- * VARE_UNDEFERR was not set.
- *
- * Parsing should continue at *pp.
- * TODO: Document the value of *pp on parse errors. It might be advanced
- * by 0, or +1, or the index of the parse error, or the guessed end of the
- * variable expression.
- *
- * If var_Error is returned, a diagnostic may or may not have been
- * printed. XXX: This is inconsistent.
- *
- * If varUndefined is returned, a diagnostic may or may not have been
- * printed. XXX: This is inconsistent.
- *
- * After using the returned value, *out_val_freeIt must be freed,
- * preferably using bmake_free since it is NULL in most cases.
+ * *pp The string to parse.
+ * When parsing a condition in ParseEmptyArg, it may also
+ * point to the "y" of "empty(VARNAME:Modifiers)", which
+ * is syntactically the same.
+ * ctxt The context for finding variables
+ * eflags Control the exact details of parsing
*
- * Side Effects:
- * Any effects from the modifiers, such as :!cmd! or ::=value.
- *-----------------------------------------------------------------------
+ * Output:
+ * *pp The position where to continue parsing.
+ * TODO: After a parse error, the value of *pp is
+ * unspecified. It may not have been updated at all,
+ * point to some random character in the string, to the
+ * location of the parse error, or at the end of the
+ * string.
+ * *out_val The value of the variable expression, never NULL.
+ * *out_val var_Error if there was a parse error.
+ * *out_val var_Error if the base variable of the expression was
+ * undefined, eflags contains VARE_UNDEFERR, and none of
+ * the modifiers turned the undefined expression into a
+ * defined expression.
+ * XXX: It is not guaranteed that an error message has
+ * been printed.
+ * *out_val varUndefined if the base variable of the expression
+ * was undefined, eflags did not contain VARE_UNDEFERR,
+ * and none of the modifiers turned the undefined
+ * expression into a defined expression.
+ * XXX: It is not guaranteed that an error message has
+ * been printed.
+ * *out_val_freeIt Must be freed by the caller after using *out_val.
*/
/* coverity[+alloc : arg-*4] */
VarParseResult
Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
const char **out_val, void **out_val_freeIt)
{
- const char *const start = *pp;
- const char *p;
+ const char *p = *pp;
+ const char *const start = p;
Boolean haveModifier; /* TRUE if have modifiers for the variable */
char startc; /* Starting character if variable in parens
* or braces */
@@ -3824,7 +3848,7 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
* result is just the expression, unaltered */
const char *extramodifiers;
Var *v;
- char *nstr;
+ char *value;
char eflags_str[VarEvalFlags_ToStringSize];
VarExprFlags exprFlags = 0;
@@ -3840,17 +3864,17 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
* initialized. */
endc = '\0';
- startc = start[1];
+ startc = p[1];
if (startc != '(' && startc != '{') {
VarParseResult res;
if (!ParseVarnameShort(startc, pp, ctxt, eflags, &res, out_val, &v))
return res;
haveModifier = FALSE;
- p = start + 1;
+ p++;
} else {
VarParseResult res;
- if (!ParseVarnameLong(pp, startc, ctxt, eflags,
- &res, out_val, out_val_freeIt,
+ if (!ParseVarnameLong(p, startc, ctxt, eflags,
+ pp, &res, out_val, out_val_freeIt,
&endc, &p, &v, &haveModifier, &extramodifiers,
&dynamic, &exprFlags))
return res;
@@ -3859,25 +3883,26 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
if (v->flags & VAR_IN_USE)
Fatal("Variable %s is recursive.", v->name);
- /*
- * Before doing any modification, we have to make sure the value
- * has been fully expanded. If it looks like recursion might be
- * necessary (there's a dollar sign somewhere in the variable's value)
- * we just call Var_Subst to do any other substitutions that are
- * necessary. Note that the value returned by Var_Subst will have
- * been dynamically-allocated, so it will need freeing when we
- * return.
- */
- nstr = Buf_GetAll(&v->val, NULL);
- if (strchr(nstr, '$') != NULL && (eflags & VARE_WANTRES)) {
+ /* XXX: This assignment creates an alias to the current value of the
+ * variable. This means that as long as the value of the expression stays
+ * the same, the value of the variable must not change.
+ * Using the '::=' modifier, it could be possible to do exactly this.
+ * At the bottom of this function, the resulting value is compared to the
+ * then-current value of the variable. This might also invoke undefined
+ * behavior. */
+ value = Buf_GetAll(&v->val, NULL);
+
+ /* Before applying any modifiers, expand any nested expressions from the
+ * variable value. */
+ if (strchr(value, '$') != NULL && (eflags & VARE_WANTRES)) {
VarEvalFlags nested_eflags = eflags;
- if (DEBUG(LINT))
+ if (opts.lint)
nested_eflags &= ~(unsigned)VARE_UNDEFERR;
v->flags |= VAR_IN_USE;
- (void)Var_Subst(nstr, ctxt, nested_eflags, &nstr);
+ (void)Var_Subst(value, ctxt, nested_eflags, &value);
v->flags &= ~(unsigned)VAR_IN_USE;
/* TODO: handle errors */
- *out_val_freeIt = nstr;
+ *out_val_freeIt = value;
}
if (haveModifier || extramodifiers != NULL) {
@@ -3886,16 +3911,16 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
extraFree = NULL;
if (extramodifiers != NULL) {
const char *em = extramodifiers;
- nstr = ApplyModifiers(&em, nstr, '(', ')',
- v, &exprFlags, ctxt, eflags, &extraFree);
+ value = ApplyModifiers(&em, value, '\0', '\0',
+ v, &exprFlags, ctxt, eflags, &extraFree);
}
if (haveModifier) {
/* Skip initial colon. */
p++;
- nstr = ApplyModifiers(&p, nstr, startc, endc,
- v, &exprFlags, ctxt, eflags, out_val_freeIt);
+ value = ApplyModifiers(&p, value, startc, endc,
+ v, &exprFlags, ctxt, eflags, out_val_freeIt);
free(extraFree);
} else {
*out_val_freeIt = extraFree;
@@ -3910,52 +3935,96 @@ Var_Parse(const char **pp, GNode *ctxt, VarEvalFlags eflags,
if (v->flags & VAR_FROM_ENV) {
/* Free the environment variable now since we own it,
* but don't free the variable value if it will be returned. */
- Boolean keepValue = nstr == Buf_GetAll(&v->val, NULL);
+ Boolean keepValue = value == Buf_GetAll(&v->val, NULL);
if (keepValue)
- *out_val_freeIt = nstr;
+ *out_val_freeIt = value;
(void)VarFreeEnv(v, !keepValue);
} else if (exprFlags & VEF_UNDEF) {
if (!(exprFlags & VEF_DEF)) {
+ /* TODO: Use a local variable instead of out_val_freeIt.
+ * Variables named out_* must only be written to. */
if (*out_val_freeIt != NULL) {
free(*out_val_freeIt);
*out_val_freeIt = NULL;
}
if (dynamic) {
- nstr = bmake_strsedup(start, p);
- *out_val_freeIt = nstr;
+ value = bmake_strsedup(start, p);
+ *out_val_freeIt = value;
} else {
/* The expression is still undefined, therefore discard the
* actual value and return an error marker instead. */
- nstr = (eflags & VARE_UNDEFERR) ? var_Error : varUndefined;
+ value = eflags & VARE_UNDEFERR ? var_Error : varUndefined;
}
}
- if (nstr != Buf_GetAll(&v->val, NULL))
+ if (value != Buf_GetAll(&v->val, NULL))
Buf_Destroy(&v->val, TRUE);
free(v->name_freeIt);
free(v);
}
- *out_val = nstr;
+ *out_val = value;
return VPR_UNKNOWN;
}
-/* Substitute for all variables in the given string in the given context.
- *
- * If eflags & VARE_UNDEFERR, Parse_Error will be called when an undefined
- * variable is encountered.
- *
- * If eflags & VARE_WANTRES, any effects from the modifiers, such as ::=,
- * :sh or !cmd! take place.
+static void
+VarSubstNested(const char **const pp, Buffer *const buf, GNode *const ctxt,
+ VarEvalFlags const eflags, Boolean *inout_errorReported)
+{
+ const char *p = *pp;
+ const char *nested_p = p;
+ const char *val;
+ void *val_freeIt;
+
+ (void)Var_Parse(&nested_p, ctxt, eflags, &val, &val_freeIt);
+ /* TODO: handle errors */
+
+ if (val == var_Error || val == varUndefined) {
+ if (!preserveUndefined) {
+ p = nested_p;
+ } else if ((eflags & VARE_UNDEFERR) || val == var_Error) {
+ /* XXX: This condition is wrong. If val == var_Error,
+ * this doesn't necessarily mean there was an undefined
+ * variable. It could equally well be a parse error; see
+ * unit-tests/varmod-order.exp. */
+
+ /*
+ * If variable is undefined, complain and skip the
+ * variable. The complaint will stop us from doing anything
+ * when the file is parsed.
+ */
+ if (!*inout_errorReported) {
+ Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",
+ (int)(size_t)(nested_p - p), p);
+ }
+ p = nested_p;
+ *inout_errorReported = TRUE;
+ } else {
+ /* Copy the initial '$' of the undefined expression,
+ * thereby deferring expansion of the expression, but
+ * expand nested expressions if already possible.
+ * See unit-tests/varparse-undef-partial.mk. */
+ Buf_AddByte(buf, *p);
+ p++;
+ }
+ } else {
+ p = nested_p;
+ Buf_AddStr(buf, val);
+ }
+
+ free(val_freeIt);
+
+ *pp = p;
+}
+
+/* Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the
+ * given string.
*
* Input:
- * str the string which to substitute
- * ctxt the context wherein to find variables
- * eflags VARE_UNDEFERR if undefineds are an error
- * VARE_WANTRES if we actually want the result
- * VARE_ASSIGN if we are in a := assignment
- *
- * Results:
- * The resulting string.
+ * str The string in which the variable expressions are
+ * expanded.
+ * ctxt The context in which to start searching for
+ * variables. The other contexts are searched as well.
+ * eflags Special effects during expansion.
*/
VarParseResult
Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res)
@@ -3965,19 +4034,24 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res)
/* Set true if an error has already been reported,
* to prevent a plethora of messages when recursing */
+ /* XXX: Why is the 'static' necessary here? */
static Boolean errorReported;
- Buf_Init(&buf, 0);
+ Buf_Init(&buf);
errorReported = FALSE;
while (*p != '\0') {
if (p[0] == '$' && p[1] == '$') {
/* A dollar sign may be escaped with another dollar sign. */
- if (save_dollars && (eflags & VARE_ASSIGN))
+ if (save_dollars && (eflags & VARE_KEEP_DOLLAR))
Buf_AddByte(&buf, '$');
Buf_AddByte(&buf, '$');
p += 2;
- } else if (*p != '$') {
+
+ } else if (p[0] == '$') {
+ VarSubstNested(&p, &buf, ctxt, eflags, &errorReported);
+
+ } else {
/*
* Skip as many characters as possible -- either to the end of
* the string or to the next dollar sign (variable expression).
@@ -3987,48 +4061,6 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res)
for (p++; *p != '$' && *p != '\0'; p++)
continue;
Buf_AddBytesBetween(&buf, plainStart, p);
- } else {
- const char *nested_p = p;
- void *freeIt;
- const char *val;
- (void)Var_Parse(&nested_p, ctxt, eflags, &val, &freeIt);
- /* TODO: handle errors */
-
- if (val == var_Error || val == varUndefined) {
- /*
- * If performing old-time variable substitution, skip over
- * the variable and continue with the substitution. Otherwise,
- * store the dollar sign and advance str so we continue with
- * the string...
- */
- if (oldVars) {
- p = nested_p;
- } else if ((eflags & VARE_UNDEFERR) || val == var_Error) {
- /*
- * If variable is undefined, complain and skip the
- * variable. The complaint will stop us from doing anything
- * when the file is parsed.
- */
- if (!errorReported) {
- Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",
- (int)(size_t)(nested_p - p), p);
- }
- p = nested_p;
- errorReported = TRUE;
- } else {
- /* Copy the initial '$' of the undefined expression,
- * thereby deferring expansion of the expression, but
- * expand nested expressions if already possible.
- * See unit-tests/varparse-undef-partial.mk. */
- Buf_AddByte(&buf, *p);
- p++;
- }
- } else {
- p = nested_p;
- Buf_AddStr(&buf, val);
- }
- free(freeIt);
- freeIt = NULL;
}
}
@@ -4040,9 +4072,9 @@ Var_Subst(const char *str, GNode *ctxt, VarEvalFlags eflags, char **out_res)
void
Var_Init(void)
{
- VAR_INTERNAL = Targ_NewGN("Internal");
- VAR_GLOBAL = Targ_NewGN("Global");
- VAR_CMDLINE = Targ_NewGN("Command");
+ VAR_INTERNAL = GNode_New("Internal");
+ VAR_GLOBAL = GNode_New("Global");
+ VAR_CMDLINE = GNode_New("Command");
}
/* Clean up the variables module. */