aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon J. Gerraty <sjg@FreeBSD.org>2022-02-05 20:26:16 +0000
committerSimon J. Gerraty <sjg@FreeBSD.org>2022-02-05 20:26:16 +0000
commit9f45a3c8c82ffead7044ae836d9257113c630d3b (patch)
tree28ee128050f4a20a077299fe36cd2a9a1ca8f883
parente515b9b44c20594288eff4628ca0cb155625fa3c (diff)
parentcdde9e894dee2074ef0dd12ddc171e5d3f1513e3 (diff)
downloadsrc-9f45a3c8c82ffead7044ae836d9257113c630d3b.tar.gz
src-9f45a3c8c82ffead7044ae836d9257113c630d3b.zip
Merge bmake-20220204
-rw-r--r--contrib/bmake/ChangeLog155
-rw-r--r--contrib/bmake/FILES41
-rw-r--r--contrib/bmake/VERSION2
-rw-r--r--contrib/bmake/arch.c90
-rw-r--r--contrib/bmake/bmake.151
-rw-r--r--contrib/bmake/bmake.cat139
-rw-r--r--contrib/bmake/buf.c15
-rw-r--r--contrib/bmake/buf.h19
-rw-r--r--contrib/bmake/compat.c93
-rw-r--r--contrib/bmake/cond.c429
-rw-r--r--contrib/bmake/dir.c21
-rw-r--r--contrib/bmake/dir.h14
-rw-r--r--contrib/bmake/filemon/filemon.h4
-rw-r--r--contrib/bmake/for.c228
-rw-r--r--contrib/bmake/hash.c80
-rw-r--r--contrib/bmake/hash.h24
-rw-r--r--contrib/bmake/job.c99
-rw-r--r--contrib/bmake/job.h34
-rw-r--r--contrib/bmake/lst.h20
-rw-r--r--contrib/bmake/main.c548
-rw-r--r--contrib/bmake/make.151
-rw-r--r--contrib/bmake/make.c32
-rw-r--r--contrib/bmake/make.h672
-rw-r--r--contrib/bmake/make_malloc.c12
-rw-r--r--contrib/bmake/make_malloc.h26
-rw-r--r--contrib/bmake/meta.c172
-rw-r--r--contrib/bmake/meta.h8
-rw-r--r--contrib/bmake/metachar.h12
-rw-r--r--contrib/bmake/mk/ChangeLog57
-rw-r--r--contrib/bmake/mk/FILES1
-rw-r--r--contrib/bmake/mk/cc-wrap.mk61
-rw-r--r--contrib/bmake/mk/dirdeps.mk101
-rw-r--r--contrib/bmake/mk/host-target.mk7
-rw-r--r--contrib/bmake/mk/init.mk8
-rwxr-xr-xcontrib/bmake/mk/install-mk4
-rwxr-xr-xcontrib/bmake/mk/meta2deps.py38
-rwxr-xr-xcontrib/bmake/mk/meta2deps.sh29
-rw-r--r--contrib/bmake/mk/mk-files.txt93
-rw-r--r--contrib/bmake/mk/sys.clean-env.mk6
-rw-r--r--contrib/bmake/nonints.h348
-rw-r--r--contrib/bmake/parse.c1608
-rw-r--r--contrib/bmake/str.c29
-rw-r--r--contrib/bmake/str.h46
-rw-r--r--contrib/bmake/suff.c66
-rw-r--r--contrib/bmake/targ.c40
-rw-r--r--contrib/bmake/trace.c17
-rw-r--r--contrib/bmake/unit-tests/Makefile30
-rw-r--r--contrib/bmake/unit-tests/comment.mk6
-rw-r--r--contrib/bmake/unit-tests/cond-func-empty.mk17
-rw-r--r--contrib/bmake/unit-tests/cond-func.exp10
-rw-r--r--contrib/bmake/unit-tests/cond-func.mk20
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.exp7
-rw-r--r--contrib/bmake/unit-tests/cond-op-parentheses.mk36
-rw-r--r--contrib/bmake/unit-tests/cond-short.mk12
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.exp2
-rw-r--r--contrib/bmake/unit-tests/cond-token-number.mk15
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.exp3
-rw-r--r--contrib/bmake/unit-tests/cond-token-plain.mk49
-rw-r--r--contrib/bmake/unit-tests/cond-token-string.exp4
-rw-r--r--contrib/bmake/unit-tests/dep-duplicate.exp4
-rw-r--r--contrib/bmake/unit-tests/dep-duplicate.mk27
-rw-r--r--contrib/bmake/unit-tests/dep-op-missing.exp4
-rw-r--r--contrib/bmake/unit-tests/dep-op-missing.mk13
-rw-r--r--contrib/bmake/unit-tests/dep-wildcards.exp2
-rw-r--r--contrib/bmake/unit-tests/dep.exp6
-rw-r--r--contrib/bmake/unit-tests/dep.mk16
-rw-r--r--contrib/bmake/unit-tests/depsrc-meta.exp2
-rw-r--r--contrib/bmake/unit-tests/depsrc-meta.mk19
-rw-r--r--contrib/bmake/unit-tests/depsrc-use.mk7
-rw-r--r--contrib/bmake/unit-tests/depsrc-usebefore.mk7
-rw-r--r--contrib/bmake/unit-tests/depsrc.exp1
-rw-r--r--contrib/bmake/unit-tests/depsrc.mk14
-rw-r--r--contrib/bmake/unit-tests/deptgt-error.exp10
-rw-r--r--contrib/bmake/unit-tests/deptgt-error.mk22
-rw-r--r--contrib/bmake/unit-tests/deptgt-ignore.exp12
-rw-r--r--contrib/bmake/unit-tests/deptgt-ignore.mk30
-rw-r--r--contrib/bmake/unit-tests/deptgt-interrupt.exp3
-rw-r--r--contrib/bmake/unit-tests/deptgt-interrupt.mk9
-rw-r--r--contrib/bmake/unit-tests/deptgt-main.exp1
-rw-r--r--contrib/bmake/unit-tests/deptgt-main.mk27
-rw-r--r--contrib/bmake/unit-tests/deptgt-notparallel.exp8
-rw-r--r--contrib/bmake/unit-tests/deptgt-notparallel.mk18
-rw-r--r--contrib/bmake/unit-tests/deptgt-order.exp7
-rw-r--r--contrib/bmake/unit-tests/deptgt-order.mk4
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.exp5
-rw-r--r--contrib/bmake/unit-tests/deptgt-path-suffix.mk10
-rw-r--r--contrib/bmake/unit-tests/deptgt.exp13
-rw-r--r--contrib/bmake/unit-tests/deptgt.mk11
-rwxr-xr-xcontrib/bmake/unit-tests/directive-dinclude.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-dinclude.mk25
-rw-r--r--contrib/bmake/unit-tests/directive-elifdef.mk21
-rw-r--r--contrib/bmake/unit-tests/directive-elifndef.mk23
-rw-r--r--contrib/bmake/unit-tests/directive-export-impl.exp16
-rw-r--r--contrib/bmake/unit-tests/directive-for-escape.exp102
-rw-r--r--contrib/bmake/unit-tests/directive-for-escape.mk62
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for-generating-endif.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.exp18
-rwxr-xr-xcontrib/bmake/unit-tests/directive-for.mk81
-rwxr-xr-xcontrib/bmake/unit-tests/directive-hyphen-include.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-hyphen-include.mk22
-rw-r--r--contrib/bmake/unit-tests/directive-if.exp23
-rw-r--r--contrib/bmake/unit-tests/directive-if.mk15
-rw-r--r--contrib/bmake/unit-tests/directive-ifdef.exp3
-rw-r--r--contrib/bmake/unit-tests/directive-ifdef.mk44
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.exp6
-rw-r--r--contrib/bmake/unit-tests/directive-ifmake.mk39
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include.exp2
-rwxr-xr-xcontrib/bmake/unit-tests/directive-include.mk33
-rw-r--r--contrib/bmake/unit-tests/directive-info.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-info.mk13
-rwxr-xr-xcontrib/bmake/unit-tests/directive-sinclude.exp5
-rwxr-xr-xcontrib/bmake/unit-tests/directive-sinclude.mk18
-rw-r--r--contrib/bmake/unit-tests/directive-undef.exp2
-rw-r--r--contrib/bmake/unit-tests/directive-unexport-env.mk5
-rw-r--r--contrib/bmake/unit-tests/directive-warning.exp16
-rw-r--r--contrib/bmake/unit-tests/directive-warning.mk9
-rw-r--r--contrib/bmake/unit-tests/directive.exp10
-rw-r--r--contrib/bmake/unit-tests/directive.mk22
-rw-r--r--contrib/bmake/unit-tests/envfirst.mk44
-rw-r--r--contrib/bmake/unit-tests/hanoi-include.mk6
-rw-r--r--contrib/bmake/unit-tests/include-main.exp12
-rw-r--r--contrib/bmake/unit-tests/include-main.mk4
-rw-r--r--contrib/bmake/unit-tests/include-sub.mk10
-rw-r--r--contrib/bmake/unit-tests/meta-cmd-cmp.exp16
-rw-r--r--contrib/bmake/unit-tests/meta-cmd-cmp.mk36
-rw-r--r--contrib/bmake/unit-tests/modts.exp14
-rw-r--r--contrib/bmake/unit-tests/modts.mk47
-rw-r--r--contrib/bmake/unit-tests/modword.exp126
-rw-r--r--contrib/bmake/unit-tests/modword.mk161
-rw-r--r--contrib/bmake/unit-tests/opt-debug-cond.exp5
-rw-r--r--contrib/bmake/unit-tests/opt-debug-cond.mk22
-rw-r--r--contrib/bmake/unit-tests/opt-debug-curdir.mk10
-rw-r--r--contrib/bmake/unit-tests/opt-debug-file.exp13
-rw-r--r--contrib/bmake/unit-tests/opt-debug-file.mk48
-rw-r--r--contrib/bmake/unit-tests/opt-debug-hash.exp7
-rw-r--r--contrib/bmake/unit-tests/opt-debug-hash.mk9
-rw-r--r--contrib/bmake/unit-tests/opt-debug-parse.exp25
-rw-r--r--contrib/bmake/unit-tests/opt-debug-parse.mk34
-rw-r--r--contrib/bmake/unit-tests/opt-debug-var.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-debug-var.mk30
-rw-r--r--contrib/bmake/unit-tests/opt-debug-x-trace.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-debug-x-trace.mk10
-rw-r--r--contrib/bmake/unit-tests/opt-define.mk30
-rw-r--r--contrib/bmake/unit-tests/opt-env.exp6
-rw-r--r--contrib/bmake/unit-tests/opt-env.mk45
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.exp7
-rw-r--r--contrib/bmake/unit-tests/opt-jobs-internal.mk11
-rw-r--r--contrib/bmake/unit-tests/opt-raw.mk16
-rw-r--r--contrib/bmake/unit-tests/opt-silent.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-silent.mk8
-rw-r--r--contrib/bmake/unit-tests/opt-version.exp2
-rw-r--r--contrib/bmake/unit-tests/opt-version.mk12
-rw-r--r--contrib/bmake/unit-tests/opt-where-am-i.exp3
-rw-r--r--contrib/bmake/unit-tests/opt-where-am-i.mk14
-rw-r--r--contrib/bmake/unit-tests/parse.exp5
-rw-r--r--contrib/bmake/unit-tests/parse.mk14
-rw-r--r--contrib/bmake/unit-tests/posix.mk7
-rw-r--r--contrib/bmake/unit-tests/sh.mk5
-rw-r--r--contrib/bmake/unit-tests/suff-incomplete.exp12
-rw-r--r--contrib/bmake/unit-tests/suff-main-several.exp30
-rw-r--r--contrib/bmake/unit-tests/suff-rebuild.exp22
-rw-r--r--contrib/bmake/unit-tests/var-class-cmdline.exp4
-rw-r--r--contrib/bmake/unit-tests/var-class-global.mk8
-rw-r--r--contrib/bmake/unit-tests/var-class-local.exp5
-rw-r--r--contrib/bmake/unit-tests/var-class-local.mk45
-rw-r--r--contrib/bmake/unit-tests/var-class.exp1
-rw-r--r--contrib/bmake/unit-tests/var-class.mk9
-rw-r--r--contrib/bmake/unit-tests/var-eval-short.exp16
-rw-r--r--contrib/bmake/unit-tests/var-eval-short.mk11
-rw-r--r--contrib/bmake/unit-tests/var-op-expand.mk4
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.exp12
-rw-r--r--contrib/bmake/unit-tests/var-op-shell.mk15
-rw-r--r--contrib/bmake/unit-tests/var-op-sunsh.mk49
-rw-r--r--contrib/bmake/unit-tests/var-recursive.exp7
-rw-r--r--contrib/bmake/unit-tests/var-recursive.mk16
-rw-r--r--contrib/bmake/unit-tests/var-scope-cmdline.exp4
-rw-r--r--contrib/bmake/unit-tests/var-scope-cmdline.mk (renamed from contrib/bmake/unit-tests/var-class-cmdline.mk)2
-rw-r--r--contrib/bmake/unit-tests/var-scope-env.exp (renamed from contrib/bmake/unit-tests/envfirst.exp)0
-rw-r--r--contrib/bmake/unit-tests/var-scope-env.mk (renamed from contrib/bmake/unit-tests/var-class-env.mk)2
-rw-r--r--contrib/bmake/unit-tests/var-scope-global.exp (renamed from contrib/bmake/unit-tests/var-class-env.exp)0
-rw-r--r--contrib/bmake/unit-tests/var-scope-global.mk18
-rw-r--r--contrib/bmake/unit-tests/var-scope-local-legacy.exp (renamed from contrib/bmake/unit-tests/var-class-global.exp)0
-rw-r--r--contrib/bmake/unit-tests/var-scope-local-legacy.mk (renamed from contrib/bmake/unit-tests/var-class-local-legacy.mk)2
-rw-r--r--contrib/bmake/unit-tests/var-scope-local.exp21
-rw-r--r--contrib/bmake/unit-tests/var-scope-local.mk200
-rw-r--r--contrib/bmake/unit-tests/var-scope.exp (renamed from contrib/bmake/unit-tests/var-class-local-legacy.exp)0
-rw-r--r--contrib/bmake/unit-tests/var-scope.mk9
-rw-r--r--contrib/bmake/unit-tests/varmod-assign-shell.exp14
-rw-r--r--contrib/bmake/unit-tests/varmod-assign-shell.mk36
-rw-r--r--contrib/bmake/unit-tests/varmod-ifelse.mk6
-rw-r--r--contrib/bmake/unit-tests/varmod-indirect.exp12
-rw-r--r--contrib/bmake/unit-tests/varmod-indirect.mk6
-rw-r--r--contrib/bmake/unit-tests/varmod-loop.exp6
-rw-r--r--contrib/bmake/unit-tests/varmod-order-numeric.mk7
-rw-r--r--contrib/bmake/unit-tests/varmod-order.mk16
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.exp1
-rw-r--r--contrib/bmake/unit-tests/varmod-quote-dollar.mk6
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.exp125
-rw-r--r--contrib/bmake/unit-tests/varmod-select-words.mk162
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.exp10
-rw-r--r--contrib/bmake/unit-tests/varmod-shell.mk10
-rw-r--r--contrib/bmake/unit-tests/varmod-sun-shell.exp11
-rw-r--r--contrib/bmake/unit-tests/varmod-sun-shell.mk7
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.exp26
-rw-r--r--contrib/bmake/unit-tests/varmod-to-separator.mk93
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.exp7
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-jobs.mk24
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-pid.mk18
-rw-r--r--contrib/bmake/unit-tests/varname-dot-make-ppid.mk25
-rwxr-xr-xcontrib/bmake/unit-tests/varname-dot-shell.exp12
-rw-r--r--contrib/bmake/unit-tests/varname-dot-suffixes.mk6
-rw-r--r--contrib/bmake/unit-tests/varname-empty.exp1
-rw-r--r--contrib/bmake/unit-tests/varname-makeflags.mk20
-rw-r--r--contrib/bmake/unit-tests/varname.mk44
-rw-r--r--contrib/bmake/unit-tests/varparse-errors.exp6
-rw-r--r--contrib/bmake/unit-tests/varparse-errors.mk22
-rw-r--r--contrib/bmake/unit-tests/varquote.mk6
-rw-r--r--contrib/bmake/util.c43
-rw-r--r--contrib/bmake/var.c692
219 files changed, 5442 insertions, 4168 deletions
diff --git a/contrib/bmake/ChangeLog b/contrib/bmake/ChangeLog
index 5eb2d7c5bb4a..a87f39fc49c9 100644
--- a/contrib/bmake/ChangeLog
+++ b/contrib/bmake/ChangeLog
@@ -1,3 +1,158 @@
+2022-02-04 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220204
+ Merge with NetBSD make, pick up
+ o use unsigned consistently for line numbers, avoid the need for %z
+ o parse.c: do not step off end of input in Parse_IsVar
+ when checking for target local variable assignments
+
+2022-02-02 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220202
+ Merge with NetBSD make, pick up
+ o remove redundant declaration of HashIter_Init
+ o make DEBUG0 simpler
+
+2022-01-30 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * cast gn->lineno to avoid %z
+
+ * VERSION (_MAKE_VERSION): 20220130
+ Merge with NetBSD make, pick up
+ o more unit tests
+ o make GNode lineno unsigned to please lint
+ o print location of recursive variable references in commands
+ o print "stack trace" (makefile includes) on fatal errors
+ o make.1: refine documentation for target local assignments
+
+2022-01-28 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220128
+ Merge with NetBSD make, pick up
+ o inline functions called only once
+ o for.c: clean up AddEscape for building the body of a .for loop
+ o hash.c: merge duplicate code for finding an entry in a hash table
+ replace HashEntry_KeyEquals with strncmp
+ o make.1: document quirks of target local variable assignments.
+ o parse.c: cleanup white-space
+
+2022-01-26 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220126
+ Merge with NetBSD make, pick up
+ o allow setting target local variables
+ o more unit tests
+ o add missing newline after "cannot continue" message
+ o meta.c: clean up eat_dots
+ o parse.c: fix filename in warning about duplicate script
+ o var.c: when expanding nested variables, check simple things first
+
+2022-01-16 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220116
+ Merge with NetBSD make, pick up
+ o fix for unit-tests/varname-makeflags on non-BSD systems
+ o use Var_Exists rather than Var_Value where appropriate
+ o remove unnecessary functions for expanding variable names
+ o cond.c: inline EvalBare
+ o main.c: lint cleanup
+ o parse.c: condense code in Parse_IsVar
+ use islower for parsing directives (none have upper case)
+
+2022-01-12 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220112
+ Merge with NetBSD make, pick up
+ o meta.c: add .MAKE.META.CMP_FILTER for filtering commands before
+ comparion, rarely needed but useful when it is.
+
+2022-01-10 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220110
+ Merge with NetBSD make, pick up
+ o inline Buf_Clear
+ o remove redundant braces
+ o rename and inline Targ_Precious
+ o cond.c: remove redundant initializer in CondParser_ComparisonOrLeaf
+ o for.c: clean up handling of .for loops
+ fix reported line numbers of continuation lines
+ add details about .for loop variables to stack traces
+ o job.c: reduce code for initializing error handling in shell
+ o main.c: in Cmd_Exec, return error message instead of format string
+ have as few statements as possible between va_start and va_end
+ add debug logging for capturing the output of external commands
+ o make.c: use consistent variable names for varargs
+ o make_malloc.c: remove duplicate code from bmake_strdup
+ o parse.c: add missing printflike annotations
+ remove redundant lines from stack traces
+ fix stack traces in -dp mode
+ reduce confusing code in ParseForLoop
+ fix line number in debug log after returning from a file
+ rename IFile and its fields to match their actual content
+ clean up ParseDependencySources
+ o var.c: shorten ApplyModifier_Assign
+ rename is_shell_metachar, fix character conversion warning
+ merge calls to ApplyModifier_Time
+ merge duplicate code for modifiers 'gmtime' and 'localtime'
+
+2022-01-04 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * parse.c: loadfile restore extra byte in buffer.
+
+2022-01-01 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20220101
+ Merge with NetBSD make, pick up
+ o more unit-tests
+ o remove unnecessary words from command line options in CmdOpts
+ o rename eunlink to unlink_file
+ o cond.c: make ParseWord in condition parser simpler
+ internally return false for irrelevant leaves in conditions
+ replace table for function lookup in conditions with simple code
+ merge duplicate types CondEvalResult and CondResult
+ o for.c: clean up handling of .for loops and .include directives
+ o main.c: constify cached_realpath
+ clean up Cmd_Exec
+ o parse.c: sync API documentation
+ fix error message when reading more than 1 GB from stdin
+ clean up parsing of makefiles
+ fix line number in error message about open conditionals
+ unexport types VarAssignOp and VarAssign
+ clean up function names
+ remove redundant parameters in dependency parsing functions
+ reduce scope of the list of wildcard target names
+ extract OP_NOTARGET into separate function
+ clean up variable names for parsing dependency lines
+ make debug logging a bit more human-friendly
+ o var.c: condense code in ApplyModifier_Assign
+
+2021-12-21 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * VERSION (_MAKE_VERSION): 20211221
+ Merge with NetBSD make, pick up
+ o more unit-tests
+ o style cleanup
+ o in CLEANUP mode, free interned strings at the very end
+ o fix memory leak for filenames in .for loops
+ o buf.c: avoid memory leak
+ o cond.c: condense CondParser_ComparisonOp
+ o hash.c: change return type of HashTable_Set to void
+ o job.c: change return type of Compat_RunCommand from int to bool
+ o main.c: remove bmake_free
+ o parse.c: condense repetetive code in ParseDirective
+ remove dead code for handling traditional include directives
+ clean up parsing of variable assignments
+ remove unreachable code for parsing the dependency operator
+ clean up loading of files
+ fix memory leak in IncludeFile
+ o var.c: fix memory leak when parsing a variable name
+ fix memory leak from ${.SUFFIXES}
+ reduce memory allocation in modifier ':?' and ':C'
+ condense RegexReplace for the modifier ':C' and avoid strlen
+ merge duplicate code for memory handling in Var_Parse
+ distinguish between short-lived and environment variables
+ rename VarFreeEnv to VarFreeShortLived
+
2021-12-15 Simon J Gerraty <sjg@beast.crufty.net>
* cond.c: fix mem leak in CondParser_Leaf
diff --git a/contrib/bmake/FILES b/contrib/bmake/FILES
index 53b961468db6..8193debe5032 100644
--- a/contrib/bmake/FILES
+++ b/contrib/bmake/FILES
@@ -53,7 +53,6 @@ metachar.c
metachar.h
missing/sys/cdefs.h
mkdeps.sh
-nonints.h
os.sh
parse.c
pathnames.h
@@ -173,10 +172,14 @@ unit-tests/dep-double-colon-indep.exp
unit-tests/dep-double-colon-indep.mk
unit-tests/dep-double-colon.exp
unit-tests/dep-double-colon.mk
+unit-tests/dep-duplicate.exp
+unit-tests/dep-duplicate.mk
unit-tests/dep-exclam.exp
unit-tests/dep-exclam.mk
unit-tests/dep-none.exp
unit-tests/dep-none.mk
+unit-tests/dep-op-missing.exp
+unit-tests/dep-op-missing.mk
unit-tests/dep-percent.exp
unit-tests/dep-percent.mk
unit-tests/dep-var.exp
@@ -371,8 +374,6 @@ unit-tests/doterror.exp
unit-tests/doterror.mk
unit-tests/dotwait.exp
unit-tests/dotwait.mk
-unit-tests/envfirst.exp
-unit-tests/envfirst.mk
unit-tests/error.exp
unit-tests/error.mk
unit-tests/escape.exp
@@ -427,10 +428,6 @@ unit-tests/modmatch.exp
unit-tests/modmatch.mk
unit-tests/modmisc.exp
unit-tests/modmisc.mk
-unit-tests/modts.exp
-unit-tests/modts.mk
-unit-tests/modword.exp
-unit-tests/modword.mk
unit-tests/objdir-writable.exp
unit-tests/objdir-writable.mk
unit-tests/opt-backwards.exp
@@ -535,6 +532,8 @@ unit-tests/opt-var-expanded.exp
unit-tests/opt-var-expanded.mk
unit-tests/opt-var-literal.exp
unit-tests/opt-var-literal.mk
+unit-tests/opt-version.exp
+unit-tests/opt-version.mk
unit-tests/opt-warnings-as-errors.exp
unit-tests/opt-warnings-as-errors.mk
unit-tests/opt-where-am-i.exp
@@ -547,6 +546,8 @@ unit-tests/order.exp
unit-tests/order.mk
unit-tests/parse-var.exp
unit-tests/parse-var.mk
+unit-tests/parse.exp
+unit-tests/parse.mk
unit-tests/phony-end.exp
unit-tests/phony-end.mk
unit-tests/posix.exp
@@ -625,18 +626,6 @@ unit-tests/unexport.exp
unit-tests/unexport.mk
unit-tests/use-inference.exp
unit-tests/use-inference.mk
-unit-tests/var-class-cmdline.exp
-unit-tests/var-class-cmdline.mk
-unit-tests/var-class-env.exp
-unit-tests/var-class-env.mk
-unit-tests/var-class-global.exp
-unit-tests/var-class-global.mk
-unit-tests/var-class-local-legacy.exp
-unit-tests/var-class-local-legacy.mk
-unit-tests/var-class-local.exp
-unit-tests/var-class-local.mk
-unit-tests/var-class.exp
-unit-tests/var-class.mk
unit-tests/var-eval-short.exp
unit-tests/var-eval-short.mk
unit-tests/var-op-append.exp
@@ -655,6 +644,18 @@ unit-tests/var-op.exp
unit-tests/var-op.mk
unit-tests/var-recursive.exp
unit-tests/var-recursive.mk
+unit-tests/var-scope-cmdline.exp
+unit-tests/var-scope-cmdline.mk
+unit-tests/var-scope-env.exp
+unit-tests/var-scope-env.mk
+unit-tests/var-scope-global.exp
+unit-tests/var-scope-global.mk
+unit-tests/var-scope-local-legacy.exp
+unit-tests/var-scope-local-legacy.mk
+unit-tests/var-scope-local.exp
+unit-tests/var-scope-local.mk
+unit-tests/var-scope.exp
+unit-tests/var-scope.mk
unit-tests/varcmd.exp
unit-tests/varcmd.mk
unit-tests/vardebug.exp
@@ -663,6 +664,8 @@ unit-tests/varfind.exp
unit-tests/varfind.mk
unit-tests/varmisc.exp
unit-tests/varmisc.mk
+unit-tests/varmod-assign-shell.exp
+unit-tests/varmod-assign-shell.mk
unit-tests/varmod-assign.exp
unit-tests/varmod-assign.mk
unit-tests/varmod-defined.exp
diff --git a/contrib/bmake/VERSION b/contrib/bmake/VERSION
index 8485c4810bf0..ae363192cf1c 100644
--- a/contrib/bmake/VERSION
+++ b/contrib/bmake/VERSION
@@ -1,2 +1,2 @@
# keep this compatible with sh and make
-_MAKE_VERSION=20211212
+_MAKE_VERSION=20220204
diff --git a/contrib/bmake/arch.c b/contrib/bmake/arch.c
index 515713af7af7..5e561b132f36 100644
--- a/contrib/bmake/arch.c
+++ b/contrib/bmake/arch.c
@@ -1,4 +1,4 @@
-/* $NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $ */
+/* $NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -147,7 +147,7 @@ struct ar_hdr {
#include "dir.h"
/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: arch.c,v 1.205 2021/12/12 22:41:47 rillig Exp $");
+MAKE_RCSID("$NetBSD: arch.c,v 1.210 2022/01/15 18:34:41 rillig Exp $");
typedef struct List ArchList;
typedef struct ListNode ArchListNode;
@@ -247,19 +247,20 @@ FullName(const char *archive, const char *member)
bool
Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
{
- char *cp; /* Pointer into line */
+ char *spec; /* For modifying some bytes of *pp */
+ const char *cp; /* Pointer into line */
GNode *gn; /* New node */
- MFStr libName; /* Library-part of specification */
+ FStr lib; /* Library-part of specification */
FStr mem; /* Member-part of specification */
char saveChar; /* Ending delimiter of member-name */
- bool expandLibName; /* Whether the parsed libName contains
- * variable expressions that need to be
- * expanded */
+ bool expandLib; /* Whether the parsed lib contains variable
+ * expressions that need to be expanded */
- libName = MFStr_InitRefer(*pp);
- expandLibName = false;
+ spec = *pp;
+ lib = FStr_InitRefer(spec);
+ expandLib = false;
- for (cp = libName.str; *cp != '(' && *cp != '\0';) {
+ for (cp = lib.str; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
/* Expand nested variable expressions. */
/* XXX: This code can probably be shortened. */
@@ -276,20 +277,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
if (isError)
return false;
- expandLibName = true;
+ expandLib = true;
cp += nested_p - cp;
} else
cp++;
}
- *cp++ = '\0';
- if (expandLibName) {
- char *expanded;
- (void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded);
- /* TODO: handle errors */
- libName = MFStr_InitOwn(expanded);
- }
-
+ spec[cp++ - spec] = '\0';
+ if (expandLib)
+ Var_Expand(&lib, scope, VARE_UNDEFERR);
for (;;) {
/*
@@ -299,13 +295,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
bool doSubst = false;
- pp_skip_whitespace(&cp);
+ cpp_skip_whitespace(&cp);
mem = FStr_InitRefer(cp);
while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) {
if (*cp == '$') {
/* Expand nested variable expressions. */
- /* XXX: This code can probably be shortened. */
+ /*
+ * XXX: This code can probably be shortened.
+ */
FStr result;
bool isError;
const char *nested_p = cp;
@@ -334,8 +332,8 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
if (*cp == '\0') {
Parse_Error(PARSE_FATAL,
- "No closing parenthesis "
- "in archive specification");
+ "No closing parenthesis "
+ "in archive specification");
return false;
}
@@ -346,7 +344,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
break;
saveChar = *cp;
- *cp = '\0';
+ spec[cp - spec] = '\0';
/*
* XXX: This should be taken care of intelligently by
@@ -363,19 +361,16 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
*/
if (doSubst) {
char *fullName;
- char *p, *expandedMem;
+ char *p;
const char *unexpandedMem = mem.str;
- (void)Var_Subst(mem.str, scope, VARE_UNDEFERR,
- &expandedMem);
- /* TODO: handle errors */
- mem = FStr_InitOwn(expandedMem);
+ Var_Expand(&mem, scope, VARE_UNDEFERR);
/*
* Now form an archive spec and recurse to deal with
* nested variables and multi-word variable values.
*/
- fullName = FullName(libName.str, mem.str);
+ fullName = FullName(lib.str, mem.str);
p = fullName;
if (strcmp(mem.str, unexpandedMem) == 0) {
@@ -404,7 +399,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
while (!Lst_IsEmpty(&members)) {
char *member = Lst_Dequeue(&members);
- char *fullname = FullName(libName.str, member);
+ char *fullname = FullName(lib.str, member);
free(member);
gn = Targ_GetNode(fullname);
@@ -416,7 +411,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
Lst_Done(&members);
} else {
- char *fullname = FullName(libName.str, mem.str);
+ char *fullname = FullName(lib.str, mem.str);
gn = Targ_GetNode(fullname);
free(fullname);
@@ -432,15 +427,15 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope)
}
FStr_Done(&mem);
- *cp = saveChar;
+ spec[cp - spec] = saveChar;
}
- MFStr_Done(&libName);
+ FStr_Done(&lib);
cp++; /* skip the ')' */
/* We promised that pp would be set up at the next non-space. */
- pp_skip_whitespace(&cp);
- *pp = cp;
+ cpp_skip_whitespace(&cp);
+ *pp += cp - *pp;
return true;
}
@@ -672,7 +667,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
if (ar->fnametab != NULL) {
DEBUG0(ARCH,
- "Attempted to redefine an SVR4 name table\n");
+ "Attempted to redefine an SVR4 name table\n");
return -1;
}
@@ -693,8 +688,9 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
entry++;
*ptr = '\0';
}
- DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n",
- (unsigned long)entry);
+ DEBUG1(ARCH,
+ "Found svr4 archive name table with %lu entries\n",
+ (unsigned long)entry);
return 0;
}
@@ -708,7 +704,7 @@ ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch)
}
if (entry >= ar->fnamesize) {
DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
- inout_name, (unsigned long)ar->fnamesize);
+ inout_name, (unsigned long)ar->fnamesize);
return 2;
}
@@ -737,8 +733,10 @@ ArchiveMember_HasName(const struct ar_hdr *hdr,
if (ar_name[namelen] == ' ')
return true;
- /* In archives created by GNU binutils 2.27, the member names end with
- * a slash. */
+ /*
+ * In archives created by GNU binutils 2.27, the member names end
+ * with a slash.
+ */
if (ar_name[namelen] == '/' &&
(namelen == ar_name_len || ar_name[namelen + 1] == ' '))
return true;
@@ -809,9 +807,9 @@ ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh,
}
DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n",
- archive,
- (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
- (int)sizeof out_arh->ar_date, out_arh->ar_date);
+ archive,
+ (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME,
+ (int)sizeof out_arh->ar_date, out_arh->ar_date);
if (ArchiveMember_HasName(out_arh, member, len)) {
/*
@@ -912,7 +910,7 @@ Arch_Touch(GNode *gn)
struct ar_hdr arh;
f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh,
- "r+");
+ "r+");
if (f == NULL)
return;
diff --git a/contrib/bmake/bmake.1 b/contrib/bmake/bmake.1
index daea73cf538d..8f34441c34b4 100644
--- a/contrib/bmake/bmake.1
+++ b/contrib/bmake/bmake.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $
+.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd December 12, 2021
+.Dd January 28, 2022
.Dt BMAKE 1
.Os
.Sh NAME
@@ -691,10 +691,38 @@ Variables defined as part of the command line.
Variables that are defined specific to a certain target.
.El
.Pp
-Local variables are all built in and their values vary magically from
-target to target.
-It is not currently possible to define new local variables.
-The seven local variables are as follows:
+Local variables can be set on a dependency line, if
+.Va .MAKE.TARGET_LOCAL_VARIABLES ,
+is not set to
+.Ql false .
+The rest of the line
+(which will already have had Global variables expanded),
+is the variable value.
+For example:
+.Bd -literal -offset indent
+COMPILER_WRAPPERS+= ccache distcc icecc
+
+${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
+.Ed
+.Pp
+Only the targets
+.Ql ${OBJS}
+will be impacted by that filter (in "meta" mode) and
+simply enabling/disabling any of the wrappers will not render all
+of those targets out-of-date.
+.Pp
+.Em NOTE :
+target local variable assignments behave differently in that;
+.Bl -tag -width Ds -offset indent
+.It Ic \&+=
+Only appends to a previous local assignment
+for the same target and variable.
+.It Ic \&:=
+Is redundant with respect to Global variables,
+which have already been expanded.
+.El
+.Pp
+The seven built-in local variables are as follows:
.Bl -tag -width ".ARCHIVE" -offset indent
.It Va .ALLSRC
The list of all sources for this target; also known as
@@ -846,6 +874,11 @@ For example:
would produce tokens like
.Ql ---make[1234] target ---
making it easier to track the degree of parallelism being achieved.
+.It .MAKE.TARGET_LOCAL_VARIABLES
+If set to
+.Ql false ,
+apparent variable assignments in dependency lines are
+treated as normal sources.
.It Ev MAKEFLAGS
The environment variable
.Ql Ev MAKEFLAGS
@@ -954,6 +987,12 @@ If a file that was generated outside of
.Va .OBJDIR
but within said bailiwick is missing,
the current target is considered out-of-date.
+.It Va .MAKE.META.CMP_FILTER
+In "meta" mode, it can (very rarely!) be useful to filter command
+lines before comparison.
+This variable can be set to a set of modifiers that will be applied to
+each line of the old and new command that differ, if the filtered
+commands still differ, the target is considered out-of-date.
.It Va .MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the meta files
updated.
diff --git a/contrib/bmake/bmake.cat1 b/contrib/bmake/bmake.cat1
index 73191dea7344..6aa6f382d54b 100644
--- a/contrib/bmake/bmake.cat1
+++ b/contrib/bmake/bmake.cat1
@@ -443,9 +443,28 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
Local variables
Variables that are defined specific to a certain target.
- Local variables are all built in and their values vary magically from
- target to target. It is not currently possible to define new local vari-
- ables. The seven local variables are as follows:
+ Local variables can be set on a dependency line, if
+ .MAKE.TARGET_LOCAL_VARIABLES, is not set to `false'. The rest of the
+ line (which will already have had Global variables expanded), is the
+ variable value. For example:
+
+ COMPILER_WRAPPERS+= ccache distcc icecc
+
+ ${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
+
+ Only the targets `${OBJS}' will be impacted by that filter (in "meta"
+ mode) and simply enabling/disabling any of the wrappers will not render
+ all of those targets out-of-date.
+
+ NOTE: target local variable assignments behave differently in that;
+
+ += Only appends to a previous local assignment for the same
+ target and variable.
+
+ := Is redundant with respect to Global variables, which have
+ already been expanded.
+
+ The seven built-in local variables are as follows:
.ALLSRC The list of all sources for this target; also known as
`>'.
@@ -538,6 +557,10 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
ing it easier to track the degree of parallelism being
achieved.
+ .MAKE.TARGET_LOCAL_VARIABLES
+ If set to `false', apparent variable assignments in de-
+ pendency lines are treated as normal sources.
+
MAKEFLAGS The environment variable `MAKEFLAGS' may contain anything
that may be specified on bmake's command line. Anything
specified on bmake's command line is appended to the
@@ -616,6 +639,14 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
generated outside of .OBJDIR but within said bailiwick is
missing, the current target is considered out-of-date.
+ .MAKE.META.CMP_FILTER
+ In "meta" mode, it can (very rarely!) be useful to filter
+ command lines before comparison. This variable can be
+ set to a set of modifiers that will be applied to each
+ line of the old and new command that differ, if the fil-
+ tered commands still differ, the target is considered
+ out-of-date.
+
.MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the
meta files updated. If not empty, it can be used to
@@ -1591,4 +1622,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1)
There is no way of escaping a space character in a filename.
-FreeBSD 13.0 December 12, 2021 FreeBSD 13.0
+FreeBSD 13.0 January 28, 2022 FreeBSD 13.0
diff --git a/contrib/bmake/buf.c b/contrib/bmake/buf.c
index 8c26cfa24955..73b2eac17d4f 100644
--- a/contrib/bmake/buf.c
+++ b/contrib/bmake/buf.c
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.c,v 1.53 2021/11/28 22:48:06 rillig Exp $ */
+/* $NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -75,7 +75,7 @@
#include "make.h"
/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: buf.c,v 1.53 2021/11/28 22:48:06 rillig Exp $");
+MAKE_RCSID("$NetBSD: buf.c,v 1.55 2022/01/08 17:25:19 rillig Exp $");
/* Make space in the buffer for adding at least 16 more bytes. */
void
@@ -138,14 +138,6 @@ Buf_AddFlag(Buffer *buf, bool flag, const char *name)
}
}
-/* Mark the buffer as empty, so it can be filled with data again. */
-void
-Buf_Empty(Buffer *buf)
-{
- buf->len = 0;
- buf->data[0] = '\0';
-}
-
/* Initialize a buffer. */
void
Buf_InitSize(Buffer *buf, size_t cap)
@@ -214,8 +206,9 @@ Buf_DoneDataCompact(Buffer *buf)
if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) {
/* We trust realloc to be smart */
char *data = bmake_realloc(buf->data, buf->len + 1);
+ buf->data = NULL;
data[buf->len] = '\0'; /* XXX: unnecessary */
- Buf_DoneData(buf);
+ Buf_Done(buf);
return data;
}
#endif
diff --git a/contrib/bmake/buf.h b/contrib/bmake/buf.h
index af36773405b0..14d048bfa514 100644
--- a/contrib/bmake/buf.h
+++ b/contrib/bmake/buf.h
@@ -1,4 +1,4 @@
-/* $NetBSD: buf.h,v 1.44 2021/11/28 22:48:06 rillig Exp $ */
+/* $NetBSD: buf.h,v 1.47 2022/01/08 17:25:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -88,6 +88,14 @@ typedef struct Buffer {
void Buf_Expand(Buffer *);
+/* Mark the buffer as empty, so it can be filled with data again. */
+MAKE_INLINE void
+Buf_Clear(Buffer *buf)
+{
+ buf->len = 0;
+ buf->data[0] = '\0';
+}
+
/* Buf_AddByte adds a single byte to a buffer. */
MAKE_INLINE void
Buf_AddByte(Buffer *buf, char byte)
@@ -101,7 +109,7 @@ Buf_AddByte(Buffer *buf, char byte)
end[1] = '\0';
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
Buf_EndsWith(const Buffer *buf, char ch)
{
return buf->len > 0 && buf->data[buf->len - 1] == ch;
@@ -112,11 +120,10 @@ void Buf_AddBytesBetween(Buffer *, const char *, const char *);
void Buf_AddStr(Buffer *, const char *);
void Buf_AddInt(Buffer *, int);
void Buf_AddFlag(Buffer *, bool, const char *);
-void Buf_Empty(Buffer *);
void Buf_Init(Buffer *);
void Buf_InitSize(Buffer *, size_t);
void Buf_Done(Buffer *);
-char *Buf_DoneData(Buffer *);
-char *Buf_DoneDataCompact(Buffer *);
+char *Buf_DoneData(Buffer *) MAKE_ATTR_USE;
+char *Buf_DoneDataCompact(Buffer *) MAKE_ATTR_USE;
-#endif /* MAKE_BUF_H */
+#endif
diff --git a/contrib/bmake/compat.c b/contrib/bmake/compat.c
index df487dbdf254..ad280206157a 100644
--- a/contrib/bmake/compat.c
+++ b/contrib/bmake/compat.c
@@ -1,4 +1,4 @@
-/* $NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $ */
+/* $NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -99,7 +99,7 @@
#include "pathnames.h"
/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: compat.c,v 1.229 2021/11/28 23:12:51 rillig Exp $");
+MAKE_RCSID("$NetBSD: compat.c,v 1.238 2022/01/22 18:59:23 rillig Exp $");
static GNode *curTarg = NULL;
static pid_t compatChild;
@@ -112,10 +112,10 @@ static int compatSigno;
static void
CompatDeleteTarget(GNode *gn)
{
- if (gn != NULL && !Targ_Precious(gn)) {
+ if (gn != NULL && !GNode_IsPrecious(gn)) {
const char *file = GNode_VarTarget(gn);
- if (!opts.noExecute && eunlink(file) != -1) {
+ if (!opts.noExecute && unlink_file(file)) {
Error("*** %s removed", file);
}
}
@@ -135,7 +135,7 @@ CompatInterrupt(int signo)
{
CompatDeleteTarget(curTarg);
- if (curTarg != NULL && !Targ_Precious(curTarg)) {
+ if (curTarg != NULL && !GNode_IsPrecious(curTarg)) {
/*
* Run .INTERRUPT only if hit with interrupt signal
*/
@@ -168,10 +168,12 @@ DebugFailedTarget(const char *cmd, const GNode *gn)
{
const char *p = cmd;
debug_printf("\n*** Failed target: %s\n*** Failed command: ",
- gn->name);
+ gn->name);
- /* Replace runs of whitespace with a single space, to reduce
- * the amount of whitespace for multi-line command lines. */
+ /*
+ * Replace runs of whitespace with a single space, to reduce the
+ * amount of whitespace for multi-line command lines.
+ */
while (*p != '\0') {
if (ch_isspace(*p)) {
debug_printf(" ");
@@ -220,24 +222,24 @@ UseShell(const char *cmd MAKE_ATTR_UNUSED)
* ln List node that contains the command
*
* Results:
- * 0 if the command succeeded, 1 if an error occurred.
+ * true if the command succeeded.
*/
-int
+bool
Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
{
char *cmdStart; /* Start of expanded command */
char *bp;
bool silent; /* Don't print command */
bool doIt; /* Execute even if -n */
- volatile bool errCheck; /* Check errors */
+ volatile bool errCheck; /* Check errors */
WAIT_T reason; /* Reason for child's death */
WAIT_T status; /* Description of child's death */
pid_t cpid; /* Child actually found */
pid_t retstat; /* Result of wait */
const char **volatile av; /* Argument vector for thing to exec */
char **volatile mav; /* Copy of the argument vector for freeing */
- bool useShell; /* True if command should be executed
- * using a shell */
+ bool useShell; /* True if command should be executed using a
+ * shell */
const char *volatile cmd = cmdp;
silent = (gn->type & OP_SILENT) != OP_NONE;
@@ -249,7 +251,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
if (cmdStart[0] == '\0') {
free(cmdStart);
- return 0;
+ return true;
}
cmd = cmdStart;
LstNode_Set(ln, cmdStart);
@@ -269,12 +271,12 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* usual '$$'.
*/
Lst_Append(&endNode->commands, cmdStart);
- return 0;
+ return true;
}
}
if (strcmp(cmdStart, "...") == 0) {
gn->type |= OP_SAVE_CMDS;
- return 0;
+ return true;
}
for (;;) {
@@ -298,7 +300,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* If we did not end up with a command, just skip it.
*/
if (cmd[0] == '\0')
- return 0;
+ return true;
useShell = UseShell(cmd);
/*
@@ -315,7 +317,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
* we go...
*/
if (!doIt && !GNode_ShouldExecute(gn))
- return 0;
+ return true;
DEBUG1(JOB, "Execute: '%s'\n", cmd);
@@ -350,25 +352,20 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
}
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_compat_start();
- }
#endif
Var_ReexportVars();
- /*
- * Fork and execute the single command. If the fork fails, we abort.
- */
compatChild = cpid = vfork();
- if (cpid < 0) {
+ if (cpid < 0)
Fatal("Could not fork");
- }
+
if (cpid == 0) {
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_compat_child();
- }
#endif
(void)execvp(av[0], (char *const *)UNCONST(av));
execDie("exec", av[0]);
@@ -382,9 +379,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
LstNode_SetNull(ln);
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_compat_parent(cpid);
- }
#endif
/*
@@ -406,9 +402,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
} else if (WIFEXITED(reason)) {
status = WEXITSTATUS(reason); /* exited */
#if defined(USE_META) && defined(USE_FILEMON_ONCE)
- if (useMeta) {
- meta_cmd_finish(NULL);
- }
+ if (useMeta)
+ meta_cmd_finish(NULL);
#endif
if (status != 0) {
if (DEBUG(ERROR))
@@ -424,9 +419,8 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
if (!WIFEXITED(reason) || status != 0) {
if (errCheck) {
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_job_error(NULL, gn, false, status);
- }
#endif
gn->made = ERROR;
if (opts.keepgoing) {
@@ -457,7 +451,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)
kill(myPid, compatSigno);
}
- return status;
+ return status == 0;
}
static void
@@ -467,7 +461,7 @@ RunCommands(GNode *gn)
for (ln = gn->commands.first; ln != NULL; ln = ln->next) {
const char *cmd = ln->datum;
- if (Compat_RunCommand(cmd, gn, ln) != 0)
+ if (!Compat_RunCommand(cmd, gn, ln))
break;
}
}
@@ -532,7 +526,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
* to tell him/her "yes".
*/
DEBUG0(MAKE, "out-of-date.\n");
- if (opts.queryFlag)
+ if (opts.query)
exit(1);
/*
@@ -547,7 +541,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
*/
if (opts.ignoreErrors)
gn->type |= OP_IGNORE;
- if (opts.beSilent)
+ if (opts.silent)
gn->type |= OP_SILENT;
if (Job_CheckCommands(gn, Fatal)) {
@@ -555,12 +549,11 @@ MakeUnmade(GNode *gn, GNode *pgn)
* Our commands are ok, but we still have to worry about
* the -t flag.
*/
- if (!opts.touchFlag || (gn->type & OP_MAKE)) {
+ if (!opts.touch || (gn->type & OP_MAKE)) {
curTarg = gn;
#ifdef USE_META
- if (useMeta && GNode_ShouldExecute(gn)) {
+ if (useMeta && GNode_ShouldExecute(gn))
meta_job_start(NULL, gn);
- }
#endif
RunCommands(gn);
curTarg = NULL;
@@ -593,7 +586,7 @@ MakeUnmade(GNode *gn, GNode *pgn)
} else if (opts.keepgoing) {
pgn->flags.remake = false;
} else {
- PrintOnError(gn, "\nStop.");
+ PrintOnError(gn, "\nStop.\n");
exit(1);
}
return true;
@@ -681,7 +674,7 @@ MakeBeginNode(void)
Compat_Make(gn, gn);
if (GNode_IsError(gn)) {
- PrintOnError(gn, "\nStop.");
+ PrintOnError(gn, "\nStop.\n");
exit(1);
}
}
@@ -715,12 +708,14 @@ Compat_Run(GNodeList *targs)
InitSignals();
- /* Create the .END node now, to keep the (debug) output of the
- * counter.mk test the same as before 2020-09-23. This implementation
- * detail probably doesn't matter though. */
+ /*
+ * Create the .END node now, to keep the (debug) output of the
+ * counter.mk test the same as before 2020-09-23. This
+ * implementation detail probably doesn't matter though.
+ */
(void)Targ_GetEndNode();
- if (!opts.queryFlag)
+ if (!opts.query)
MakeBeginNode();
/*
@@ -737,7 +732,7 @@ Compat_Run(GNodeList *targs)
printf("`%s' is up to date.\n", gn->name);
} else if (gn->made == ABORTED) {
printf("`%s' not remade because of errors.\n",
- gn->name);
+ gn->name);
}
if (GNode_IsError(gn) && errorNode == NULL)
errorNode = gn;
@@ -756,7 +751,7 @@ Compat_Run(GNodeList *targs)
Targ_PrintGraph(2);
else if (DEBUG(GRAPH3))
Targ_PrintGraph(3);
- PrintOnError(errorNode, "\nStop.");
+ PrintOnError(errorNode, "\nStop.\n");
exit(1);
}
}
diff --git a/contrib/bmake/cond.c b/contrib/bmake/cond.c
index 96609682866b..df0129a979a9 100644
--- a/contrib/bmake/cond.c
+++ b/contrib/bmake/cond.c
@@ -1,4 +1,4 @@
-/* $NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $ */
+/* $NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -95,10 +95,10 @@
#include "dir.h"
/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.327 2022/01/29 01:12:36 rillig Exp $");
/*
- * The parsing of conditional expressions is based on this grammar:
+ * Conditional expressions conform to this grammar:
* Or -> And ('||' And)*
* And -> Term ('&&' Term)*
* Term -> Function '(' Argument ')'
@@ -109,13 +109,13 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
* Leaf -> "string"
* Leaf -> Number
* Leaf -> VariableExpression
- * Leaf -> Symbol
+ * Leaf -> BareWord
* Operator -> '==' | '!=' | '>' | '<' | '>=' | '<='
*
- * 'Symbol' is an unquoted string literal to which the default function is
- * applied.
+ * BareWord is an unquoted string literal, its evaluation depends on the kind
+ * of '.if' directive.
*
- * The tokens are scanned by CondToken, which returns:
+ * The tokens are scanned by CondParser_Token, which returns:
* TOK_AND for '&&'
* TOK_OR for '||'
* TOK_NOT for '!'
@@ -123,18 +123,14 @@ MAKE_RCSID("$NetBSD: cond.c,v 1.302 2021/12/12 09:36:00 rillig Exp $");
* TOK_RPAREN for ')'
*
* Other terminal symbols are evaluated using either the default function or
- * the function given in the terminal, they return either TOK_TRUE or
- * TOK_FALSE.
+ * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE
+ * or TOK_ERROR.
*/
typedef enum Token {
TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT,
TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
} Token;
-typedef enum CondResult {
- CR_FALSE, CR_TRUE, CR_ERROR
-} CondResult;
-
typedef enum ComparisonOp {
LT, LE, GT, GE, EQ, NE
} ComparisonOp;
@@ -186,10 +182,14 @@ static unsigned int cond_min_depth = 0; /* depth at makefile open */
/* Names for ComparisonOp. */
static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" };
-static bool
-is_token(const char *str, const char *tok, unsigned char len)
+MAKE_INLINE bool
+skip_string(const char **pp, const char *str)
{
- return strncmp(str, tok, (size_t)len) == 0 && !ch_isalpha(str[len]);
+ size_t len = strlen(str);
+ bool ok = strncmp(*pp, str, len) == 0;
+ if (ok)
+ *pp += len;
+ return ok;
}
static Token
@@ -208,31 +208,13 @@ CondParser_SkipWhitespace(CondParser *par)
* Parse a single word, taking into account balanced parentheses as well as
* embedded expressions. Used for the argument of a built-in function as
* well as for bare words, which are then passed to the default function.
- *
- * Arguments:
- * *pp initially points at the '(',
- * upon successful return it points right after the ')'.
- *
- * *out_arg receives the argument as string.
- *
- * func says whether the argument belongs to an actual function, or
- * NULL when parsing a bare word.
- *
- * Return the length of the argument, or an ambiguous 0 on error.
*/
-static size_t
-ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
- char **out_arg)
+static char *
+ParseWord(const char **pp, bool doEval)
{
const char *p = *pp;
Buffer argBuf;
int paren_depth;
- size_t argLen;
-
- if (func != NULL)
- p++; /* Skip opening '(' - verified by caller */
-
- cpp_skip_hspace(&p);
Buf_InitSize(&argBuf, 16);
@@ -243,7 +225,7 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
break;
if ((ch == '&' || ch == '|') && paren_depth == 0)
break;
- if (*p == '$') {
+ if (ch == '$') {
/*
* Parse the variable expression and install it as
* part of the argument if it's valid. We tell
@@ -266,58 +248,73 @@ ParseWord(CondParser *par, const char **pp, bool doEval, const char *func,
paren_depth++;
else if (ch == ')' && --paren_depth < 0)
break;
- Buf_AddByte(&argBuf, *p);
+ Buf_AddByte(&argBuf, ch);
p++;
}
- argLen = argBuf.len;
- *out_arg = Buf_DoneData(&argBuf);
+ cpp_skip_hspace(&p);
+ *pp = p;
+ return Buf_DoneData(&argBuf);
+}
+
+/* Parse the function argument, including the surrounding parentheses. */
+static char *
+ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func)
+{
+ const char *p = *pp;
+ char *res;
+
+ p++; /* Skip opening '(' - verified by caller */
cpp_skip_hspace(&p);
+ res = ParseWord(&p, doEval);
+ cpp_skip_hspace(&p);
+
+ if (*p++ != ')') {
+ int len = 0;
+ while (ch_isalpha(func[len]))
+ len++;
- if (func != NULL && *p++ != ')') {
Parse_Error(PARSE_FATAL,
- "Missing closing parenthesis for %s()", func);
+ "Missing closing parenthesis for %.*s()", len, func);
par->printedError = true;
- return 0;
+ free(res);
+ return NULL;
}
*pp = p;
- return argLen;
+ return res;
}
/* Test whether the given variable is defined. */
static bool
-FuncDefined(const char *arg)
+FuncDefined(const char *var)
{
- FStr value = Var_Value(SCOPE_CMDLINE, arg);
- bool result = value.str != NULL;
- FStr_Done(&value);
- return result;
+ return Var_Exists(SCOPE_CMDLINE, var);
}
/* See if the given target is requested to be made. */
static bool
-FuncMake(const char *arg)
+FuncMake(const char *target)
{
StringListNode *ln;
for (ln = opts.create.first; ln != NULL; ln = ln->next)
- if (Str_Match(ln->datum, arg))
+ if (Str_Match(ln->datum, target))
return true;
return false;
}
/* See if the given file exists. */
static bool
-FuncExists(const char *arg)
+FuncExists(const char *file)
{
bool result;
char *path;
- path = Dir_FindFile(arg, &dirSearchPath);
+ path = Dir_FindFile(file, &dirSearchPath);
DEBUG2(COND, "exists(%s) result is \"%s\"\n",
- arg, path != NULL ? path : "");
+ file, path != NULL ? path : "");
result = path != NULL;
free(path);
return result;
@@ -325,9 +322,9 @@ FuncExists(const char *arg)
/* See if the given node exists and is an actual target. */
static bool
-FuncTarget(const char *arg)
+FuncTarget(const char *node)
{
- GNode *gn = Targ_FindNode(arg);
+ GNode *gn = Targ_FindNode(node);
return gn != NULL && GNode_IsTarget(gn);
}
@@ -336,20 +333,16 @@ FuncTarget(const char *arg)
* associated with it.
*/
static bool
-FuncCommands(const char *arg)
+FuncCommands(const char *node)
{
- GNode *gn = Targ_FindNode(arg);
- return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands);
+ GNode *gn = Targ_FindNode(node);
+ return gn != NULL && GNode_IsTarget(gn) &&
+ !Lst_IsEmpty(&gn->commands);
}
/*
- * Convert the given number into a double.
- * We try a base 10 or 16 integer conversion first, if that fails
- * then we try a floating point conversion instead.
- *
- * Results:
- * Returns true if the conversion succeeded.
- * Sets 'out_value' to the converted number.
+ * Convert the string into a floating-point number. Accepted formats are
+ * base-10 integer, base-16 integer and finite floating point numbers.
*/
static bool
TryParseNumber(const char *str, double *out_value)
@@ -399,7 +392,7 @@ CondParser_StringExpr(CondParser *par, const char *start,
Buffer *buf, FStr *inout_str)
{
VarEvalMode emode;
- const char *nested_p;
+ const char *p;
bool atStart;
VarParseResult parseResult;
@@ -407,9 +400,9 @@ CondParser_StringExpr(CondParser *par, const char *start,
: doEval ? VARE_UNDEFERR
: VARE_PARSE_ONLY;
- nested_p = par->p;
- atStart = nested_p == start;
- parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, emode, inout_str);
+ p = par->p;
+ atStart = p == start;
+ parseResult = Var_Parse(&p, SCOPE_CMDLINE, emode, inout_str);
/* TODO: handle errors */
if (inout_str->str == var_Error) {
if (parseResult == VPR_ERR) {
@@ -433,7 +426,7 @@ CondParser_StringExpr(CondParser *par, const char *start,
*inout_str = FStr_InitRefer(NULL);
return false;
}
- par->p = nested_p;
+ par->p = p;
/*
* If the '$' started the string literal (which means no quotes), and
@@ -445,17 +438,16 @@ CondParser_StringExpr(CondParser *par, const char *start,
Buf_AddStr(buf, inout_str->str);
FStr_Done(inout_str);
- *inout_str = FStr_InitRefer(NULL); /* not finished yet */
+ *inout_str = FStr_InitRefer(NULL); /* not finished yet */
return true;
}
/*
- * Parse a string from a variable expression or an optionally quoted
- * string. This is called for the left-hand and right-hand sides of
- * comparisons.
+ * Parse a string from a variable expression or an optionally quoted string,
+ * on the left-hand and right-hand sides of comparisons.
*
* Results:
- * Returns the string, absent any quotes, or NULL on error.
+ * Returns the string without any enclosing quotes, or NULL on error.
* Sets out_quoted if the leaf was a quoted string literal.
*/
static void
@@ -486,7 +478,7 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
case '"':
par->p++;
if (quoted)
- goto got_str; /* skip the closing quote */
+ goto return_buf; /* skip the closing quote */
Buf_AddByte(&buf, '"');
continue;
case ')': /* see is_separator */
@@ -497,14 +489,14 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
case ' ':
case '\t':
if (!quoted)
- goto got_str;
+ goto return_buf;
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
case '$':
if (!CondParser_StringExpr(par,
start, doEval, quoted, &buf, &str))
- goto cleanup;
+ goto return_str;
continue;
default:
if (!unquotedOK && !quoted && *start != '$' &&
@@ -514,28 +506,21 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK,
* a variable expression or a number.
*/
str = FStr_InitRefer(NULL);
- goto cleanup;
+ goto return_str;
}
Buf_AddByte(&buf, par->p[0]);
par->p++;
continue;
}
}
-got_str:
+return_buf:
str = FStr_InitOwn(buf.data);
buf.data = NULL;
-cleanup:
+return_str:
Buf_Done(&buf);
*out_str = str;
}
-static bool
-EvalBare(const CondParser *par, const char *arg)
-{
- bool res = par->evalBare(arg);
- return par->negateEvalBare ? !res : res;
-}
-
/*
* Evaluate a "comparison without operator", such as in ".if ${VAR}" or
* ".if 0".
@@ -553,9 +538,11 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted)
if (TryParseNumber(value, &num))
return num != 0.0;
- /* For .if ${...}, check for non-empty string. This is different from
- * the evaluation function from that .if variant, which would test
- * whether a variable of the given name were defined. */
+ /*
+ * For .if ${...}, check for non-empty string. This is different
+ * from the evaluation function from that .if variant, which would
+ * test whether a variable of the given name were defined.
+ */
/*
* XXX: Whitespace should count as empty, just as in
* CondParser_FuncCallEmpty.
@@ -563,7 +550,7 @@ EvalNotEmpty(CondParser *par, const char *value, bool quoted)
if (par->plain)
return value[0] != '\0';
- return EvalBare(par, value);
+ return par->evalBare(value) != par->negateEvalBare;
}
/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
@@ -623,33 +610,19 @@ CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op)
{
const char *p = par->p;
- if (p[0] == '<' && p[1] == '=') {
- *out_op = LE;
- goto length_2;
- } else if (p[0] == '<') {
- *out_op = LT;
- goto length_1;
- } else if (p[0] == '>' && p[1] == '=') {
- *out_op = GE;
- goto length_2;
- } else if (p[0] == '>') {
- *out_op = GT;
- goto length_1;
- } else if (p[0] == '=' && p[1] == '=') {
- *out_op = EQ;
- goto length_2;
- } else if (p[0] == '!' && p[1] == '=') {
- *out_op = NE;
- goto length_2;
- }
+ if (p[0] == '<' && p[1] == '=')
+ return par->p += 2, *out_op = LE, true;
+ if (p[0] == '<')
+ return par->p += 1, *out_op = LT, true;
+ if (p[0] == '>' && p[1] == '=')
+ return par->p += 2, *out_op = GE, true;
+ if (p[0] == '>')
+ return par->p += 1, *out_op = GT, true;
+ if (p[0] == '=' && p[1] == '=')
+ return par->p += 2, *out_op = EQ, true;
+ if (p[0] == '!' && p[1] == '=')
+ return par->p += 2, *out_op = NE, true;
return false;
-
-length_2:
- par->p = p + 2;
- return true;
-length_1:
- par->p = p + 1;
- return true;
}
/*
@@ -718,9 +691,8 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
Token tok;
FStr val;
- if (!is_token(cp, "empty", 5))
+ if (!skip_string(&cp, "empty"))
return false;
- cp += 5;
cpp_skip_whitespace(&cp);
if (*cp != '(')
@@ -735,7 +707,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
tok = TOK_ERROR;
else {
cpp_skip_whitespace(&val.str);
- tok = val.str[0] != '\0' && doEval ? TOK_FALSE : TOK_TRUE;
+ tok = ToToken(doEval && val.str[0] == '\0');
}
FStr_Done(&val);
@@ -748,37 +720,34 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token)
static bool
CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token)
{
- static const struct fn_def {
- const char fn_name[9];
- unsigned char fn_name_len;
- bool (*fn_eval)(const char *);
- } fns[] = {
- { "defined", 7, FuncDefined },
- { "make", 4, FuncMake },
- { "exists", 6, FuncExists },
- { "target", 6, FuncTarget },
- { "commands", 8, FuncCommands }
- };
- const struct fn_def *fn;
- char *arg = NULL;
- size_t arglen;
- const char *cp = par->p;
- const struct fn_def *last_fn = fns + sizeof fns / sizeof fns[0] - 1;
-
- for (fn = fns; !is_token(cp, fn->fn_name, fn->fn_name_len); fn++)
- if (fn == last_fn)
- return false;
-
- cp += fn->fn_name_len;
- cpp_skip_whitespace(&cp);
- if (*cp != '(')
+ char *arg;
+ const char *p = par->p;
+ bool (*fn)(const char *);
+ const char *fn_name = p;
+
+ if (skip_string(&p, "defined"))
+ fn = FuncDefined;
+ else if (skip_string(&p, "make"))
+ fn = FuncMake;
+ else if (skip_string(&p, "exists"))
+ fn = FuncExists;
+ else if (skip_string(&p, "target"))
+ fn = FuncTarget;
+ else if (skip_string(&p, "commands"))
+ fn = FuncCommands;
+ else
return false;
- arglen = ParseWord(par, &cp, doEval, fn->fn_name, &arg);
- *out_token = ToToken(arglen != 0 && (!doEval || fn->fn_eval(arg)));
+ cpp_skip_whitespace(&p);
+ if (*p != '(')
+ return false;
+ arg = ParseFuncArg(par, &p, doEval, fn_name);
+ *out_token = ToToken(doEval &&
+ arg != NULL && arg[0] != '\0' && fn(arg));
free(arg);
- par->p = cp;
+
+ par->p = p;
return true;
}
@@ -793,9 +762,8 @@ static Token
CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
{
Token t;
- char *arg = NULL;
+ char *arg;
const char *cp;
- const char *cp1;
/* Push anything numeric through the compare expression */
cp = par->p;
@@ -811,13 +779,13 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
* as an expression.
*/
/*
- * XXX: Is it possible to have a variable expression evaluated twice
- * at this point?
+ * XXX: In edge cases, a variable expression may be evaluated twice,
+ * see cond-token-plain.mk, keyword 'twice'.
*/
- (void)ParseWord(par, &cp, doEval, NULL, &arg);
- cp1 = cp;
- cpp_skip_whitespace(&cp1);
- if (*cp1 == '=' || *cp1 == '!' || *cp1 == '<' || *cp1 == '>')
+ arg = ParseWord(&cp, doEval);
+ assert(arg[0] != '\0');
+
+ if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>')
return CondParser_Comparison(par, doEval);
par->p = cp;
@@ -827,7 +795,7 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval)
* after .if must have been taken literally, so the argument cannot
* be empty - even if it contained a variable expansion.
*/
- t = ToToken(!doEval || EvalBare(par, arg));
+ t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare);
free(arg);
return t;
}
@@ -998,42 +966,32 @@ CondParser_Or(CondParser *par, bool doEval)
return res;
}
-static CondEvalResult
-CondParser_Eval(CondParser *par, bool *out_value)
+static CondResult
+CondParser_Eval(CondParser *par)
{
CondResult res;
DEBUG1(COND, "CondParser_Eval: %s\n", par->p);
res = CondParser_Or(par, true);
- if (res == CR_ERROR)
- return COND_INVALID;
+ if (res != CR_ERROR && CondParser_Token(par, false) != TOK_EOF)
+ return CR_ERROR;
- if (CondParser_Token(par, false) != TOK_EOF)
- return COND_INVALID;
-
- *out_value = res == CR_TRUE;
- return COND_PARSE;
+ return res;
}
/*
* Evaluate the condition, including any side effects from the variable
* expressions in the condition. The condition consists of &&, ||, !,
* function(arg), comparisons and parenthetical groupings thereof.
- *
- * Results:
- * COND_PARSE if the condition was valid grammatically
- * COND_INVALID if not a valid conditional.
- *
- * *out_value is set to the boolean value of the condition
*/
-static CondEvalResult
-CondEvalExpression(const char *cond, bool *out_value, bool plain,
+static CondResult
+CondEvalExpression(const char *cond, bool plain,
bool (*evalBare)(const char *), bool negate,
bool eprint, bool leftUnquotedOK)
{
CondParser par;
- CondEvalResult rval;
+ CondResult rval;
cpp_skip_hspace(&cond);
@@ -1045,9 +1003,9 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain,
par.curr = TOK_NONE;
par.printedError = false;
- rval = CondParser_Eval(&par, out_value);
+ rval = CondParser_Eval(&par);
- if (rval == COND_INVALID && eprint && !par.printedError)
+ if (rval == CR_ERROR && eprint && !par.printedError)
Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond);
return rval;
@@ -1057,10 +1015,10 @@ CondEvalExpression(const char *cond, bool *out_value, bool plain,
* Evaluate a condition in a :? modifier, such as
* ${"${VAR}" == value:?yes:no}.
*/
-CondEvalResult
-Cond_EvalCondition(const char *cond, bool *out_value)
+CondResult
+Cond_EvalCondition(const char *cond)
{
- return CondEvalExpression(cond, out_value, true,
+ return CondEvalExpression(cond, true,
FuncDefined, false, false, true);
}
@@ -1076,36 +1034,33 @@ DetermineKindOfConditional(const char **pp, bool *out_plain,
bool (**out_evalBare)(const char *),
bool *out_negate)
{
- const char *p = *pp;
+ const char *p = *pp + 2;
- p += 2;
*out_plain = false;
*out_evalBare = FuncDefined;
- *out_negate = false;
- if (*p == 'n') {
- p++;
- *out_negate = true;
- }
- if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */
- p += 3;
- } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */
- p += 4;
+ *out_negate = skip_string(&p, "n");
+
+ if (skip_string(&p, "def")) { /* .ifdef and .ifndef */
+ } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */
*out_evalBare = FuncMake;
- } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */
+ else if (!*out_negate) /* plain .if */
*out_plain = true;
- } else {
- /*
- * TODO: Add error message about unknown directive,
- * since there is no other known directive that starts
- * with 'el' or 'if'.
- *
- * Example: .elifx 123
- */
- return false;
- }
+ else
+ goto unknown_directive;
+ if (ch_isalpha(*p))
+ goto unknown_directive;
*pp = p;
return true;
+
+unknown_directive:
+ /*
+ * TODO: Add error message about unknown directive, since there is no
+ * other known directive that starts with 'el' or 'if'.
+ *
+ * Example: .elifx 123
+ */
+ return false;
}
/*
@@ -1129,16 +1084,16 @@ DetermineKindOfConditional(const char **pp, bool *out_plain,
* parenthetical groupings thereof.
*
* Results:
- * COND_PARSE to continue parsing the lines that follow the
+ * CR_TRUE to continue parsing the lines that follow the
* conditional (when <cond> evaluates to true)
- * COND_SKIP to skip the lines after the conditional
+ * CR_FALSE to skip the lines after the conditional
* (when <cond> evaluates to false, or when a previous
* branch has already been taken)
- * COND_INVALID if the conditional was not valid, either because of
+ * CR_ERROR if the conditional was not valid, either because of
* a syntax error or because some variable was undefined
* or because the condition could not be evaluated
*/
-CondEvalResult
+CondResult
Cond_EvalLine(const char *line)
{
typedef enum IfState {
@@ -1146,8 +1101,10 @@ Cond_EvalLine(const char *line)
/* None of the previous <cond> evaluated to true. */
IFS_INITIAL = 0,
- /* The previous <cond> evaluated to true.
- * The lines following this condition are interpreted. */
+ /*
+ * The previous <cond> evaluated to true. The lines following
+ * this condition are interpreted.
+ */
IFS_ACTIVE = 1 << 0,
/* The previous directive was an '.else'. */
@@ -1165,7 +1122,7 @@ Cond_EvalLine(const char *line)
bool (*evalBare)(const char *);
bool negate;
bool isElif;
- bool value;
+ CondResult res;
IfState state;
const char *p = line;
@@ -1186,13 +1143,13 @@ Cond_EvalLine(const char *line)
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less endif");
- return COND_PARSE;
+ return CR_TRUE;
}
/* Return state for previous conditional */
cond_depth--;
return cond_states[cond_depth] & IFS_ACTIVE
- ? COND_PARSE : COND_SKIP;
+ ? CR_TRUE : CR_FALSE;
}
/* Parse the name of the directive, such as 'if', 'elif', 'endif'. */
@@ -1203,13 +1160,12 @@ Cond_EvalLine(const char *line)
* transformation rule like '.err.txt',
* therefore no error message here.
*/
- return COND_INVALID;
+ return CR_ERROR;
}
/* Quite likely this is 'else' or 'elif' */
p += 2;
- if (is_token(p, "se", 2)) { /* It is an 'else'. */
-
+ if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) {
if (p[2] != '\0')
Parse_Error(PARSE_FATAL,
"The .else directive "
@@ -1217,7 +1173,7 @@ Cond_EvalLine(const char *line)
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less else");
- return COND_PARSE;
+ return CR_TRUE;
}
state = cond_states[cond_depth];
@@ -1226,12 +1182,12 @@ Cond_EvalLine(const char *line)
} else {
if (state & IFS_SEEN_ELSE)
Parse_Error(PARSE_WARNING,
- "extra else");
+ "extra else");
state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
}
cond_states[cond_depth] = state;
- return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP;
+ return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE;
}
/* Assume for now it is an elif */
isElif = true;
@@ -1243,27 +1199,27 @@ Cond_EvalLine(const char *line)
* Unknown directive. It might still be a transformation rule
* like '.elisp.scm', therefore no error message here.
*/
- return COND_INVALID; /* Not an ifxxx or elifxxx line */
+ return CR_ERROR; /* Not an ifxxx or elifxxx line */
}
if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate))
- return COND_INVALID;
+ return CR_ERROR;
if (isElif) {
if (cond_depth == cond_min_depth) {
Parse_Error(PARSE_FATAL, "if-less elif");
- return COND_PARSE;
+ return CR_TRUE;
}
state = cond_states[cond_depth];
if (state & IFS_SEEN_ELSE) {
Parse_Error(PARSE_WARNING, "extra elif");
cond_states[cond_depth] =
IFS_WAS_ACTIVE | IFS_SEEN_ELSE;
- return COND_SKIP;
+ return CR_FALSE;
}
if (state != IFS_INITIAL) {
cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
+ return CR_FALSE;
}
} else {
/* Normal .if */
@@ -1275,8 +1231,7 @@ Cond_EvalLine(const char *line)
*/
cond_states_cap += 32;
cond_states = bmake_realloc(cond_states,
- cond_states_cap *
- sizeof *cond_states);
+ cond_states_cap * sizeof *cond_states);
}
state = cond_states[cond_depth];
cond_depth++;
@@ -1286,26 +1241,22 @@ Cond_EvalLine(const char *line)
* treat as always false.
*/
cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
+ return CR_FALSE;
}
}
/* And evaluate the conditional expression */
- if (CondEvalExpression(p, &value, plain, evalBare, negate,
- true, false) == COND_INVALID) {
- /* Syntax error in conditional, error message already output. */
- /* Skip everything to matching .endif */
- /* XXX: An extra '.else' is not detected in this case. */
+ res = CondEvalExpression(p, plain, evalBare, negate, true, false);
+ if (res == CR_ERROR) {
+ /* Syntax error, error message already output. */
+ /* Skip everything to the matching '.endif'. */
+ /* An extra '.else' is not detected in this case. */
cond_states[cond_depth] = IFS_WAS_ACTIVE;
- return COND_SKIP;
+ return CR_FALSE;
}
- if (!value) {
- cond_states[cond_depth] = IFS_INITIAL;
- return COND_SKIP;
- }
- cond_states[cond_depth] = IFS_ACTIVE;
- return COND_PARSE;
+ cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL;
+ return res;
}
void
@@ -1315,7 +1266,7 @@ Cond_restore_depth(unsigned int saved_depth)
if (open_conds != 0 || saved_depth > cond_depth) {
Parse_Error(PARSE_FATAL, "%u open conditional%s",
- open_conds, open_conds == 1 ? "" : "s");
+ open_conds, open_conds == 1 ? "" : "s");
cond_depth = cond_min_depth;
}
diff --git a/contrib/bmake/dir.c b/contrib/bmake/dir.c
index e10d7b7421db..93479271c94a 100644
--- a/contrib/bmake/dir.c
+++ b/contrib/bmake/dir.c
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $ */
+/* $NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -138,7 +138,7 @@
#include "job.h"
/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */
-MAKE_RCSID("$NetBSD: dir.c,v 1.275 2021/11/28 21:46:17 rillig Exp $");
+MAKE_RCSID("$NetBSD: dir.c,v 1.278 2022/02/04 23:22:19 rillig Exp $");
/*
* A search path is a list of CachedDir structures. A CachedDir has in it the
@@ -672,8 +672,8 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions)
{
char *fullName = isDot
- ? bmake_strdup(base)
- : str_concat3(dirName, "/", base);
+ ? bmake_strdup(base)
+ : str_concat3(dirName, "/", base);
Lst_Append(expansions, fullName);
}
}
@@ -792,7 +792,7 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path,
size_t piece_len = (size_t)(piece_end - piece);
char *file = concat3(prefix, prefix_len, piece, piece_len,
- suffix, suffix_len);
+ suffix, suffix_len);
if (contains_wildcard(file)) {
SearchPath_Expand(path, file, expansions);
@@ -984,8 +984,9 @@ static char *
DirLookupSubdir(CachedDir *dir, const char *name)
{
struct cached_stat cst;
- char *file = dir == dot ? bmake_strdup(name)
- : str_concat3(dir->name, "/", name);
+ char *file = dir == dot
+ ? bmake_strdup(name)
+ : str_concat3(dir->name, "/", name);
DEBUG1(DIR, "checking %s ...\n", file);
@@ -1424,9 +1425,9 @@ ResolveMovedDepends(GNode *gn)
gn->path = bmake_strdup(fullName);
if (!Job_RunTarget(".STALE", gn->fname))
fprintf(stdout, /* XXX: Why stdout? */
- "%s: %s, %d: ignoring stale %s for %s, found %s\n",
- progname, gn->fname, gn->lineno,
- makeDependfile, gn->name, fullName);
+ "%s: %s, %u: ignoring stale %s for %s, found %s\n",
+ progname, gn->fname, gn->lineno,
+ makeDependfile, gn->name, fullName);
return fullName;
}
diff --git a/contrib/bmake/dir.h b/contrib/bmake/dir.h
index d96393c62ebb..3d98fb0201b4 100644
--- a/contrib/bmake/dir.h
+++ b/contrib/bmake/dir.h
@@ -1,4 +1,4 @@
-/* $NetBSD: dir.h,v 1.44 2021/04/03 11:08:40 rillig Exp $ */
+/* $NetBSD: dir.h,v 1.46 2021/12/15 12:08:25 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -82,18 +82,18 @@ void Dir_InitCur(const char *);
void Dir_InitDot(void);
void Dir_End(void);
void Dir_SetPATH(void);
-bool Dir_HasWildcards(const char *);
+bool Dir_HasWildcards(const char *) MAKE_ATTR_USE;
void SearchPath_Expand(SearchPath *, const char *, StringList *);
-char *Dir_FindFile(const char *, SearchPath *);
-char *Dir_FindHereOrAbove(const char *, const char *);
+char *Dir_FindFile(const char *, SearchPath *) MAKE_ATTR_USE;
+char *Dir_FindHereOrAbove(const char *, const char *) MAKE_ATTR_USE;
void Dir_UpdateMTime(GNode *, bool);
CachedDir *SearchPath_Add(SearchPath *, const char *);
-char *SearchPath_ToFlags(SearchPath *, const char *);
+char *SearchPath_ToFlags(SearchPath *, const char *) MAKE_ATTR_USE;
void SearchPath_Clear(SearchPath *);
void SearchPath_AddAll(SearchPath *, SearchPath *);
void Dir_PrintDirectories(void);
void SearchPath_Print(const SearchPath *);
-SearchPath *Dir_CopyDirSearchPath(void);
+SearchPath *Dir_CopyDirSearchPath(void) MAKE_ATTR_USE;
/* Stripped-down variant of struct stat. */
struct cached_stat {
@@ -104,4 +104,4 @@ struct cached_stat {
int cached_lstat(const char *, struct cached_stat *);
int cached_stat(const char *, struct cached_stat *);
-#endif /* MAKE_DIR_H */
+#endif
diff --git a/contrib/bmake/filemon/filemon.h b/contrib/bmake/filemon/filemon.h
index 139d62f1a1f6..d74c99160ba5 100644
--- a/contrib/bmake/filemon/filemon.h
+++ b/contrib/bmake/filemon/filemon.h
@@ -1,4 +1,4 @@
-/* $NetBSD: filemon.h,v 1.5 2021/01/19 20:51:46 rillig Exp $ */
+/* $NetBSD: filemon.h,v 1.6 2021/12/15 12:08:25 rillig Exp $ */
/*
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -50,4 +50,4 @@ int filemon_setpid_child(const struct filemon *, pid_t);
int filemon_readfd(const struct filemon *);
int filemon_process(struct filemon *);
-#endif /* MAKE_FILEMON_H */
+#endif
diff --git a/contrib/bmake/for.c b/contrib/bmake/for.c
index 013b0f21d841..c36641fa8c4e 100644
--- a/contrib/bmake/for.c
+++ b/contrib/bmake/for.c
@@ -1,4 +1,4 @@
-/* $NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $ */
+/* $NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1992, The Regents of the University of California.
@@ -58,26 +58,18 @@
#include "make.h"
/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: for.c,v 1.150 2021/12/12 15:44:41 rillig Exp $");
+MAKE_RCSID("$NetBSD: for.c,v 1.167 2022/02/04 23:22:19 rillig Exp $");
-/* One of the variables to the left of the "in" in a .for loop. */
-typedef struct ForVar {
- char *name;
- size_t nameLen;
-} ForVar;
-
typedef struct ForLoop {
- Buffer body; /* Unexpanded body of the loop */
- Vector /* of ForVar */ vars; /* Iteration variables */
+ Vector /* of 'char *' */ vars; /* Iteration variables */
SubstringWords items; /* Substitution items */
- Buffer curBody; /* Expanded body of the current iteration */
+ Buffer body; /* Unexpanded body of the loop */
unsigned int nextItem; /* Where to continue iterating */
} ForLoop;
static ForLoop *accumFor; /* Loop being accumulated */
-static int forLevel = 0; /* Nesting level */
static ForLoop *
@@ -85,38 +77,49 @@ ForLoop_New(void)
{
ForLoop *f = bmake_malloc(sizeof *f);
- Buf_Init(&f->body);
- Vector_Init(&f->vars, sizeof(ForVar));
+ Vector_Init(&f->vars, sizeof(char *));
SubstringWords_Init(&f->items);
- Buf_Init(&f->curBody);
+ Buf_Init(&f->body);
f->nextItem = 0;
return f;
}
-static void
+void
ForLoop_Free(ForLoop *f)
{
- Buf_Done(&f->body);
-
- while (f->vars.len > 0) {
- ForVar *var = Vector_Pop(&f->vars);
- free(var->name);
- }
+ while (f->vars.len > 0)
+ free(*(char **)Vector_Pop(&f->vars));
Vector_Done(&f->vars);
SubstringWords_Free(f->items);
- Buf_Done(&f->curBody);
+ Buf_Done(&f->body);
free(f);
}
-static void
-ForLoop_AddVar(ForLoop *f, const char *name, size_t len)
+char *
+ForLoop_Details(ForLoop *f)
{
- ForVar *var = Vector_Push(&f->vars);
- var->name = bmake_strldup(name, len);
- var->nameLen = len;
+ size_t i, n;
+ const char **vars;
+ const Substring *items;
+ Buffer buf;
+
+ n = f->vars.len;
+ vars = f->vars.items;
+ assert(f->nextItem >= n);
+ items = f->items.words + f->nextItem - n;
+
+ Buf_Init(&buf);
+ for (i = 0; i < n; i++) {
+ if (i > 0)
+ Buf_AddStr(&buf, ", ");
+ Buf_AddStr(&buf, vars[i]);
+ Buf_AddStr(&buf, " = ");
+ Buf_AddBytesBetween(&buf, items[i].start, items[i].end);
+ }
+ return Buf_DoneData(&buf);
}
static bool
@@ -145,7 +148,7 @@ ForLoop_ParseVarnames(ForLoop *f, const char **pp)
break;
}
- ForLoop_AddVar(f, p, len);
+ *(char **)Vector_Push(&f->vars) = bmake_strldup(p, len);
p += len;
}
@@ -174,9 +177,9 @@ ForLoop_ParseItems(ForLoop *f, const char *p)
free(items);
if (f->items.len == 1 && Substring_IsEmpty(f->items.words[0]))
- f->items.len = 0; /* .for var in ${:U} */
+ f->items.len = 0; /* .for var in ${:U} */
- if (f->items.len != 0 && f->items.len % f->vars.len != 0) {
+ if (f->items.len % f->vars.len != 0) {
Parse_Error(PARSE_FATAL,
"Wrong number of words (%u) in .for "
"substitution list with %u variables",
@@ -204,47 +207,38 @@ IsEndfor(const char *p)
* Evaluate the for loop in the passed line. The line looks like this:
* .for <varname...> in <value...>
*
- * Input:
- * line Line to parse
- *
* Results:
- * 0: Not a .for statement, parse the line
- * 1: We found a for loop
- * -1: A .for statement with a bad syntax error, discard.
+ * 0 not a .for directive
+ * 1 found a .for directive
+ * -1 erroneous .for directive
*/
int
For_Eval(const char *line)
{
- ForLoop *f;
const char *p;
+ ForLoop *f;
p = line + 1; /* skip the '.' */
cpp_skip_whitespace(&p);
- if (!IsFor(p)) {
- if (IsEndfor(p)) {
- Parse_Error(PARSE_FATAL, "for-less endfor");
+ if (IsFor(p)) {
+ p += 3;
+
+ f = ForLoop_New();
+ if (!ForLoop_ParseVarnames(f, &p)) {
+ ForLoop_Free(f);
return -1;
}
- return 0;
- }
- p += 3;
+ if (!ForLoop_ParseItems(f, p))
+ f->items.len = 0; /* don't iterate */
- f = ForLoop_New();
-
- if (!ForLoop_ParseVarnames(f, &p)) {
- ForLoop_Free(f);
+ accumFor = f;
+ return 1;
+ } else if (IsEndfor(p)) {
+ Parse_Error(PARSE_FATAL, "for-less endfor");
return -1;
- }
-
- if (!ForLoop_ParseItems(f, p)) {
- /* Continue parsing the .for loop, but don't iterate. */
- f->items.len = 0;
- }
-
- accumFor = f;
- forLevel = 1;
- return 1;
+ } else
+ return 0;
}
/*
@@ -252,7 +246,7 @@ For_Eval(const char *line)
* Returns false when the matching .endfor is reached.
*/
bool
-For_Accum(const char *line)
+For_Accum(const char *line, int *forLevel)
{
const char *p = line;
@@ -261,12 +255,12 @@ For_Accum(const char *line)
cpp_skip_whitespace(&p);
if (IsEndfor(p)) {
- DEBUG1(FOR, "For: end for %d\n", forLevel);
- if (--forLevel <= 0)
+ DEBUG1(FOR, "For: end for %d\n", *forLevel);
+ if (--*forLevel == 0)
return false;
} else if (IsFor(p)) {
- forLevel++;
- DEBUG1(FOR, "For: new loop %d\n", forLevel);
+ (*forLevel)++;
+ DEBUG1(FOR, "For: new loop %d\n", *forLevel);
}
}
@@ -325,12 +319,11 @@ NeedsEscapes(Substring value, char endc)
/*
* While expanding the body of a .for loop, write the item in the ${:U...}
- * expression, escaping characters as needed.
- *
- * The result is later unescaped by ApplyModifier_Defined.
+ * expression, escaping characters as needed. The result is later unescaped
+ * by ApplyModifier_Defined.
*/
static void
-Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
+AddEscaped(Buffer *cmds, Substring item, char endc)
{
const char *p;
char ch;
@@ -340,9 +333,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
return;
}
- /* Escape ':', '$', '\\' and 'endc' - these will be removed later by
- * :U processing, see ApplyModifier_Defined. */
- for (p = item.start; p != item.end; p++) {
+ for (p = item.start; p != item.end;) {
ch = *p;
if (ch == '$') {
size_t len = ExprLen(p + 1, item.end);
@@ -352,7 +343,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
* See directive-for-escape.mk, ExprLen.
*/
Buf_AddBytes(cmds, p, 1 + len);
- p += len;
+ p += 1 + len;
continue;
}
Buf_AddByte(cmds, '\\');
@@ -363,6 +354,7 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
ch = ' '; /* prevent newline injection */
}
Buf_AddByte(cmds, ch);
+ p++;
}
}
@@ -371,36 +363,30 @@ Buf_AddEscaped(Buffer *cmds, Substring item, char endc)
* expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue".
*/
static void
-ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd,
- char endc, const char **inout_mark)
+ForLoop_SubstVarLong(ForLoop *f, unsigned int firstItem, Buffer *body,
+ const char **pp, char endc, const char **inout_mark)
{
size_t i;
- const char *p = *pp;
+ const char *start = *pp;
+ const char **vars = Vector_Get(&f->vars, 0);
for (i = 0; i < f->vars.len; i++) {
- const ForVar *forVar = Vector_Get(&f->vars, i);
- const char *varname = forVar->name;
- size_t varnameLen = forVar->nameLen;
+ const char *p = start;
- if (varnameLen >= (size_t)(bodyEnd - p))
- continue;
- if (memcmp(p, varname, varnameLen) != 0)
+ if (!cpp_skip_string(&p, vars[i]))
continue;
/* XXX: why test for backslash here? */
- if (p[varnameLen] != ':' && p[varnameLen] != endc &&
- p[varnameLen] != '\\')
+ if (*p != ':' && *p != endc && *p != '\\')
continue;
/*
* Found a variable match. Skip over the variable name and
* instead add ':U<value>' to the current body.
*/
- Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
- Buf_AddStr(&f->curBody, ":U");
- Buf_AddEscaped(&f->curBody,
- f->items.words[f->nextItem + i], endc);
+ Buf_AddBytesBetween(body, *inout_mark, start);
+ Buf_AddStr(body, ":U");
+ AddEscaped(body, f->items.words[firstItem + i], endc);
- p += varnameLen;
*inout_mark = p;
*pp = p;
return;
@@ -412,10 +398,11 @@ ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd,
* variable expressions like $i with their ${:U...} expansion.
*/
static void
-ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark)
+ForLoop_SubstVarShort(ForLoop *f, unsigned int firstItem, Buffer *body,
+ const char *p, const char **inout_mark)
{
const char ch = *p;
- const ForVar *vars;
+ const char **vars;
size_t i;
/* Skip $$ and stupid ones. */
@@ -424,20 +411,20 @@ ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark)
vars = Vector_Get(&f->vars, 0);
for (i = 0; i < f->vars.len; i++) {
- const char *varname = vars[i].name;
+ const char *varname = vars[i];
if (varname[0] == ch && varname[1] == '\0')
goto found;
}
return;
found:
- Buf_AddBytesBetween(&f->curBody, *inout_mark, p);
+ Buf_AddBytesBetween(body, *inout_mark, p);
*inout_mark = p + 1;
/* Replace $<ch> with ${:U<value>} */
- Buf_AddStr(&f->curBody, "{:U");
- Buf_AddEscaped(&f->curBody, f->items.words[f->nextItem + i], '}');
- Buf_AddByte(&f->curBody, '}');
+ Buf_AddStr(body, "{:U");
+ AddEscaped(body, f->items.words[firstItem + i], '}');
+ Buf_AddByte(body, '}');
}
/*
@@ -454,68 +441,59 @@ found:
* possible to contrive a makefile where an unwanted substitution happens.
*/
static void
-ForLoop_SubstBody(ForLoop *f)
+ForLoop_SubstBody(ForLoop *f, unsigned int firstItem, Buffer *body)
{
- const char *p, *bodyEnd;
+ const char *p, *end;
const char *mark; /* where the last substitution left off */
- Buf_Empty(&f->curBody);
+ Buf_Clear(body);
mark = f->body.data;
- bodyEnd = f->body.data + f->body.len;
+ end = f->body.data + f->body.len;
for (p = mark; (p = strchr(p, '$')) != NULL;) {
if (p[1] == '{' || p[1] == '(') {
char endc = p[1] == '{' ? '}' : ')';
p += 2;
- ForLoop_SubstVarLong(f, &p, bodyEnd, endc, &mark);
+ ForLoop_SubstVarLong(f, firstItem, body,
+ &p, endc, &mark);
} else if (p[1] != '\0') {
- ForLoop_SubstVarShort(f, p + 1, &mark);
+ ForLoop_SubstVarShort(f, firstItem, body,
+ p + 1, &mark);
p += 2;
} else
break;
}
- Buf_AddBytesBetween(&f->curBody, mark, bodyEnd);
+ Buf_AddBytesBetween(body, mark, end);
}
/*
* Compute the body for the current iteration by copying the unexpanded body,
* replacing the expressions for the iteration variables on the way.
*/
-static char *
-ForReadMore(void *v_arg, size_t *out_len)
+bool
+For_NextIteration(ForLoop *f, Buffer *body)
{
- ForLoop *f = v_arg;
-
- if (f->nextItem == f->items.len) {
- /* No more iterations */
- ForLoop_Free(f);
- return NULL;
- }
+ if (f->nextItem == f->items.len)
+ return false;
- ForLoop_SubstBody(f);
- DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data);
f->nextItem += (unsigned int)f->vars.len;
-
- *out_len = f->curBody.len;
- return f->curBody.data;
+ ForLoop_SubstBody(f, f->nextItem - (unsigned int)f->vars.len, body);
+ DEBUG1(FOR, "For: loop body:\n%s", body->data);
+ return true;
}
/* Run the .for loop, imitating the actions of an include file. */
void
-For_Run(int lineno)
+For_Run(unsigned headLineno, unsigned bodyReadLines)
{
+ Buffer buf;
ForLoop *f = accumFor;
accumFor = NULL;
- if (f->items.len == 0) {
- /*
- * Nothing to expand - possibly due to an earlier syntax
- * error.
- */
+ if (f->items.len > 0) {
+ Buf_Init(&buf);
+ Parse_PushInput(NULL, headLineno, bodyReadLines, buf, f);
+ } else
ForLoop_Free(f);
- return;
- }
-
- Parse_PushInput(NULL, lineno, -1, ForReadMore, f);
}
diff --git a/contrib/bmake/hash.c b/contrib/bmake/hash.c
index ec0079fa4ba4..beef2a8419de 100644
--- a/contrib/bmake/hash.c
+++ b/contrib/bmake/hash.c
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $ */
+/* $NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -74,7 +74,7 @@
#include "make.h"
/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $");
+MAKE_RCSID("$NetBSD: hash.c,v 1.71 2022/01/27 11:00:07 rillig Exp $");
/*
* The ratio of # entries to # buckets at which we rebuild the table to
@@ -84,7 +84,7 @@ MAKE_RCSID("$NetBSD: hash.c,v 1.66 2021/12/07 21:58:01 rillig Exp $");
/* This hash function matches Gosling's Emacs and java.lang.String. */
static unsigned int
-Hash_String(const char *key, size_t *out_keylen)
+Hash_String(const char *key, const char **out_keyEnd)
{
unsigned int h;
const char *p;
@@ -93,8 +93,7 @@ Hash_String(const char *key, size_t *out_keylen)
for (p = key; *p != '\0'; p++)
h = 31 * h + (unsigned char)*p;
- if (out_keylen != NULL)
- *out_keylen = (size_t)(p - key);
+ *out_keyEnd = p;
return h;
}
@@ -112,53 +111,22 @@ Hash_Substring(Substring key)
}
static HashEntry *
-HashTable_Find(HashTable *t, unsigned int h, const char *key)
+HashTable_Find(HashTable *t, Substring key, unsigned int h)
{
HashEntry *e;
unsigned int chainlen = 0;
+ size_t keyLen = Substring_Length(key);
#ifdef DEBUG_HASH_LOOKUP
- DEBUG4(HASH, "%s: %p h=%08x key=%s\n", __func__, t, h, key);
+ DEBUG4(HASH, "HashTable_Find: %p h=%08x key=%.*s\n",
+ t, h, (int)keyLen, key.start);
#endif
for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
chainlen++;
- if (e->key_hash == h && strcmp(e->key, key) == 0)
- break;
- }
-
- if (chainlen > t->maxchain)
- t->maxchain = chainlen;
-
- return e;
-}
-
-static bool
-HashEntry_KeyEquals(const HashEntry *he, Substring key)
-{
- const char *heKey, *p;
-
- heKey = he->key;
- for (p = key.start; p != key.end; p++, heKey++)
- if (*p != *heKey || *heKey == '\0')
- return false;
- return *heKey == '\0';
-}
-
-static HashEntry *
-HashTable_FindEntryBySubstring(HashTable *t, Substring key, unsigned int h)
-{
- HashEntry *e;
- unsigned int chainlen = 0;
-
-#ifdef DEBUG_HASH_LOOKUP
- DEBUG5(HASH, "%s: %p h=%08x key=%.*s\n", __func__, t, h,
- (int)Substring_Length(key), key.start);
-#endif
-
- for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
- chainlen++;
- if (e->key_hash == h && HashEntry_KeyEquals(e, key))
+ if (e->key_hash == h &&
+ strncmp(e->key, key.start, keyLen) == 0 &&
+ e->key[keyLen] == '\0')
break;
}
@@ -213,8 +181,9 @@ HashTable_Done(HashTable *t)
HashEntry *
HashTable_FindEntry(HashTable *t, const char *key)
{
- unsigned int h = Hash_String(key, NULL);
- return HashTable_Find(t, h, key);
+ const char *keyEnd;
+ unsigned int h = Hash_String(key, &keyEnd);
+ return HashTable_Find(t, Substring_Init(key, keyEnd), h);
}
/* Find the value corresponding to the key, or return NULL. */
@@ -232,7 +201,7 @@ HashTable_FindValue(HashTable *t, const char *key)
void *
HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h)
{
- HashEntry *he = HashTable_FindEntryBySubstring(t, key, h);
+ HashEntry *he = HashTable_Find(t, key, h);
return he != NULL ? he->value : NULL;
}
@@ -268,8 +237,8 @@ HashTable_Enlarge(HashTable *t)
t->bucketsSize = newSize;
t->bucketsMask = newMask;
t->buckets = newBuckets;
- DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n",
- __func__, (void *)t, t->bucketsSize, t->numEntries, t->maxchain);
+ DEBUG4(HASH, "HashTable_Enlarge: %p size=%d entries=%d maxchain=%d\n",
+ (void *)t, t->bucketsSize, t->numEntries, t->maxchain);
t->maxchain = 0;
}
@@ -280,9 +249,9 @@ HashTable_Enlarge(HashTable *t)
HashEntry *
HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
{
- size_t keylen;
- unsigned int h = Hash_String(key, &keylen);
- HashEntry *he = HashTable_Find(t, h, key);
+ const char *keyEnd;
+ unsigned int h = Hash_String(key, &keyEnd);
+ HashEntry *he = HashTable_Find(t, Substring_Init(key, keyEnd), h);
if (he != NULL) {
if (out_isNew != NULL)
@@ -293,10 +262,10 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
if (t->numEntries >= rebuildLimit * t->bucketsSize)
HashTable_Enlarge(t);
- he = bmake_malloc(sizeof *he + keylen);
+ he = bmake_malloc(sizeof *he + (size_t)(keyEnd - key));
he->value = NULL;
he->key_hash = h;
- memcpy(he->key, key, keylen + 1);
+ memcpy(he->key, key, (size_t)(keyEnd - key) + 1);
he->next = t->buckets[h & t->bucketsMask];
t->buckets[h & t->bucketsMask] = he;
@@ -307,12 +276,11 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew)
return he;
}
-HashEntry *
+void
HashTable_Set(HashTable *t, const char *key, void *value)
{
HashEntry *he = HashTable_CreateEntry(t, key, NULL);
HashEntry_Set(he, value);
- return he;
}
/* Delete the entry from the table and free the associated memory. */
@@ -361,5 +329,5 @@ void
HashTable_DebugStats(HashTable *t, const char *name)
{
DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n",
- name, t->bucketsSize, t->numEntries, t->maxchain);
+ name, t->bucketsSize, t->numEntries, t->maxchain);
}
diff --git a/contrib/bmake/hash.h b/contrib/bmake/hash.h
index 8ff5490bdd95..016108c39060 100644
--- a/contrib/bmake/hash.h
+++ b/contrib/bmake/hash.h
@@ -1,4 +1,4 @@
-/* $NetBSD: hash.h,v 1.41 2021/12/07 21:58:01 rillig Exp $ */
+/* $NetBSD: hash.h,v 1.46 2022/01/31 22:58:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -88,8 +88,8 @@ typedef struct HashEntry {
/* The hash table containing the entries. */
typedef struct HashTable {
- HashEntry **buckets; /* Pointers to HashEntry, one
- * for each bucket in the table. */
+ HashEntry **buckets; /* Pointers to HashEntry, one for each bucket
+ * in the table. */
unsigned int bucketsSize;
unsigned int numEntries; /* Number of entries in the table. */
unsigned int bucketsMask; /* Used to select the bucket for a hash. */
@@ -108,7 +108,7 @@ typedef struct HashSet {
HashTable tbl;
} HashSet;
-MAKE_INLINE void *
+MAKE_INLINE void * MAKE_ATTR_USE
HashEntry_Get(HashEntry *h)
{
return h->value;
@@ -131,16 +131,16 @@ HashIter_Init(HashIter *hi, HashTable *t)
void HashTable_Init(HashTable *);
void HashTable_Done(HashTable *);
-HashEntry *HashTable_FindEntry(HashTable *, const char *);
-void *HashTable_FindValue(HashTable *, const char *);
-unsigned int Hash_Substring(Substring);
-void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int);
+HashEntry *HashTable_FindEntry(HashTable *, const char *) MAKE_ATTR_USE;
+void *HashTable_FindValue(HashTable *, const char *) MAKE_ATTR_USE;
+unsigned int Hash_Substring(Substring) MAKE_ATTR_USE;
+void *HashTable_FindValueBySubstringHash(HashTable *, Substring, unsigned int)
+ MAKE_ATTR_USE;
HashEntry *HashTable_CreateEntry(HashTable *, const char *, bool *);
-HashEntry *HashTable_Set(HashTable *, const char *, void *);
+void HashTable_Set(HashTable *, const char *, void *);
void HashTable_DeleteEntry(HashTable *, HashEntry *);
void HashTable_DebugStats(HashTable *, const char *);
-void HashIter_Init(HashIter *, HashTable *);
HashEntry *HashIter_Next(HashIter *);
MAKE_INLINE void
@@ -164,7 +164,7 @@ HashSet_Add(HashSet *set, const char *key)
return isNew;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
HashSet_Contains(HashSet *set, const char *key)
{
return HashTable_FindEntry(&set->tbl, key) != NULL;
@@ -176,4 +176,4 @@ HashIter_InitSet(HashIter *hi, HashSet *set)
HashIter_Init(hi, &set->tbl);
}
-#endif /* MAKE_HASH_H */
+#endif
diff --git a/contrib/bmake/job.c b/contrib/bmake/job.c
index f90c35bb6d49..0dd202394e4d 100644
--- a/contrib/bmake/job.c
+++ b/contrib/bmake/job.c
@@ -1,4 +1,4 @@
-/* $NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $ */
+/* $NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -155,7 +155,7 @@
#include "trace.h"
/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: job.c,v 1.440 2021/11/28 19:51:06 rillig Exp $");
+MAKE_RCSID("$NetBSD: job.c,v 1.451 2022/02/04 23:22:19 rillig Exp $");
/*
* A shell defines how the commands are run. All commands for a target are
@@ -214,13 +214,15 @@ typedef struct Shell {
const char *errOff; /* command to turn off error checking */
const char *echoTmpl; /* template to echo a command */
- const char *runIgnTmpl; /* template to run a command
- * without error checking */
- const char *runChkTmpl; /* template to run a command
- * with error checking */
+ const char *runIgnTmpl; /* template to run a command without error
+ * checking */
+ const char *runChkTmpl; /* template to run a command with error
+ * checking */
- /* string literal that results in a newline character when it appears
- * outside of any 'quote' or "quote" characters */
+ /*
+ * A string literal that results in a newline character when it
+ * occurs outside of any 'quote' or "quote" characters.
+ */
const char *newline;
char commentChar; /* character used by shell for comment lines */
@@ -454,7 +456,7 @@ static void watchfd(Job *);
static void clearfd(Job *);
static bool readyfd(Job *);
-static char *targPrefix = NULL; /* To identify a job change in the output. */
+static char *targPrefix = NULL; /* To identify a job change in the output. */
static Job tokenWaitJob; /* token wait pseudo-job */
static Job childExitJob; /* child exit pseudo-job */
@@ -533,13 +535,13 @@ JobDeleteTarget(GNode *gn)
return;
if (gn->type & OP_PHONY)
return;
- if (Targ_Precious(gn))
+ if (GNode_IsPrecious(gn))
return;
if (opts.noExecute)
return;
file = GNode_Path(gn);
- if (eunlink(file) != -1)
+ if (unlink_file(file))
Error("*** %s removed", file);
}
@@ -940,7 +942,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
run = GNode_ShouldExecute(job->node);
- Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd);
+ (void)Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd);
/* TODO: handle errors */
xcmdStart = xcmd;
@@ -954,7 +956,7 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd)
* We're not actually executing anything...
* but this one needs to be - use compat mode just for it.
*/
- Compat_RunCommand(ucmd, job->node, ln);
+ (void)Compat_RunCommand(ucmd, job->node, ln);
free(xcmdStart);
return;
}
@@ -1135,7 +1137,7 @@ JobFinishDoneExitedError(Job *job, WAIT_T *inout_status)
else {
if (deleteOnError)
JobDeleteTarget(job->node);
- PrintOnError(job->node, NULL);
+ PrintOnError(job->node, "\n");
}
}
@@ -1295,9 +1297,11 @@ TouchRegular(GNode *gn)
return; /* XXX: What about propagating the error? */
}
- /* Last resort: update the file's time stamps in the traditional way.
+ /*
+ * Last resort: update the file's time stamps in the traditional way.
* XXX: This doesn't work for empty files, which are sometimes used
- * as marker files. */
+ * as marker files.
+ */
if (read(fd, &c, 1) == 1) {
(void)lseek(fd, 0, SEEK_SET);
while (write(fd, &c, 1) == -1 && errno == EAGAIN)
@@ -1399,7 +1403,7 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
if (gn->flags.fromDepend) {
if (!Job_RunTarget(".STALE", gn->fname))
fprintf(stdout,
- "%s: %s, %d: ignoring stale %s for %s\n",
+ "%s: %s, %u: ignoring stale %s for %s\n",
progname, gn->fname, gn->lineno, makeDependfile,
gn->name);
return true;
@@ -1471,9 +1475,8 @@ JobExec(Job *job, char **argv)
sigset_t tmask;
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_job_child(job);
- }
#endif
/*
* Reset all signal handlers; this is necessary because we
@@ -1556,9 +1559,8 @@ JobExec(Job *job, char **argv)
Trace_Log(JOBSTART, job);
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
meta_job_parent(job, cpid);
- }
#endif
/*
@@ -1652,7 +1654,7 @@ JobWriteShellCommands(Job *job, GNode *gn, bool *out_run)
#ifdef USE_META
if (useMeta) {
meta_job_start(job, gn);
- if (gn->type & OP_SILENT) /* might have changed */
+ if (gn->type & OP_SILENT) /* might have changed */
job->echo = false;
}
#endif
@@ -1696,7 +1698,7 @@ JobStart(GNode *gn, bool special)
job->special = special || gn->type & OP_SPECIAL;
job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE;
- job->echo = !(opts.beSilent || gn->type & OP_SILENT);
+ job->echo = !(opts.silent || gn->type & OP_SILENT);
/*
* Check the commands now so any attributes from .DEFAULT have a
@@ -1715,11 +1717,11 @@ JobStart(GNode *gn, bool special)
* also dead...
*/
if (!cmdsOK) {
- PrintOnError(gn, NULL); /* provide some clue */
+ PrintOnError(gn, "\n"); /* provide some clue */
DieHorribly();
}
} else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) ||
- (!opts.noExecute && !opts.touchFlag)) {
+ (!opts.noExecute && !opts.touch)) {
/*
* The above condition looks very similar to
* GNode_ShouldExecute but is subtly different. It prevents
@@ -1732,7 +1734,7 @@ JobStart(GNode *gn, bool special)
* also dead...
*/
if (!cmdsOK) {
- PrintOnError(gn, NULL); /* provide some clue */
+ PrintOnError(gn, "\n"); /* provide some clue */
DieHorribly();
}
@@ -1952,7 +1954,7 @@ again:
* we add one of our own free will.
*/
if (*cp != '\0') {
- if (!opts.beSilent)
+ if (!opts.silent)
SwitchOutputTo(job->node);
#ifdef USE_META
if (useMeta) {
@@ -2016,7 +2018,7 @@ JobRun(GNode *targ)
Compat_Make(targ, targ);
/* XXX: Replace with GNode_IsError(gn) */
if (targ->made == ERROR) {
- PrintOnError(targ, "\n\nStop.");
+ PrintOnError(targ, "\n\nStop.\n");
exit(1);
}
#endif
@@ -2164,9 +2166,8 @@ Job_CatchOutput(void)
* than job->inPollfd.
*/
if (useMeta && job->inPollfd != &fds[i]) {
- if (meta_job_event(job) <= 0) {
- fds[i].events = 0; /* never mind */
- }
+ if (meta_job_event(job) <= 0)
+ fds[i].events = 0; /* never mind */
}
#endif
if (--nready == 0)
@@ -2220,14 +2221,8 @@ Shell_Init(void)
free(shellErrFlag);
shellErrFlag = NULL;
}
- if (shellErrFlag == NULL) {
- size_t n = strlen(shell->errFlag) + 2;
-
- shellErrFlag = bmake_malloc(n);
- if (shellErrFlag != NULL)
- snprintf(shellErrFlag, n, "-%s",
- shell->errFlag);
- }
+ if (shellErrFlag == NULL)
+ shellErrFlag = str_concat2("-", shell->errFlag);
} else if (shellErrFlag != NULL) {
free(shellErrFlag);
shellErrFlag = NULL;
@@ -2352,8 +2347,10 @@ Job_Init(void)
AddSig(SIGCONT, JobContinueSig);
(void)Job_RunTarget(".BEGIN", NULL);
- /* Create the .END node now, even though no code in the unit tests
- * depends on it. See also Targ_GetEndNode in Compat_Run. */
+ /*
+ * Create the .END node now, even though no code in the unit tests
+ * depends on it. See also Targ_GetEndNode in Compat_Run.
+ */
(void)Targ_GetEndNode();
}
@@ -2493,13 +2490,17 @@ Job_ParseShell(char *line)
} else if (strncmp(arg, "newline=", 8) == 0) {
newShell.newline = arg + 8;
} else if (strncmp(arg, "check=", 6) == 0) {
- /* Before 2020-12-10, these two variables
- * had been a single variable. */
+ /*
+ * Before 2020-12-10, these two variables had
+ * been a single variable.
+ */
newShell.errOn = arg + 6;
newShell.echoTmpl = arg + 6;
} else if (strncmp(arg, "ignore=", 7) == 0) {
- /* Before 2020-12-10, these two variables
- * had been a single variable. */
+ /*
+ * Before 2020-12-10, these two variables had
+ * been a single variable.
+ */
newShell.errOff = arg + 7;
newShell.runIgnTmpl = arg + 7;
} else if (strncmp(arg, "errout=", 7) == 0) {
@@ -2641,7 +2642,7 @@ JobInterrupt(bool runINTERRUPT, int signo)
JobSigUnlock(&mask);
- if (runINTERRUPT && !opts.touchFlag) {
+ if (runINTERRUPT && !opts.touch) {
interrupt = Targ_FindNode(".INTERRUPT");
if (interrupt != NULL) {
opts.ignoreErrors = false;
@@ -2867,7 +2868,7 @@ Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz)
JobSigLock(&mask);
fd = mkTempFile(pattern, tfile, tfile_sz);
if (tfile != NULL && !DEBUG(SCRIPT))
- unlink(tfile);
+ unlink(tfile);
JobSigUnlock(&mask);
return fd;
@@ -2999,7 +3000,7 @@ Job_RunTarget(const char *target, const char *fname)
JobRun(gn);
/* XXX: Replace with GNode_IsError(gn) */
if (gn->made == ERROR) {
- PrintOnError(gn, "\n\nStop.");
+ PrintOnError(gn, "\n\nStop.\n");
exit(1);
}
return true;
@@ -3064,4 +3065,4 @@ emul_poll(struct pollfd *fd, int nfd, int timeout)
return npoll;
}
-#endif /* USE_SELECT */
+#endif /* USE_SELECT */
diff --git a/contrib/bmake/job.h b/contrib/bmake/job.h
index ef66602518d7..574c12ed16a0 100644
--- a/contrib/bmake/job.h
+++ b/contrib/bmake/job.h
@@ -1,4 +1,4 @@
-/* $NetBSD: job.h,v 1.73 2021/04/03 11:08:40 rillig Exp $ */
+/* $NetBSD: job.h,v 1.77 2021/12/15 12:58:01 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -80,7 +80,7 @@
#ifndef MAKE_JOB_H
#define MAKE_JOB_H
-#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
+#define TMPPAT "makeXXXXXX" /* relative to tmpdir */
#ifdef USE_SELECT
/*
@@ -92,16 +92,15 @@
#define pollfd emul_pollfd
struct emul_pollfd {
- int fd;
- short events;
- short revents;
+ int fd;
+ short events;
+ short revents;
};
#define POLLIN 0x0001
#define POLLOUT 0x0004
-int
-emul_poll(struct pollfd *fd, int nfd, int timeout);
+int emul_poll(struct pollfd *, int, int);
#endif
/*
@@ -145,9 +144,11 @@ typedef struct Job {
/* The target the child is making */
GNode *node;
- /* If one of the shell commands is "...", all following commands are
- * delayed until the .END node is made. This list node points to the
- * first of these commands, if any. */
+ /*
+ * If one of the shell commands is "...", all following commands are
+ * delayed until the .END node is made. This list node points to the
+ * first of these commands, if any.
+ */
StringListNode *tailCmds;
/* This is where the shell commands go. */
@@ -187,24 +188,25 @@ extern char *shellErrFlag;
extern int jobTokensRunning; /* tokens currently "out" */
void Shell_Init(void);
-const char *Shell_GetNewline(void);
+const char *Shell_GetNewline(void) MAKE_ATTR_USE;
void Job_Touch(GNode *, bool);
-bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...));
+bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...))
+ MAKE_ATTR_USE;
void Job_CatchChildren(void);
void Job_CatchOutput(void);
void Job_Make(GNode *);
void Job_Init(void);
-bool Job_ParseShell(char *);
+bool Job_ParseShell(char *) MAKE_ATTR_USE;
int Job_Finish(void);
void Job_End(void);
void Job_Wait(void);
void Job_AbortAll(void);
void Job_TokenReturn(void);
-bool Job_TokenWithdraw(void);
+bool Job_TokenWithdraw(void) MAKE_ATTR_USE;
void Job_ServerStart(int, int, int);
void Job_SetPrefix(void);
bool Job_RunTarget(const char *, const char *);
void Job_FlagsToString(const Job *, char *, size_t);
-int Job_TempFile(const char *, char *, size_t);
+int Job_TempFile(const char *, char *, size_t) MAKE_ATTR_USE;
-#endif /* MAKE_JOB_H */
+#endif
diff --git a/contrib/bmake/lst.h b/contrib/bmake/lst.h
index 02f1ae9ec38c..597b687215f7 100644
--- a/contrib/bmake/lst.h
+++ b/contrib/bmake/lst.h
@@ -1,4 +1,4 @@
-/* $NetBSD: lst.h,v 1.99 2021/12/05 10:11:31 rillig Exp $ */
+/* $NetBSD: lst.h,v 1.102 2021/12/15 12:24:13 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -109,7 +109,7 @@ typedef void LstFreeProc(void *);
/* Create or destroy a list */
/* Create a new list. */
-List *Lst_New(void);
+List *Lst_New(void) MAKE_ATTR_USE;
/* Free the list nodes, but not the list itself. */
void Lst_Done(List *);
/* Free the list nodes, freeing the node data using the given function. */
@@ -129,14 +129,14 @@ Lst_Init(List *list)
/* Get information about a list */
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
Lst_IsEmpty(List *list)
{
return list->first == NULL;
}
/* Find the first node that contains the given datum, or NULL. */
-ListNode *Lst_FindDatum(List *, const void *);
+ListNode *Lst_FindDatum(List *, const void *) MAKE_ATTR_USE;
/* Modify a list */
@@ -163,12 +163,13 @@ void LstNode_SetNull(ListNode *);
/* Add a datum at the tail of the queue. */
MAKE_INLINE void
-Lst_Enqueue(List *list, void *datum) {
+Lst_Enqueue(List *list, void *datum)
+{
Lst_Append(list, datum);
}
/* Remove the head node of the queue and return its datum. */
-void *Lst_Dequeue(List *);
+void *Lst_Dequeue(List *) MAKE_ATTR_USE;
/*
* A vector is an ordered collection of items, allowing for fast indexed
@@ -187,7 +188,7 @@ void Vector_Init(Vector *, size_t);
* Return the pointer to the given item in the vector.
* The returned data is valid until the next modifying operation.
*/
-MAKE_INLINE void *
+MAKE_INLINE void * MAKE_ATTR_USE
Vector_Get(Vector *v, size_t i)
{
unsigned char *items = v->items;
@@ -198,8 +199,9 @@ void *Vector_Push(Vector *);
void *Vector_Pop(Vector *);
MAKE_INLINE void
-Vector_Done(Vector *v) {
+Vector_Done(Vector *v)
+{
free(v->items);
}
-#endif /* MAKE_LST_H */
+#endif
diff --git a/contrib/bmake/main.c b/contrib/bmake/main.c
index 60be60c7ca90..d328779ac082 100644
--- a/contrib/bmake/main.c
+++ b/contrib/bmake/main.c
@@ -1,4 +1,4 @@
-/* $NetBSD: main.c,v 1.541 2021/08/14 13:32:12 rillig Exp $ */
+/* $NetBSD: main.c,v 1.577 2022/01/29 09:38:26 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -111,7 +111,7 @@
#include "trace.h"
/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: main.c,v 1.541 2021/08/14 13:32:12 rillig Exp $");
+MAKE_RCSID("$NetBSD: main.c,v 1.577 2022/01/29 09:38:26 rillig Exp $");
#if defined(MAKE_NATIVE) && !defined(lint)
__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
"The Regents of the University of California. "
@@ -135,10 +135,10 @@ static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */
bool doing_depend; /* Set while reading .depend */
static bool jobsRunning; /* true if the jobs might be running */
static const char *tracefile;
-static int ReadMakefile(const char *);
+static bool ReadMakefile(const char *);
static void purge_relative_cached_realpaths(void);
-static bool ignorePWD; /* if we use -C, PWD is meaningless */
+static bool ignorePWD; /* if we use -C, PWD is meaningless */
static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */
char curdir[MAXPATHLEN + 1]; /* Startup directory */
const char *progname;
@@ -157,35 +157,26 @@ static HashTable cached_realpaths;
static char *
explode(const char *flags)
{
- size_t len;
- char *nf, *st;
- const char *f;
+ char *exploded, *ep;
+ const char *p;
if (flags == NULL)
return NULL;
- for (f = flags; *f != '\0'; f++)
- if (!ch_isalpha(*f))
- break;
-
- if (*f != '\0')
- return bmake_strdup(flags);
+ for (p = flags; *p != '\0'; p++)
+ if (!ch_isalpha(*p))
+ return bmake_strdup(flags);
- len = strlen(flags);
- st = nf = bmake_malloc(len * 3 + 1);
- while (*flags != '\0') {
- *nf++ = '-';
- *nf++ = *flags++;
- *nf++ = ' ';
+ exploded = bmake_malloc((size_t)(p - flags) * 3 + 1);
+ for (p = flags, ep = exploded; *p != '\0'; p++) {
+ *ep++ = '-';
+ *ep++ = *p;
+ *ep++ = ' ';
}
- *nf = '\0';
- return st;
+ *ep = '\0';
+ return exploded;
}
-/*
- * usage --
- * exit with usage message
- */
MAKE_ATTR_DEAD static void
usage(void)
{
@@ -229,15 +220,15 @@ MainParseArgDebugFile(const char *arg)
fname = bmake_malloc(len + 20);
memcpy(fname, arg, len + 1);
- /* Let the filename be modified by the pid */
- if (strcmp(fname + len - 3, ".%d") == 0)
+ /* Replace the trailing '%d' after '.%d' with the pid. */
+ if (len >= 3 && memcmp(fname + len - 3, ".%d", 3) == 0)
snprintf(fname + len - 2, 20, "%d", getpid());
opts.debug_file = fopen(fname, mode);
if (opts.debug_file == NULL) {
- fprintf(stderr, "Cannot open debug file %s\n",
+ fprintf(stderr, "Cannot open debug file \"%s\"\n",
fname);
- usage();
+ exit(2);
}
free(fname);
}
@@ -251,79 +242,79 @@ MainParseArgDebug(const char *argvalue)
for (modules = argvalue; *modules != '\0'; modules++) {
switch (*modules) {
case '0': /* undocumented, only intended for tests */
- debug = DEBUG_NONE;
+ memset(&debug, 0, sizeof(debug));
break;
case 'A':
- debug = DEBUG_ALL;
+ memset(&debug, ~0, sizeof(debug));
break;
case 'a':
- debug |= DEBUG_ARCH;
+ debug.DEBUG_ARCH = true;
break;
case 'C':
- debug |= DEBUG_CWD;
+ debug.DEBUG_CWD = true;
break;
case 'c':
- debug |= DEBUG_COND;
+ debug.DEBUG_COND = true;
break;
case 'd':
- debug |= DEBUG_DIR;
+ debug.DEBUG_DIR = true;
break;
case 'e':
- debug |= DEBUG_ERROR;
+ debug.DEBUG_ERROR = true;
break;
case 'f':
- debug |= DEBUG_FOR;
+ debug.DEBUG_FOR = true;
break;
case 'g':
if (modules[1] == '1') {
- debug |= DEBUG_GRAPH1;
+ debug.DEBUG_GRAPH1 = true;
modules++;
} else if (modules[1] == '2') {
- debug |= DEBUG_GRAPH2;
+ debug.DEBUG_GRAPH2 = true;
modules++;
} else if (modules[1] == '3') {
- debug |= DEBUG_GRAPH3;
+ debug.DEBUG_GRAPH3 = true;
modules++;
}
break;
case 'h':
- debug |= DEBUG_HASH;
+ debug.DEBUG_HASH = true;
break;
case 'j':
- debug |= DEBUG_JOB;
+ debug.DEBUG_JOB = true;
break;
case 'L':
opts.strict = true;
break;
case 'l':
- debug |= DEBUG_LOUD;
+ debug.DEBUG_LOUD = true;
break;
case 'M':
- debug |= DEBUG_META;
+ debug.DEBUG_META = true;
break;
case 'm':
- debug |= DEBUG_MAKE;
+ debug.DEBUG_MAKE = true;
break;
case 'n':
- debug |= DEBUG_SCRIPT;
+ debug.DEBUG_SCRIPT = true;
break;
case 'p':
- debug |= DEBUG_PARSE;
+ debug.DEBUG_PARSE = true;
break;
case 's':
- debug |= DEBUG_SUFF;
+ debug.DEBUG_SUFF = true;
break;
case 't':
- debug |= DEBUG_TARG;
+ debug.DEBUG_TARG = true;
break;
case 'V':
opts.debugVflag = true;
break;
case 'v':
- debug |= DEBUG_VAR;
+ debug.DEBUG_VAR = true;
break;
case 'x':
- debug |= DEBUG_SHELL;
+ debug.DEBUG_SHELL = true;
break;
case 'F':
MainParseArgDebugFile(modules + 1);
@@ -403,12 +394,6 @@ MainParseArgJobsInternal(const char *argvalue)
}
if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
(fcntl(jp_1, F_GETFD, 0) < 0)) {
-#if 0
- (void)fprintf(stderr,
- "%s: ###### warning -- J descriptors were closed!\n",
- progname);
- exit(2);
-#endif
jp_0 = -1;
jp_1 = -1;
opts.compatMake = true;
@@ -469,8 +454,9 @@ MainParseArg(char c, const char *argvalue)
MainParseArgChdir(argvalue);
break;
case 'D':
- if (argvalue[0] == '\0') return false;
- Global_SetExpand(argvalue, "1");
+ if (argvalue[0] == '\0')
+ return false;
+ Var_SetExpand(SCOPE_GLOBAL, argvalue, "1");
Global_Append(MAKEFLAGS, "-D");
Global_Append(MAKEFLAGS, argvalue);
break;
@@ -506,7 +492,7 @@ MainParseArg(char c, const char *argvalue)
break;
case 'W':
opts.parseWarnFatal = true;
- /* XXX: why no Var_Append? */
+ /* XXX: why no Global_Append? */
break;
case 'X':
opts.varNoExportEnv = true;
@@ -549,7 +535,7 @@ MainParseArg(char c, const char *argvalue)
Global_Append(MAKEFLAGS, "-n");
break;
case 'q':
- opts.queryFlag = true;
+ opts.query = true;
/* Kind of nonsensical, wot? */
Global_Append(MAKEFLAGS, "-q");
break;
@@ -558,11 +544,11 @@ MainParseArg(char c, const char *argvalue)
Global_Append(MAKEFLAGS, "-r");
break;
case 's':
- opts.beSilent = true;
+ opts.silent = true;
Global_Append(MAKEFLAGS, "-s");
break;
case 't':
- opts.touchFlag = true;
+ opts.touch = true;
Global_Append(MAKEFLAGS, "-t");
break;
case 'w':
@@ -570,7 +556,6 @@ MainParseArg(char c, const char *argvalue)
Global_Append(MAKEFLAGS, "-w");
break;
default:
- case '?':
usage();
}
return true;
@@ -622,7 +607,10 @@ rearg:
/* '-' found at some earlier point */
optspec = strchr(optspecs, c);
if (c != '\0' && optspec != NULL && optspec[1] == ':') {
- /* -<something> found, and <something> should have an arg */
+ /*
+ * -<something> found, and <something> should have an
+ * argument
+ */
inOption = false;
arginc = 1;
argvalue = optscan;
@@ -657,10 +645,7 @@ rearg:
* on the end of the "create" list.
*/
for (; argc > 1; argv++, argc--) {
- VarAssign var;
- if (Parse_IsVar(argv[1], &var)) {
- Parse_Var(&var, SCOPE_CMDLINE);
- } else {
+ if (!Parse_VarAssign(argv[1], false, SCOPE_CMDLINE)) {
if (argv[1][0] == '\0')
Punt("illegal (null) argument.");
if (argv[1][0] == '-' && !dashDash)
@@ -735,7 +720,6 @@ Main_SetObjdir(bool writable, const char *fmt, ...)
char *path;
char buf[MAXPATHLEN + 1];
char buf2[MAXPATHLEN + 1];
- bool rc = false;
va_list ap;
va_start(ap, fmt);
@@ -748,49 +732,39 @@ Main_SetObjdir(bool writable, const char *fmt, ...)
}
/* look for the directory and try to chdir there */
- if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
- 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);
- Global_Set(".OBJDIR", objdir);
- setenv("PWD", objdir, 1);
- Dir_InitDot();
- purge_relative_cached_realpaths();
- rc = true;
- if (opts.enterFlag && strcmp(objdir, curdir) != 0)
- enterFlagObj = true;
- }
+ if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode))
+ return false;
+
+ if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) {
+ (void)fprintf(stderr, "%s warning: %s: %s.\n",
+ progname, path, strerror(errno));
+ return false;
}
- return rc;
+ snprintf(objdir, sizeof objdir, "%s", path);
+ Global_Set(".OBJDIR", objdir);
+ setenv("PWD", objdir, 1);
+ Dir_InitDot();
+ purge_relative_cached_realpaths();
+ if (opts.enterFlag && strcmp(objdir, curdir) != 0)
+ enterFlagObj = true;
+ return true;
}
static bool
SetVarObjdir(bool writable, const char *var, const char *suffix)
{
FStr path = Var_Value(SCOPE_CMDLINE, var);
- FStr xpath;
if (path.str == NULL || path.str[0] == '\0') {
FStr_Done(&path);
return false;
}
- /* expand variable substitutions */
- xpath = FStr_InitRefer(path.str);
- if (strchr(path.str, '$') != 0) {
- char *expanded;
- (void)Var_Subst(path.str, SCOPE_GLOBAL, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- xpath = FStr_InitOwn(expanded);
- }
+ Var_Expand(&path, SCOPE_GLOBAL, VARE_WANTRES);
- (void)Main_SetObjdir(writable, "%s%s", xpath.str, suffix);
+ (void)Main_SetObjdir(writable, "%s%s", path.str, suffix);
- FStr_Done(&xpath);
FStr_Done(&path);
return true;
}
@@ -861,7 +835,7 @@ PrintVar(const char *varname, bool expandVars)
(void)Var_Subst(varname, SCOPE_GLOBAL, VARE_WANTRES, &evalue);
/* TODO: handle errors */
printf("%s\n", evalue);
- bmake_free(evalue);
+ free(evalue);
} else if (expandVars) {
char *expr = str_concat3("${", varname, "}");
@@ -870,7 +844,7 @@ PrintVar(const char *varname, bool expandVars)
/* TODO: handle errors */
free(expr);
printf("%s\n", evalue);
- bmake_free(evalue);
+ free(evalue);
} else {
FStr value = Var_Value(SCOPE_GLOBAL, varname);
@@ -923,7 +897,7 @@ static bool
runTargets(void)
{
GNodeList targs = LST_INIT; /* target nodes to create */
- bool outOfDate; /* false if all targets up to date */
+ bool outOfDate; /* false if all targets up to date */
/*
* Have now read the entire graph and need to make a list of
@@ -944,7 +918,7 @@ runTargets(void)
* (to prevent the .BEGIN from being executed should
* it exist).
*/
- if (!opts.queryFlag) {
+ if (!opts.query) {
Job_Init();
jobsRunning = true;
}
@@ -964,9 +938,9 @@ runTargets(void)
}
/*
- * Set up the .TARGETS variable to contain the list of targets to be
- * created. If none specified, make the variable empty -- the parser
- * will fill the thing in with the default or .MAIN target.
+ * Set up the .TARGETS variable to contain the list of targets to be created.
+ * If none specified, make the variable empty for now, the parser will fill
+ * in the default or .MAIN target later.
*/
static void
InitVarTargets(void)
@@ -1071,17 +1045,14 @@ static void
HandlePWD(const struct stat *curdir_st)
{
char *pwd;
- FStr prefix, makeobjdir;
+ FStr makeobjdir;
struct stat pwd_st;
if (ignorePWD || (pwd = getenv("PWD")) == NULL)
return;
- prefix = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX");
- if (prefix.str != NULL) {
- FStr_Done(&prefix);
+ if (Var_Exists(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX"))
return;
- }
makeobjdir = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIR");
if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != NULL)
@@ -1098,13 +1069,13 @@ ignore_pwd:
#endif
/*
- * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that,
- * MAKEOBJDIR is set in the environment, try only that value
- * and fall back to .CURDIR if it does not exist.
+ * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, MAKEOBJDIR is set
+ * in the environment, try only that value and fall back to .CURDIR if it
+ * does not exist.
*
* Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE,
- * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none
- * of these paths exist, just use .CURDIR.
+ * and finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none of these
+ * paths exist, just use .CURDIR.
*/
static void
InitObjdir(const char *machine, const char *machine_arch)
@@ -1141,7 +1112,7 @@ static void
CmdOpts_Init(void)
{
opts.compatMake = false;
- opts.debug = DEBUG_NONE;
+ memset(&opts.debug, 0, sizeof(opts.debug));
/* opts.debug_file has already been initialized earlier */
opts.strict = false;
opts.debugVflag = false;
@@ -1152,10 +1123,10 @@ CmdOpts_Init(void)
opts.keepgoing = false; /* Stop on error */
opts.noRecursiveExecute = false; /* Execute all .MAKE targets */
opts.noExecute = false; /* Execute all commands */
- opts.queryFlag = false;
+ opts.query = false;
opts.noBuiltins = false; /* Read the built-in rules */
- opts.beSilent = false; /* Print commands as executed */
- opts.touchFlag = false;
+ opts.silent = false; /* Print commands as executed */
+ opts.touch = false;
opts.printVars = PVM_NONE;
Lst_Init(&opts.variables);
opts.parseWarnFatal = false;
@@ -1245,16 +1216,14 @@ ReadBuiltinRules(void)
Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK);
for (ln = sysMkFiles.first; ln != NULL; ln = ln->next)
- if (ReadMakefile(ln->datum) == 0)
+ if (ReadMakefile(ln->datum))
break;
if (ln == NULL)
Fatal("%s: cannot open %s.",
progname, (const char *)sysMkFiles.first->datum);
- /* Free the list nodes but not the actual filenames since these may
- * still be used in GNodes. */
- Lst_Done(&sysMkFiles);
+ Lst_DoneCall(&sysMkFiles, free);
}
static void
@@ -1322,13 +1291,13 @@ InitVpath(void)
}
static void
-ReadAllMakefiles(StringList *makefiles)
+ReadAllMakefiles(const StringList *makefiles)
{
StringListNode *ln;
for (ln = makefiles->first; ln != NULL; ln = ln->next) {
const char *fname = ln->datum;
- if (ReadMakefile(fname) != 0)
+ if (!ReadMakefile(fname))
Fatal("%s: cannot open %s.", progname, fname);
}
}
@@ -1336,6 +1305,7 @@ ReadAllMakefiles(StringList *makefiles)
static void
ReadFirstDefaultMakefile(void)
{
+ StringList makefiles = LST_INIT;
StringListNode *ln;
char *prefs;
@@ -1343,16 +1313,13 @@ ReadFirstDefaultMakefile(void)
SCOPE_CMDLINE, VARE_WANTRES, &prefs);
/* TODO: handle errors */
- /* 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);
+ (void)str2Lst_Append(&makefiles, prefs);
- for (ln = opts.makefiles.first; ln != NULL; ln = ln->next)
- if (ReadMakefile(ln->datum) == 0)
+ for (ln = makefiles.first; ln != NULL; ln = ln->next)
+ if (ReadMakefile(ln->datum))
break;
+ Lst_Done(&makefiles);
free(prefs);
}
@@ -1373,6 +1340,7 @@ main_Init(int argc, char **argv)
/* default to writing debug to stderr */
opts.debug_file = stderr;
+ Str_Intern_Init();
HashTable_Init(&cached_realpaths);
#ifdef SIGINFO
@@ -1415,11 +1383,9 @@ main_Init(int argc, char **argv)
#ifdef MAKE_VERSION
Global_Set("MAKE_VERSION", MAKE_VERSION);
#endif
- Global_Set(".newline", "\n"); /* handy for :@ loops */
- /*
- * This is the traditional preference for makefiles.
- */
+ Global_Set(".newline", "\n"); /* handy for :@ loops */
#ifndef MAKEFILE_PREFERENCE_LIST
+ /* This is the traditional preference for makefiles. */
# define MAKEFILE_PREFERENCE_LIST "makefile Makefile"
#endif
Global_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST);
@@ -1451,26 +1417,25 @@ main_Init(int argc, char **argv)
Global_Set(MAKEOVERRIDES, "");
Global_Set("MFLAGS", "");
Global_Set(".ALLTARGETS", "");
- /* some makefiles need to know this */
Var_Set(SCOPE_CMDLINE, MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV);
/* Set some other useful variables. */
{
- char tmp[64], *ep = getenv(MAKE_LEVEL_ENV);
+ char buf[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);
- Global_Set(MAKE_LEVEL, tmp);
- snprintf(tmp, sizeof tmp, "%u", myPid);
- Global_Set(".MAKE.PID", tmp);
- snprintf(tmp, sizeof tmp, "%u", getppid());
- Global_Set(".MAKE.PPID", tmp);
- snprintf(tmp, sizeof tmp, "%u", getuid());
- Global_Set(".MAKE.UID", tmp);
- snprintf(tmp, sizeof tmp, "%u", getgid());
- Global_Set(".MAKE.GID", tmp);
+ snprintf(buf, sizeof buf, "%d", makelevel);
+ Global_Set(MAKE_LEVEL, buf);
+ snprintf(buf, sizeof buf, "%u", myPid);
+ Global_Set(".MAKE.PID", buf);
+ snprintf(buf, sizeof buf, "%u", getppid());
+ Global_Set(".MAKE.PPID", buf);
+ snprintf(buf, sizeof buf, "%u", getuid());
+ Global_Set(".MAKE.UID", buf);
+ snprintf(buf, sizeof buf, "%u", getgid());
+ Global_Set(".MAKE.GID", buf);
}
if (makelevel > 0) {
char pn[1024];
@@ -1483,25 +1448,21 @@ main_Init(int argc, char **argv)
#endif
Dir_Init();
+#ifdef POSIX
+ {
+ char *makeflags = explode(getenv("MAKEFLAGS"));
+ Main_ParseArgLine(makeflags);
+ free(makeflags);
+ }
+#else
/*
* First snag any flags out of the MAKE environment variable.
* (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
* in a different format).
*/
-#ifdef POSIX
- {
- char *p1 = explode(getenv("MAKEFLAGS"));
- Main_ParseArgLine(p1);
- free(p1);
- }
-#else
Main_ParseArgLine(getenv("MAKE"));
#endif
- /*
- * Find where we are (now).
- * We take care of PWD for the automounter below...
- */
if (getcwd(curdir, MAXPATHLEN) == NULL) {
(void)fprintf(stderr, "%s: getcwd: %s.\n",
progname, strerror(errno));
@@ -1513,9 +1474,6 @@ main_Init(int argc, char **argv)
if (opts.enterFlag)
printf("%s: Entering directory `%s'\n", progname, curdir);
- /*
- * Verify that cwd is sane.
- */
if (stat(curdir, &sa) == -1) {
(void)fprintf(stderr, "%s: %s: %s.\n",
progname, curdir, strerror(errno));
@@ -1529,10 +1487,6 @@ main_Init(int argc, char **argv)
InitObjdir(machine, machine_arch);
- /*
- * Initialize archive, target and suffix modules in preparation for
- * parsing the makefile(s)
- */
Arch_Init();
Suff_Init();
Trace_Init(tracefile);
@@ -1593,10 +1547,6 @@ main_PrepareMaking(void)
InitMaxJobs();
- /*
- * Be compatible if the user did not specify -j and did not explicitly
- * turn compatibility on.
- */
if (!opts.compatMake && !forceJobs)
opts.compatMake = true;
@@ -1649,15 +1599,10 @@ main_CleanUp(void)
{
#ifdef CLEANUP
Lst_DoneCall(&opts.variables, free);
- /*
- * Don't free the actual strings from opts.makefiles, they may be
- * used in GNodes.
- */
- Lst_Done(&opts.makefiles);
+ Lst_DoneCall(&opts.makefiles, free);
Lst_DoneCall(&opts.create, free);
#endif
- /* print the graph now it's been processed if the user requested it */
if (DEBUG(GRAPH2))
Targ_PrintGraph(2);
@@ -1679,6 +1624,7 @@ main_CleanUp(void)
Dir_End();
Job_End();
Trace_End();
+ Str_Intern_End();
}
/* Determine the exit code. */
@@ -1705,18 +1651,16 @@ main(int argc, char **argv)
/*
* Open and parse the given makefile, with all its side effects.
- *
- * Results:
- * 0 if ok. -1 if couldn't open file.
+ * Return false if the file could not be opened.
*/
-static int
+static bool
ReadMakefile(const char *fname)
{
int fd;
char *name, *path = NULL;
if (strcmp(fname, "-") == 0) {
- Parse_File(NULL /*stdin*/, -1);
+ Parse_File("(stdin)", -1);
Var_Set(SCOPE_INTERNAL, "MAKEFILE", "");
} else {
/* if we've chdir'd, rebuild the path name */
@@ -1745,13 +1689,13 @@ ReadMakefile(const char *fname)
name = Dir_FindFile(fname, parseIncPath);
if (name == NULL) {
SearchPath *sysInc = Lst_IsEmpty(&sysIncPath->dirs)
- ? defSysIncPath : sysIncPath;
+ ? defSysIncPath : sysIncPath;
name = Dir_FindFile(fname, sysInc);
}
if (name == NULL || (fd = open(name, O_RDONLY)) == -1) {
free(name);
free(path);
- return -1;
+ return false;
}
fname = name;
/*
@@ -1765,154 +1709,127 @@ found:
Parse_File(fname, fd);
}
free(path);
- return 0;
+ return true;
}
/*
- * Cmd_Exec --
- * Execute the command in cmd, and return the output of that command
- * in a string. In the output, newlines are replaced with spaces.
- *
- * Results:
- * A string containing the output of the command, or the empty string.
- * *errfmt returns a format string describing the command failure,
- * if any, using a single %s conversion specification.
- *
- * Side Effects:
- * The string must be freed by the caller.
+ * Execute the command in cmd, and return its output (only stdout, not
+ * stderr, possibly empty). In the output, replace newlines with spaces.
*/
char *
-Cmd_Exec(const char *cmd, const char **errfmt)
+Cmd_Exec(const char *cmd, char **error)
{
- const char *args[4]; /* Args for invoking the shell */
+ const char *args[4]; /* Arguments for invoking the shell */
int pipefds[2];
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 *output;
char *cp;
- int savederr; /* saved errno */
-
- *errfmt = NULL;
+ int saved_errno;
if (shellName == NULL)
Shell_Init();
- /*
- * Set up arguments for shell
- */
+
args[0] = shellName;
args[1] = "-c";
args[2] = cmd;
args[3] = NULL;
+ DEBUG1(VAR, "Capturing the output of command \"%s\"\n", cmd);
- /*
- * Open a pipe for fetching its output
- */
if (pipe(pipefds) == -1) {
- *errfmt = "Couldn't create pipe for \"%s\"";
- goto bad;
+ *error = str_concat3(
+ "Couldn't create pipe for \"", cmd, "\"");
+ return bmake_strdup("");
}
Var_ReexportVars();
- /*
- * Fork
- */
switch (cpid = vfork()) {
case 0:
- (void)close(pipefds[0]); /* Close input side of pipe */
-
- /*
- * 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(pipefds[1], 1);
+ (void)close(pipefds[0]);
+ (void)dup2(pipefds[1], STDOUT_FILENO);
(void)close(pipefds[1]);
(void)execv(shellPath, UNCONST(args));
_exit(1);
- /*NOTREACHED*/
+ /* NOTREACHED */
case -1:
- *errfmt = "Couldn't exec \"%s\"";
- goto bad;
-
- default:
- (void)close(pipefds[1]); /* No need for the writing half */
-
- savederr = 0;
- Buf_Init(&buf);
-
- do {
- char result[BUFSIZ];
- bytes_read = read(pipefds[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(pipefds[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;
- res = Buf_DoneData(&buf);
-
- 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;
+ *error = str_concat3("Couldn't exec \"", cmd, "\"");
+ return bmake_strdup("");
}
- return res;
-bad:
- return bmake_strdup("");
+
+ (void)close(pipefds[1]); /* No need for the writing half */
+
+ saved_errno = 0;
+ Buf_Init(&buf);
+
+ do {
+ char result[BUFSIZ];
+ bytes_read = read(pipefds[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)
+ saved_errno = errno;
+
+ (void)close(pipefds[0]); /* Close the input side of the pipe. */
+
+ while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0)
+ JobReapChild(pid, status, false);
+
+ if (Buf_EndsWith(&buf, '\n'))
+ buf.data[buf.len - 1] = '\0';
+
+ output = Buf_DoneData(&buf);
+ for (cp = output; *cp != '\0'; cp++)
+ if (*cp == '\n')
+ *cp = ' ';
+
+ if (WIFSIGNALED(status))
+ *error = str_concat3("\"", cmd, "\" exited on a signal");
+ else if (WEXITSTATUS(status) != 0)
+ *error = str_concat3(
+ "\"", cmd, "\" returned non-zero status");
+ else if (saved_errno != 0)
+ *error = str_concat3(
+ "Couldn't read shell's output for \"", cmd, "\"");
+ else
+ *error = NULL;
+ return output;
}
/*
* Print a printf-style error message.
*
- * 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.
+ * In default mode, this error message has no consequences, for compatibility
+ * reasons, in particular it does not affect the exit status. Only in lint
+ * mode (-dL) it does.
*/
void
Error(const char *fmt, ...)
{
va_list ap;
- FILE *err_file;
+ FILE *f;
- err_file = opts.debug_file;
- if (err_file == stdout)
- err_file = stderr;
+ f = opts.debug_file;
+ if (f == stdout)
+ f = stderr;
(void)fflush(stdout);
+
for (;;) {
+ fprintf(f, "%s: ", progname);
va_start(ap, fmt);
- fprintf(err_file, "%s: ", progname);
- (void)vfprintf(err_file, fmt, ap);
+ (void)vfprintf(f, fmt, ap);
va_end(ap);
- (void)fprintf(err_file, "\n");
- (void)fflush(err_file);
- if (err_file == stderr)
+ (void)fprintf(f, "\n");
+ (void)fflush(f);
+ if (f == stderr)
break;
- err_file = stderr;
+ f = stderr;
}
main_errors++;
}
@@ -1938,8 +1855,9 @@ Fatal(const char *fmt, ...)
va_end(ap);
(void)fprintf(stderr, "\n");
(void)fflush(stderr);
+ PrintStackTrace(true);
- PrintOnError(NULL, NULL);
+ PrintOnError(NULL, "\n");
if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
Targ_PrintGraph(2);
@@ -1956,15 +1874,15 @@ Punt(const char *fmt, ...)
{
va_list ap;
- va_start(ap, fmt);
(void)fflush(stdout);
(void)fprintf(stderr, "%s: ", progname);
+ va_start(ap, fmt);
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
(void)fprintf(stderr, "\n");
(void)fflush(stderr);
- PrintOnError(NULL, NULL);
+ PrintOnError(NULL, "\n");
DieHorribly();
}
@@ -1994,23 +1912,19 @@ Finish(int errs)
Fatal("%d error%s", errs, errs == 1 ? "" : "s");
}
-/*
- * eunlink --
- * Remove a file carefully, avoiding directories.
- */
-int
-eunlink(const char *file)
+bool
+unlink_file(const char *file)
{
struct stat st;
if (lstat(file, &st) == -1)
- return -1;
+ return false;
if (S_ISDIR(st.st_mode)) {
errno = EISDIR;
- return -1;
+ return false;
}
- return unlink(file);
+ return unlink(file) == 0;
}
static void
@@ -2020,6 +1934,7 @@ write_all(int fd, const void *data, size_t n)
while (n > 0) {
ssize_t written = write(fd, mem, n);
+ /* XXX: Should this EAGAIN be EINTR? */
if (written == -1 && errno == EAGAIN)
continue;
if (written == -1)
@@ -2029,10 +1944,7 @@ write_all(int fd, const void *data, size_t n)
}
}
-/*
- * execDie --
- * Print why exec failed, avoiding stdio.
- */
+/* Print why exec failed, avoiding stdio. */
void MAKE_ATTR_DEAD
execDie(const char *af, const char *av)
{
@@ -2054,7 +1966,6 @@ execDie(const char *af, const char *av)
_exit(1);
}
-/* purge any relative paths */
static void
purge_relative_cached_realpaths(void)
{
@@ -2068,14 +1979,16 @@ purge_relative_cached_realpaths(void)
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. */
+ /*
+ * XXX: What about the allocated he->value? Either
+ * free them or document why they cannot be freed.
+ */
}
he = nhe;
}
}
-char *
+const char *
cached_realpath(const char *pathname, char *resolved)
{
const char *rp;
@@ -2160,9 +2073,7 @@ PrintOnError(GNode *gn, const char *msg)
if (errorNode != NULL)
return; /* we've been here! */
- if (msg != NULL)
- printf("%s", msg);
- printf("\n%s: stopped in %s\n", progname, curdir);
+ printf("%s%s: stopped in %s\n", msg, progname, curdir);
/* we generally want to keep quiet if a sub-make died */
if (shouldDieQuietly(gn, -1))
@@ -2174,7 +2085,7 @@ PrintOnError(GNode *gn, const char *msg)
{
char *errorVarsValues;
(void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
- SCOPE_GLOBAL, VARE_WANTRES, &errorVarsValues);
+ SCOPE_GLOBAL, VARE_WANTRES, &errorVarsValues);
/* TODO: handle errors */
printf("%s", errorVarsValues);
free(errorVarsValues);
@@ -2196,21 +2107,21 @@ void
Main_ExportMAKEFLAGS(bool first)
{
static bool once = true;
- const char *expr;
- char *s;
+ char *flags;
if (once != first)
return;
once = false;
- expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}";
- (void)Var_Subst(expr, SCOPE_CMDLINE, VARE_WANTRES, &s);
+ (void)Var_Subst(
+ "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
+ SCOPE_CMDLINE, VARE_WANTRES, &flags);
/* TODO: handle errors */
- if (s[0] != '\0') {
+ if (flags[0] != '\0') {
#ifdef POSIX
- setenv("MAKEFLAGS", s, 1);
+ setenv("MAKEFLAGS", flags, 1);
#else
- setenv("MAKE", s, 1);
+ setenv("MAKE", flags, 1);
#endif
}
}
@@ -2224,7 +2135,7 @@ getTmpdir(void)
if (tmpdir != NULL)
return tmpdir;
- /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */
+ /* Honor $TMPDIR if it is valid, strip a trailing '/'. */
(void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/",
SCOPE_GLOBAL, VARE_WANTRES, &tmpdir);
/* TODO: handle errors */
@@ -2253,20 +2164,21 @@ mkTempFile(const char *pattern, char *tfile, size_t tfile_sz)
if (tmpdir == NULL)
tmpdir = getTmpdir();
if (tfile == NULL) {
- tfile = tbuf;
- tfile_sz = sizeof tbuf;
+ tfile = tbuf;
+ tfile_sz = sizeof tbuf;
}
- if (pattern[0] == '/') {
+
+ if (pattern[0] == '/')
snprintf(tfile, tfile_sz, "%s", pattern);
- } else {
+ else
snprintf(tfile, tfile_sz, "%s%s", tmpdir, pattern);
- }
+
if ((fd = mkstemp(tfile)) < 0)
Punt("Could not create temporary file %s: %s", tfile,
strerror(errno));
- if (tfile == tbuf) {
+ if (tfile == tbuf)
unlink(tfile); /* we just want the descriptor */
- }
+
return fd;
}
diff --git a/contrib/bmake/make.1 b/contrib/bmake/make.1
index dbbff753673a..726b1afae0a4 100644
--- a/contrib/bmake/make.1
+++ b/contrib/bmake/make.1
@@ -1,4 +1,4 @@
-.\" $NetBSD: make.1,v 1.300 2021/12/12 20:45:48 sjg Exp $
+.\" $NetBSD: make.1,v 1.304 2022/01/29 20:54:58 sjg Exp $
.\"
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
@@ -29,7 +29,7 @@
.\"
.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94
.\"
-.Dd December 12, 2021
+.Dd January 28, 2022
.Dt MAKE 1
.Os
.Sh NAME
@@ -691,10 +691,38 @@ Variables defined as part of the command line.
Variables that are defined specific to a certain target.
.El
.Pp
-Local variables are all built in and their values vary magically from
-target to target.
-It is not currently possible to define new local variables.
-The seven local variables are as follows:
+Local variables can be set on a dependency line, if
+.Va .MAKE.TARGET_LOCAL_VARIABLES ,
+is not set to
+.Ql false .
+The rest of the line
+(which will already have had Global variables expanded),
+is the variable value.
+For example:
+.Bd -literal -offset indent
+COMPILER_WRAPPERS+= ccache distcc icecc
+
+${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,}
+.Ed
+.Pp
+Only the targets
+.Ql ${OBJS}
+will be impacted by that filter (in "meta" mode) and
+simply enabling/disabling any of the wrappers will not render all
+of those targets out-of-date.
+.Pp
+.Em NOTE :
+target local variable assignments behave differently in that;
+.Bl -tag -width Ds -offset indent
+.It Ic \&+=
+Only appends to a previous local assignment
+for the same target and variable.
+.It Ic \&:=
+Is redundant with respect to Global variables,
+which have already been expanded.
+.El
+.Pp
+The seven built-in local variables are as follows:
.Bl -tag -width ".ARCHIVE" -offset indent
.It Va .ALLSRC
The list of all sources for this target; also known as
@@ -857,6 +885,11 @@ For example:
would produce tokens like
.Ql ---make[1234] target ---
making it easier to track the degree of parallelism being achieved.
+.It .MAKE.TARGET_LOCAL_VARIABLES
+If set to
+.Ql false ,
+apparent variable assignments in dependency lines are
+treated as normal sources.
.It Ev MAKEFLAGS
The environment variable
.Ql Ev MAKEFLAGS
@@ -965,6 +998,12 @@ If a file that was generated outside of
.Va .OBJDIR
but within said bailiwick is missing,
the current target is considered out-of-date.
+.It Va .MAKE.META.CMP_FILTER
+In "meta" mode, it can (very rarely!) be useful to filter command
+lines before comparison.
+This variable can be set to a set of modifiers that will be applied to
+each line of the old and new command that differ, if the filtered
+commands still differ, the target is considered out-of-date.
.It Va .MAKE.META.CREATED
In "meta" mode, this variable contains a list of all the meta files
updated.
diff --git a/contrib/bmake/make.c b/contrib/bmake/make.c
index 2c1a243965f1..5fb8f8840772 100644
--- a/contrib/bmake/make.c
+++ b/contrib/bmake/make.c
@@ -1,4 +1,4 @@
-/* $NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $ */
+/* $NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -104,7 +104,7 @@
#include "job.h"
/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */
-MAKE_RCSID("$NetBSD: make.c,v 1.248 2021/11/28 23:12:51 rillig Exp $");
+MAKE_RCSID("$NetBSD: make.c,v 1.252 2022/01/09 15:48:30 rillig Exp $");
/* Sequence # to detect recursion. */
static unsigned int checked_seqno = 1;
@@ -120,11 +120,11 @@ static GNodeList toBeMade = LST_INIT;
void
debug_printf(const char *fmt, ...)
{
- va_list args;
+ va_list ap;
- va_start(args, fmt);
- vfprintf(opts.debug_file, fmt, args);
- va_end(args);
+ va_start(ap, fmt);
+ vfprintf(opts.debug_file, fmt, ap);
+ va_end(ap);
}
MAKE_ATTR_DEAD static void
@@ -370,9 +370,8 @@ GNode_IsOODate(GNode *gn)
}
#ifdef USE_META
- if (useMeta) {
+ if (useMeta)
oodate = meta_oodate(gn, oodate);
- }
#endif
/*
@@ -599,8 +598,9 @@ Make_Recheck(GNode *gn)
}
#endif
- /* XXX: The returned mtime may differ from gn->mtime.
- * Intentionally? */
+ /*
+ * XXX: The returned mtime may differ from gn->mtime. Intentionally?
+ */
return mtime;
}
@@ -952,7 +952,9 @@ MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext)
/* If this node is on the RHS of a .ORDER, check LHSs. */
if (IsWaitingForOrder(cn)) {
- /* Can't build this (or anything else in this child list) yet */
+ /*
+ * Can't build this (or anything else in this child list) yet
+ */
cn->made = DEFERRED;
return false; /* but keep looking */
}
@@ -1070,7 +1072,7 @@ MakeStartJobs(void)
gn->made = BEINGMADE;
if (GNode_IsOODate(gn)) {
DEBUG0(MAKE, "out-of-date\n");
- if (opts.queryFlag)
+ if (opts.query)
return true;
GNode_SetLocalVars(gn);
Job_Make(gn);
@@ -1327,7 +1329,9 @@ add_wait_dependency(GNodeListNode *owln, GNode *wn)
DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n",
cn->name, cn->cohort_num, wn->name);
- /* XXX: This pattern should be factored out, it repeats often */
+ /*
+ * XXX: This pattern should be factored out, it repeats often
+ */
Lst_Append(&wn->children, cn);
wn->unmade++;
Lst_Append(&cn->parents, wn);
@@ -1436,7 +1440,7 @@ Make_Run(GNodeList *targs)
Targ_PrintGraph(1);
}
- if (opts.queryFlag) {
+ if (opts.query) {
/*
* We wouldn't do any work unless we could start some jobs
* in the next loop... (we won't actually start any, of
diff --git a/contrib/bmake/make.h b/contrib/bmake/make.h
index 6f14c44eb067..091b9dc05648 100644
--- a/contrib/bmake/make.h
+++ b/contrib/bmake/make.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make.h,v 1.270 2021/11/28 23:12:51 rillig Exp $ */
+/* $NetBSD: make.h,v 1.298 2022/02/05 00:26:21 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -74,7 +74,7 @@
/*
* make.h --
- * The global definitions for pmake
+ * The global definitions for make
*/
#ifndef MAKE_MAKE_H
@@ -110,9 +110,9 @@
#define MAKE_GNUC_PREREQ(x, y) \
((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
(__GNUC__ > (x)))
-#else /* defined(__GNUC__) */
+#else
#define MAKE_GNUC_PREREQ(x, y) 0
-#endif /* defined(__GNUC__) */
+#endif
#if MAKE_GNUC_PREREQ(2, 7)
#define MAKE_ATTR_UNUSED __attribute__((__unused__))
@@ -135,7 +135,17 @@
#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */
#endif
+#if MAKE_GNUC_PREREQ(4, 0)
+#define MAKE_ATTR_USE __attribute__((__warn_unused_result__))
+#else
+#define MAKE_ATTR_USE /* delete */
+#endif
+
+#if __STDC__ >= 199901L || defined(lint)
#define MAKE_INLINE static inline MAKE_ATTR_UNUSED
+#else
+#define MAKE_INLINE static MAKE_ATTR_UNUSED
+#endif
/* MAKE_STATIC marks a function that may or may not be inlined. */
#if defined(lint)
@@ -206,26 +216,34 @@ typedef unsigned char bool;
typedef enum GNodeMade {
/* Not examined yet. */
UNMADE,
- /* The node has been examined but is not yet ready since its
- * dependencies have to be made first. */
+ /*
+ * The node has been examined but is not yet ready since its
+ * dependencies have to be made first.
+ */
DEFERRED,
/* The node is on the toBeMade list. */
REQUESTED,
- /* The node is already being made. Trying to build a node in this
- * state indicates a cycle in the graph. */
+ /*
+ * The node is already being made. Trying to build a node in this
+ * state indicates a cycle in the graph.
+ */
BEINGMADE,
/* Was out-of-date and has been made. */
MADE,
/* Was already up-to-date, does not need to be made. */
UPTODATE,
- /* An error occurred while it was being made.
- * Used only in compat mode. */
+ /*
+ * An error occurred while it was being made. Used only in compat
+ * mode.
+ */
ERROR,
- /* The target was aborted due to an error making a dependency.
- * Used only in compat mode. */
+ /*
+ * The target was aborted due to an error making a dependency. Used
+ * only in compat mode.
+ */
ABORTED
} GNodeMade;
@@ -241,16 +259,22 @@ typedef enum GNodeMade {
typedef enum GNodeType {
OP_NONE = 0,
- /* The dependency operator ':' is the most common one. The commands
- * of this node are executed if any child is out-of-date. */
+ /*
+ * The dependency operator ':' is the most common one. The commands
+ * of this node are executed if any child is out-of-date.
+ */
OP_DEPENDS = 1 << 0,
- /* The dependency operator '!' always executes its commands, even if
- * its children are up-to-date. */
+ /*
+ * The dependency operator '!' always executes its commands, even if
+ * its children are up-to-date.
+ */
OP_FORCE = 1 << 1,
- /* The dependency operator '::' behaves like ':', except that it
+ /*
+ * The dependency operator '::' behaves like ':', except that it
* allows multiple dependency groups to be defined. Each of these
- * groups is executed on its own, independently from the others.
- * Each individual dependency group is called a cohort. */
+ * groups is executed on its own, independently from the others. Each
+ * individual dependency group is called a cohort.
+ */
OP_DOUBLEDEP = 1 << 2,
/* Matches the dependency operators ':', '!' and '::'. */
@@ -260,21 +284,29 @@ typedef enum GNodeType {
OP_OPTIONAL = 1 << 3,
/* Use associated commands for parents. */
OP_USE = 1 << 4,
- /* Target is never out of date, but always execute commands anyway.
- * Its time doesn't matter, so it has none...sort of. */
+ /*
+ * Target is never out of date, but always execute commands anyway.
+ * Its time doesn't matter, so it has none...sort of.
+ */
OP_EXEC = 1 << 5,
- /* Ignore non-zero exit status from shell commands when creating the
- * node. */
+ /*
+ * Ignore non-zero exit status from shell commands when creating the
+ * node.
+ */
OP_IGNORE = 1 << 6,
/* Don't remove the target when interrupted. */
OP_PRECIOUS = 1 << 7,
/* Don't echo commands when executed. */
OP_SILENT = 1 << 8,
- /* Target is a recursive make so its commands should always be
- * executed when it is out of date, regardless of the state of the
- * -n or -t flags. */
+ /*
+ * Target is a recursive make so its commands should always be
+ * executed when it is out of date, regardless of the state of the -n
+ * or -t flags.
+ */
OP_MAKE = 1 << 9,
- /* Target is out-of-date only if any of its children was out-of-date. */
+ /*
+ * Target is out-of-date only if any of its children was out-of-date.
+ */
OP_JOIN = 1 << 10,
/* Assume the children of the node have been already made. */
OP_MADE = 1 << 11,
@@ -282,20 +314,26 @@ typedef enum GNodeType {
OP_SPECIAL = 1 << 12,
/* Like .USE, only prepend commands. */
OP_USEBEFORE = 1 << 13,
- /* The node is invisible to its parents. I.e. it doesn't show up in
- * the parents' local variables (.IMPSRC, .ALLSRC). */
+ /*
+ * The node is invisible to its parents. I.e. it doesn't show up in
+ * the parents' local variables (.IMPSRC, .ALLSRC).
+ */
OP_INVISIBLE = 1 << 14,
- /* The node does not become the main target, even if it is the first
- * target in the first makefile. */
+ /*
+ * The node does not become the main target, even if it is the first
+ * target in the first makefile.
+ */
OP_NOTMAIN = 1 << 15,
/* Not a file target; run always. */
OP_PHONY = 1 << 16,
/* Don't search for the file in the path. */
OP_NOPATH = 1 << 17,
- /* In a dependency line "target: source1 .WAIT source2", source1 is
+ /*
+ * In a dependency line "target: source1 .WAIT source2", source1 is
* made first, including its children. Once that is finished,
* source2 is made, including its children. The .WAIT keyword may
- * appear more than once in a single dependency declaration. */
+ * appear more than once in a single dependency declaration.
+ */
OP_WAIT = 1 << 18,
/* .NOMETA do not create a .meta file */
OP_NOMETA = 1 << 19,
@@ -313,28 +351,35 @@ typedef enum GNodeType {
/* Target is a member of an archive */
/* XXX: How does this differ from OP_ARCHV? */
OP_MEMBER = 1 << 29,
- /* The node is a library,
- * its name has the form "-l<libname>" */
+ /*
+ * The node is a library, its name has the form "-l<libname>".
+ */
OP_LIB = 1 << 28,
- /* The node is an archive member,
- * its name has the form "archive(member)" */
+ /*
+ * The node is an archive member, its name has the form
+ * "archive(member)".
+ */
/* XXX: How does this differ from OP_MEMBER? */
OP_ARCHV = 1 << 27,
- /* Target has all the commands it should. Used when parsing to catch
+ /*
+ * Target has all the commands it should. Used when parsing to catch
* multiple command groups for a target. Only applies to the
- * dependency operators ':' and '!', but not to '::'. */
+ * dependency operators ':' and '!', but not to '::'.
+ */
OP_HAS_COMMANDS = 1 << 26,
- /* The special command "..." has been seen. All further commands from
- * this node will be saved on the .END node instead, to be executed at
- * the very end. */
+ /*
+ * The special command "..." has been seen. All further commands from
+ * this node will be saved on the .END node instead, to be executed
+ * at the very end.
+ */
OP_SAVE_CMDS = 1 << 25,
- /* Already processed by Suff_FindDeps, to find dependencies from
- * suffix transformation rules. */
+ /*
+ * Already processed by Suff_FindDeps, to find dependencies from
+ * suffix transformation rules.
+ */
OP_DEPS_FOUND = 1 << 24,
/* Node found while expanding .ALLSRC */
- OP_MARK = 1 << 23,
-
- OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
+ OP_MARK = 1 << 23
} GNodeType;
typedef struct GNodeFlags {
@@ -377,14 +422,20 @@ typedef struct GNode {
char *name;
/* The unexpanded name of a .USE node */
char *uname;
- /* The full pathname of the file belonging to the target.
+ /*
+ * The full pathname of the file belonging to the target.
+ *
* XXX: What about .PHONY targets? These don't have an associated
- * path. */
+ * path.
+ */
char *path;
- /* The type of operator used to define the sources (see the OP flags
+ /*
+ * The type of operator used to define the sources (see the OP flags
* below).
- * XXX: This looks like a wild mixture of type and flags. */
+ *
+ * XXX: This looks like a wild mixture of type and flags.
+ */
GNodeType type;
GNodeFlags flags;
@@ -393,29 +444,39 @@ typedef struct GNode {
/* The number of unmade children */
int unmade;
- /* The modification time; 0 means the node does not have a
- * corresponding file; see GNode_IsOODate. */
+ /*
+ * The modification time; 0 means the node does not have a
+ * corresponding file; see GNode_IsOODate.
+ */
time_t mtime;
struct GNode *youngestChild;
- /* The GNodes for which this node is an implied source. May be empty.
- * For example, when there is an inference rule for .c.o, the node for
- * file.c has the node for file.o in this list. */
+ /*
+ * The GNodes for which this node is an implied source. May be empty.
+ * For example, when there is an inference rule for .c.o, the node
+ * for file.c has the node for file.o in this list.
+ */
GNodeList implicitParents;
- /* The nodes that depend on this one, or in other words, the nodes for
- * which this is a source. */
+ /*
+ * The nodes that depend on this one, or in other words, the nodes
+ * for which this is a source.
+ */
GNodeList parents;
/* The nodes on which this one depends. */
GNodeList children;
- /* .ORDER nodes we need made. The nodes that must be made (if they're
+ /*
+ * .ORDER nodes we need made. The nodes that must be made (if they're
* made) before this node can be made, but that do not enter into the
- * datedness of this node. */
+ * datedness of this node.
+ */
GNodeList order_pred;
- /* .ORDER nodes who need us. The nodes that must be made (if they're
+ /*
+ * .ORDER nodes who need us. The nodes that must be made (if they're
* made at all) after this node is made, but that do not depend on
- * this node, in the normal sense. */
+ * this node, in the normal sense.
+ */
GNodeList order_succ;
/*
@@ -427,8 +488,10 @@ typedef struct GNode {
char cohort_num[8];
/* The number of unmade instances on the cohorts list */
int unmade_cohorts;
- /* Pointer to the first instance of a '::' node; only set when on a
- * cohorts list */
+ /*
+ * Pointer to the first instance of a '::' node; only set when on a
+ * cohorts list
+ */
struct GNode *centurion;
/* Last time (sequence number) we tried to make this node */
@@ -447,21 +510,24 @@ typedef struct GNode {
/* The commands to be given to a shell to create this target. */
StringList commands;
- /* Suffix for the node (determined by Suff_FindDeps and opaque to
- * everyone but the Suff module) */
+ /*
+ * Suffix for the node (determined by Suff_FindDeps and opaque to
+ * everyone but the Suff module)
+ */
struct Suffix *suffix;
- /* Filename where the GNode got defined */
- /* XXX: What is the lifetime of this string? */
+ /* Filename where the GNode got defined, unlimited lifetime */
const char *fname;
- /* Line number where the GNode got defined */
- int lineno;
+ /* Line number where the GNode got defined, 1-based */
+ unsigned lineno;
} GNode;
/* Error levels for diagnostics during parsing. */
typedef enum ParseErrorLevel {
- /* Exit when the current top-level makefile has been parsed
- * completely. */
+ /*
+ * Exit when the current top-level makefile has been parsed
+ * completely.
+ */
PARSE_FATAL = 1,
/* Print "warning"; may be upgraded to fatal by the -w option. */
PARSE_WARNING,
@@ -472,20 +538,20 @@ typedef enum ParseErrorLevel {
/*
* Values returned by Cond_EvalLine and Cond_EvalCondition.
*/
-typedef enum CondEvalResult {
- COND_PARSE, /* Parse the next lines */
- COND_SKIP, /* Skip the next lines */
- COND_INVALID /* Not a conditional statement */
-} CondEvalResult;
+typedef enum CondResult {
+ CR_TRUE, /* Parse the next lines */
+ CR_FALSE, /* Skip the next lines */
+ CR_ERROR /* Unknown directive or parse error */
+} CondResult;
/* Names of the variables that are "local" to a specific target. */
-#define TARGET "@" /* Target of dependency */
-#define OODATE "?" /* All out-of-date sources */
-#define ALLSRC ">" /* All sources */
-#define IMPSRC "<" /* Source implied by transformation */
-#define PREFIX "*" /* Common prefix */
-#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
-#define MEMBER "%" /* Member in "archive(member)" syntax */
+#define TARGET "@" /* Target of dependency */
+#define OODATE "?" /* All out-of-date sources */
+#define ALLSRC ">" /* All sources */
+#define IMPSRC "<" /* Source implied by transformation */
+#define PREFIX "*" /* Common prefix */
+#define ARCHIVE "!" /* Archive in "archive(member)" syntax */
+#define MEMBER "%" /* Member in "archive(member)" syntax */
/*
* Global Variables
@@ -543,6 +609,7 @@ extern int makelevel;
extern char *makeDependfile;
/* If we replaced environ, this will be non-NULL. */
extern char **savedEnv;
+extern GNode *mainNode;
extern pid_t myPid;
@@ -560,34 +627,32 @@ extern pid_t myPid;
# define MAKE_LEVEL_ENV "MAKELEVEL"
#endif
-typedef enum DebugFlags {
- DEBUG_NONE = 0,
- DEBUG_ARCH = 1 << 0,
- DEBUG_COND = 1 << 1,
- DEBUG_CWD = 1 << 2,
- DEBUG_DIR = 1 << 3,
- DEBUG_ERROR = 1 << 4,
- DEBUG_FOR = 1 << 5,
- DEBUG_GRAPH1 = 1 << 6,
- DEBUG_GRAPH2 = 1 << 7,
- DEBUG_GRAPH3 = 1 << 8,
- DEBUG_HASH = 1 << 9,
- DEBUG_JOB = 1 << 10,
- DEBUG_LOUD = 1 << 11,
- DEBUG_MAKE = 1 << 12,
- DEBUG_META = 1 << 13,
- DEBUG_PARSE = 1 << 14,
- DEBUG_SCRIPT = 1 << 15,
- DEBUG_SHELL = 1 << 16,
- DEBUG_SUFF = 1 << 17,
- DEBUG_TARG = 1 << 18,
- DEBUG_VAR = 1 << 19,
- DEBUG_ALL = (1 << 20) - 1
+typedef struct DebugFlags {
+ bool DEBUG_ARCH:1;
+ bool DEBUG_COND:1;
+ bool DEBUG_CWD:1;
+ bool DEBUG_DIR:1;
+ bool DEBUG_ERROR:1;
+ bool DEBUG_FOR:1;
+ bool DEBUG_GRAPH1:1;
+ bool DEBUG_GRAPH2:1;
+ bool DEBUG_GRAPH3:1;
+ bool DEBUG_HASH:1;
+ bool DEBUG_JOB:1;
+ bool DEBUG_LOUD:1;
+ bool DEBUG_MAKE:1;
+ bool DEBUG_META:1;
+ bool DEBUG_PARSE:1;
+ bool DEBUG_SCRIPT:1;
+ bool DEBUG_SHELL:1;
+ bool DEBUG_SUFF:1;
+ bool DEBUG_TARG:1;
+ bool DEBUG_VAR:1;
} DebugFlags;
#define CONCAT(a, b) a##b
-#define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0)
+#define DEBUG(module) (opts.debug.CONCAT(DEBUG_, module))
void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
@@ -597,8 +662,8 @@ void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
debug_printf args; \
} while (false)
-#define DEBUG0(module, text) \
- DEBUG_IMPL(module, ("%s", text))
+#define DEBUG0(module, fmt) \
+ DEBUG_IMPL(module, (fmt))
#define DEBUG1(module, fmt, arg1) \
DEBUG_IMPL(module, (fmt, arg1))
#define DEBUG2(module, fmt, arg1, arg2) \
@@ -621,17 +686,21 @@ typedef struct CmdOpts {
/* -B: whether we are make compatible */
bool compatMake;
- /* -d: debug control: There is one bit per module. It is up to the
- * module what debug information to print. */
+ /*
+ * -d: debug control: There is one bit per module. It is up to the
+ * module what debug information to print.
+ */
DebugFlags debug;
/* -df: debug output is written here - default stderr */
FILE *debug_file;
- /* -dL: lint mode
+ /*
+ * -dL: lint mode
*
* Runs make in strict mode, with additional checks and better error
- * handling. */
+ * handling.
+ */
bool strict;
/* -dV: for the -V option, print unexpanded variable values */
@@ -646,12 +715,16 @@ typedef struct CmdOpts {
/* -i: if true, ignore all errors from shell commands */
bool ignoreErrors;
- /* -j: the maximum number of jobs that can run in parallel;
- * this is coordinated with the submakes */
+ /*
+ * -j: the maximum number of jobs that can run in parallel; this is
+ * coordinated with the submakes
+ */
int maxJobs;
- /* -k: if true and an error occurs while making a node, continue
- * making nodes that do not depend on the erroneous node */
+ /*
+ * -k: if true and an error occurs while making a node, continue
+ * making nodes that do not depend on the erroneous node
+ */
bool keepgoing;
/* -N: execute no commands from the targets */
@@ -664,17 +737,19 @@ typedef struct CmdOpts {
* -q: if true, do not really make anything, just see if the targets
* are out-of-date
*/
- bool queryFlag;
+ bool query;
/* -r: raw mode, do not load the builtin rules. */
bool noBuiltins;
/* -s: don't echo the shell commands before executing them */
- bool beSilent;
+ bool silent;
- /* -t: touch the targets if they are out-of-date, but don't actually
- * make them */
- bool touchFlag;
+ /*
+ * -t: touch the targets if they are out-of-date, but don't actually
+ * make them
+ */
+ bool touch;
/* -[Vv]: print expanded or unexpanded selected variables */
PrintVarsMode printVars;
@@ -687,90 +762,364 @@ typedef struct CmdOpts {
/* -w: print 'Entering' and 'Leaving' for submakes */
bool enterFlag;
- /* -X: if true, do not export variables set on the command line to the
- * environment. */
+ /*
+ * -X: if true, do not export variables set on the command line to
+ * the environment.
+ */
bool varNoExportEnv;
- /* The target names specified on the command line.
- * Used to resolve .if make(...) statements. */
+ /*
+ * The target names specified on the command line. Used to resolve
+ * .if make(...) statements.
+ */
StringList create;
} CmdOpts;
extern CmdOpts opts;
-#include "nonints.h"
+/* arch.c */
+void Arch_Init(void);
+void Arch_End(void);
+
+bool Arch_ParseArchive(char **, GNodeList *, GNode *);
+void Arch_Touch(GNode *);
+void Arch_TouchLib(GNode *);
+void Arch_UpdateMTime(GNode *gn);
+void Arch_UpdateMemberMTime(GNode *gn);
+void Arch_FindLib(GNode *, SearchPath *);
+bool Arch_LibOODate(GNode *) MAKE_ATTR_USE;
+bool Arch_IsLib(GNode *) MAKE_ATTR_USE;
+
+/* compat.c */
+bool Compat_RunCommand(const char *, GNode *, StringListNode *);
+void Compat_Run(GNodeList *);
+void Compat_Make(GNode *, GNode *);
+
+/* cond.c */
+CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE;
+CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE;
+void Cond_restore_depth(unsigned int);
+unsigned int Cond_save_depth(void) MAKE_ATTR_USE;
+
+/* dir.c; see also dir.h */
+
+MAKE_INLINE const char * MAKE_ATTR_USE
+str_basename(const char *pathname)
+{
+ const char *lastSlash = strrchr(pathname, '/');
+ return lastSlash != NULL ? lastSlash + 1 : pathname;
+}
+
+MAKE_INLINE SearchPath * MAKE_ATTR_USE
+SearchPath_New(void)
+{
+ SearchPath *path = bmake_malloc(sizeof *path);
+ Lst_Init(&path->dirs);
+ return path;
+}
+
+void SearchPath_Free(SearchPath *);
+
+/* for.c */
+struct ForLoop;
+int For_Eval(const char *) MAKE_ATTR_USE;
+bool For_Accum(const char *, int *) MAKE_ATTR_USE;
+void For_Run(unsigned, unsigned);
+bool For_NextIteration(struct ForLoop *, Buffer *);
+char *ForLoop_Details(struct ForLoop *);
+void ForLoop_Free(struct ForLoop *);
+
+/* job.c */
+void JobReapChild(pid_t, int, bool);
+
+/* main.c */
+void Main_ParseArgLine(const char *);
+char *Cmd_Exec(const char *, char **) MAKE_ATTR_USE;
+void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
+void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
+void DieHorribly(void) MAKE_ATTR_DEAD;
+void Finish(int) MAKE_ATTR_DEAD;
+bool unlink_file(const char *) MAKE_ATTR_USE;
+void execDie(const char *, const char *);
+char *getTmpdir(void) MAKE_ATTR_USE;
+bool ParseBoolean(const char *, bool) MAKE_ATTR_USE;
+const char *cached_realpath(const char *, char *);
+bool GetBooleanExpr(const char *, bool);
+
+/* parse.c */
+void Parse_Init(void);
+void Parse_End(void);
+
+void PrintLocation(FILE *, bool, const char *, unsigned);
+void PrintStackTrace(bool);
+void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
+bool Parse_VarAssign(const char *, bool, GNode *) MAKE_ATTR_USE;
+void Parse_AddIncludeDir(const char *);
+void Parse_File(const char *, int);
+void Parse_PushInput(const char *, unsigned, unsigned, Buffer,
+ struct ForLoop *);
+void Parse_MainName(GNodeList *);
+int Parse_NumErrors(void) MAKE_ATTR_USE;
+
+
+/* suff.c */
+void Suff_Init(void);
+void Suff_End(void);
+
+void Suff_ClearSuffixes(void);
+bool Suff_IsTransform(const char *) MAKE_ATTR_USE;
+GNode *Suff_AddTransform(const char *);
+void Suff_EndTransform(GNode *);
+void Suff_AddSuffix(const char *);
+SearchPath *Suff_GetPath(const char *) MAKE_ATTR_USE;
+void Suff_ExtendPaths(void);
+void Suff_AddInclude(const char *);
+void Suff_AddLib(const char *);
+void Suff_FindDeps(GNode *);
+SearchPath *Suff_FindPath(GNode *) MAKE_ATTR_USE;
+void Suff_SetNull(const char *);
+void Suff_PrintAll(void);
+char *Suff_NamesStr(void) MAKE_ATTR_USE;
+
+/* targ.c */
+void Targ_Init(void);
+void Targ_End(void);
+
+void Targ_Stats(void);
+GNodeList *Targ_List(void) MAKE_ATTR_USE;
+GNode *GNode_New(const char *) MAKE_ATTR_USE;
+GNode *Targ_FindNode(const char *) MAKE_ATTR_USE;
+GNode *Targ_GetNode(const char *) MAKE_ATTR_USE;
+GNode *Targ_NewInternalNode(const char *) MAKE_ATTR_USE;
+GNode *Targ_GetEndNode(void);
+void Targ_FindList(GNodeList *, StringList *);
+void Targ_PrintCmds(GNode *);
+void Targ_PrintNode(GNode *, int);
+void Targ_PrintNodes(GNodeList *, int);
+const char *Targ_FmtTime(time_t) MAKE_ATTR_USE;
+void Targ_PrintType(GNodeType);
+void Targ_PrintGraph(int);
+void Targ_Propagate(void);
+const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE;
+
+/* var.c */
+void Var_Init(void);
+void Var_End(void);
+
+typedef enum VarEvalMode {
+
+ /*
+ * Only parse the expression but don't evaluate any part of it.
+ *
+ * TODO: Document what Var_Parse and Var_Subst return in this mode.
+ * As of 2021-03-15, they return unspecified, inconsistent results.
+ */
+ VARE_PARSE_ONLY,
+
+ /* Parse and evaluate the expression. */
+ VARE_WANTRES,
+
+ /*
+ * Parse and evaluate the expression. It is an error if a
+ * subexpression evaluates to undefined.
+ */
+ VARE_UNDEFERR,
+
+ /*
+ * Parse and evaluate the expression. Keep '$$' as '$$' instead of
+ * reducing it to a single '$'. Subexpressions that evaluate to
+ * undefined expand to an empty string.
+ *
+ * Used in variable assignments using the ':=' operator. It allows
+ * multiple such assignments to be chained without accidentally
+ * expanding '$$file' to '$file' in the first assignment and
+ * interpreting it as '${f}' followed by 'ile' in the next assignment.
+ */
+ VARE_EVAL_KEEP_DOLLAR,
+
+ /*
+ * Parse and evaluate the expression. Keep undefined variables as-is
+ * instead of expanding them to an empty string.
+ *
+ * Example for a ':=' assignment:
+ * CFLAGS = $(.INCLUDES)
+ * CFLAGS := -I.. $(CFLAGS)
+ * # If .INCLUDES (an undocumented special variable, by the
+ * # way) is still undefined, the updated CFLAGS becomes
+ * # "-I.. $(.INCLUDES)".
+ */
+ VARE_EVAL_KEEP_UNDEF,
+
+ /*
+ * Parse and evaluate the expression. Keep '$$' as '$$' and preserve
+ * undefined subexpressions.
+ */
+ VARE_KEEP_DOLLAR_UNDEF
+} VarEvalMode;
+
+typedef enum VarSetFlags {
+ VAR_SET_NONE = 0,
+
+ /* do not export */
+ VAR_SET_NO_EXPORT = 1 << 0,
+
+ /*
+ * Make the variable read-only. No further modification is possible,
+ * except for another call to Var_Set with the same flag.
+ */
+ VAR_SET_READONLY = 1 << 1
+} VarSetFlags;
+
+/* The state of error handling returned by Var_Parse. */
+typedef enum VarParseResult {
+
+ /* Both parsing and evaluation succeeded. */
+ VPR_OK,
+ /* Parsing or evaluating failed, with an error message. */
+ VPR_ERR,
+
+ /*
+ * Parsing succeeded, undefined expressions are allowed and the
+ * expression was still undefined after applying all modifiers.
+ * No error message is printed in this case.
+ *
+ * Some callers handle this case differently, so return this
+ * information to them, for now.
+ *
+ * TODO: Instead of having this special return value, rather ensure
+ * that VARE_EVAL_KEEP_UNDEF is processed properly.
+ */
+ VPR_UNDEF
+
+} VarParseResult;
+
+typedef enum VarExportMode {
+ /* .export-env */
+ VEM_ENV,
+ /* .export: Initial export or update an already exported variable. */
+ VEM_PLAIN,
+ /* .export-literal: Do not expand the variable value. */
+ VEM_LITERAL
+} VarExportMode;
+
+void Var_Delete(GNode *, const char *);
+void Var_Undef(const char *);
+void Var_Set(GNode *, const char *, const char *);
+void Var_SetExpand(GNode *, const char *, const char *);
+void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags);
+void Var_Append(GNode *, const char *, const char *);
+void Var_AppendExpand(GNode *, const char *, const char *);
+bool Var_Exists(GNode *, const char *) MAKE_ATTR_USE;
+bool Var_ExistsExpand(GNode *, const char *) MAKE_ATTR_USE;
+FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE;
+const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE;
+VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *);
+VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **);
+void Var_Expand(FStr *, GNode *, VarEvalMode);
+void Var_Stats(void);
+void Var_Dump(GNode *);
+void Var_ReexportVars(void);
+void Var_Export(VarExportMode, const char *);
+void Var_ExportVars(const char *);
+void Var_UnExport(bool, const char *);
+
+void Global_Set(const char *, const char *);
+void Global_Append(const char *, const char *);
+void Global_Delete(const char *);
+
+/* util.c */
+typedef void (*SignalProc)(int);
+SignalProc bmake_signal(int, SignalProc);
+
+/* make.c */
void GNode_UpdateYoungestChild(GNode *, GNode *);
-bool GNode_IsOODate(GNode *);
+bool GNode_IsOODate(GNode *) MAKE_ATTR_USE;
void Make_ExpandUse(GNodeList *);
-time_t Make_Recheck(GNode *);
+time_t Make_Recheck(GNode *) MAKE_ATTR_USE;
void Make_HandleUse(GNode *, GNode *);
void Make_Update(GNode *);
void GNode_SetLocalVars(GNode *);
bool Make_Run(GNodeList *);
-bool shouldDieQuietly(GNode *, int);
+bool shouldDieQuietly(GNode *, int) MAKE_ATTR_USE;
void PrintOnError(GNode *, const char *);
void Main_ExportMAKEFLAGS(bool);
bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
-int mkTempFile(const char *, char *, size_t);
+int mkTempFile(const char *, char *, size_t) MAKE_ATTR_USE;
int str2Lst_Append(StringList *, char *);
void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
-bool GNode_ShouldExecute(GNode *gn);
+bool GNode_ShouldExecute(GNode *gn) MAKE_ATTR_USE;
/* See if the node was seen on the left-hand side of a dependency operator. */
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsTarget(const GNode *gn)
{
return (gn->type & OP_OPMASK) != OP_NONE;
}
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_Path(const GNode *gn)
{
return gn->path != NULL ? gn->path : gn->name;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsWaitingFor(const GNode *gn)
{
return gn->flags.remake && gn->made <= REQUESTED;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsReady(const GNode *gn)
{
return gn->made > DEFERRED;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsDone(const GNode *gn)
{
return gn->made >= MADE;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
GNode_IsError(const GNode *gn)
{
return gn->made == ERROR || gn->made == ABORTED;
}
-MAKE_INLINE const char *
+MAKE_INLINE bool MAKE_ATTR_USE
+GNode_IsMainCandidate(const GNode *gn)
+{
+ return (gn->type & (OP_NOTMAIN | OP_USE | OP_USEBEFORE |
+ OP_EXEC | OP_TRANSFORM)) == 0;
+}
+
+/* Return whether the target file should be preserved on interrupt. */
+MAKE_INLINE bool MAKE_ATTR_USE
+GNode_IsPrecious(const GNode *gn)
+{
+ /* XXX: Why are '::' targets precious? */
+ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
+}
+
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); }
-MAKE_INLINE const char *
+MAKE_INLINE const char * MAKE_ATTR_USE
GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); }
-MAKE_INLINE void *
+MAKE_INLINE void * MAKE_ATTR_USE
UNCONST(const void *ptr)
{
void *ret;
@@ -795,19 +1144,21 @@ UNCONST(const void *ptr)
#define KILLPG(pid, sig) killpg((pid), (sig))
#endif
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; }
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; }
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; }
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
+ch_islower(char ch) { return islower((unsigned char)ch) != 0; }
+MAKE_INLINE bool MAKE_ATTR_USE
ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; }
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; }
-MAKE_INLINE char
+MAKE_INLINE char MAKE_ATTR_USE
ch_tolower(char ch) { return (char)tolower((unsigned char)ch); }
-MAKE_INLINE char
+MAKE_INLINE char MAKE_ATTR_USE
ch_toupper(char ch) { return (char)toupper((unsigned char)ch); }
MAKE_INLINE void
@@ -824,6 +1175,17 @@ cpp_skip_hspace(const char **pp)
(*pp)++;
}
+MAKE_INLINE bool
+cpp_skip_string(const char **pp, const char *s)
+{
+ const char *p = *pp;
+ while (*p == *s && *s != '\0')
+ p++, s++;
+ if (*s == '\0')
+ *pp = p;
+ return *s == '\0';
+}
+
MAKE_INLINE void
pp_skip_whitespace(char **pp)
{
@@ -863,4 +1225,4 @@ pp_skip_hspace(char **pp)
# define MAKE_RCSID(id) static volatile char rcsid[] = id
#endif
-#endif /* MAKE_MAKE_H */
+#endif
diff --git a/contrib/bmake/make_malloc.c b/contrib/bmake/make_malloc.c
index 42a69f43704f..ea347e0ec2ca 100644
--- a/contrib/bmake/make_malloc.c
+++ b/contrib/bmake/make_malloc.c
@@ -1,4 +1,4 @@
-/* $NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $ */
+/* $NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $ */
/*
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $");
+MAKE_RCSID("$NetBSD: make_malloc.c,v 1.26 2022/01/07 08:30:04 rillig Exp $");
#ifndef USE_EMALLOC
@@ -57,12 +57,12 @@ bmake_malloc(size_t len)
char *
bmake_strdup(const char *str)
{
- size_t len;
+ size_t size;
char *p;
- len = strlen(str) + 1;
- p = bmake_malloc(len);
- return memcpy(p, str, len);
+ size = strlen(str) + 1;
+ p = bmake_malloc(size);
+ return memcpy(p, str, size);
}
/* Allocate a string starting from str with exactly len characters. */
diff --git a/contrib/bmake/make_malloc.h b/contrib/bmake/make_malloc.h
index 72f6c11fd882..b1cfe8487837 100644
--- a/contrib/bmake/make_malloc.h
+++ b/contrib/bmake/make_malloc.h
@@ -1,4 +1,4 @@
-/* $NetBSD: make_malloc.h,v 1.16 2021/01/19 20:51:46 rillig Exp $ */
+/* $NetBSD: make_malloc.h,v 1.18 2021/12/15 11:01:39 rillig Exp $ */
/*
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -27,10 +27,10 @@
*/
#ifndef USE_EMALLOC
-void *bmake_malloc(size_t);
-void *bmake_realloc(void *, size_t);
-char *bmake_strdup(const char *);
-char *bmake_strldup(const char *, size_t);
+void *bmake_malloc(size_t) MAKE_ATTR_USE;
+void *bmake_realloc(void *, size_t) MAKE_ATTR_USE;
+char *bmake_strdup(const char *) MAKE_ATTR_USE;
+char *bmake_strldup(const char *, size_t) MAKE_ATTR_USE;
#else
#include <util.h>
#define bmake_malloc(n) emalloc(n)
@@ -39,18 +39,4 @@ char *bmake_strldup(const char *, size_t);
#define bmake_strldup(s, n) estrndup(s, n)
#endif
-char *bmake_strsedup(const char *, const char *);
-
-/*
- * Thin wrapper around free(3) to avoid the extra function call in case
- * p is NULL, to save a few machine instructions.
- *
- * The case of a NULL pointer happens especially often after Var_Value,
- * since only environment variables need to be freed, but not others.
- */
-MAKE_INLINE void
-bmake_free(void *p)
-{
- if (p != NULL)
- free(p);
-}
+char *bmake_strsedup(const char *, const char *) MAKE_ATTR_USE;
diff --git a/contrib/bmake/meta.c b/contrib/bmake/meta.c
index 67c57647f2af..2d6ab3ab8a3d 100644
--- a/contrib/bmake/meta.c
+++ b/contrib/bmake/meta.c
@@ -1,4 +1,4 @@
-/* $NetBSD: meta.c,v 1.185 2021/11/27 22:04:02 rillig Exp $ */
+/* $NetBSD: meta.c,v 1.196 2022/02/04 23:22:19 rillig Exp $ */
/*
* Implement 'meta' mode.
@@ -69,6 +69,9 @@ static char *metaIgnorePathsStr; /* string storage for the list */
#ifndef MAKE_META_IGNORE_FILTER
#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER"
#endif
+#ifndef MAKE_META_CMP_FILTER
+#define MAKE_META_CMP_FILTER ".MAKE.META.CMP_FILTER"
+#endif
bool useMeta = false;
static bool useFilemon = false;
@@ -80,6 +83,7 @@ static bool metaVerbose = false;
static bool metaIgnoreCMDs = false; /* ignore CMDs in .meta files */
static bool metaIgnorePatterns = false; /* do we need to do pattern matches */
static bool metaIgnoreFilter = false; /* do we have more complex filtering? */
+static bool metaCmpFilter = false; /* do we have CMP_FILTER ? */
static bool metaCurdirOk = false; /* write .meta in .CURDIR Ok? */
static bool metaSilent = false; /* if we have a .meta be SILENT */
@@ -208,42 +212,25 @@ filemon_read(FILE *mfp, int fd)
* we use this, to clean up ./ and ../
*/
static void
-eat_dots(char *buf, size_t bufsz, int dots)
+eat_dots(char *buf)
{
- char *cp;
- char *cp2;
- const char *eat;
- size_t eatlen;
-
- switch (dots) {
- case 1:
- eat = "/./";
- eatlen = 2;
- break;
- case 2:
- eat = "/../";
- eatlen = 3;
- break;
- default:
- return;
- }
+ char *p;
- do {
- cp = strstr(buf, eat);
- if (cp != NULL) {
- cp2 = cp + eatlen;
- if (dots == 2 && cp > buf) {
- do {
- cp--;
- } while (cp > buf && *cp != '/');
- }
- if (*cp == '/') {
- strlcpy(cp, cp2, bufsz - (size_t)(cp - buf));
- } else {
- return; /* can't happen? */
- }
+ while ((p = strstr(buf, "/./")) != NULL)
+ memmove(p, p + 2, strlen(p + 2) + 1);
+
+ while ((p = strstr(buf, "/../")) != NULL) {
+ char *p2 = p + 3;
+ if (p > buf) {
+ do {
+ p--;
+ } while (p > buf && *p != '/');
}
- } while (cp != NULL);
+ if (*p == '/')
+ memmove(p, p2, strlen(p2) + 1);
+ else
+ return; /* can't happen? */
+ }
}
static char *
@@ -287,8 +274,7 @@ meta_name(char *mname, size_t mnamelen,
} else {
snprintf(buf, sizeof buf, "%s/%s", cwd, tname);
}
- eat_dots(buf, sizeof buf, 1); /* ./ */
- eat_dots(buf, sizeof buf, 2); /* ../ */
+ eat_dots(buf);
tname = buf;
}
}
@@ -330,15 +316,14 @@ is_submake(const char *cmd, GNode *gn)
static const char *p_make = NULL;
static size_t p_len;
char *mp = NULL;
- const char *cp, *cp2;
+ const char *cp2;
bool rc = false;
if (p_make == NULL) {
p_make = Var_Value(gn, ".MAKE").str;
p_len = strlen(p_make);
}
- cp = strchr(cmd, '$');
- if (cp != NULL) {
+ if (strchr(cmd, '$') != NULL) {
(void)Var_Subst(cmd, gn, VARE_WANTRES, &mp);
/* TODO: handle errors */
cmd = mp;
@@ -385,13 +370,7 @@ printCMD(const char *ucmd, FILE *fp, GNode *gn)
{
FStr xcmd = FStr_InitRefer(ucmd);
- if (strchr(ucmd, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- xcmd = FStr_InitOwn(expanded);
- }
-
+ Var_Expand(&xcmd, gn, VARE_WANTRES);
fprintf(fp, "CMD %s\n", xcmd.str);
FStr_Done(&xcmd);
}
@@ -601,7 +580,6 @@ meta_mode_init(const char *make_mode)
{
static bool once = false;
const char *cp;
- FStr value;
useMeta = true;
useFilemon = true;
@@ -658,16 +636,9 @@ meta_mode_init(const char *make_mode)
/*
* We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS}
*/
- value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS);
- if (value.str != NULL) {
- metaIgnorePatterns = true;
- FStr_Done(&value);
- }
- value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER);
- if (value.str != NULL) {
- metaIgnoreFilter = true;
- FStr_Done(&value);
- }
+ metaIgnorePatterns = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS);
+ metaIgnoreFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER);
+ metaCmpFilter = Var_Exists(SCOPE_GLOBAL, MAKE_META_CMP_FILTER);
}
/*
@@ -1061,7 +1032,7 @@ meta_ignore(GNode *gn, const char *p)
* Setting oodate true will have that effect.
*/
#define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \
- warnx("%s: %d: malformed", fname, lineno); \
+ warnx("%s: %u: malformed", fname, lineno); \
oodate = true; \
continue; \
}
@@ -1084,6 +1055,39 @@ append_if_new(StringList *list, const char *str)
Lst_Append(list, bmake_strdup(str));
}
+static char *
+meta_filter_cmd(Buffer *buf, GNode *gn, char *s)
+{
+ Buf_Clear(buf);
+ Buf_AddStr(buf, "${");
+ Buf_AddStr(buf, s);
+ Buf_AddStr(buf, ":L:${" MAKE_META_CMP_FILTER ":ts:}}");
+ Var_Subst(buf->data, gn, VARE_WANTRES, &s);
+ return s;
+}
+
+static int
+meta_cmd_cmp(GNode *gn, char *a, char *b, bool filter)
+{
+ static bool once = false;
+ static Buffer buf;
+ int rc;
+
+ rc = strcmp(a, b);
+ if (rc == 0 || !filter)
+ return rc;
+ if (!once) {
+ once = true;
+ Buf_Init(&buf);
+ }
+ a = meta_filter_cmd(&buf, gn, a);
+ b = meta_filter_cmd(&buf, gn, b);
+ rc = strcmp(a, b);
+ free(a);
+ free(b);
+ return rc;
+}
+
bool
meta_oodate(GNode *gn, bool oodate)
{
@@ -1108,6 +1112,7 @@ meta_oodate(GNode *gn, bool oodate)
bool needOODATE = false;
StringList missingFiles;
bool have_filemon = false;
+ bool cmp_filter;
if (oodate)
return oodate; /* we're done */
@@ -1139,7 +1144,7 @@ meta_oodate(GNode *gn, bool oodate)
if ((fp = fopen(fname, "r")) != NULL) {
static char *buf = NULL;
static size_t bufsz;
- int lineno = 0;
+ unsigned lineno = 0;
int lastpid = 0;
int pid;
int x;
@@ -1167,13 +1172,16 @@ meta_oodate(GNode *gn, bool oodate)
/* we want to track all the .meta we read */
Global_Append(".MAKE.META.FILES", fname);
+ cmp_filter = metaCmpFilter ? metaCmpFilter :
+ Var_Exists(gn, MAKE_META_CMP_FILTER);
+
cmdNode = gn->commands.first;
while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
lineno++;
if (buf[x - 1] == '\n')
buf[x - 1] = '\0';
else {
- warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ warnx("%s: %u: line truncated at %u", fname, lineno, x);
oodate = true;
break;
}
@@ -1194,7 +1202,7 @@ meta_oodate(GNode *gn, bool oodate)
/* Delimit the record type. */
p = buf;
#ifdef DEBUG_META_MODE
- DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf);
+ DEBUG3(META, "%s: %u: %s\n", fname, lineno, buf);
#endif
strsep(&p, " ");
if (have_filemon) {
@@ -1240,8 +1248,8 @@ meta_oodate(GNode *gn, bool oodate)
if (lastpid > 0) {
/* We need to remember these. */
- Global_SetExpand(lcwd_vname, lcwd);
- Global_SetExpand(ldir_vname, latestdir);
+ Global_Set(lcwd_vname, lcwd);
+ Global_Set(ldir_vname, latestdir);
}
snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid);
snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid);
@@ -1262,7 +1270,7 @@ meta_oodate(GNode *gn, bool oodate)
continue;
#ifdef DEBUG_META_MODE
if (DEBUG(META))
- debug_printf("%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
+ debug_printf("%s: %u: %d: %c: cwd=%s lcwd=%s ldir=%s\n",
fname, lineno,
pid, buf[0], cwd, lcwd, latestdir);
#endif
@@ -1274,8 +1282,8 @@ meta_oodate(GNode *gn, bool oodate)
/* Process according to record type. */
switch (buf[0]) {
case 'X': /* eXit */
- Var_DeleteExpand(SCOPE_GLOBAL, lcwd_vname);
- Var_DeleteExpand(SCOPE_GLOBAL, ldir_vname);
+ Var_Delete(SCOPE_GLOBAL, lcwd_vname);
+ Var_Delete(SCOPE_GLOBAL, ldir_vname);
lastpid = 0; /* no need to save ldir_vname */
break;
@@ -1287,13 +1295,13 @@ meta_oodate(GNode *gn, bool oodate)
child = atoi(p);
if (child > 0) {
snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child);
- Global_SetExpand(cldir, lcwd);
+ Global_Set(cldir, lcwd);
snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child);
- Global_SetExpand(cldir, latestdir);
+ Global_Set(cldir, latestdir);
#ifdef DEBUG_META_MODE
if (DEBUG(META))
debug_printf(
- "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n",
+ "%s: %u: %d: cwd=%s lcwd=%s ldir=%s\n",
fname, lineno,
child, cwd, lcwd, latestdir);
#endif
@@ -1305,10 +1313,10 @@ meta_oodate(GNode *gn, bool oodate)
/* Update lcwd and latest directory. */
strlcpy(latestdir, p, sizeof latestdir);
strlcpy(lcwd, p, sizeof lcwd);
- Global_SetExpand(lcwd_vname, lcwd);
- Global_SetExpand(ldir_vname, lcwd);
+ Global_Set(lcwd_vname, lcwd);
+ Global_Set(ldir_vname, lcwd);
#ifdef DEBUG_META_MODE
- DEBUG4(META, "%s: %d: cwd=%s ldir=%s\n",
+ DEBUG4(META, "%s: %u: cwd=%s ldir=%s\n",
fname, lineno, cwd, lcwd);
#endif
break;
@@ -1461,7 +1469,7 @@ meta_oodate(GNode *gn, bool oodate)
for (sdp = sdirs; *sdp != NULL && !found; sdp++) {
#ifdef DEBUG_META_MODE
- DEBUG3(META, "%s: %d: looking for: %s\n",
+ DEBUG3(META, "%s: %u: looking for: %s\n",
fname, lineno, *sdp);
#endif
if (cached_stat(*sdp, &cst) == 0) {
@@ -1471,12 +1479,12 @@ meta_oodate(GNode *gn, bool oodate)
}
if (found) {
#ifdef DEBUG_META_MODE
- DEBUG3(META, "%s: %d: found: %s\n",
+ DEBUG3(META, "%s: %u: found: %s\n",
fname, lineno, p);
#endif
if (!S_ISDIR(cst.cst_mode) &&
cst.cst_mtime > gn->mtime) {
- DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n",
+ DEBUG3(META, "%s: %u: file '%s' is newer than the target...\n",
fname, lineno, p);
oodate = true;
} else if (S_ISDIR(cst.cst_mode)) {
@@ -1508,7 +1516,7 @@ meta_oodate(GNode *gn, bool oodate)
* meta data file.
*/
if (cmdNode == NULL) {
- DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n",
+ DEBUG2(META, "%s: %u: there were more build commands in the meta data file than there are now...\n",
fname, lineno);
oodate = true;
} else {
@@ -1525,7 +1533,7 @@ meta_oodate(GNode *gn, bool oodate)
}
if (hasOODATE) {
needOODATE = true;
- DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n",
+ DEBUG2(META, "%s: %u: cannot compare command using .OODATE\n",
fname, lineno);
}
(void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd);
@@ -1548,7 +1556,7 @@ meta_oodate(GNode *gn, bool oodate)
x = n;
lineno++;
if (buf[x - 1] != '\n') {
- warnx("%s: %d: line truncated at %u", fname, lineno, x);
+ warnx("%s: %u: line truncated at %u", fname, lineno, x);
break;
}
cp = strchr(cp + 1, '\n');
@@ -1559,8 +1567,8 @@ meta_oodate(GNode *gn, bool oodate)
if (p != NULL &&
!hasOODATE &&
!(gn->type & OP_NOMETA_CMP) &&
- (strcmp(p, cmd) != 0)) {
- DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n",
+ (meta_cmd_cmp(gn, p, cmd, cmp_filter) != 0)) {
+ DEBUG4(META, "%s: %u: a build command has changed\n%s\nvs\n%s\n",
fname, lineno, p, cmd);
if (!metaIgnoreCMDs)
oodate = true;
@@ -1574,13 +1582,13 @@ meta_oodate(GNode *gn, bool oodate)
* that weren't in the meta data file.
*/
if (!oodate && cmdNode != NULL) {
- DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n",
+ DEBUG2(META, "%s: %u: there are extra build commands now that weren't in the meta data file\n",
fname, lineno);
oodate = true;
}
CHECK_VALID_META(p);
if (strcmp(p, cwd) != 0) {
- DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n",
+ DEBUG4(META, "%s: %u: the current working directory has changed from '%s' to '%s'\n",
fname, lineno, p, curdir);
oodate = true;
}
diff --git a/contrib/bmake/meta.h b/contrib/bmake/meta.h
index d4f02da4e78b..5d247422ef8c 100644
--- a/contrib/bmake/meta.h
+++ b/contrib/bmake/meta.h
@@ -1,4 +1,4 @@
-/* $NetBSD: meta.h,v 1.10 2021/04/03 11:08:40 rillig Exp $ */
+/* $NetBSD: meta.h,v 1.11 2021/12/15 09:53:41 rillig Exp $ */
/*
* Things needed for 'meta' mode.
@@ -46,13 +46,13 @@ void meta_mode_init(const char *);
void meta_job_start(struct Job *, GNode *);
void meta_job_child(struct Job *);
void meta_job_parent(struct Job *, pid_t);
-int meta_job_fd(struct Job *);
-int meta_job_event(struct Job *);
+int meta_job_fd(struct Job *) MAKE_ATTR_USE;
+int meta_job_event(struct Job *) MAKE_ATTR_USE;
void meta_job_error(struct Job *, GNode *, bool, int);
void meta_job_output(struct Job *, char *, const char *);
int meta_cmd_finish(void *);
int meta_job_finish(struct Job *);
-bool meta_oodate(GNode *, bool);
+bool meta_oodate(GNode *, bool) MAKE_ATTR_USE;
void meta_compat_start(void);
void meta_compat_child(void);
void meta_compat_parent(pid_t);
diff --git a/contrib/bmake/metachar.h b/contrib/bmake/metachar.h
index d6fd2299d43e..11711e876017 100644
--- a/contrib/bmake/metachar.h
+++ b/contrib/bmake/metachar.h
@@ -1,4 +1,4 @@
-/* $NetBSD: metachar.h,v 1.17 2021/06/21 18:54:41 rillig Exp $ */
+/* $NetBSD: metachar.h,v 1.20 2022/01/08 11:04:13 rillig Exp $ */
/*
* Copyright (c) 2015 The NetBSD Foundation, Inc.
@@ -35,18 +35,18 @@
extern const unsigned char _metachar[];
-MAKE_INLINE bool
-is_shell_metachar(char c)
+MAKE_INLINE bool MAKE_ATTR_USE
+ch_is_shell_meta(char c)
{
return _metachar[c & 0x7f] != 0;
}
-MAKE_INLINE bool
+MAKE_INLINE bool MAKE_ATTR_USE
needshell(const char *cmd)
{
- while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=')
+ while (!ch_is_shell_meta(*cmd) && *cmd != ':' && *cmd != '=')
cmd++;
return *cmd != '\0';
}
-#endif /* MAKE_METACHAR_H */
+#endif
diff --git a/contrib/bmake/mk/ChangeLog b/contrib/bmake/mk/ChangeLog
index 93bde20d754d..f196b086e7a1 100644
--- a/contrib/bmake/mk/ChangeLog
+++ b/contrib/bmake/mk/ChangeLog
@@ -1,3 +1,60 @@
+2022-02-04 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20220204
+
+ * host-target.mk: use .MAKE.OS if available
+
+2022-02-02 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20220202
+
+ * cc-wrap.mk: allow other entries in CC_WRAP_FILTER
+ We add our filter on extensions last, so prior filters
+ can apply to the whole value of .IMPSRC
+
+2022-02-01 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * cc-wrap.mk: take advantage of target local variables to
+ wrap compilers like CC CXX with wrappers like ccache distcc etc
+
+2022-01-28 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * meta2deps: we do not expect any trace data for setid apps
+
+2022-01-26 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * dirdeps.mk: ensure TARGET_SPEC and TARGET_SPEC_VARS are passed
+ to sub-make using DIRDEPS_CACHE
+
+2022-01-07 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * dirdeps.mk: use _cache_script to minimize the number of shells
+ forked when generating dirdeps.cache
+
+2022-01-02 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20220101
+
+ * dirdeps.mk: initialize DEP_* and _debug_reldir earlier.
+ If initial DIRDEPS are from command line, create the target
+ _dirdeps_cmdline as an indication.
+
+2022-01-01 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * init.mk (_SKIP_BUILD): when doing DIRDEPS_BUILD
+ at top-level only some targets are allowed at level 0,
+ for leaf makefiles only the default (all) target is restricted
+
+2021-12-28 Simon J Gerraty <sjg@beast.crufty.net>
+
+ * install-mk (MK_VERSION): 20211228
+
+ * meta2deps.py: filemon on Linux is not as reliable as we might
+ like, we do not want to update DIRDEPS if filemon output is
+ incomplete. Track pids that we 'E'xec and make sure we see an
+ e'X'it for each one. Throw an error if we are missing any 'X'
+ records.
+
2021-12-12 Simon J Gerraty <sjg@beast.crufty.net>
* sys.mk: simplify; include meta.sys.mk if MK_META_MODE is yes.
diff --git a/contrib/bmake/mk/FILES b/contrib/bmake/mk/FILES
index 91c4387e8ccb..de6259531e6e 100644
--- a/contrib/bmake/mk/FILES
+++ b/contrib/bmake/mk/FILES
@@ -6,6 +6,7 @@ auto.obj.mk
autoconf.mk
autodep.mk
auto.dep.mk
+cc-wrap.mk
compiler.mk
cython.mk
dep.mk
diff --git a/contrib/bmake/mk/cc-wrap.mk b/contrib/bmake/mk/cc-wrap.mk
new file mode 100644
index 000000000000..7b9c6e0cb8c0
--- /dev/null
+++ b/contrib/bmake/mk/cc-wrap.mk
@@ -0,0 +1,61 @@
+# $Id: cc-wrap.mk,v 1.4 2022/02/02 17:41:56 sjg Exp $
+#
+# @(#) Copyright (c) 2022, Simon J. Gerraty
+#
+# This file is provided in the hope that it will
+# be of use. There is absolutely NO WARRANTY.
+# Permission to copy, redistribute or otherwise
+# use this file is hereby granted provided that
+# the above copyright notice and this notice are
+# left intact.
+#
+# Please send copies of changes and bug-fixes to:
+# sjg@crufty.net
+#
+
+.if ${MAKE_VERSION} >= 20220126
+# which targets are we interested in?
+CC_WRAP_TARGETS ?= ${OBJS:U} ${POBJS:U} ${SOBJS:U}
+
+.if !empty(CC_WRAP_TARGETS)
+# cleanup
+# all the target assignments below are effectively := anyway
+# so we might as well do this once
+CC_WRAP_TARGETS := ${CC_WRAP_TARGETS:O:u}
+
+# what do we wrap?
+CC_WRAP_LIST += CC CXX
+CC_WRAP_LIST := ${CC_WRAP_LIST:O:u}
+
+# what might we wrap them with?
+CC_WRAPPERS += ccache distcc icecc
+CC_WRAPPERS := ${CC_WRAPPERS:O:u}
+.for w in ${CC_WRAPPERS}
+${w:tu} ?= $w
+.endfor
+
+# we do not want to make all these targets out-of-date
+# just because one of the above wrappers are enabled/disabled
+${CC_WRAP_TARGETS}: .MAKE.META.CMP_FILTER = ${CC_WRAPPERS:tu@W@${$W}@:S,^,N,}
+
+# some object src types we should not wrap
+CC_WRAP_SKIP_EXTS += s
+
+# We add the sequence we care about - excluding CC_WRAP_SKIP_EXTS
+# but prior filters can apply to full value of .IMPSRC
+CC_WRAP_FILTER += E:tl:${CC_WRAP_SKIP_EXTS:${M_ListToSkip}}
+CC_WRAP_FILTER := ${CC_WRAP_FILTER:ts:}
+
+# last one enabled wins!
+.for W in ${CC_WRAPPERS:tu}
+.if ${MK_$W:U} == "yes"
+.for C in ${CC_WRAP_LIST}
+# we have to protect the check of .IMPSRC from Global expansion
+${CC_WRAP_TARGETS}: $C = $${"$${.IMPSRC:${CC_WRAP_FILTER}}":?${$W}:} ${$C}
+.endfor
+.endif
+.endfor
+
+.endif
+.endif
+
diff --git a/contrib/bmake/mk/dirdeps.mk b/contrib/bmake/mk/dirdeps.mk
index 5e735f236b9f..ee31e47c76c5 100644
--- a/contrib/bmake/mk/dirdeps.mk
+++ b/contrib/bmake/mk/dirdeps.mk
@@ -1,6 +1,6 @@
-# $Id: dirdeps.mk,v 1.147 2021/12/14 02:09:53 sjg Exp $
+# $Id: dirdeps.mk,v 1.151 2022/01/28 01:13:14 sjg Exp $
-# Copyright (c) 2010-2021, Simon J. Gerraty
+# Copyright (c) 2010-2022, Simon J. Gerraty
# Copyright (c) 2010-2018, Juniper Networks, Inc.
# All rights reserved.
#
@@ -134,6 +134,16 @@
# A list of MACHINEs the current directory should not be
# built for.
#
+# DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS)
+# It is discouraged, but sometimes necessary for a
+# Makefile.depend file to influence the environment.
+# Doing this is correctly (especially if using DIRDEPS_CACHE) is
+# tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS
+# and dirdeps.mk will do the deed:
+#
+# MK_UEFI = yes
+# DIRDEPS_EXPORT_VARS = MK_UEFI
+#
# _build_xtra_dirs
# local.dirdeps.mk can add targets to this variable.
# They will be hooked into the build, but independent of
@@ -161,6 +171,8 @@ _DIRDEP_USE_LEVEL?= 0
.if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL}
# only the first instance is interested in all this
+# the first time we are included the _DIRDEP_USE target will not be defined
+# we can use this as a clue to do initialization and other one time things.
.if !target(_DIRDEP_USE)
# do some setup we only need once
@@ -184,6 +196,8 @@ TARGET_MACHINE := ${MACHINE}
.endif
# disable DIRDEPS_CACHE as it does not like this trick
MK_DIRDEPS_CACHE = no
+# incase anyone needs to know
+_dirdeps_cmdline:
.endif
# make sure we get the behavior we expect
@@ -257,12 +271,36 @@ _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*}
.endif
.endif
-
# this is how we identify non-machine specific dependfiles
N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}}
+# this gets reset for each dirdep we check
+DEP_RELDIR ?= ${RELDIR}
+
+# remember the initial value of DEP_RELDIR - we test for it below.
+_DEP_RELDIR := ${DEP_RELDIR}
+
+# this can cause lots of output!
+# set to a set of glob expressions that might match RELDIR
+DEBUG_DIRDEPS ?= no
+
+# make sure this target exists
+dirdeps: beforedirdeps .WAIT
+beforedirdeps:
+
.endif # !target(_DIRDEP_USE)
+.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != ""
+_debug_reldir = 1
+.else
+_debug_reldir = 0
+.endif
+.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend depend:L:M$x}@} != ""
+_debug_search = 1
+.else
+_debug_search = 0
+.endif
+
# First off, we want to know what ${MACHINE} to build for.
# This can be complicated if we are using a mixture of ${MACHINE} specific
# and non-specific Makefile.depend*
@@ -297,26 +335,6 @@ DEP_MACHINE := ${_DEP_TARGET_SPEC}
_build_all_dirs =
_build_xtra_dirs =
-# the first time we are included the _DIRDEP_USE target will not be defined
-# we can use this as a clue to do initialization and other one time things.
-.if !target(_DIRDEP_USE)
-# make sure this target exists
-dirdeps: beforedirdeps .WAIT
-beforedirdeps:
-
-# We normally expect to be included by Makefile.depend.*
-# which sets the DEP_* macros below.
-DEP_RELDIR ?= ${RELDIR}
-
-# this can cause lots of output!
-# set to a set of glob expressions that might match RELDIR
-DEBUG_DIRDEPS ?= no
-
-# remember the initial value of DEP_RELDIR - we test for it below.
-_DEP_RELDIR := ${DEP_RELDIR}
-
-.endif
-
# DIRDEPS_CACHE can be very handy for debugging.
# Also if repeatedly building the same target,
# we can avoid the overhead of re-computing the tree dependencies.
@@ -329,16 +347,6 @@ BUILD_DIRDEPS ?= yes
DIRDEPS_CACHE ?= ${_OBJDIR:tA}/dirdeps.cache${_TARGETS:U${.TARGETS}:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.}
.endif
-.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != ""
-_debug_reldir = 1
-.else
-_debug_reldir = 0
-.endif
-.if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend:L:M$x}@} != ""
-_debug_search = 1
-.else
-_debug_search = 0
-.endif
# pickup customizations
# as below you can use !target(_DIRDEP_USE) to protect things
@@ -532,7 +540,10 @@ BUILD_DIRDEPS = no
dirdeps: dirdeps-cached
dirdeps-cached: ${DIRDEPS_CACHE} .MAKE
@echo "${TRACER}Using ${DIRDEPS_CACHE}"
- @MAKELEVEL=${.MAKE.LEVEL} ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \
+ @MAKELEVEL=${.MAKE.LEVEL} \
+ TARGET_SPEC=${TARGET_SPEC} \
+ ${TARGET_SPEC_VARS:@v@$v=${$v}@} \
+ ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \
dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no
# leaf makefiles rarely work for building DIRDEPS_CACHE
@@ -701,15 +712,18 @@ _build_all_dirs := ${_build_all_dirs:O:u}
.if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS:U}} == ""
.if !empty(_build_all_dirs)
.if ${BUILD_DIRDEPS_CACHE} == "yes"
-x!= echo; { echo; echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; } >&3
+# we use _cache_script to minimize the number of times we fork the shell
+_cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}';
# guard against _new_dirdeps being too big for a single command line
_new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,}
_cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,}
.export _cache_xtra_deps _new_dirdeps
-.if !empty(DEP_EXPORT_VARS)
+.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
# Discouraged, but there are always exceptions.
# Handle it here rather than explain how.
-x!= echo; { echo; ${DEP_EXPORT_VARS:@v@echo '$v=${$v}';@} echo '.export ${DEP_EXPORT_VARS}'; echo; } >&3
+DIRDEPS_EXPORT_VARS ?= ${DEP_EXPORT_VARS}
+_cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo;
+_cache_script += ${_cache_xvars}
.endif
.else
# this makes it all happen
@@ -721,16 +735,17 @@ ${_build_all_dirs}: _DIRDEP_USE
.info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,}
.endif
-.if !empty(DEP_EXPORT_VARS)
-.export ${DEP_EXPORT_VARS}
-DEP_EXPORT_VARS=
+.if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS)
+.export ${DIRDEPS_EXPORT_VARS} ${DEP_EXPORT_VARS}
+DIRDEPS_EXPORT_VARS =
+DEP_EXPORT_VARS =
.endif
# this builds the dependency graph
.for m in ${_machines}
.if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs)
_cache_deps =
-x!= echo; { echo; echo 'DIRDEPS.${_this_dir}.$m = \'; } >&3
+_cache_script += echo; echo 'DIRDEPS.${_this_dir}.$m = \';
.endif
# it would be nice to do :N${.TARGET}
.if !empty(__qual_depdirs)
@@ -753,10 +768,10 @@ ${_this_dir}.$m: ${_build_dirs:M*.$q}
_cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m:S,^${SRCTOP}/,,}
.if !empty(_cache_deps)
.export _cache_deps
-x!= echo; for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done >&3
+_cache_script += for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done;
.endif
# anything in _{build,env}_xtra_dirs is hooked to dirdeps: only
-x!= echo; { echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \
+x!= echo; { echo; ${_cache_script} echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \
echo; echo 'dirdeps: ${_this_dir}.$m \'; \
for x in $$_cache_xtra_deps; do echo " _{SRCTOP}/$$x \\"; done; \
echo; for x in $$_new_dirdeps; do echo "_{SRCTOP}/$$x: _DIRDEP_USE"; done; } >&3
diff --git a/contrib/bmake/mk/host-target.mk b/contrib/bmake/mk/host-target.mk
index 3e6094f16730..8d906e431779 100644
--- a/contrib/bmake/mk/host-target.mk
+++ b/contrib/bmake/mk/host-target.mk
@@ -1,9 +1,10 @@
# RCSid:
-# $Id: host-target.mk,v 1.13 2020/08/05 23:32:08 sjg Exp $
+# $Id: host-target.mk,v 1.14 2022/02/04 18:05:22 sjg Exp $
# Host platform information; may be overridden
.if !defined(_HOST_OSNAME)
-_HOST_OSNAME != uname -s
+# use .MAKE.OS if available
+_HOST_OSNAME := ${.MAKE.OS:U${uname -s:L:sh}}
.export _HOST_OSNAME
.endif
.if !defined(_HOST_OSREL)
@@ -15,7 +16,7 @@ _HOST_MACHINE != uname -m
.export _HOST_MACHINE
.endif
.if !defined(_HOST_ARCH)
-# for NetBSD prefer $MACHINE (amd64 rather than x86_64)
+# for Darwin and NetBSD prefer $MACHINE (amd64 rather than x86_64)
.if ${_HOST_OSNAME:NDarwin:NNetBSD} == ""
_HOST_ARCH := ${_HOST_MACHINE}
.else
diff --git a/contrib/bmake/mk/init.mk b/contrib/bmake/mk/init.mk
index 356c8501367e..b740378cd69a 100644
--- a/contrib/bmake/mk/init.mk
+++ b/contrib/bmake/mk/init.mk
@@ -1,4 +1,4 @@
-# $Id: init.mk,v 1.26 2021/12/08 05:56:50 sjg Exp $
+# $Id: init.mk,v 1.27 2022/01/01 17:32:18 sjg Exp $
#
# @(#) Copyright (c) 2002, Simon J. Gerraty
#
@@ -66,13 +66,17 @@ CXX_PIC?= ${CC_PIC}
PROFFLAGS?= -DGPROF -DPROF
.if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes"
-# targets that are ok at level 0
+.if ${RELDIR} == "."
+# top-level targets that are ok at level 0
DIRDEPS_BUILD_LEVEL0_TARGETS += clean* destroy*
M_ListToSkip?= O:u:S,^,N,:ts:
.if ${.TARGETS:Uall:${DIRDEPS_BUILD_LEVEL0_TARGETS:${M_ListToSkip}}} != ""
# this tells lib.mk and prog.mk to not actually build anything
_SKIP_BUILD = not building at level 0
.endif
+.elif ${.TARGETS:U:Nall} == ""
+_SKIP_BUILD = not building at level 0
+.endif
.endif
.if !defined(.PARSEDIR)
diff --git a/contrib/bmake/mk/install-mk b/contrib/bmake/mk/install-mk
index 21d00ae919a5..d309f116f4ff 100755
--- a/contrib/bmake/mk/install-mk
+++ b/contrib/bmake/mk/install-mk
@@ -55,7 +55,7 @@
# Simon J. Gerraty <sjg@crufty.net>
# RCSid:
-# $Id: install-mk,v 1.206 2021/12/11 18:57:41 sjg Exp $
+# $Id: install-mk,v 1.213 2022/02/05 01:39:12 sjg Exp $
#
# @(#) Copyright (c) 1994 Simon J. Gerraty
#
@@ -70,7 +70,7 @@
# sjg@crufty.net
#
-MK_VERSION=20211212
+MK_VERSION=20220204
OWNER=
GROUP=
MODE=444
diff --git a/contrib/bmake/mk/meta2deps.py b/contrib/bmake/mk/meta2deps.py
index 76ac59465b9b..bc6975182429 100755
--- a/contrib/bmake/mk/meta2deps.py
+++ b/contrib/bmake/mk/meta2deps.py
@@ -37,7 +37,7 @@ We only pay attention to a subset of the information in the
"""
RCSid:
- $Id: meta2deps.py,v 1.40 2021/12/13 19:32:46 sjg Exp $
+ $Id: meta2deps.py,v 1.44 2022/01/29 02:42:01 sjg Exp $
Copyright (c) 2011-2020, Simon J. Gerraty
Copyright (c) 2011-2017, Juniper Networks, Inc.
@@ -66,7 +66,10 @@ RCSid:
"""
-import os, re, sys
+import os
+import re
+import sys
+import stat
def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr):
"""
@@ -244,6 +247,7 @@ class MetaFile:
self.curdir = conf.get('CURDIR')
self.reldir = conf.get('RELDIR')
self.dpdeps = conf.get('DPDEPS')
+ self.pids = {}
self.line = 0
if not self.conf:
@@ -449,7 +453,7 @@ class MetaFile:
if self.curdir:
self.seenit(self.curdir) # we ignore this
- interesting = 'CEFLRV'
+ interesting = 'CEFLRVX'
for line in f:
self.line += 1
# ignore anything we don't care about
@@ -505,6 +509,13 @@ class MetaFile:
print("cwd=", cwd, file=self.debug_out)
continue
+ if w[0] == 'X':
+ try:
+ del self.pids[pid]
+ except KeyError:
+ pass
+ continue
+
if w[2] in self.seen:
if self.debug > 2:
print("seen:", w[2], file=self.debug_out)
@@ -518,11 +529,30 @@ class MetaFile:
continue
elif w[0] in 'ERWS':
path = w[2]
- if path == '.':
+ if w[0] == 'E':
+ self.pids[pid] = path
+ elif path == '.':
continue
self.parse_path(path, cwd, w[0], w)
assert(version > 0)
+ setid_pids = []
+ # self.pids should be empty!
+ for pid,path in self.pids.items():
+ try:
+ # no guarantee that path is still valid
+ if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID):
+ # we do not expect anything after Exec
+ setid_pids.append(pid)
+ continue
+ except:
+ # we do not care why the above fails,
+ # we do not want to miss the ERROR below.
+ pass
+ print("ERROR: missing eXit for {} pid {}".format(path, pid))
+ for pid in setid_pids:
+ del self.pids[pid]
+ assert(len(self.pids) == 0)
if not file:
f.close()
diff --git a/contrib/bmake/mk/meta2deps.sh b/contrib/bmake/mk/meta2deps.sh
index d2064bc5cb59..4b7f9588987a 100755
--- a/contrib/bmake/mk/meta2deps.sh
+++ b/contrib/bmake/mk/meta2deps.sh
@@ -75,7 +75,7 @@
# RCSid:
-# $Id: meta2deps.sh,v 1.15 2020/11/08 06:31:08 sjg Exp $
+# $Id: meta2deps.sh,v 1.18 2022/01/28 21:17:43 sjg Exp $
# Copyright (c) 2010-2013, Juniper Networks, Inc.
# All rights reserved.
@@ -239,8 +239,8 @@ meta2deps() {
;;
*) cat /dev/null "$@";;
esac 2> /dev/null |
- sed -e 's,^CWD,C C,;/^[CREFLMV] /!d' -e "s,',,g" |
- $_excludes | ( version=no
+ sed -e 's,^CWD,C C,;/^[CREFLMVX] /!d' -e "s,',,g" |
+ $_excludes | ( version=no epids= xpids=
while read op pid path junk
do
: op=$op pid=$pid path=$path
@@ -271,8 +271,9 @@ meta2deps() {
;;
esac
+ : op=$op path=$path
case "$op,$path" in
- V,*) version=$path; continue;;
+ V,*) version=$pid; continue;;
W,*srcrel|*.dirdep) continue;;
C,*)
case "$path" in
@@ -289,6 +290,15 @@ meta2deps() {
continue
;;
*) dir=${path%/*}
+ case "$op" in
+ E) # setid apps get no tracing so we won't see eXit
+ case `'ls' -l $path 2> /dev/null | sed 's, .*,,'` in
+ *s*) ;;
+ *) epids="$epids $pid";;
+ esac
+ ;;
+ X) xpids="$xpids $pid"; continue;;
+ esac
case "$path" in
$src_re|$obj_re) ;;
/*/stage/*) ;;
@@ -378,9 +388,18 @@ meta2deps() {
echo $dir;;
esac
done > $tf.dirdep
+ : version=$version
case "$version" in
0) error "no filemon data";;
- esac ) || exit 1
+ esac
+ for p in $epids
+ do
+ : p=$p
+ case " $xpids " in
+ *" $p "*) ;;
+ *) error "missing eXit for pid $p";;
+ esac
+ done ) || exit 1
_nl=echo
for f in $tf.dirdep $tf.qual $tf.srcdep
do
diff --git a/contrib/bmake/mk/mk-files.txt b/contrib/bmake/mk/mk-files.txt
index 282f9b87a63c..ca0a2868fd8f 100644
--- a/contrib/bmake/mk/mk-files.txt
+++ b/contrib/bmake/mk/mk-files.txt
@@ -143,6 +143,8 @@ examples/sys.clean-env.mk
host-target.mk
Is used to set macros like ``HOST_TARGET``, ``HOST_OS`` and
``host_os`` which are used to find the next step.
+ Note: since 20130303 bmake provides ``.MAKE.OS`` set to
+ the equivalent of ``HOST_OS``.
sys/\*.mk
Platform specific additions, such as ``Darwin.mk`` or ``SunOS.mk``
@@ -159,27 +161,40 @@ local.sys.mk
The above arrangement makes it easy for the mk files to be part of a
src tree on an NFS volume and to allow building on multiple platforms.
+options.mk
+----------
+
+Inspired by FreeBSD's ``bsd.own.mk`` but more flexible.
+FreeBSD now have similar functionality in ``bsd.mkopt.mk``.
+
+It allows users to express their intent with respect to options
+``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``.
+
+Note: ``WITHOUT_*`` wins if both are set, and makefiles can set
+``NO_*`` to say they cannot handle that option, or even ``MK_*`` if
+they really need to.
+
lib.mk
------
This file is used to build a number of different libraries from the
same SRCS.
-lib${LIB}.a
+``lib${LIB}.a``
An archive lib of ``.o`` files, this is the default
-lib${LIB}_p.a
+``lib${LIB}_p.a``
A profiled lib of ``.po`` files.
Still an archive lib, but all the objects are built with
profiling in mind - hence the different extension.
- It is skipped if ``MKPROFILE`` is "no".
+ It is skipped if ``MK_PROFILE`` is "no".
-lib${LIB}_pic.a
+``lib${LIB}_pic.a``
An archive of ``.so`` objects compiled for relocation.
On NetBSD this is the input to ``lib${LIB}.${LD_so}``, it is
- skipped if ``MKPICLIB`` is "no".
+ skipped if ``MK_PIC`` or ``MK_PICLIB`` are "no".
-lib${LIB}.${LD_so}
+``lib${LIB}.${LD_so}``
A shared library. The value of ``LD_so`` is very platform
specific. For example::
@@ -190,7 +205,7 @@ lib${LIB}.${LD_so}
libsslfd.1.dylib
This library will only be built if ``SHLIB_MAJOR`` has
- a value, and ``MKPIC`` is not set to "no".
+ a value, and ``MK_PIC`` is not set to "no".
There is a lot of platform specific tweaking in ``lib.mk``, largely the
result of the original distributions trying to avoid interfering with
@@ -202,7 +217,7 @@ libnames.mk
This is included by both ``prog.mk`` and ``lib.mk`` and tries to
include ``*.libnames.mk`` of which:
-local.libnames.mk
+``local.libnames.mk``
does not exist unless you create it. It is a handy way for you
to customize without touching the distributed files.
For example, on a test machine I needed to build openssl but
@@ -217,7 +232,7 @@ local.libnames.mk
The makefile created an openssl dir in ``${OBJ_libcrypto}`` to
gather all the headers. dpadd.mk_ did the rest.
-host.libnames.mk
+``host.libnames.mk``
contains logic to find any libs named in ``HOST_LIBS`` in
``HOST_LIBDIRS``.
@@ -256,15 +271,15 @@ else in various ways::
Any library (referenced by its full path) in any of the above, is
added to ``DPMAGIC_LIBS`` with the following results, for each lib *foo*.
-SRC_libfoo
+``SRC_libfoo``
Is set to indicate where the src for libfoo is.
By default it is derived from ``LIBFOO`` by replacing
``${OBJTOP}`` with ``${SRCTOP}``.
-OBJ_libfoo
+``OBJ_libfoo``
Not very exciting, is just the dir where libfoo lives.
-INCLUDES_libfoo
+``INCLUDES_libfoo``
What to add to ``CFLAGS`` to find the public headers.
The default varies. If ``${SRC_libfoo}/h`` exists, it is assumed
to be the home of all public headers and thus the default is
@@ -273,7 +288,7 @@ INCLUDES_libfoo
Otherwise we make no assumptions and the default is
``-I${SRC_libfoo} -I${OBJ_libfoo}``
-LDADD_libfoo
+``LDADD_libfoo``
This only applies to libs reference via ``DPLIBS``.
The default is ``-lfoo``, ``LDADD_*`` provides a hook to
instantiate other linker flags at the appropriate point
@@ -303,13 +318,16 @@ object files from the src tree. This is also the source of much
confusion to some.
Traditionally one had to do a separate ``make obj`` pass through the
-tree. If ``MKOBJDIRS`` is "auto", we include auto.obj.mk_.
+tree. If ``MK_AUTO_OBJ`` is set we include auto.obj.mk_.
+
+In fact if ``MKOBJDIRS`` is set to "auto", `sys.mk`_ will set
+``MK_AUTO_OBJ=yes`` and include auto.obj.mk_ since it is best done early.
auto.obj.mk
-----------
This leverages the ``.OBJDIR`` target introduced some years ago to
-NetBSD make, to automatically create the desired object dir.
+NetBSD make, to automatically create and use the desired object dir.
subdir.mk
---------
@@ -334,6 +352,8 @@ you can suppress that - or enhance it by setting ``ECHO_DIR``::
# print time stamps
ECHO_DIR=echo @ `date "+%s [%Y-%m-%d %T] "`
+I prefer to use `dirdeps.mk`_ which makes ``subdir.mk`` irrelevant.
+
links.mk
--------
@@ -367,9 +387,12 @@ dep.mk
Deals with collecting dependencies. Another useful feature of BSD
make is the separation of this sort of information into a ``.depend``
-file. ``MKDEP`` needs to point to a suitable tool (like mkdeps.sh_)
+file. ``MKDEP_CMD`` needs to point to a suitable tool (like mkdeps.sh_)
+
+If ``MK_AUTODEP`` is "yes" it sets ``MKDEP_MK`` to autodep.mk_ by default.
-If ``USE_AUTODEP_MK`` is "yes" includes autodep.mk_
+``MKDEP_MK`` can also be set to `auto.dep.mk`_ which is more efficient
+but does not support an explicit ``depend`` target.
autodep.mk
----------
@@ -397,19 +420,9 @@ to avoid possible conflicts during parallel builds.
This precludes the use of suffix rules to drive ``make depend``, so
dep.mk_ handles that if specifically requested.
-options.mk
-----------
-
-Inspired by FreeBSD's ``bsd.own.mk`` more flexible.
-FreeBSD now have similar functionality in ``bsd.mkopt.mk``.
-
-It allows users to express their intent with respect to options
-``MK_*`` by setting ``WITH_*`` or ``WITHOUT_*``.
-
-Note: ``WITHOUT_*`` wins if both are set, and makefiles can set
-``NO_*`` to say they cannot handle that option, or even ``MK_*`` if
-they really need to.
-
+If ``bmake`` is 20160218 or newer, ``auto.dep.mk`` uses ``.dinclude``
+to includes the ``*.d`` files directly thus avoiding the need to
+create a ``.depend`` file from them.
own.mk
------
@@ -424,7 +437,7 @@ ldorder.mk
Leverages ``bmake`` to compute optimal link order for libraries.
This works nicely and makes refactoring a breeze - so long as you
-have not (or few) cicular dependencies between libraries.
+have no (or few) cicular dependencies between libraries.
man.mk
------
@@ -463,6 +476,22 @@ Logic to build Python C interface modules using Cython_
.. _Cython: http://www.cython.org/
+cc-wrap.mk
+----------
+
+This makefile leverages two new features in bmake 20220126 and later.
+
+First is the ablity to set target local variables (GNU make has done
+this for ages).
+
+The second (only intersting if using `meta mode`_)
+allows filtering commands before comparison with previous run to
+decide if a target is out-of-date.
+
+In the past, making use of compiler wrappers like ``ccache``,
+``distcc`` or the newer ``icecc`` could get quite ugly.
+Using ``cc-wrap.mk`` it could not be simpler.
+
Meta mode
=========
@@ -496,5 +525,5 @@ where you unpacked the tar file, you can::
.. _mk.tar.gz: http://www.crufty.net/ftp/pub/sjg/mk.tar.gz
:Author: sjg@crufty.net
-:Revision: $Id: mk-files.txt,v 1.20 2020/08/19 17:51:53 sjg Exp $
+:Revision: $Id: mk-files.txt,v 1.21 2022/02/04 19:01:05 sjg Exp $
:Copyright: Crufty.NET
diff --git a/contrib/bmake/mk/sys.clean-env.mk b/contrib/bmake/mk/sys.clean-env.mk
index 88d32cb3c6e9..85e829c0a8f2 100644
--- a/contrib/bmake/mk/sys.clean-env.mk
+++ b/contrib/bmake/mk/sys.clean-env.mk
@@ -1,4 +1,4 @@
-# $Id: sys.clean-env.mk,v 1.23 2020/08/19 17:51:53 sjg Exp $
+# $Id: sys.clean-env.mk,v 1.24 2022/01/15 17:34:42 sjg Exp $
#
# @(#) Copyright (c) 2009, Simon J. Gerraty
#
@@ -52,7 +52,7 @@ MAKE_ENV_SAVE_PREFIX_LIST += \
# This could be a list of vars or patterns to explicitly exclude.
-MAKE_ENV_SAVE_EXCLUDE_LIST ?= _
+MAKE_ENV_SAVE_EXCLUDE_LIST += _
# This is the actual list that we will save
# HOME is probably something worth clobbering eg.
@@ -115,7 +115,7 @@ MAKEOBJDIR = $${.CURDIR:S,${_srctop},$${OBJTOP},}
$v := ${$v}
.endfor
.else
-# we cannot use the '$$' trick, anymore
+# we cannot rely on the '$$' trick (depending on .MAKE.SAVE_DOLLARS)
# but we can export a literal (unexpanded) value
SRCTOP := ${_srctop}
OBJROOT := ${_objroot}
diff --git a/contrib/bmake/nonints.h b/contrib/bmake/nonints.h
deleted file mode 100644
index 8583e50270fd..000000000000
--- a/contrib/bmake/nonints.h
+++ /dev/null
@@ -1,348 +0,0 @@
-/* $NetBSD: nonints.h,v 1.217 2021/12/12 20:45:48 sjg Exp $ */
-
-/*
- * Copyright (c) 1988, 1989, 1990, 1993
- * The Regents of the University of California. All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Adam de Boor.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
- */
-
-/*
- * Copyright (c) 1989 by Berkeley Softworks
- * All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Adam de Boor.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94
- */
-
-/* arch.c */
-void Arch_Init(void);
-void Arch_End(void);
-
-bool Arch_ParseArchive(char **, GNodeList *, GNode *);
-void Arch_Touch(GNode *);
-void Arch_TouchLib(GNode *);
-void Arch_UpdateMTime(GNode *gn);
-void Arch_UpdateMemberMTime(GNode *gn);
-void Arch_FindLib(GNode *, SearchPath *);
-bool Arch_LibOODate(GNode *);
-bool Arch_IsLib(GNode *);
-
-/* compat.c */
-int Compat_RunCommand(const char *, GNode *, StringListNode *);
-void Compat_Run(GNodeList *);
-void Compat_Make(GNode *, GNode *);
-
-/* cond.c */
-CondEvalResult Cond_EvalCondition(const char *, bool *);
-CondEvalResult Cond_EvalLine(const char *);
-void Cond_restore_depth(unsigned int);
-unsigned int Cond_save_depth(void);
-
-/* dir.c; see also dir.h */
-
-MAKE_INLINE const char *
-str_basename(const char *pathname)
-{
- const char *lastSlash = strrchr(pathname, '/');
- return lastSlash != NULL ? lastSlash + 1 : pathname;
-}
-
-MAKE_INLINE SearchPath *
-SearchPath_New(void)
-{
- SearchPath *path = bmake_malloc(sizeof *path);
- Lst_Init(&path->dirs);
- return path;
-}
-
-void SearchPath_Free(SearchPath *);
-
-/* for.c */
-int For_Eval(const char *);
-bool For_Accum(const char *);
-void For_Run(int);
-
-/* job.c */
-#ifdef WAIT_T
-void JobReapChild(pid_t, WAIT_T, bool);
-#endif
-
-/* main.c */
-bool GetBooleanExpr(const char *, bool);
-void Main_ParseArgLine(const char *);
-char *Cmd_Exec(const char *, const char **);
-void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2);
-void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
-void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD;
-void DieHorribly(void) MAKE_ATTR_DEAD;
-void Finish(int) MAKE_ATTR_DEAD;
-int eunlink(const char *);
-void execDie(const char *, const char *);
-char *getTmpdir(void);
-bool ParseBoolean(const char *, bool);
-char *cached_realpath(const char *, char *);
-
-/* parse.c */
-void Parse_Init(void);
-void Parse_End(void);
-
-typedef enum VarAssignOp {
- VAR_NORMAL, /* = */
- VAR_SUBST, /* := */
- VAR_SHELL, /* != or :sh= */
- VAR_APPEND, /* += */
- VAR_DEFAULT /* ?= */
-} VarAssignOp;
-
-typedef struct VarAssign {
- char *varname; /* unexpanded */
- VarAssignOp op;
- const char *value; /* unexpanded */
-} VarAssign;
-
-typedef char *(*ReadMoreProc)(void *, size_t *);
-
-void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
-bool Parse_IsVar(const char *, VarAssign *out_var);
-void Parse_Var(VarAssign *, GNode *);
-void Parse_AddIncludeDir(const char *);
-void Parse_File(const char *, int);
-void Parse_PushInput(const char *, int, int, ReadMoreProc, void *);
-void Parse_MainName(GNodeList *);
-int Parse_NumErrors(void);
-
-
-#ifndef HAVE_STRLCPY
-/* strlcpy.c */
-size_t strlcpy(char *, const char *, size_t);
-#endif
-
-/* suff.c */
-void Suff_Init(void);
-void Suff_End(void);
-
-void Suff_ClearSuffixes(void);
-bool Suff_IsTransform(const char *);
-GNode *Suff_AddTransform(const char *);
-void Suff_EndTransform(GNode *);
-void Suff_AddSuffix(const char *, GNode **);
-SearchPath *Suff_GetPath(const char *);
-void Suff_ExtendPaths(void);
-void Suff_AddInclude(const char *);
-void Suff_AddLib(const char *);
-void Suff_FindDeps(GNode *);
-SearchPath *Suff_FindPath(GNode *);
-void Suff_SetNull(const char *);
-void Suff_PrintAll(void);
-const char *Suff_NamesStr(void);
-
-/* targ.c */
-void Targ_Init(void);
-void Targ_End(void);
-
-void Targ_Stats(void);
-GNodeList *Targ_List(void);
-GNode *GNode_New(const char *);
-GNode *Targ_FindNode(const char *);
-GNode *Targ_GetNode(const char *);
-GNode *Targ_NewInternalNode(const char *);
-GNode *Targ_GetEndNode(void);
-void Targ_FindList(GNodeList *, StringList *);
-bool Targ_Precious(const GNode *);
-void Targ_SetMain(GNode *);
-void Targ_PrintCmds(GNode *);
-void Targ_PrintNode(GNode *, int);
-void Targ_PrintNodes(GNodeList *, int);
-const char *Targ_FmtTime(time_t);
-void Targ_PrintType(GNodeType);
-void Targ_PrintGraph(int);
-void Targ_Propagate(void);
-const char *GNodeMade_Name(GNodeMade);
-
-/* var.c */
-void Var_Init(void);
-void Var_End(void);
-
-typedef enum VarEvalMode {
-
- /*
- * Only parse the expression but don't evaluate any part of it.
- *
- * TODO: Document what Var_Parse and Var_Subst return in this mode.
- * As of 2021-03-15, they return unspecified, inconsistent results.
- */
- VARE_PARSE_ONLY,
-
- /* Parse and evaluate the expression. */
- VARE_WANTRES,
-
- /*
- * Parse and evaluate the expression. It is an error if a
- * subexpression evaluates to undefined.
- */
- VARE_UNDEFERR,
-
- /*
- * Parse and evaluate the expression. Keep '$$' as '$$' instead of
- * reducing it to a single '$'. Subexpressions that evaluate to
- * undefined expand to an empty string.
- *
- * Used in variable assignments using the ':=' operator. It allows
- * multiple such assignments to be chained without accidentally
- * expanding '$$file' to '$file' in the first assignment and
- * interpreting it as '${f}' followed by 'ile' in the next assignment.
- */
- VARE_EVAL_KEEP_DOLLAR,
-
- /*
- * Parse and evaluate the expression. Keep undefined variables as-is
- * instead of expanding them to an empty string.
- *
- * Example for a ':=' assignment:
- * CFLAGS = $(.INCLUDES)
- * CFLAGS := -I.. $(CFLAGS)
- * # If .INCLUDES (an undocumented special variable, by the
- * # way) is still undefined, the updated CFLAGS becomes
- * # "-I.. $(.INCLUDES)".
- */
- VARE_EVAL_KEEP_UNDEF,
-
- /*
- * Parse and evaluate the expression. Keep '$$' as '$$' and preserve
- * undefined subexpressions.
- */
- VARE_KEEP_DOLLAR_UNDEF
-} VarEvalMode;
-
-typedef enum VarSetFlags {
- VAR_SET_NONE = 0,
-
- /* do not export */
- VAR_SET_NO_EXPORT = 1 << 0,
-
- /* Make the variable read-only. No further modification is possible,
- * except for another call to Var_Set with the same flag. */
- VAR_SET_READONLY = 1 << 1
-} VarSetFlags;
-
-/* The state of error handling returned by Var_Parse. */
-typedef enum VarParseResult {
-
- /* Both parsing and evaluation succeeded. */
- VPR_OK,
-
- /* Parsing or evaluating failed, with an error message. */
- VPR_ERR,
-
- /*
- * Parsing succeeded, undefined expressions are allowed and the
- * expression was still undefined after applying all modifiers.
- * No error message is printed in this case.
- *
- * Some callers handle this case differently, so return this
- * information to them, for now.
- *
- * TODO: Instead of having this special return value, rather ensure
- * that VARE_EVAL_KEEP_UNDEF is processed properly.
- */
- VPR_UNDEF
-
-} VarParseResult;
-
-typedef enum VarExportMode {
- /* .export-env */
- VEM_ENV,
- /* .export: Initial export or update an already exported variable. */
- VEM_PLAIN,
- /* .export-literal: Do not expand the variable value. */
- VEM_LITERAL
-} VarExportMode;
-
-void Var_Delete(GNode *, const char *);
-void Var_DeleteExpand(GNode *, const char *);
-void Var_Undef(const char *);
-void Var_Set(GNode *, const char *, const char *);
-void Var_SetExpand(GNode *, const char *, const char *);
-void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags);
-void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags);
-void Var_Append(GNode *, const char *, const char *);
-void Var_AppendExpand(GNode *, const char *, const char *);
-bool Var_Exists(GNode *, const char *);
-bool Var_ExistsExpand(GNode *, const char *);
-FStr Var_Value(GNode *, const char *);
-const char *GNode_ValueDirect(GNode *, const char *);
-VarParseResult Var_Parse(const char **, GNode *, VarEvalMode, FStr *);
-VarParseResult Var_Subst(const char *, GNode *, VarEvalMode, char **);
-void Var_Stats(void);
-void Var_Dump(GNode *);
-void Var_ReexportVars(void);
-void Var_Export(VarExportMode, const char *);
-void Var_ExportVars(const char *);
-void Var_UnExport(bool, const char *);
-
-void Global_Set(const char *, const char *);
-void Global_SetExpand(const char *, const char *);
-void Global_Append(const char *, const char *);
-void Global_Delete(const char *);
-
-/* util.c */
-typedef void (*SignalProc)(int);
-SignalProc bmake_signal(int, SignalProc);
diff --git a/contrib/bmake/parse.c b/contrib/bmake/parse.c
index 6a310c74ba5c..3faeafe1c739 100644
--- a/contrib/bmake/parse.c
+++ b/contrib/bmake/parse.c
@@ -1,4 +1,4 @@
-/* $NetBSD: parse.c,v 1.574 2021/12/12 15:44:41 rillig Exp $ */
+/* $NetBSD: parse.c,v 1.662 2022/02/05 00:37:19 sjg Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -74,10 +74,6 @@
* Parse_File is the main entry point and controls most of the other
* functions in this module.
*
- * The directories for the .include "..." directive are kept in
- * 'parseIncPath', while those for .include <...> are kept in 'sysIncPath'.
- * The targets currently being defined are kept in 'targets'.
- *
* Interface:
* Parse_Init Initialize the module
*
@@ -86,15 +82,16 @@
* Parse_File Parse a top-level makefile. Included files are
* handled by IncludeFile instead.
*
- * Parse_IsVar Return true if the given line is a variable
- * assignment. Used by MainParseArgs to determine if
- * an argument is a target or a variable assignment.
- * Used internally for pretty much the same thing.
+ * Parse_VarAssign
+ * Try to parse the given line as a variable assignment.
+ * Used by MainParseArgs to determine if an argument is
+ * a target or a variable assignment. Used internally
+ * for pretty much the same thing.
*
* Parse_Error Report a parse error, a warning or an informational
* message.
*
- * Parse_MainName Returns a list of the main target to create.
+ * Parse_MainName Returns a list of the single main target to create.
*/
#include <sys/types.h>
@@ -124,40 +121,32 @@
#include "pathnames.h"
/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: parse.c,v 1.574 2021/12/12 15:44:41 rillig Exp $");
-
-/* types and constants */
+MAKE_RCSID("$NetBSD: parse.c,v 1.662 2022/02/05 00:37:19 sjg Exp $");
/*
- * Structure for a file being read ("included file")
+ * A file being read.
*/
-typedef struct IFile {
- char *fname; /* name of file (relative? absolute?) */
- bool fromForLoop; /* simulated .include by the .for loop */
- int lineno; /* current line number in file */
- int first_lineno; /* line number of start of text */
+typedef struct IncludedFile {
+ FStr name; /* absolute or relative to the cwd */
+ unsigned lineno; /* 1-based */
+ unsigned readLines; /* the number of physical lines that have
+ * been read from the file */
+ unsigned forHeadLineno; /* 1-based */
+ unsigned forBodyReadLines; /* the number of physical lines that have
+ * been read from the file above the body of
+ * the .for loop */
unsigned int cond_depth; /* 'if' nesting when file opened */
- bool depending; /* state of doing_depend on EOF */
+ bool depending; /* state of doing_depend on EOF */
- /*
- * The buffer from which the file's content is read. The buffer
- * always ends with '\n', the buffer is not null-terminated, that is,
- * buf_end[0] is already out of bounds.
- */
- char *buf_freeIt;
+ Buffer buf; /* the file's content or the body of the .for
+ * loop; always ends with '\n' */
char *buf_ptr; /* next char to be read */
char *buf_end; /* buf_end[-1] == '\n' */
- /* Function to read more data, with a single opaque argument. */
- ReadMoreProc readMore;
- void *readMoreArg;
-
- struct loadedfile *lf; /* loadedfile object, if any */
-} IFile;
+ struct ForLoop *forLoop;
+} IncludedFile;
-/*
- * Tokens for target attributes
- */
+/* Special attributes for target nodes. */
typedef enum ParseSpecial {
SP_ATTRIBUTE, /* Generic attribute */
SP_BEGIN, /* .BEGIN */
@@ -169,8 +158,7 @@ typedef enum ParseSpecial {
SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */
SP_INTERRUPT, /* .INTERRUPT */
SP_LIBS, /* .LIBS; not mentioned in the manual page */
- /* .MAIN and we don't have anything user-specified to make */
- SP_MAIN,
+ SP_MAIN, /* .MAIN and no user-specified targets to make */
SP_META, /* .META */
SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */
SP_NOMETA, /* .NOMETA */
@@ -199,15 +187,29 @@ typedef enum ParseSpecial {
typedef List SearchPathList;
typedef ListNode SearchPathListNode;
-/* result data */
+
+typedef enum VarAssignOp {
+ VAR_NORMAL, /* = */
+ VAR_APPEND, /* += */
+ VAR_DEFAULT, /* ?= */
+ VAR_SUBST, /* := */
+ VAR_SHELL /* != or :sh= */
+} VarAssignOp;
+
+typedef struct VarAssign {
+ char *varname; /* unexpanded */
+ VarAssignOp op;
+ const char *value; /* unexpanded */
+} VarAssign;
+
+static bool Parse_IsVar(const char *, VarAssign *);
+static void Parse_Var(VarAssign *, GNode *);
/*
- * The main target to create. This is the first target on the first
- * dependency line in the first makefile.
+ * The target to be made if no targets are specified in the command line.
+ * This is the first target defined in any of the makefiles.
*/
-static GNode *mainNode;
-
-/* eval state */
+GNode *mainNode;
/*
* During parsing, the targets from the left-hand side of the currently
@@ -230,48 +232,25 @@ static StringList targCmds = LST_INIT;
/*
* Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
- * seen, then set to each successive source on the line.
+ * is seen, then set to each successive source on the line.
*/
static GNode *order_pred;
-/* parser state */
-
-/* number of fatal errors */
static int parseErrors = 0;
/*
- * Variables for doing includes
- */
-
-/*
* The include chain of makefiles. At index 0 is the top-level makefile from
* the command line, followed by the included files or .for loops, up to and
* including the current file.
*
* See PrintStackTrace for how to interpret the data.
*/
-static Vector /* of IFile */ includes;
-
-static IFile *
-GetInclude(size_t i)
-{
- return Vector_Get(&includes, i);
-}
-
-/* The file that is currently being read. */
-static IFile *
-CurFile(void)
-{
- return GetInclude(includes.len - 1);
-}
+static Vector /* of IncludedFile */ includes;
-/* include paths */
SearchPath *parseIncPath; /* directories for "..." includes */
SearchPath *sysIncPath; /* directories for <...> includes */
SearchPath *defSysIncPath; /* default for sysIncPath */
-/* parser tables */
-
/*
* The parseKeywords table is searched using binary search when deciding
* if a target or source is special. The 'spec' field is the ParseSpecial
@@ -280,9 +259,9 @@ SearchPath *defSysIncPath; /* default for sysIncPath */
* keyword is used as a source ("0" if the keyword isn't special as a source)
*/
static const struct {
- const char name[17]; /* Name of keyword */
- ParseSpecial spec; /* Type when used as a target */
- GNodeType op; /* Operator when used as a source */
+ const char name[17];
+ ParseSpecial special; /* when used as a target */
+ GNodeType targetAttr; /* when used as a source */
} parseKeywords[] = {
{ ".BEGIN", SP_BEGIN, OP_NONE },
{ ".DEFAULT", SP_DEFAULT, OP_NONE },
@@ -330,120 +309,36 @@ static const struct {
{ ".WAIT", SP_WAIT, OP_NONE },
};
-/* file loader */
-
-struct loadedfile {
- char *buf; /* contents buffer */
- size_t len; /* length of contents */
- bool 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(char *buf, size_t buflen)
-{
- struct loadedfile *lf;
-
- lf = bmake_malloc(sizeof *lf);
- lf->buf = buf;
- lf->len = buflen;
- lf->used = false;
- return lf;
-}
-
-static void
-loadedfile_destroy(struct loadedfile *lf)
-{
- free(lf->buf);
- free(lf);
-}
-/*
- * readMore() operation for loadedfile, as needed by the weird and twisted
- * logic below. Once that's cleaned up, we can get rid of lf->used.
- */
-static char *
-loadedfile_readMore(void *x, size_t *len)
+static IncludedFile *
+GetInclude(size_t i)
{
- struct loadedfile *lf = x;
-
- if (lf->used)
- return NULL;
-
- lf->used = true;
- *len = lf->len;
- return lf->buf;
+ return Vector_Get(&includes, i);
}
-/*
- * Try to get the size of a file.
- */
-static bool
-load_getsize(int fd, size_t *ret)
+/* The file that is currently being read. */
+static IncludedFile *
+CurFile(void)
{
- struct stat st;
-
- if (fstat(fd, &st) < 0)
- return false;
-
- if (!S_ISREG(st.st_mode))
- return false;
-
- /*
- * st_size is an off_t, which is 64 bits signed; *ret is
- * size_t, which might be 32 bits unsigned or 64 bits
- * unsigned. Rather than being elaborate, just punt on
- * files that are more than 1 GiB. We should never
- * see a makefile that size in practice.
- *
- * While we're at it reject negative sizes too, just in case.
- */
- if (st.st_size < 0 || st.st_size > 0x3fffffff)
- return false;
-
- *ret = (size_t)st.st_size;
- return true;
+ return GetInclude(includes.len - 1);
}
-/*
- * Read in a file.
- *
- * Until the path search logic can be moved under here instead of
- * 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.
- */
-static struct loadedfile *
+static Buffer
loadfile(const char *path, int fd)
{
ssize_t n;
Buffer buf;
- size_t filesize;
-
+ size_t bufSize;
+ struct stat st;
- if (path == NULL) {
- assert(fd == -1);
- fd = STDIN_FILENO;
- }
-
- if (load_getsize(fd, &filesize)) {
- /*
- * Avoid resizing the buffer later for no reason.
- *
- * At the same time leave space for adding a final '\n',
- * just in case it is missing in the file.
- */
- filesize++;
- } else
- filesize = 1024;
- Buf_InitSize(&buf, filesize);
+ bufSize = fstat(fd, &st) == 0 && S_ISREG(st.st_mode) &&
+ st.st_size > 0 && st.st_size < 1024 * 1024 * 1024
+ ? (size_t)st.st_size : 1024;
+ Buf_InitSize(&buf, bufSize);
for (;;) {
- assert(buf.len <= buf.cap);
if (buf.len == buf.cap) {
- if (buf.cap > 0x1fffffff) {
- errno = EFBIG;
+ if (buf.cap >= 512 * 1024 * 1024) {
Error("%s: file too large", path);
exit(2); /* Not 1 so -q can distinguish error */
}
@@ -465,79 +360,57 @@ loadfile(const char *path, int fd)
if (!Buf_EndsWith(&buf, '\n'))
Buf_AddByte(&buf, '\n');
- if (path != NULL)
- close(fd);
-
- {
- struct loadedfile *lf = loadedfile_create(buf.data, buf.len);
- Buf_DoneData(&buf);
- return lf;
- }
+ return buf; /* may not be null-terminated */
}
-static void
-PrintStackTrace(void)
+/*
+ * Print the current chain of .include and .for directives. In Parse_Fatal
+ * or other functions that already print the location, includingInnermost
+ * would be redundant, but in other cases like Error or Fatal it needs to be
+ * included.
+ */
+void
+PrintStackTrace(bool includingInnermost)
{
- const IFile *entries;
+ const IncludedFile *entries;
size_t i, n;
- if (!(DEBUG(PARSE)))
- return;
-
entries = GetInclude(0);
n = includes.len;
if (n == 0)
return;
- n--; /* This entry is already in the diagnostic. */
- /*
- * For the IFiles with fromForLoop, lineno seems to be sorted
- * backwards. This is because lineno is the number of completely
- * parsed lines, which for a .for loop is right after the
- * corresponding .endfor. The intuitive line number comes from
- * first_lineno instead, which points at the start of the .for loop.
- *
- * To make the stack trace intuitive, the entry below each chain of
- * .for loop entries must be ignored completely since neither its
- * lineno nor its first_lineno is useful. Instead, the topmost of
- * each chain of .for loop entries needs to be printed twice, once
- * with its first_lineno and once with its lineno.
- */
+ if (!includingInnermost && entries[n - 1].forLoop == NULL)
+ n--; /* already in the diagnostic */
for (i = n; i-- > 0;) {
- const IFile *entry = entries + i;
- const char *fname = entry->fname;
- bool printLineno;
+ const IncludedFile *entry = entries + i;
+ const char *fname = entry->name.str;
char dirbuf[MAXPATHLEN + 1];
if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0)
fname = realpath(fname, dirbuf);
- printLineno = !entry->fromForLoop;
- if (i + 1 < n && entries[i + 1].fromForLoop == printLineno)
- printLineno = entry->fromForLoop;
-
- if (printLineno)
- debug_printf("\tin .include from %s:%d\n",
- fname, entry->lineno);
- if (entry->fromForLoop)
- debug_printf("\tin .for loop from %s:%d\n",
- fname, entry->first_lineno);
+ if (entry->forLoop != NULL) {
+ char *details = ForLoop_Details(entry->forLoop);
+ debug_printf("\tin .for loop from %s:%u with %s\n",
+ fname, entry->forHeadLineno, details);
+ free(details);
+ } else if (i + 1 < n && entries[i + 1].forLoop != NULL) {
+ /* entry->lineno is not a useful line number */
+ } else
+ debug_printf("\tin %s:%u\n", fname, entry->lineno);
}
}
/* Check if the current character is escaped on the current line. */
static bool
-ParseIsEscaped(const char *line, const char *c)
+IsEscaped(const char *line, const char *p)
{
- bool active = false;
- for (;;) {
- if (line == c)
- return active;
- if (*--c != '\\')
- return active;
- active = !active;
- }
+ bool escaped = false;
+ while (p > line && *--p == '\\')
+ escaped = !escaped;
+ return escaped;
}
/*
@@ -547,8 +420,8 @@ ParseIsEscaped(const char *line, const char *c)
static void
RememberLocation(GNode *gn)
{
- IFile *curFile = CurFile();
- gn->fname = curFile->fname;
+ IncludedFile *curFile = CurFile();
+ gn->fname = Str_Intern(curFile->name.str);
gn->lineno = curFile->lineno;
}
@@ -557,12 +430,12 @@ RememberLocation(GNode *gn)
* Return the index of the keyword, or -1 if it isn't there.
*/
static int
-ParseFindKeyword(const char *str)
+FindKeyword(const char *str)
{
int start = 0;
int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1;
- do {
+ while (start <= end) {
int curr = start + (end - start) / 2;
int diff = strcmp(str, parseKeywords[curr].name);
@@ -572,25 +445,22 @@ ParseFindKeyword(const char *str)
end = curr - 1;
else
start = curr + 1;
- } while (start <= end);
+ }
return -1;
}
-static void
-PrintLocation(FILE *f, const char *fname, size_t lineno)
+void
+PrintLocation(FILE *f, bool useVars, const char *fname, unsigned lineno)
{
char dirbuf[MAXPATHLEN + 1];
FStr dir, base;
- if (*fname == '/' || strcmp(fname, "(stdin)") == 0) {
- (void)fprintf(f, "\"%s\" line %u: ", fname, (unsigned)lineno);
+ if (!useVars || fname[0] == '/' || strcmp(fname, "(stdin)") == 0) {
+ (void)fprintf(f, "\"%s\" line %u: ", fname, lineno);
return;
}
- /* Find out which makefile is the culprit.
- * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */
-
dir = Var_Value(SCOPE_GLOBAL, ".PARSEDIR");
if (dir.str == NULL)
dir.str = ".";
@@ -601,15 +471,14 @@ PrintLocation(FILE *f, const char *fname, size_t lineno)
if (base.str == NULL)
base.str = str_basename(fname);
- (void)fprintf(f, "\"%s/%s\" line %u: ",
- dir.str, base.str, (unsigned)lineno);
+ (void)fprintf(f, "\"%s/%s\" line %u: ", dir.str, base.str, lineno);
FStr_Done(&base);
FStr_Done(&dir);
}
-static void
-ParseVErrorInternal(FILE *f, const char *fname, size_t lineno,
+static void MAKE_ATTR_PRINTFLIKE(6, 0)
+ParseVErrorInternal(FILE *f, bool useVars, const char *fname, unsigned lineno,
ParseErrorLevel type, const char *fmt, va_list ap)
{
static bool fatal_warning_error_printed = false;
@@ -617,42 +486,42 @@ ParseVErrorInternal(FILE *f, const char *fname, size_t lineno,
(void)fprintf(f, "%s: ", progname);
if (fname != NULL)
- PrintLocation(f, fname, lineno);
+ PrintLocation(f, useVars, fname, lineno);
if (type == PARSE_WARNING)
(void)fprintf(f, "warning: ");
(void)vfprintf(f, fmt, ap);
(void)fprintf(f, "\n");
(void)fflush(f);
- if (type == PARSE_INFO)
- goto print_stack_trace;
- if (type == PARSE_WARNING && !opts.parseWarnFatal)
- goto print_stack_trace;
- parseErrors++;
- if (type == PARSE_WARNING && !fatal_warning_error_printed) {
- Error("parsing warnings being treated as errors");
- fatal_warning_error_printed = true;
+ if (type == PARSE_FATAL)
+ parseErrors++;
+ if (type == PARSE_WARNING && opts.parseWarnFatal) {
+ if (!fatal_warning_error_printed) {
+ Error("parsing warnings being treated as errors");
+ fatal_warning_error_printed = true;
+ }
+ parseErrors++;
}
-print_stack_trace:
- PrintStackTrace();
+ if (DEBUG(PARSE))
+ PrintStackTrace(false);
}
-static void
-ParseErrorInternal(const char *fname, size_t lineno,
+static void MAKE_ATTR_PRINTFLIKE(4, 5)
+ParseErrorInternal(const char *fname, unsigned lineno,
ParseErrorLevel type, const char *fmt, ...)
{
va_list ap;
(void)fflush(stdout);
va_start(ap, fmt);
- ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
+ ParseVErrorInternal(stderr, false, fname, lineno, type, fmt, ap);
va_end(ap);
- if (opts.debug_file != stderr && opts.debug_file != stdout) {
+ if (opts.debug_file != stdout && opts.debug_file != stderr) {
va_start(ap, fmt);
- ParseVErrorInternal(opts.debug_file, fname, lineno, type,
- fmt, ap);
+ ParseVErrorInternal(opts.debug_file, false, fname, lineno,
+ type, fmt, ap);
va_end(ap);
}
}
@@ -670,37 +539,37 @@ Parse_Error(ParseErrorLevel type, const char *fmt, ...)
{
va_list ap;
const char *fname;
- size_t lineno;
+ unsigned lineno;
if (includes.len == 0) {
fname = NULL;
lineno = 0;
} else {
- IFile *curFile = CurFile();
- fname = curFile->fname;
- lineno = (size_t)curFile->lineno;
+ IncludedFile *curFile = CurFile();
+ fname = curFile->name.str;
+ lineno = curFile->lineno;
}
- va_start(ap, fmt);
(void)fflush(stdout);
- ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
+ va_start(ap, fmt);
+ ParseVErrorInternal(stderr, true, fname, lineno, type, fmt, ap);
va_end(ap);
- if (opts.debug_file != stderr && opts.debug_file != stdout) {
+ if (opts.debug_file != stdout && opts.debug_file != stderr) {
va_start(ap, fmt);
- ParseVErrorInternal(opts.debug_file, fname, lineno, type,
- fmt, ap);
+ ParseVErrorInternal(opts.debug_file, true, fname, lineno,
+ type, fmt, ap);
va_end(ap);
}
}
/*
- * Parse and handle an .info, .warning or .error directive.
- * For an .error directive, immediately exit.
+ * Handle an .info, .warning or .error directive. For an .error directive,
+ * exit immediately.
*/
static void
-ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
+HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
{
char *xmsg;
@@ -717,17 +586,15 @@ ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg)
free(xmsg);
if (level == PARSE_FATAL) {
- PrintOnError(NULL, NULL);
+ PrintOnError(NULL, "\n");
exit(1);
}
}
/*
- * Add the child to the parent's children.
- *
- * Additionally, add the parent to the child's parents, but only if the
- * target is not special. An example for such a special target is .END,
- * which does not need to be informed once the child target has been made.
+ * Add the child to the parent's children, and for non-special targets, vice
+ * versa. Special targets such as .END do not need to be informed once the
+ * child target has been made.
*/
static void
LinkSource(GNode *pgn, GNode *cgn, bool isSpecial)
@@ -743,8 +610,8 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial)
Lst_Append(&cgn->parents, pgn);
if (DEBUG(PARSE)) {
- debug_printf("# %s: added child %s - %s\n",
- __func__, pgn->name, cgn->name);
+ debug_printf("# LinkSource: added child %s - %s\n",
+ pgn->name, cgn->name);
Targ_PrintNode(pgn, 0);
Targ_PrintNode(cgn, 0);
}
@@ -843,9 +710,9 @@ ApplyDependencyOperator(GNodeType op)
* We give each .WAIT node a unique name (mainly for diagnostics).
*/
static void
-ParseDependencySourceWait(bool isSpecial)
+ApplyDependencySourceWait(bool isSpecial)
{
- static int wait_number = 0;
+ static unsigned wait_number = 0;
char wait_src[16];
GNode *gn;
@@ -859,41 +726,41 @@ ParseDependencySourceWait(bool isSpecial)
}
static bool
-ParseDependencySourceKeyword(const char *src, ParseSpecial specType)
+ApplyDependencySourceKeyword(const char *src, ParseSpecial special)
{
int keywd;
- GNodeType op;
+ GNodeType targetAttr;
if (*src != '.' || !ch_isupper(src[1]))
return false;
- keywd = ParseFindKeyword(src);
+ keywd = FindKeyword(src);
if (keywd == -1)
return false;
- op = parseKeywords[keywd].op;
- if (op != OP_NONE) {
- ApplyDependencyOperator(op);
+ targetAttr = parseKeywords[keywd].targetAttr;
+ if (targetAttr != OP_NONE) {
+ ApplyDependencyOperator(targetAttr);
return true;
}
- if (parseKeywords[keywd].spec == SP_WAIT) {
- ParseDependencySourceWait(specType != SP_NOT);
+ if (parseKeywords[keywd].special == SP_WAIT) {
+ ApplyDependencySourceWait(special != SP_NOT);
return true;
}
return false;
}
+/*
+ * In a line like ".MAIN: source1 source2", add all sources to the list of
+ * things to create, but only if the user didn't specify a target on the
+ * command line and .MAIN occurs for the first time.
+ *
+ * See HandleDependencyTargetSpecial, branch SP_MAIN.
+ * See unit-tests/cond-func-make-main.mk.
+ */
static void
-ParseDependencySourceMain(const char *src)
+ApplyDependencySourceMain(const char *src)
{
- /*
- * In a line like ".MAIN: source1 source2", add all sources to the
- * list of things to create, but only if the user didn't specify a
- * target on the command line and .MAIN occurs for the first time.
- *
- * See ParseDependencyTargetSpecial, branch SP_MAIN.
- * See unit-tests/cond-func-make-main.mk.
- */
Lst_Append(&opts.create, bmake_strdup(src));
/*
* Add the name to the .TARGETS variable as well, so the user can
@@ -903,7 +770,7 @@ ParseDependencySourceMain(const char *src)
}
static void
-ParseDependencySourceOrder(const char *src)
+ApplyDependencySourceOrder(const char *src)
{
GNode *gn;
/*
@@ -917,8 +784,9 @@ ParseDependencySourceOrder(const char *src)
Lst_Append(&order_pred->order_succ, gn);
Lst_Append(&gn->order_pred, order_pred);
if (DEBUG(PARSE)) {
- debug_printf("# %s: added Order dependency %s - %s\n",
- __func__, order_pred->name, gn->name);
+ debug_printf(
+ "# .ORDER forces '%s' to be made before '%s'\n",
+ order_pred->name, gn->name);
Targ_PrintNode(order_pred, 0);
Targ_PrintNode(gn, 0);
}
@@ -929,56 +797,42 @@ ParseDependencySourceOrder(const char *src)
order_pred = gn;
}
+/* The source is not an attribute, so find/create a node for it. */
static void
-ParseDependencySourceOther(const char *src, GNodeType tOp,
- ParseSpecial specType)
+ApplyDependencySourceOther(const char *src, GNodeType targetAttr,
+ ParseSpecial special)
{
GNode *gn;
- /*
- * The source is not an attribute, so find/create a node for it.
- * After that, apply any operator to it from a special target or
- * link it to its parents, as appropriate.
- *
- * In the case of a source that was the object of a '::' operator,
- * the attribute is applied to all of its instances (as kept in
- * the 'cohorts' list of the node) or all the cohorts are linked
- * to all the targets.
- */
-
- /* Find/create the 'src' node and attach to all targets */
gn = Targ_GetNode(src);
if (doing_depend)
RememberLocation(gn);
- if (tOp != OP_NONE)
- gn->type |= tOp;
+ if (targetAttr != OP_NONE)
+ gn->type |= targetAttr;
else
- LinkToTargets(gn, specType != SP_NOT);
+ LinkToTargets(gn, special != SP_NOT);
}
/*
* Given the name of a source in a dependency line, figure out if it is an
- * attribute (such as .SILENT) and apply it to the targets if it is. Else
+ * attribute (such as .SILENT) and if so, apply it to all targets. Otherwise
* decide if there is some attribute which should be applied *to* the source
* because of some special target (such as .PHONY) and apply it if so.
- * Otherwise, make the source a child of the targets in the list 'targets'.
- *
- * Input:
- * tOp operator (if any) from special targets
- * src name of the source to handle
+ * Otherwise, make the source a child of the targets.
*/
static void
-ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType)
+ApplyDependencySource(GNodeType targetAttr, const char *src,
+ ParseSpecial special)
{
- if (ParseDependencySourceKeyword(src, specType))
+ if (ApplyDependencySourceKeyword(src, special))
return;
- if (specType == SP_MAIN)
- ParseDependencySourceMain(src);
- else if (specType == SP_ORDER)
- ParseDependencySourceOrder(src);
+ if (special == SP_MAIN)
+ ApplyDependencySourceMain(src);
+ else if (special == SP_ORDER)
+ ApplyDependencySourceOrder(src);
else
- ParseDependencySourceOther(src, tOp, specType);
+ ApplyDependencySourceOther(src, targetAttr, special);
}
/*
@@ -987,7 +841,7 @@ ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType)
* actually a real target (i.e. isn't a .USE or .EXEC rule) to be made.
*/
static void
-FindMainTarget(void)
+MaybeUpdateMainTarget(void)
{
GNodeListNode *ln;
@@ -996,32 +850,24 @@ FindMainTarget(void)
for (ln = targets->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
- if (!(gn->type & OP_NOTARGET)) {
+ if (GNode_IsMainCandidate(gn)) {
DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name);
mainNode = gn;
- Targ_SetMain(gn);
return;
}
}
}
-/*
- * We got to the end of the line while we were still looking at targets.
- *
- * Ending a dependency line without an operator is a Bozo no-no. As a
- * heuristic, this is also often triggered by undetected conflicts from
- * cvs/rcs merges.
- */
static void
-ParseErrorNoDependency(const char *lstart)
+InvalidLineType(const char *line)
{
- if ((strncmp(lstart, "<<<<<<", 6) == 0) ||
- (strncmp(lstart, "======", 6) == 0) ||
- (strncmp(lstart, ">>>>>>", 6) == 0))
+ if (strncmp(line, "<<<<<<", 6) == 0 ||
+ strncmp(line, "======", 6) == 0 ||
+ strncmp(line, ">>>>>>", 6) == 0)
Parse_Error(PARSE_FATAL,
"Makefile appears to contain unresolved CVS/RCS/??? merge conflicts");
- else if (lstart[0] == '.') {
- const char *dirstart = lstart + 1;
+ else if (line[0] == '.') {
+ const char *dirstart = line + 1;
const char *dirend;
cpp_skip_whitespace(&dirstart);
dirend = dirstart;
@@ -1034,39 +880,35 @@ ParseErrorNoDependency(const char *lstart)
}
static void
-ParseDependencyTargetWord(const char **pp, const char *lstart)
+ParseDependencyTargetWord(char **pp, const char *lstart)
{
const char *cp = *pp;
while (*cp != '\0') {
if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' ||
*cp == '(') &&
- !ParseIsEscaped(lstart, cp))
+ !IsEscaped(lstart, cp))
break;
if (*cp == '$') {
/*
* Must be a dynamic source (would have been expanded
- * otherwise), so call the Var module to parse the
- * puppy so we can safely advance beyond it.
+ * otherwise).
*
* There should be no errors in this, as they would
* have been discovered in the initial Var_Subst and
* we wouldn't be here.
*/
- const char *nested_p = cp;
- FStr nested_val;
+ FStr val;
- (void)Var_Parse(&nested_p, SCOPE_CMDLINE,
- VARE_PARSE_ONLY, &nested_val);
- /* TODO: handle errors */
- FStr_Done(&nested_val);
- cp += nested_p - cp;
+ (void)Var_Parse(&cp, SCOPE_CMDLINE,
+ VARE_PARSE_ONLY, &val);
+ FStr_Done(&val);
} else
cp++;
}
- *pp = cp;
+ *pp += cp - *pp;
}
/*
@@ -1075,11 +917,11 @@ ParseDependencyTargetWord(const char **pp, const char *lstart)
* See the tests deptgt-*.mk.
*/
static void
-ParseDependencyTargetSpecial(ParseSpecial *inout_specType,
- const char *targetName,
- SearchPathList **inout_paths)
+HandleDependencyTargetSpecial(const char *targetName,
+ ParseSpecial *inout_special,
+ SearchPathList **inout_paths)
{
- switch (*inout_specType) {
+ switch (*inout_special) {
case SP_PATH:
if (*inout_paths == NULL)
*inout_paths = Lst_New();
@@ -1091,7 +933,7 @@ ParseDependencyTargetSpecial(ParseSpecial *inout_specType,
* .MAIN node.
*/
if (!Lst_IsEmpty(&opts.create))
- *inout_specType = SP_NOT;
+ *inout_special = SP_NOT;
break;
case SP_BEGIN:
case SP_END:
@@ -1136,13 +978,9 @@ ParseDependencyTargetSpecial(ParseSpecial *inout_specType,
}
}
-/*
- * .PATH<suffix> has to be handled specially.
- * Call on the suffix module to give us a path to modify.
- */
static bool
-ParseDependencyTargetPath(const char *suffixName,
- SearchPathList **inout_paths)
+HandleDependencyTargetPath(const char *suffixName,
+ SearchPathList **inout_paths)
{
SearchPath *path;
@@ -1160,13 +998,12 @@ ParseDependencyTargetPath(const char *suffixName,
return true;
}
-/*
- * See if it's a special target and if so set specType to match it.
- */
+/* See if it's a special target and if so set inout_special to match it. */
static bool
-ParseDependencyTarget(const char *targetName,
- ParseSpecial *inout_specType,
- GNodeType *out_tOp, SearchPathList **inout_paths)
+HandleDependencyTarget(const char *targetName,
+ ParseSpecial *inout_special,
+ GNodeType *inout_targetAttr,
+ SearchPathList **inout_paths)
{
int keywd;
@@ -1177,55 +1014,42 @@ ParseDependencyTarget(const char *targetName,
* See if the target is a special target that must have it
* or its sources handled specially.
*/
- keywd = ParseFindKeyword(targetName);
+ keywd = FindKeyword(targetName);
if (keywd != -1) {
- if (*inout_specType == SP_PATH &&
- parseKeywords[keywd].spec != SP_PATH) {
+ if (*inout_special == SP_PATH &&
+ parseKeywords[keywd].special != SP_PATH) {
Parse_Error(PARSE_FATAL, "Mismatched special targets");
return false;
}
- *inout_specType = parseKeywords[keywd].spec;
- *out_tOp = parseKeywords[keywd].op;
+ *inout_special = parseKeywords[keywd].special;
+ *inout_targetAttr = parseKeywords[keywd].targetAttr;
- ParseDependencyTargetSpecial(inout_specType, targetName,
+ HandleDependencyTargetSpecial(targetName, inout_special,
inout_paths);
} else if (strncmp(targetName, ".PATH", 5) == 0) {
- *inout_specType = SP_PATH;
- if (!ParseDependencyTargetPath(targetName + 5, inout_paths))
+ *inout_special = SP_PATH;
+ if (!HandleDependencyTargetPath(targetName + 5, inout_paths))
return false;
}
return true;
}
static void
-ParseDependencyTargetMundane(char *targetName, StringList *curTargs)
+HandleDependencyTargetMundane(char *targetName)
{
+ StringList targetNames = LST_INIT;
+
if (Dir_HasWildcards(targetName)) {
- /*
- * Targets are to be sought only in the current directory,
- * so create an empty path for the thing. Note we need to
- * use Dir_Destroy in the destruction of the path as the
- * Dir module could have added a directory to the path...
- */
SearchPath *emptyPath = SearchPath_New();
-
- SearchPath_Expand(emptyPath, targetName, curTargs);
-
+ SearchPath_Expand(emptyPath, targetName, &targetNames);
SearchPath_Free(emptyPath);
- } else {
- /*
- * No wildcards, but we want to avoid code duplication,
- * so create a list with the word on it.
- */
- Lst_Append(curTargs, targetName);
- }
-
- /* Apply the targets. */
+ } else
+ Lst_Append(&targetNames, targetName);
- while (!Lst_IsEmpty(curTargs)) {
- char *targName = Lst_Dequeue(curTargs);
+ while (!Lst_IsEmpty(&targetNames)) {
+ char *targName = Lst_Dequeue(&targetNames);
GNode *gn = Suff_IsTransform(targName)
? Suff_AddTransform(targName)
: Targ_GetNode(targName);
@@ -1237,33 +1061,28 @@ ParseDependencyTargetMundane(char *targetName, StringList *curTargs)
}
static void
-ParseDependencyTargetExtraWarn(char **pp, const char *lstart)
+SkipExtraTargets(char **pp, const char *lstart)
{
bool warning = false;
- char *cp = *pp;
+ const char *p = *pp;
- while (*cp != '\0') {
- if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':'))
+ while (*p != '\0') {
+ if (!IsEscaped(lstart, p) && (*p == '!' || *p == ':'))
break;
- if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t'))
+ if (IsEscaped(lstart, p) || (*p != ' ' && *p != '\t'))
warning = true;
- cp++;
+ p++;
}
if (warning)
Parse_Error(PARSE_WARNING, "Extra target ignored");
- *pp = cp;
+ *pp += p - *pp;
}
static void
-ParseDependencyCheckSpec(ParseSpecial specType)
+CheckSpecialMundaneMixture(ParseSpecial special)
{
- switch (specType) {
- default:
- Parse_Error(PARSE_WARNING,
- "Special and mundane targets don't mix. "
- "Mundane ones ignored");
- break;
+ switch (special) {
case SP_DEFAULT:
case SP_STALE:
case SP_BEGIN:
@@ -1275,7 +1094,14 @@ ParseDependencyCheckSpec(ParseSpecial specType)
* 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;
+ default:
+ Parse_Error(PARSE_WARNING,
+ "Special and mundane targets don't mix. "
+ "Mundane ones ignored");
break;
}
}
@@ -1284,34 +1110,15 @@ ParseDependencyCheckSpec(ParseSpecial specType)
* In a dependency line like 'targets: sources' or 'targets! sources', parse
* the operator ':', '::' or '!' from between the targets and the sources.
*/
-static bool
-ParseDependencyOp(char **pp, const char *lstart, GNodeType *out_op)
+static GNodeType
+ParseDependencyOp(char **pp)
{
- const char *cp = *pp;
-
- if (*cp == '!') {
- *out_op = OP_FORCE;
- (*pp)++;
- return true;
- }
-
- if (*cp == ':') {
- if (cp[1] == ':') {
- *out_op = OP_DOUBLEDEP;
- (*pp) += 2;
- } else {
- *out_op = OP_DEPENDS;
- (*pp)++;
- }
- return true;
- }
-
- {
- const char *msg = lstart[0] == '.'
- ? "Unknown directive" : "Missing dependency operator";
- Parse_Error(PARSE_FATAL, "%s", msg);
- return false;
- }
+ if (**pp == '!')
+ return (*pp)++, OP_FORCE;
+ if ((*pp)[1] == ':')
+ return *pp += 2, OP_DOUBLEDEP;
+ else
+ return (*pp)++, OP_DEPENDS;
}
static void
@@ -1326,19 +1133,11 @@ ClearPaths(SearchPathList *paths)
Dir_SetPATH();
}
-/*
- * Several special targets take different actions if present with no
- * sources:
- * a .SUFFIXES line with no sources clears out all old suffixes
- * a .PRECIOUS line makes all targets precious
- * a .IGNORE line ignores errors for all targets
- * a .SILENT line creates silence when making all targets
- * a .PATH removes all directories from the search path(s).
- */
+/* Handle a "dependency" line like '.SPECIAL:' without any sources. */
static void
-ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths)
+HandleDependencySourcesEmpty(ParseSpecial special, SearchPathList *paths)
{
- switch (specType) {
+ switch (special) {
case SP_SUFFIXES:
Suff_ClearSuffixes();
break;
@@ -1349,7 +1148,7 @@ ParseDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths)
opts.ignoreErrors = true;
break;
case SP_SILENT:
- opts.beSilent = true;
+ opts.silent = true;
break;
case SP_PATH:
ClearPaths(paths);
@@ -1375,39 +1174,17 @@ AddToPaths(const char *dir, SearchPathList *paths)
}
/*
- * If the target was one that doesn't take files as its sources
- * but takes something like suffixes, we take each
- * space-separated word on the line as a something and deal
- * with it accordingly.
- *
- * If the target was .SUFFIXES, we take each source as a
- * suffix and add it to the list of suffixes maintained by the
- * Suff module.
- *
- * If the target was a .PATH, we add the source as a directory
- * to search on the search path.
- *
- * If it was .INCLUDES, the source is taken to be the suffix of
- * files which will be #included and whose search path should
- * be present in the .INCLUDES variable.
- *
- * If it was .LIBS, the source is taken to be the suffix of
- * files which are considered libraries and whose search path
- * should be present in the .LIBS variable.
- *
- * If it was .NULL, the source is the suffix to use when a file
- * has no valid suffix.
- *
- * If it was .OBJDIR, the source is a new definition for .OBJDIR,
- * and will cause make to do a new chdir to that path.
+ * If the target was one that doesn't take files as its sources but takes
+ * something like suffixes, we take each space-separated word on the line as
+ * a something and deal with it accordingly.
*/
static void
-ParseDependencySourceSpecial(ParseSpecial specType, char *word,
+ParseDependencySourceSpecial(ParseSpecial special, const char *word,
SearchPathList *paths)
{
- switch (specType) {
+ switch (special) {
case SP_SUFFIXES:
- Suff_AddSuffix(word, &mainNode);
+ Suff_AddSuffix(word);
break;
case SP_PATH:
AddToPaths(word, paths);
@@ -1430,121 +1207,92 @@ ParseDependencySourceSpecial(ParseSpecial specType, char *word,
}
static bool
+ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special,
+ GNodeType *inout_targetAttr,
+ SearchPathList **inout_paths)
+{
+ char savec = *nameEnd;
+ *nameEnd = '\0';
+
+ if (!HandleDependencyTarget(name, inout_special,
+ inout_targetAttr, inout_paths))
+ return false;
+
+ if (*inout_special == SP_NOT && *name != '\0')
+ HandleDependencyTargetMundane(name);
+ else if (*inout_special == SP_PATH && *name != '.' && *name != '\0')
+ Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", name);
+
+ *nameEnd = savec;
+ return true;
+}
+
+static bool
ParseDependencyTargets(char **inout_cp,
- char **inout_line,
const char *lstart,
- ParseSpecial *inout_specType,
- GNodeType *inout_tOp,
- SearchPathList **inout_paths,
- StringList *curTargs)
+ ParseSpecial *inout_special,
+ GNodeType *inout_targetAttr,
+ SearchPathList **inout_paths)
{
- char *cp;
- char *tgt = *inout_line;
- char savec;
- const char *p;
+ char *cp = *inout_cp;
for (;;) {
- /*
- * Here LINE points to the beginning of the next word, and
- * LSTART points to the actual beginning of the line.
- */
+ char *tgt = cp;
- /* Find the end of the next word. */
- cp = tgt;
- p = cp;
- ParseDependencyTargetWord(&p, lstart);
- cp += p - cp;
+ ParseDependencyTargetWord(&cp, lstart);
/*
* If the word is followed by a left parenthesis, it's the
- * name of an object file inside an archive (ar file).
+ * name of one or more files inside an archive.
*/
- if (!ParseIsEscaped(lstart, cp) && *cp == '(') {
- /*
- * Archives must be handled specially to make sure the
- * OP_ARCHV flag is set in their 'type' field, for one
- * thing, and because things like "archive(file1.o
- * file2.o file3.o)" are permissible.
- *
- * Arch_ParseArchive will set 'line' to be the first
- * non-blank after the archive-spec. It creates/finds
- * nodes for the members and places them on the given
- * list, returning true if all went well and false if
- * there was an error in the specification. On error,
- * line should remain untouched.
- */
- if (!Arch_ParseArchive(&tgt, targets, SCOPE_CMDLINE)) {
+ if (!IsEscaped(lstart, cp) && *cp == '(') {
+ cp = tgt;
+ if (!Arch_ParseArchive(&cp, targets, SCOPE_CMDLINE)) {
Parse_Error(PARSE_FATAL,
"Error in archive specification: \"%s\"",
tgt);
return false;
}
-
- cp = tgt;
continue;
}
if (*cp == '\0') {
- ParseErrorNoDependency(lstart);
+ InvalidLineType(lstart);
return false;
}
- /* Insert a null terminator. */
- savec = *cp;
- *cp = '\0';
-
- if (!ParseDependencyTarget(tgt, inout_specType, inout_tOp,
- inout_paths))
+ if (!ApplyDependencyTarget(tgt, cp, inout_special,
+ inout_targetAttr, inout_paths))
return false;
- /*
- * Have word in line. Get or create its node and stick it at
- * the end of the targets list
- */
- if (*inout_specType == SP_NOT && *tgt != '\0')
- ParseDependencyTargetMundane(tgt, curTargs);
- else if (*inout_specType == SP_PATH && *tgt != '.' &&
- *tgt != '\0')
- Parse_Error(PARSE_WARNING, "Extra target (%s) ignored",
- tgt);
-
- /* Don't need the inserted null terminator any more. */
- *cp = savec;
-
- /*
- * 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)
- ParseDependencyTargetExtraWarn(&cp, lstart);
+ if (*inout_special != SP_NOT && *inout_special != SP_PATH)
+ SkipExtraTargets(&cp, lstart);
else
pp_skip_whitespace(&cp);
- tgt = cp;
- if (*tgt == '\0')
+ if (*cp == '\0')
break;
- if ((*tgt == '!' || *tgt == ':') &&
- !ParseIsEscaped(lstart, tgt))
+ if ((*cp == '!' || *cp == ':') && !IsEscaped(lstart, cp))
break;
}
*inout_cp = cp;
- *inout_line = tgt;
return true;
}
static void
-ParseDependencySourcesSpecial(char *start, char *end,
- ParseSpecial specType, SearchPathList *paths)
+ParseDependencySourcesSpecial(char *start,
+ ParseSpecial special, SearchPathList *paths)
{
char savec;
while (*start != '\0') {
+ char *end = start;
while (*end != '\0' && !ch_isspace(*end))
end++;
savec = *end;
*end = '\0';
- ParseDependencySourceSpecial(specType, start, paths);
+ ParseDependencySourceSpecial(special, start, paths);
*end = savec;
if (savec != '\0')
end++;
@@ -1553,11 +1301,42 @@ ParseDependencySourcesSpecial(char *start, char *end,
}
}
+static void
+LinkVarToTargets(VarAssign *var)
+{
+ GNodeListNode *ln;
+
+ for (ln = targets->first; ln != NULL; ln = ln->next)
+ Parse_Var(var, ln->datum);
+}
+
static bool
-ParseDependencySourcesMundane(char *start, char *end,
- ParseSpecial specType, GNodeType tOp)
+ParseDependencySourcesMundane(char *start,
+ ParseSpecial special, GNodeType targetAttr)
{
while (*start != '\0') {
+ char *end = start;
+ VarAssign var;
+
+ /*
+ * Check for local variable assignment,
+ * rest of the line is the value.
+ */
+ if (Parse_IsVar(start, &var)) {
+ /*
+ * Check if this makefile has disabled
+ * setting local variables.
+ */
+ bool target_vars = GetBooleanExpr(
+ "${.MAKE.TARGET_LOCAL_VARIABLES}", true);
+
+ if (target_vars)
+ LinkVarToTargets(&var);
+ free(var.varname);
+ if (target_vars)
+ return true;
+ }
+
/*
* The targets take real sources, so we must beware of archive
* specifications (i.e. things with left parentheses in them)
@@ -1588,7 +1367,8 @@ ParseDependencySourcesMundane(char *start, char *end,
while (!Lst_IsEmpty(&sources)) {
GNode *gn = Lst_Dequeue(&sources);
- ParseDependencySource(tOp, gn->name, specType);
+ ApplyDependencySource(targetAttr, gn->name,
+ special);
}
Lst_Done(&sources);
end = start;
@@ -1598,7 +1378,7 @@ ParseDependencySourcesMundane(char *start, char *end,
end++;
}
- ParseDependencySource(tOp, start, specType);
+ ApplyDependencySource(targetAttr, start, special);
}
pp_skip_whitespace(&end);
start = end;
@@ -1607,54 +1387,49 @@ ParseDependencySourcesMundane(char *start, char *end,
}
/*
- * In a dependency line like 'targets: sources', parse the sources.
+ * From a dependency line like 'targets: sources', parse the sources.
*
* See the tests depsrc-*.mk.
*/
static void
-ParseDependencySources(char *line, char *cp, GNodeType tOp,
- ParseSpecial specType, SearchPathList **inout_paths)
+ParseDependencySources(char *p, GNodeType targetAttr,
+ ParseSpecial special, SearchPathList **inout_paths)
{
- if (line[0] == '\0') {
- ParseDependencySourcesEmpty(specType, *inout_paths);
- } else if (specType == SP_MFLAGS) {
- Main_ParseArgLine(line);
- /*
- * Set the initial character to a null-character so the loop
- * to get sources won't get anything.
- */
- *line = '\0';
- } else if (specType == SP_SHELL) {
- if (!Job_ParseShell(line)) {
+ if (*p == '\0') {
+ HandleDependencySourcesEmpty(special, *inout_paths);
+ } else if (special == SP_MFLAGS) {
+ Main_ParseArgLine(p);
+ return;
+ } else if (special == SP_SHELL) {
+ if (!Job_ParseShell(p)) {
Parse_Error(PARSE_FATAL,
"improper shell specification");
return;
}
- *line = '\0';
- } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL ||
- specType == SP_DELETE_ON_ERROR) {
- *line = '\0';
+ return;
+ } else if (special == SP_NOTPARALLEL || special == SP_SINGLESHELL ||
+ special == SP_DELETE_ON_ERROR) {
+ return;
}
/* Now go for the sources. */
- if (specType == SP_SUFFIXES || specType == SP_PATH ||
- specType == SP_INCLUDES || specType == SP_LIBS ||
- specType == SP_NULL || specType == SP_OBJDIR) {
- ParseDependencySourcesSpecial(line, cp, specType,
- *inout_paths);
+ if (special == SP_SUFFIXES || special == SP_PATH ||
+ special == SP_INCLUDES || special == SP_LIBS ||
+ special == SP_NULL || special == SP_OBJDIR) {
+ ParseDependencySourcesSpecial(p, special, *inout_paths);
if (*inout_paths != NULL) {
Lst_Free(*inout_paths);
*inout_paths = NULL;
}
- if (specType == SP_PATH)
+ if (special == SP_PATH)
Dir_SetPATH();
} else {
assert(*inout_paths == NULL);
- if (!ParseDependencySourcesMundane(line, cp, specType, tOp))
+ if (!ParseDependencySourcesMundane(p, special, targetAttr))
return;
}
- FindMainTarget();
+ MaybeUpdateMainTarget();
}
/*
@@ -1665,8 +1440,7 @@ ParseDependencySources(char *line, char *cp, GNodeType tOp,
* targets. Nodes are created as necessary.
*
* The operator is applied to each node in the global 'targets' list,
- * which is where the nodes found for the targets are kept, by means of
- * the ParseOp function.
+ * which is where the nodes found for the targets are kept.
*
* 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,
@@ -1674,109 +1448,61 @@ ParseDependencySources(char *line, char *cp, GNodeType tOp,
* 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.
+ * specially, see ParseSpecial.
*
- * The storing of transformation rules such as '.c.o' is also taken care of
- * here. A target is recognized as a transformation rule by calling
- * 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.
+ * Transformation rules such as '.c.o' are also handled here, see
+ * Suff_AddTransform.
*
* Upon return, the value of the line is unspecified.
*/
static void
ParseDependency(char *line)
{
- char *cp; /* our current position */
- GNodeType op; /* the operator on the line */
- SearchPathList *paths; /* search paths to alter when parsing
- * a list of .PATH targets */
- GNodeType tOp; /* operator from special target */
- /* target names to be found and added to the targets list */
- StringList curTargs = LST_INIT;
- char *lstart = line;
-
- /*
- * specType contains the SPECial TYPE of the current target. It is
- * SP_NOT if the target is unspecial. If it *is* special, however, the
- * children are linked as children of the parent but not vice versa.
- */
- ParseSpecial specType = SP_NOT;
+ char *p;
+ SearchPathList *paths; /* search paths to alter when parsing a list
+ * of .PATH targets */
+ GNodeType targetAttr; /* from special sources */
+ ParseSpecial special; /* in special targets, the children are
+ * linked as children of the parent but not
+ * vice versa */
DEBUG1(PARSE, "ParseDependency(%s)\n", line);
- tOp = OP_NONE;
-
+ p = line;
paths = NULL;
+ targetAttr = OP_NONE;
+ special = SP_NOT;
- /*
- * First, grind through the targets.
- */
- /* XXX: don't use 'line' as an iterator variable */
- if (!ParseDependencyTargets(&cp, &line, lstart, &specType, &tOp,
- &paths, &curTargs))
+ if (!ParseDependencyTargets(&p, line, &special, &targetAttr, &paths))
goto out;
- /*
- * Don't need the list of target names anymore.
- * The targets themselves are now in the global variable 'targets'.
- */
- Lst_Done(&curTargs);
- Lst_Init(&curTargs);
-
if (!Lst_IsEmpty(targets))
- ParseDependencyCheckSpec(specType);
-
- /*
- * Have now parsed all the target names. Must parse the operator next.
- */
- if (!ParseDependencyOp(&cp, lstart, &op))
- goto out;
+ CheckSpecialMundaneMixture(special);
- /*
- * Apply the operator to the target. This is how we remember which
- * operator a target was defined with. It fails if the operator
- * used isn't consistent across all references.
- */
- ApplyDependencyOperator(op);
+ ApplyDependencyOperator(ParseDependencyOp(&p));
- /*
- * Onward to the sources.
- *
- * LINE will now point to the first source word, if any, or the
- * end of the string if not.
- */
- pp_skip_whitespace(&cp);
- line = cp; /* XXX: 'line' is an inappropriate name */
+ pp_skip_whitespace(&p);
- ParseDependencySources(line, cp, tOp, specType, &paths);
+ ParseDependencySources(p, targetAttr, special, &paths);
out:
if (paths != NULL)
Lst_Free(paths);
- Lst_Done(&curTargs);
}
-typedef struct VarAssignParsed {
- const char *nameStart; /* unexpanded */
- const char *nameEnd; /* before operator adjustment */
- const char *eq; /* the '=' of the assignment operator */
-} VarAssignParsed;
-
/*
* Determine the assignment operator and adjust the end of the variable
* name accordingly.
*/
-static void
-AdjustVarassignOp(const VarAssignParsed *pvar, const char *value,
- VarAssign *out_var)
+static VarAssign
+AdjustVarassignOp(const char *name, const char *nameEnd, const char *op,
+ const char *value)
{
- const char *op = pvar->eq;
- const char *const name = pvar->nameStart;
VarAssignOp type;
+ VarAssign va;
if (op > name && op[-1] == '+') {
- type = VAR_APPEND;
op--;
+ type = VAR_APPEND;
} else if (op > name && op[-1] == '?') {
op--;
@@ -1796,20 +1522,17 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value,
while (op > name && ch_isspace(op[-1]))
op--;
- if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' &&
- op[-1] == 'h') {
- type = VAR_SHELL;
+ if (op - name >= 3 && memcmp(op - 3, ":sh", 3) == 0) {
op -= 3;
+ type = VAR_SHELL;
}
#endif
}
- {
- const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op;
- out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd);
- out_var->op = type;
- out_var->value = value;
- }
+ va.varname = bmake_strsedup(name, nameEnd < op ? nameEnd : op);
+ va.op = type;
+ va.value = value;
+ return va;
}
/*
@@ -1825,11 +1548,10 @@ AdjustVarassignOp(const VarAssignParsed *pvar, const char *value,
*
* Used for both lines in a file and command line arguments.
*/
-bool
+static bool
Parse_IsVar(const char *p, VarAssign *out_var)
{
- VarAssignParsed pvar;
- const char *firstSpace = NULL;
+ const char *nameStart, *nameEnd, *firstSpace, *eq;
int level = 0;
cpp_skip_hspace(&p); /* Skip to variable name */
@@ -1837,14 +1559,12 @@ Parse_IsVar(const char *p, VarAssign *out_var)
/*
* During parsing, the '+' of the '+=' operator is initially parsed
* as part of the variable name. It is later corrected, as is the
- * ':sh' modifier. Of these two (nameEnd and op), the earlier one
+ * ':sh' modifier. Of these two (nameEnd and eq), the earlier one
* determines the actual end of the variable name.
*/
- pvar.nameStart = p;
-#ifdef CLEANUP
- pvar.nameEnd = NULL;
- pvar.eq = NULL;
-#endif
+
+ nameStart = p;
+ firstSpace = NULL;
/*
* Scan for one of the assignment operators outside a variable
@@ -1864,36 +1584,34 @@ Parse_IsVar(const char *p, VarAssign *out_var)
if (level != 0)
continue;
- if (ch == ' ' || ch == '\t')
- if (firstSpace == NULL)
- firstSpace = p - 1;
+ if ((ch == ' ' || ch == '\t') && firstSpace == NULL)
+ firstSpace = p - 1;
while (ch == ' ' || ch == '\t')
ch = *p++;
+ if (ch == '\0')
+ return false;
#ifdef SUNSHCMD
if (ch == ':' && p[0] == 's' && p[1] == 'h') {
p += 2;
continue;
}
#endif
- if (ch == '=') {
- pvar.eq = p - 1;
- pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1;
- cpp_skip_whitespace(&p);
- AdjustVarassignOp(&pvar, p, out_var);
- return true;
- }
- if (*p == '=' &&
- (ch == '+' || ch == ':' || ch == '?' || ch == '!')) {
- pvar.eq = p;
- pvar.nameEnd = firstSpace != NULL ? firstSpace : p;
- p++;
- cpp_skip_whitespace(&p);
- AdjustVarassignOp(&pvar, p, out_var);
- return true;
- }
- if (firstSpace != NULL)
+ if (ch == '=')
+ eq = p - 1;
+ else if (*p == '=' &&
+ (ch == '+' || ch == ':' || ch == '?' || ch == '!'))
+ eq = p;
+ else if (firstSpace != NULL)
return false;
+ else
+ continue;
+
+ nameEnd = firstSpace != NULL ? firstSpace : eq;
+ p = eq + 1;
+ cpp_skip_whitespace(&p);
+ *out_var = AdjustVarassignOp(nameStart, nameEnd, eq, p);
+ return true;
}
return false;
@@ -1917,6 +1635,7 @@ VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope)
}
}
+/* Perform a variable assignment that uses the operator ':='. */
static void
VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue,
FStr *out_avalue)
@@ -1929,6 +1648,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue,
*
* TODO: Add a test that demonstrates why this code is needed,
* apart from making the debug log longer.
+ *
+ * XXX: The variable name is expanded up to 3 times.
*/
if (!Var_ExistsExpand(scope, name))
Var_SetExpand(scope, name, "");
@@ -1941,29 +1662,24 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue,
*out_avalue = FStr_InitOwn(evalue);
}
+/* Perform a variable assignment that uses the operator '!='. */
static void
VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope,
FStr *out_avalue)
{
FStr cmd;
- const char *errfmt;
- char *cmdOut;
+ char *output, *error;
cmd = FStr_InitRefer(uvalue);
- if (strchr(cmd.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(cmd.str, SCOPE_CMDLINE, VARE_UNDEFERR,
- &expanded);
- /* TODO: handle errors */
- cmd = FStr_InitOwn(expanded);
- }
-
- cmdOut = Cmd_Exec(cmd.str, &errfmt);
- Var_SetExpand(scope, name, cmdOut);
- *out_avalue = FStr_InitOwn(cmdOut);
+ Var_Expand(&cmd, SCOPE_CMDLINE, VARE_UNDEFERR);
- if (errfmt != NULL)
- Parse_Error(PARSE_WARNING, errfmt, cmd.str);
+ output = Cmd_Exec(cmd.str, &error);
+ Var_SetExpand(scope, name, output);
+ *out_avalue = FStr_InitOwn(output);
+ if (error != NULL) {
+ Parse_Error(PARSE_WARNING, "%s", error);
+ free(error);
+ }
FStr_Done(&cmd);
}
@@ -1992,6 +1708,7 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue,
else if (op == VAR_SHELL)
VarAssign_EvalShell(name, uvalue, scope, &avalue);
else {
+ /* XXX: The variable name is expanded up to 2 times. */
if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name))
return false;
@@ -2007,7 +1724,7 @@ static void
VarAssignSpecial(const char *name, const char *avalue)
{
if (strcmp(name, MAKEOVERRIDES) == 0)
- Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */
+ Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */
else if (strcmp(name, ".CURDIR") == 0) {
/*
* Someone is being (too?) clever...
@@ -2022,19 +1739,17 @@ VarAssignSpecial(const char *name, const char *avalue)
Var_ExportVars(avalue);
}
-/* Perform the variable variable assignment in the given scope. */
-void
+/* Perform the variable assignment in the given scope. */
+static void
Parse_Var(VarAssign *var, GNode *scope)
{
- FStr avalue; /* actual value (maybe expanded) */
+ FStr avalue; /* actual value (maybe expanded) */
VarCheckSyntax(var->op, var->value, scope);
if (VarAssign_Eval(var->varname, var->op, var->value, scope, &avalue)) {
VarAssignSpecial(var->varname, avalue.str);
FStr_Done(&avalue);
}
-
- free(var->varname);
}
@@ -2052,7 +1767,7 @@ MaybeSubMake(const char *cmd)
char endc;
/* XXX: What if progname != "make"? */
- if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e')
+ if (strncmp(p, "make", 4) == 0)
if (start == cmd || !ch_isalnum(p[-1]))
if (!ch_isalnum(p[4]))
return true;
@@ -2072,9 +1787,8 @@ MaybeSubMake(const char *cmd)
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;
+ if (strncmp(p, "MAKE", 4) == 0 && p[4] == endc)
+ return true;
}
return false;
}
@@ -2086,7 +1800,7 @@ MaybeSubMake(const char *cmd)
* be that.
*/
static void
-ParseAddCmd(GNode *gn, char *cmd)
+GNode_AddCommand(GNode *gn, char *cmd)
{
/* Add to last (ie current) cohort for :: targets */
if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL)
@@ -2104,13 +1818,13 @@ ParseAddCmd(GNode *gn, char *cmd)
Lst_Append(&gn->commands, cmd);
Parse_Error(PARSE_WARNING,
"overriding commands for target \"%s\"; "
- "previous commands defined at %s: %d ignored",
+ "previous commands defined at %s: %u ignored",
gn->name, gn->fname, gn->lineno);
#else
Parse_Error(PARSE_WARNING,
"duplicate script for target \"%s\" ignored",
gn->name);
- ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING,
+ ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING,
"using previous script for \"%s\" defined here",
gn->name);
#endif
@@ -2140,7 +1854,7 @@ Parse_AddIncludeDir(const char *dir)
static void
IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
{
- struct loadedfile *lf;
+ Buffer buf;
char *fullname; /* full pathname of file */
char *newName;
char *slash, *incdir;
@@ -2158,7 +1872,7 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
* we can locate the file.
*/
- incdir = bmake_strdup(CurFile()->fname);
+ incdir = bmake_strdup(CurFile()->name.str);
slash = strrchr(incdir, '/');
if (slash != NULL) {
*slash = '\0';
@@ -2233,15 +1947,13 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent)
return;
}
- /* load it */
- lf = loadfile(fullname, fd);
+ buf = loadfile(fullname, fd);
+ (void)close(fd);
- /* Start reading from this file next */
- Parse_PushInput(fullname, 0, -1, loadedfile_readMore, lf);
- CurFile()->lf = lf;
+ Parse_PushInput(fullname, 1, 0, buf, NULL);
if (depinc)
doing_depend = depinc; /* only turn it on */
- /* TODO: consider free(fullname); */
+ free(fullname);
}
/*
@@ -2285,13 +1997,7 @@ ParseInclude(char *directive)
*p = '\0';
- if (strchr(file.str, '$') != NULL) {
- char *xfile;
- Var_Subst(file.str, SCOPE_CMDLINE, VARE_WANTRES, &xfile);
- /* TODO: handle errors */
- file = FStr_InitOwn(xfile);
- }
-
+ Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES);
IncludeFile(file.str, endc == '>', directive[0] == 'd', silent);
FStr_Done(&file);
}
@@ -2318,8 +2024,8 @@ SetFilenameVars(const char *filename, const char *dirvar, const char *filevar)
Global_Set(dirvar, dirname.str);
Global_Set(filevar, basename);
- DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n",
- __func__, dirvar, dirname.str, filevar, basename);
+ DEBUG4(PARSE, "SetFilenameVars: ${%s} = `%s' ${%s} = `%s'\n",
+ dirvar, dirname.str, filevar, basename);
FStr_Done(&dirname);
}
@@ -2333,17 +2039,17 @@ static const char *
GetActuallyIncludingFile(void)
{
size_t i;
- const IFile *incs = GetInclude(0);
+ const IncludedFile *incs = GetInclude(0);
for (i = includes.len; i >= 2; i--)
- if (!incs[i - 1].fromForLoop)
- return incs[i - 2].fname;
+ if (incs[i - 1].forLoop == NULL)
+ return incs[i - 2].name.str;
return NULL;
}
/* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */
static void
-ParseSetParseFile(const char *filename)
+SetParseFile(const char *filename)
{
const char *including;
@@ -2364,17 +2070,16 @@ StrContainsWord(const char *str, const char *word)
{
size_t strLen = strlen(str);
size_t wordLen = strlen(word);
- const char *p, *end;
+ const char *p;
if (strLen < wordLen)
- return false; /* str is too short to contain word */
+ return false;
- end = str + strLen - wordLen;
for (p = str; p != NULL; p = strchr(p, ' ')) {
if (*p == ' ')
p++;
- if (p > end)
- return false; /* cannot contain word */
+ if (p > str + strLen - wordLen)
+ return false;
if (memcmp(p, word, wordLen) == 0 &&
(p[wordLen] == '\0' || p[wordLen] == ' '))
@@ -2406,67 +2111,45 @@ VarContainsWord(const char *varname, const char *word)
* of makefiles that have been loaded.
*/
static void
-ParseTrackInput(const char *name)
+TrackInput(const char *name)
{
if (!VarContainsWord(MAKE_MAKEFILES, name))
Global_Append(MAKE_MAKEFILES, name);
}
-/*
- * Start parsing from the given source.
- *
- * The given file is added to the includes stack.
- */
+/* Parse from the given buffer, later return to the current file. */
void
-Parse_PushInput(const char *name, int lineno, int fd,
- ReadMoreProc readMore, void *readMoreArg)
+Parse_PushInput(const char *name, unsigned lineno, unsigned readLines,
+ Buffer buf, struct ForLoop *forLoop)
{
- IFile *curFile;
- char *buf;
- size_t len;
- bool fromForLoop = name == NULL;
+ IncludedFile *curFile;
- if (fromForLoop)
- name = CurFile()->fname;
+ if (forLoop != NULL)
+ name = CurFile()->name.str;
else
- ParseTrackInput(name);
+ TrackInput(name);
- DEBUG3(PARSE, "Parse_PushInput: %s %s, line %d\n",
- readMore == loadedfile_readMore ? "file" : ".for loop in",
- name, lineno);
-
- if (fd == -1 && readMore == NULL)
- /* sanity */
- return;
+ DEBUG3(PARSE, "Parse_PushInput: %s %s, line %u\n",
+ forLoop != NULL ? ".for loop in": "file", name, lineno);
curFile = Vector_Push(&includes);
- curFile->fname = bmake_strdup(name);
- curFile->fromForLoop = fromForLoop;
+ curFile->name = FStr_InitOwn(bmake_strdup(name));
curFile->lineno = lineno;
- curFile->first_lineno = lineno;
- curFile->readMore = readMore;
- curFile->readMoreArg = readMoreArg;
- curFile->lf = NULL;
+ curFile->readLines = readLines;
+ curFile->forHeadLineno = lineno;
+ curFile->forBodyReadLines = readLines;
+ curFile->buf = buf;
curFile->depending = doing_depend; /* restore this on EOF */
+ curFile->forLoop = forLoop;
- assert(readMore != NULL);
-
- /* Get first block of input data */
- buf = curFile->readMore(curFile->readMoreArg, &len);
- if (buf == NULL) {
- /* Was all a waste of time ... */
- if (curFile->fname != NULL)
- free(curFile->fname);
- free(curFile);
- return;
- }
- curFile->buf_freeIt = buf;
- curFile->buf_ptr = buf;
- curFile->buf_end = buf + len;
+ if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf))
+ abort(); /* see For_Run */
+ curFile->buf_ptr = curFile->buf.data;
+ curFile->buf_end = curFile->buf.data + curFile->buf.len;
curFile->cond_depth = Cond_save_depth();
- ParseSetParseFile(name);
+ SetParseFile(name);
}
/* Check if the directive is an include directive. */
@@ -2518,22 +2201,13 @@ ParseTraditionalInclude(char *line)
char *file = line + (silent ? 8 : 7);
char *all_files;
- DEBUG2(PARSE, "%s: %s\n", __func__, file);
+ DEBUG1(PARSE, "ParseTraditionalInclude: %s\n", file);
pp_skip_whitespace(&file);
- /*
- * Substitute for any variables in the file name before trying to
- * find the thing.
- */
(void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &all_files);
/* TODO: handle errors */
- if (*file == '\0') {
- Parse_Error(PARSE_FATAL, "Filename missing from \"include\"");
- goto out;
- }
-
for (file = all_files; !done; file = cp + 1) {
/* Skip to end of line or next whitespace */
for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++)
@@ -2546,7 +2220,7 @@ ParseTraditionalInclude(char *line)
IncludeFile(file, false, false, silent);
}
-out:
+
free(all_files);
}
#endif
@@ -2559,7 +2233,7 @@ ParseGmakeExport(char *line)
char *variable = line + 6;
char *value;
- DEBUG2(PARSE, "%s: %s\n", __func__, variable);
+ DEBUG1(PARSE, "ParseGmakeExport: %s\n", variable);
pp_skip_whitespace(&variable);
@@ -2596,33 +2270,27 @@ ParseGmakeExport(char *line)
static bool
ParseEOF(void)
{
- char *ptr;
- size_t len;
- IFile *curFile = CurFile();
-
- assert(curFile->readMore != NULL);
+ IncludedFile *curFile = CurFile();
- doing_depend = curFile->depending; /* restore this */
- /* get next input buffer, if any */
- ptr = curFile->readMore(curFile->readMoreArg, &len);
- curFile->buf_ptr = ptr;
- curFile->buf_freeIt = ptr;
- curFile->buf_end = ptr == NULL ? NULL : ptr + len;
- curFile->lineno = curFile->first_lineno;
- if (ptr != NULL)
- return true; /* Iterate again */
+ doing_depend = curFile->depending;
+ if (curFile->forLoop != NULL &&
+ For_NextIteration(curFile->forLoop, &curFile->buf)) {
+ curFile->buf_ptr = curFile->buf.data;
+ curFile->buf_end = curFile->buf.data + curFile->buf.len;
+ curFile->readLines = curFile->forBodyReadLines;
+ return true;
+ }
- /* Ensure the makefile (or loop) didn't have mismatched conditionals */
+ /*
+ * Ensure the makefile (or .for loop) didn't have mismatched
+ * conditionals.
+ */
Cond_restore_depth(curFile->cond_depth);
- if (curFile->lf != NULL) {
- loadedfile_destroy(curFile->lf);
- curFile->lf = NULL;
- }
-
- /* Dispose of curFile info */
- /* Leak curFile->fname because all the GNodes have pointers to it. */
- free(curFile->buf_freeIt);
+ FStr_Done(&curFile->name);
+ Buf_Done(&curFile->buf);
+ if (curFile->forLoop != NULL)
+ ForLoop_Free(curFile->forLoop);
Vector_Pop(&includes);
if (includes.len == 0) {
@@ -2635,10 +2303,10 @@ ParseEOF(void)
}
curFile = CurFile();
- DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n",
- curFile->fname, curFile->lineno);
+ DEBUG2(PARSE, "ParseEOF: returning to file %s, line %u\n",
+ curFile->name.str, curFile->readLines + 1);
- ParseSetParseFile(curFile->fname);
+ SetParseFile(curFile->name.str);
return true;
}
@@ -2654,18 +2322,18 @@ typedef enum ParseRawLineResult {
* the line is not null-terminated.
*/
static ParseRawLineResult
-ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
- char **out_firstBackslash, char **out_firstComment)
+ParseRawLine(IncludedFile *curFile, char **out_line, char **out_line_end,
+ char **out_firstBackslash, char **out_commentLineEnd)
{
char *line = curFile->buf_ptr;
char *buf_end = curFile->buf_end;
char *p = line;
char *line_end = line;
char *firstBackslash = NULL;
- char *firstComment = NULL;
+ char *commentLineEnd = NULL;
ParseRawLineResult res = PRLR_LINE;
- curFile->lineno++;
+ curFile->readLines++;
for (;;) {
char ch;
@@ -2676,8 +2344,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
}
ch = *p;
- if (ch == '\0' ||
- (ch == '\\' && p + 1 < buf_end && p[1] == '\0')) {
+ if (ch == '\0' || (ch == '\\' && p[1] == '\0')) {
Parse_Error(PARSE_FATAL, "Zero byte read from file");
return PRLR_ERROR;
}
@@ -2687,7 +2354,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
if (firstBackslash == NULL)
firstBackslash = p;
if (p[1] == '\n') {
- curFile->lineno++;
+ curFile->readLines++;
if (p + 2 == buf_end) {
line_end = p;
*line_end = '\n';
@@ -2705,9 +2372,9 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
* Remember the first '#' for comment stripping, unless
* the previous char was '[', as in the modifier ':[#]'.
*/
- if (ch == '#' && firstComment == NULL &&
+ if (ch == '#' && commentLineEnd == NULL &&
!(p > line && p[-1] == '['))
- firstComment = line_end;
+ commentLineEnd = line_end;
p++;
if (ch == '\n')
@@ -2718,11 +2385,11 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
line_end = p;
}
- *out_line = line;
curFile->buf_ptr = p;
+ *out_line = line;
*out_line_end = line_end;
*out_firstBackslash = firstBackslash;
- *out_firstComment = firstComment;
+ *out_commentLineEnd = commentLineEnd;
return res;
}
@@ -2733,7 +2400,7 @@ ParseRawLine(IFile *curFile, char **out_line, char **out_line_end,
static void
UnescapeBackslash(char *line, char *start)
{
- char *src = start;
+ const char *src = start;
char *dst = start;
char *spaceStart = line;
@@ -2748,35 +2415,24 @@ UnescapeBackslash(char *line, char *start)
ch = *src++;
if (ch == '\0') {
- /* Delete '\\' at end of buffer */
+ /* Delete '\\' at the end of the buffer. */
dst--;
break;
}
- /* Delete '\\' from before '#' on non-command lines */
- if (ch == '#' && line[0] != '\t') {
+ /* Delete '\\' from before '#' on non-command lines. */
+ if (ch == '#' && line[0] != '\t')
*dst++ = ch;
- continue;
- }
-
- if (ch != '\n') {
- /* Leave '\\' in buffer for later */
+ else if (ch == '\n') {
+ cpp_skip_hspace(&src);
+ *dst++ = ' ';
+ } else {
+ /* Leave '\\' in the buffer for later. */
*dst++ = '\\';
- /*
- * Make sure we don't delete an escaped ' ' from the
- * line end.
- */
- spaceStart = dst + 1;
*dst++ = ch;
- continue;
+ /* Keep an escaped ' ' at the line end. */
+ spaceStart = dst;
}
-
- /*
- * Escaped '\n' -- replace following whitespace with a single
- * ' '.
- */
- pp_skip_hspace(&src);
- *dst++ = ' ';
}
/* Delete any trailing spaces - eg from empty continuations */
@@ -2785,13 +2441,13 @@ UnescapeBackslash(char *line, char *start)
*dst = '\0';
}
-typedef enum GetLineMode {
+typedef enum LineKind {
/*
* Return the next line that is neither empty nor a comment.
* Backslash line continuations are folded into a single space.
* A trailing comment, if any, is discarded.
*/
- GLM_NONEMPTY,
+ LK_NONEMPTY,
/*
* Return the next line, even if it is empty or a comment.
@@ -2800,7 +2456,7 @@ typedef enum GetLineMode {
* Used in .for loops to collect the body of the loop while waiting
* for the corresponding .endfor.
*/
- GLM_FOR_BODY,
+ LK_FOR_BODY,
/*
* Return the next line that starts with a dot.
@@ -2810,29 +2466,34 @@ typedef enum GetLineMode {
* Used in .if directives to skip over irrelevant branches while
* waiting for the corresponding .endif.
*/
- GLM_DOT
-} GetLineMode;
+ LK_DOT
+} LineKind;
-/* Return the next "interesting" logical line from the current file. */
+/*
+ * Return the next "interesting" logical line from the current file. The
+ * returned string will be freed at the end of including the file.
+ */
static char *
-ParseGetLine(GetLineMode mode)
+ReadLowLevelLine(LineKind kind)
{
- IFile *curFile = CurFile();
+ IncludedFile *curFile = CurFile();
+ ParseRawLineResult res;
char *line;
char *line_end;
char *firstBackslash;
- char *firstComment;
+ char *commentLineEnd;
for (;;) {
- ParseRawLineResult res = ParseRawLine(curFile,
- &line, &line_end, &firstBackslash, &firstComment);
+ curFile->lineno = curFile->readLines + 1;
+ res = ParseRawLine(curFile,
+ &line, &line_end, &firstBackslash, &commentLineEnd);
if (res == PRLR_ERROR)
return NULL;
- if (line_end == line || firstComment == line) {
+ if (line == line_end || line == commentLineEnd) {
if (res == PRLR_EOF)
return NULL;
- if (mode != GLM_FOR_BODY)
+ if (kind != LK_FOR_BODY)
continue;
}
@@ -2840,59 +2501,53 @@ ParseGetLine(GetLineMode mode)
assert(ch_isspace(*line_end));
*line_end = '\0';
- if (mode == GLM_FOR_BODY)
+ if (kind == LK_FOR_BODY)
return line; /* Don't join the physical lines. */
- if (mode == GLM_DOT && line[0] != '.')
+ if (kind == LK_DOT && line[0] != '.')
continue;
break;
}
- /* Brutally ignore anything after a non-escaped '#' in non-commands. */
- if (firstComment != NULL && line[0] != '\t')
- *firstComment = '\0';
-
- /* If we didn't see a '\\' then the in-situ data is fine. */
- if (firstBackslash == NULL)
- return line;
-
- /* Remove escapes from '\n' and '#' */
- UnescapeBackslash(line, firstBackslash);
-
+ if (commentLineEnd != NULL && line[0] != '\t')
+ *commentLineEnd = '\0';
+ if (firstBackslash != NULL)
+ UnescapeBackslash(line, firstBackslash);
return line;
}
static bool
-ParseSkippedBranches(void)
+SkipIrrelevantBranches(void)
{
- char *line;
+ const char *line;
- while ((line = ParseGetLine(GLM_DOT)) != NULL) {
- if (Cond_EvalLine(line) == COND_PARSE)
- break;
+ while ((line = ReadLowLevelLine(LK_DOT)) != NULL) {
+ if (Cond_EvalLine(line) == CR_TRUE)
+ return true;
/*
- * TODO: Check for typos in .elif directives
- * such as .elsif or .elseif.
+ * TODO: Check for typos in .elif directives such as .elsif
+ * or .elseif.
*
- * This check will probably duplicate some of
- * the code in ParseLine. Most of the code
- * there cannot apply, only ParseVarassign and
- * ParseDependencyLine can, and to prevent code
- * duplication, these would need to be called
- * with a flag called onlyCheckSyntax.
+ * This check will probably duplicate some of the code in
+ * ParseLine. Most of the code there cannot apply, only
+ * ParseVarassign and ParseDependencyLine can, and to prevent
+ * code duplication, these would need to be called with a
+ * flag called onlyCheckSyntax.
*
* See directive-elif.mk for details.
*/
}
- return line != NULL;
+ return false;
}
static bool
ParseForLoop(const char *line)
{
int rval;
- int firstLineno;
+ unsigned forHeadLineno;
+ unsigned bodyReadLines;
+ int forLevel;
rval = For_Eval(line);
if (rval == 0)
@@ -2900,21 +2555,22 @@ ParseForLoop(const char *line)
if (rval < 0)
return true; /* Syntax error - error printed, ignore line */
- firstLineno = CurFile()->lineno;
+ forHeadLineno = CurFile()->lineno;
+ bodyReadLines = CurFile()->readLines;
- /* Accumulate loop lines until matching .endfor */
+ /* Accumulate the loop body until the matching '.endfor'. */
+ forLevel = 1;
do {
- line = ParseGetLine(GLM_FOR_BODY);
+ line = ReadLowLevelLine(LK_FOR_BODY);
if (line == NULL) {
Parse_Error(PARSE_FATAL,
"Unexpected end of file in .for loop");
break;
}
- } while (For_Accum(line));
+ } while (For_Accum(line, &forLevel));
- For_Run(firstLineno); /* Stash each iteration as a new 'input file' */
-
- return true; /* Read next line from for-loop buffer */
+ For_Run(forHeadLineno, bodyReadLines);
+ return true;
}
/*
@@ -2924,35 +2580,30 @@ ParseForLoop(const char *line)
* leaving only variable assignments, other directives, dependency lines
* and shell commands to the caller.
*
- * Results:
- * A line without its newline and without any trailing whitespace,
- * or NULL.
+ * Return a line without trailing whitespace, or NULL for EOF. The returned
+ * string will be freed at the end of including the file.
*/
static char *
-ParseReadLine(void)
+ReadHighLevelLine(void)
{
char *line;
for (;;) {
- line = ParseGetLine(GLM_NONEMPTY);
+ line = ReadLowLevelLine(LK_NONEMPTY);
if (line == NULL)
return NULL;
if (line[0] != '.')
return line;
- /*
- * The line might be a conditional. Ask the conditional module
- * about it and act accordingly
- */
switch (Cond_EvalLine(line)) {
- case COND_SKIP:
- if (!ParseSkippedBranches())
+ case CR_FALSE: /* May also mean a syntax error. */
+ if (!SkipIrrelevantBranches())
return NULL;
continue;
- case COND_PARSE:
+ case CR_TRUE:
continue;
- case COND_INVALID: /* Not a conditional line */
+ case CR_ERROR: /* Not a conditional line */
if (ParseForLoop(line))
continue;
break;
@@ -3007,7 +2658,7 @@ ParseLine_ShellCommand(const char *p)
for (ln = targets->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
- ParseAddCmd(gn, cmd);
+ GNode_AddCommand(gn, cmd);
}
#ifdef CLEANUP
Lst_Append(&targCmds, cmd);
@@ -3033,7 +2684,7 @@ ParseDirective(char *line)
}
dir.start = cp;
- while (ch_isalpha(*cp) || *cp == '-')
+ while (ch_islower(*cp) || *cp == '-')
cp++;
dir.end = cp;
@@ -3043,47 +2694,40 @@ ParseDirective(char *line)
pp_skip_whitespace(&cp);
arg = cp;
- if (Substring_Equals(dir, "undef")) {
+ if (Substring_Equals(dir, "undef"))
Var_Undef(arg);
- return true;
- } else if (Substring_Equals(dir, "export")) {
+ else if (Substring_Equals(dir, "export"))
Var_Export(VEM_PLAIN, arg);
- return true;
- } else if (Substring_Equals(dir, "export-env")) {
+ else if (Substring_Equals(dir, "export-env"))
Var_Export(VEM_ENV, arg);
- return true;
- } else if (Substring_Equals(dir, "export-literal")) {
+ else if (Substring_Equals(dir, "export-literal"))
Var_Export(VEM_LITERAL, arg);
- return true;
- } else if (Substring_Equals(dir, "unexport")) {
+ else if (Substring_Equals(dir, "unexport"))
Var_UnExport(false, arg);
- return true;
- } else if (Substring_Equals(dir, "unexport-env")) {
+ else if (Substring_Equals(dir, "unexport-env"))
Var_UnExport(true, arg);
- return true;
- } else if (Substring_Equals(dir, "info")) {
- ParseMessage(PARSE_INFO, "info", arg);
- return true;
- } else if (Substring_Equals(dir, "warning")) {
- ParseMessage(PARSE_WARNING, "warning", arg);
- return true;
- } else if (Substring_Equals(dir, "error")) {
- ParseMessage(PARSE_FATAL, "error", arg);
- return true;
- }
- return false;
+ else if (Substring_Equals(dir, "info"))
+ HandleMessage(PARSE_INFO, "info", arg);
+ else if (Substring_Equals(dir, "warning"))
+ HandleMessage(PARSE_WARNING, "warning", arg);
+ else if (Substring_Equals(dir, "error"))
+ HandleMessage(PARSE_FATAL, "error", arg);
+ else
+ return false;
+ return true;
}
-static bool
-ParseVarassign(const char *line)
+bool
+Parse_VarAssign(const char *line, bool finishDependencyGroup, GNode *scope)
{
VarAssign var;
if (!Parse_IsVar(line, &var))
return false;
-
- FinishDependencyGroup();
- Parse_Var(&var, SCOPE_GLOBAL);
+ if (finishDependencyGroup)
+ FinishDependencyGroup();
+ Parse_Var(&var, scope);
+ free(var.varname);
return true;
}
@@ -3109,7 +2753,7 @@ FindSemicolon(char *p)
}
/*
- * dependency -> target... op [source...] [';' command]
+ * dependency -> [target...] op [source...] [';' command]
* op -> ':' | '::' | '!'
*/
static void
@@ -3122,7 +2766,7 @@ ParseDependencyLine(char *line)
/*
* For some reason - probably to make the parser impossible -
* a ';' can be used to separate commands from dependencies.
- * Attempt to avoid ';' inside substitution patterns.
+ * Attempt to skip over ';' inside substitution patterns.
*/
{
char *semicolon = FindSemicolon(line);
@@ -3150,20 +2794,22 @@ ParseDependencyLine(char *line)
* in which the middle is interpreted as a source, not a target.
*/
- /* In lint mode, allow undefined variables to appear in
- * dependency lines.
+ /*
+ * In lint mode, allow undefined variables to appear in dependency
+ * lines.
*
- * Ideally, only the right-hand side would allow undefined
- * 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.
+ * Ideally, only the right-hand side would allow undefined 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.
*
- * In normal mode, it does not matter whether undefined
- * variables are allowed or not since as of 2020-09-14,
- * 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. */
+ * In normal mode, it does not matter whether undefined variables are
+ * allowed or not since as of 2020-09-14, 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.
+ */
emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR;
(void)Var_Subst(line, SCOPE_CMDLINE, emode, &expanded_line);
/* TODO: handle errors */
@@ -3219,7 +2865,7 @@ ParseLine(char *line)
}
#endif
- if (ParseVarassign(line))
+ if (Parse_VarAssign(line, true, SCOPE_GLOBAL))
return;
FinishDependencyGroup();
@@ -3230,30 +2876,24 @@ ParseLine(char *line)
/*
* Parse a top-level makefile, incorporating its content into the global
* dependency graph.
- *
- * Input:
- * name The name of the file being read
- * fd The open file to parse; will be closed at the end
*/
void
Parse_File(const char *name, int fd)
{
- char *line; /* the line we're working on */
- struct loadedfile *lf;
+ char *line;
+ Buffer buf;
- lf = loadfile(name, fd);
+ buf = loadfile(name, fd != -1 ? fd : STDIN_FILENO);
+ if (fd != -1)
+ (void)close(fd);
assert(targets == NULL);
- if (name == NULL)
- name = "(stdin)";
-
- Parse_PushInput(name, 0, -1, loadedfile_readMore, lf);
- CurFile()->lf = lf;
+ Parse_PushInput(name, 1, 0, buf, NULL);
do {
- while ((line = ParseReadLine()) != NULL) {
- DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n",
+ while ((line = ReadHighLevelLine()) != NULL) {
+ DEBUG2(PARSE, "Parsing line %u: %s\n",
CurFile()->lineno, line);
ParseLine(line);
}
@@ -3265,9 +2905,9 @@ Parse_File(const char *name, int fd)
if (parseErrors != 0) {
(void)fflush(stdout);
(void)fprintf(stderr,
- "%s: Fatal errors encountered -- cannot continue",
+ "%s: Fatal errors encountered -- cannot continue\n",
progname);
- PrintOnError(NULL, NULL);
+ PrintOnError(NULL, "");
exit(1);
}
}
@@ -3280,7 +2920,7 @@ Parse_Init(void)
parseIncPath = SearchPath_New();
sysIncPath = SearchPath_New();
defSysIncPath = SearchPath_New();
- Vector_Init(&includes, sizeof(IFile));
+ Vector_Init(&includes, sizeof(IncludedFile));
}
/* Clean up the parsing module. */
diff --git a/contrib/bmake/str.c b/contrib/bmake/str.c
index 11e7595dd867..5c529c894300 100644
--- a/contrib/bmake/str.c
+++ b/contrib/bmake/str.c
@@ -1,4 +1,4 @@
-/* $NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $ */
+/* $NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -71,7 +71,11 @@
#include "make.h"
/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */
-MAKE_RCSID("$NetBSD: str.c,v 1.86 2021/06/21 16:59:18 rillig Exp $");
+MAKE_RCSID("$NetBSD: str.c,v 1.88 2021/12/15 10:57:01 rillig Exp $");
+
+
+static HashTable interned_strings;
+
/* Return the concatenation of s1 and s2, freshly allocated. */
char *
@@ -395,3 +399,24 @@ Str_Match(const char *str, const char *pat)
str++;
}
}
+
+void
+Str_Intern_Init(void)
+{
+ HashTable_Init(&interned_strings);
+}
+
+void
+Str_Intern_End(void)
+{
+#ifdef CLEANUP
+ HashTable_Done(&interned_strings);
+#endif
+}
+
+/* Return a canonical instance of str, with unlimited lifetime. */
+const char *
+Str_Intern(const char *str)
+{
+ return HashTable_CreateEntry(&interned_strings, str, NULL)->key;
+}
diff --git a/contrib/bmake/str.h b/contrib/bmake/str.h
index bd56a2981785..7a4047cb27c3 100644
--- a/contrib/bmake/str.h
+++ b/contrib/bmake/str.h
@@ -1,4 +1,4 @@
-/* $NetBSD: str.h,v 1.12 2021/12/12 13:43:47 rillig Exp $ */
+/* $NetBSD: str.h,v 1.15 2021/12/15 10:57:01 rillig Exp $ */
/*
Copyright (c) 2021 Roland Illig <rillig@NetBSD.org>
@@ -39,12 +39,6 @@ typedef struct FStr {
void *freeIt;
} FStr;
-/* A modifiable string that may need to be freed after use. */
-typedef struct MFStr {
- char *str;
- void *freeIt;
-} MFStr;
-
/* A read-only range of a character array, NOT null-terminated. */
typedef struct Substring {
const char *start;
@@ -111,40 +105,6 @@ FStr_Done(FStr *fstr)
}
-MAKE_INLINE MFStr
-MFStr_Init(char *str, void *freeIt)
-{
- MFStr mfstr;
- mfstr.str = str;
- mfstr.freeIt = freeIt;
- return mfstr;
-}
-
-/* Return a string that is the sole owner of str. */
-MAKE_INLINE MFStr
-MFStr_InitOwn(char *str)
-{
- return MFStr_Init(str, str);
-}
-
-/* Return a string that refers to the shared str. */
-MAKE_INLINE MFStr
-MFStr_InitRefer(char *str)
-{
- return MFStr_Init(str, NULL);
-}
-
-MAKE_INLINE void
-MFStr_Done(MFStr *mfstr)
-{
- free(mfstr->freeIt);
-#ifdef CLEANUP
- mfstr->str = NULL;
- mfstr->freeIt = NULL;
-#endif
-}
-
-
MAKE_STATIC Substring
Substring_Init(const char *start, const char *end)
{
@@ -383,3 +343,7 @@ char *str_concat2(const char *, const char *);
char *str_concat3(const char *, const char *, const char *);
bool Str_Match(const char *, const char *);
+
+void Str_Intern_Init(void);
+void Str_Intern_End(void);
+const char *Str_Intern(const char *);
diff --git a/contrib/bmake/suff.c b/contrib/bmake/suff.c
index ab7ac6184173..430dec6bafd2 100644
--- a/contrib/bmake/suff.c
+++ b/contrib/bmake/suff.c
@@ -1,4 +1,4 @@
-/* $NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $ */
+/* $NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -115,7 +115,7 @@
#include "dir.h"
/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */
-MAKE_RCSID("$NetBSD: suff.c,v 1.357 2021/12/12 20:45:48 sjg Exp $");
+MAKE_RCSID("$NetBSD: suff.c,v 1.364 2022/01/07 20:54:45 rillig Exp $");
typedef List SuffixList;
typedef ListNode SuffixListNode;
@@ -207,20 +207,26 @@ typedef struct Suffix {
typedef struct Candidate {
/* The file or node to look for. */
char *file;
- /* The prefix from which file was formed.
- * Its memory is shared among all candidates. */
+ /*
+ * The prefix from which file was formed. Its memory is shared among
+ * all candidates.
+ */
char *prefix;
/* The suffix on the file. */
Suffix *suff;
- /* The candidate that can be made from this,
- * or NULL for the top-level candidate. */
+ /*
+ * The candidate that can be made from this, or NULL for the
+ * top-level candidate.
+ */
struct Candidate *parent;
/* The node describing the file. */
GNode *node;
- /* Count of existing children, only used for memory management, so we
- * don't free this candidate too early or too late. */
+ /*
+ * Count of existing children, only used for memory management, so we
+ * don't free this candidate too early or too late.
+ */
int numChildren;
#ifdef DEBUG_SRC
CandidateList childrenList;
@@ -240,7 +246,7 @@ typedef struct CandidateSearcher {
/* TODO: Document the difference between nullSuff and emptySuff. */
-/* The NULL suffix for this run */
+/* The NULL suffix is used when a file has no known suffix */
static Suffix *nullSuff;
/* The empty suffix required for POSIX single-suffix transformation rules */
static Suffix *emptySuff;
@@ -692,7 +698,9 @@ RebuildGraph(GNode *transform, Suffix *suff)
size_t nameLen = strlen(name);
const char *toName;
- /* See if it is a transformation from this suffix to another suffix. */
+ /*
+ * See if it is a transformation from this suffix to another suffix.
+ */
toName = StrTrimPrefix(suff->name, name);
if (toName != NULL) {
Suffix *to = FindSuffixByName(toName);
@@ -702,7 +710,9 @@ RebuildGraph(GNode *transform, Suffix *suff)
}
}
- /* See if it is a transformation from another suffix to this suffix. */
+ /*
+ * See if it is a transformation from another suffix to this suffix.
+ */
toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen);
if (toName != NULL) {
Suffix *from = FindSuffixByNameLen(name,
@@ -724,17 +734,15 @@ RebuildGraph(GNode *transform, Suffix *suff)
* true iff a new main target has been selected.
*/
static bool
-UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
- bool *inout_removedMain)
+UpdateTarget(GNode *target, Suffix *suff, bool *inout_removedMain)
{
Suffix *srcSuff, *targSuff;
char *ptr;
- if (*inout_main == NULL && *inout_removedMain &&
- !(target->type & OP_NOTARGET)) {
+ if (mainNode == NULL && *inout_removedMain &&
+ GNode_IsMainCandidate(target)) {
DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name);
- *inout_main = target;
- Targ_SetMain(target);
+ mainNode = target;
/*
* XXX: Why could it be a good idea to return true here?
* The main task of this function is to turn ordinary nodes
@@ -772,13 +780,12 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
return false;
if (ParseTransform(target->name, &srcSuff, &targSuff)) {
- if (*inout_main == target) {
+ if (mainNode == target) {
DEBUG1(MAKE,
"Setting main node from \"%s\" back to null\n",
target->name);
*inout_removedMain = true;
- *inout_main = NULL;
- Targ_SetMain(NULL);
+ mainNode = NULL;
}
Lst_Done(&target->children);
Lst_Init(&target->children);
@@ -802,14 +809,14 @@ UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff,
* suffix rules.
*/
static void
-UpdateTargets(GNode **inout_main, Suffix *suff)
+UpdateTargets(Suffix *suff)
{
bool removedMain = false;
GNodeListNode *ln;
for (ln = Targ_List()->first; ln != NULL; ln = ln->next) {
GNode *gn = ln->datum;
- if (UpdateTarget(gn, inout_main, suff, &removedMain))
+ if (UpdateTarget(gn, suff, &removedMain))
break;
}
}
@@ -828,7 +835,7 @@ UpdateTargets(GNode **inout_main, Suffix *suff)
* name the name of the suffix to add
*/
void
-Suff_AddSuffix(const char *name, GNode **inout_main)
+Suff_AddSuffix(const char *name)
{
GNodeListNode *ln;
@@ -840,7 +847,7 @@ Suff_AddSuffix(const char *name, GNode **inout_main)
Lst_Append(&sufflist, suff);
DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name);
- UpdateTargets(inout_main, suff);
+ UpdateTargets(suff);
/*
* Look for any existing transformations from or to this suffix.
@@ -1197,7 +1204,9 @@ FindCmds(Candidate *targ, CandidateSearcher *cs)
base = str_basename(sgn->name);
if (strncmp(base, targ->prefix, prefLen) != 0)
continue;
- /* The node matches the prefix, see if it has a known suffix. */
+ /*
+ * The node matches the prefix, see if it has a known suffix.
+ */
suff = FindSuffixByName(base + prefLen);
if (suff == NULL)
continue;
@@ -1254,7 +1263,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn)
DEBUG1(SUFF, "%s...", cp);
gn = Targ_GetNode(cp);
- /* Add gn to the parents child list before the original child */
+ /* Insert gn before the original child. */
Lst_InsertBefore(&pgn->children, cln, gn);
Lst_Append(&gn->parents, pgn);
pgn->unmade++;
@@ -1767,8 +1776,7 @@ FindDepsRegularPath(GNode *gn, Candidate *targ)
free(gn->path);
gn->path = Dir_FindFile(gn->name,
- (targ == NULL ? &dirSearchPath :
- targ->suff->searchPath));
+ targ == NULL ? &dirSearchPath : targ->suff->searchPath);
if (gn->path == NULL)
return;
@@ -2178,7 +2186,7 @@ Suff_PrintAll(void)
}
}
-const char *
+char *
Suff_NamesStr(void)
{
Buffer buf;
diff --git a/contrib/bmake/targ.c b/contrib/bmake/targ.c
index beb038fa1a4b..52c5531845d8 100644
--- a/contrib/bmake/targ.c
+++ b/contrib/bmake/targ.c
@@ -1,4 +1,4 @@
-/* $NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $ */
+/* $NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -78,10 +78,8 @@
*
* Targ_List Return the list of all targets so far.
*
- * GNode_New Create a new GNode for the passed target
- * (string). The node is *not* placed in the
- * hash table, though all its fields are
- * initialized.
+ * GNode_New Create a new GNode with the given name, don't add it
+ * to allNodes.
*
* Targ_FindNode Find the node, or return NULL.
*
@@ -93,9 +91,6 @@
* Targ_FindList Given a list of names, find nodes for all
* of them, creating them as necessary.
*
- * Targ_Precious Return true if the target is precious and
- * should not be removed if we are interrupted.
- *
* Targ_Propagate Propagate information between related nodes.
* Should be called after the makefiles are parsed
* but before any action is taken.
@@ -103,8 +98,7 @@
* Debugging:
* Targ_PrintGraph
* Print out the entire graph, all variables and
- * statistics for the directory cache. Should print
- * something for suffixes, too, but...
+ * statistics for the directory cache.
*/
#include <time.h>
@@ -113,7 +107,7 @@
#include "dir.h"
/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: targ.c,v 1.173 2021/11/28 19:51:06 rillig Exp $");
+MAKE_RCSID("$NetBSD: targ.c,v 1.176 2022/01/07 20:50:35 rillig Exp $");
/*
* All target nodes that appeared on the left-hand side of one of the
@@ -346,27 +340,6 @@ Targ_FindList(GNodeList *gns, StringList *names)
}
}
-/* See if the given target is precious. */
-bool
-Targ_Precious(const GNode *gn)
-{
- /* XXX: Why are '::' targets precious? */
- return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP);
-}
-
-/*
- * The main target to be made; only for debugging output.
- * See mainNode in parse.c for the definitive source.
- */
-static GNode *mainTarg;
-
-/* Remember the main target to make; only used for debugging. */
-void
-Targ_SetMain(GNode *gn)
-{
- mainTarg = gn;
-}
-
static void
PrintNodeNames(GNodeList *gnodes)
{
@@ -508,7 +481,7 @@ Targ_PrintNode(GNode *gn, int pass)
return;
debug_printf("#\n");
- if (gn == mainTarg)
+ if (gn == mainNode)
debug_printf("# *** MAIN TARGET ***\n");
if (pass >= 2) {
@@ -556,7 +529,6 @@ Targ_PrintNodes(GNodeList *gnodes, int pass)
Targ_PrintNode(ln->datum, pass);
}
-/* Print only those targets that are just a source. */
static void
PrintOnlySources(void)
{
diff --git a/contrib/bmake/trace.c b/contrib/bmake/trace.c
index 8e2c507d14dc..b48f02296cdf 100644
--- a/contrib/bmake/trace.c
+++ b/contrib/bmake/trace.c
@@ -1,4 +1,4 @@
-/* $NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $ */
+/* $NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $ */
/*
* Copyright (c) 2000 The NetBSD Foundation, Inc.
@@ -48,7 +48,7 @@
#include "job.h"
#include "trace.h"
-MAKE_RCSID("$NetBSD: trace.c,v 1.29 2021/09/21 23:06:18 rillig Exp $");
+MAKE_RCSID("$NetBSD: trace.c,v 1.31 2022/02/05 00:26:21 rillig Exp $");
static FILE *trfile;
static pid_t trpid;
@@ -69,8 +69,10 @@ Trace_Init(const char *pathname)
if (pathname != NULL) {
FStr curDir;
trpid = getpid();
- /* XXX: This variable may get overwritten later, which
- * would make trwd point to undefined behavior. */
+ /*
+ * XXX: This variable may get overwritten later, which would
+ * make trwd point to undefined behavior.
+ */
curDir = Var_Value(SCOPE_GLOBAL, ".CURDIR");
trwd = curDir.str;
@@ -88,10 +90,17 @@ Trace_Log(TrEvent event, Job *job)
gettimeofday(&rightnow, NULL);
+#if __STDC__ >= 199901L
fprintf(trfile, "%lld.%06ld %d %s %d %s",
(long long)rightnow.tv_sec, (long)rightnow.tv_usec,
jobTokensRunning,
evname[event], trpid, trwd);
+#else
+ fprintf(trfile, "%ld.%06ld %d %s %d %s",
+ (long)rightnow.tv_sec, (long)rightnow.tv_usec,
+ jobTokensRunning,
+ evname[event], trpid, trwd);
+#endif
if (job != NULL) {
char flags[4];
diff --git a/contrib/bmake/unit-tests/Makefile b/contrib/bmake/unit-tests/Makefile
index 98ed3907cd5a..2a300133eccc 100644
--- a/contrib/bmake/unit-tests/Makefile
+++ b/contrib/bmake/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.164 2021/12/12 22:50:00 sjg Exp $
+# $Id: Makefile,v 1.171 2022/01/28 21:33:18 sjg Exp $
#
-# $NetBSD: Makefile,v 1.288 2021/12/12 22:16:48 rillig Exp $
+# $NetBSD: Makefile,v 1.302 2022/01/27 21:50:50 sjg Exp $
#
# Unit tests for make(1)
#
@@ -91,8 +91,10 @@ TESTS+= dep-colon
TESTS+= dep-colon-bug-cross-file
TESTS+= dep-double-colon
TESTS+= dep-double-colon-indep
+TESTS+= dep-duplicate
TESTS+= dep-exclam
TESTS+= dep-none
+TESTS+= dep-op-missing
TESTS+= dep-percent
TESTS+= dep-var
TESTS+= dep-wildcards
@@ -189,7 +191,6 @@ TESTS+= directive-warning
TESTS+= dollar
TESTS+= doterror
TESTS+= dotwait
-TESTS+= envfirst
TESTS+= error
TESTS+= # escape # broken by reverting POSIX changes
TESTS+= export
@@ -216,8 +217,6 @@ TESTS+= meta-cmd-cmp
TESTS+= moderrs
TESTS+= modmatch
TESTS+= modmisc
-TESTS+= modts
-TESTS+= modword
.if ${.MAKE.UID} > 0
TESTS+= objdir-writable
.endif
@@ -273,10 +272,12 @@ TESTS+= opt-touch-jobs
TESTS+= opt-tracefile
TESTS+= opt-var-expanded
TESTS+= opt-var-literal
+TESTS+= opt-version
TESTS+= opt-warnings-as-errors
TESTS+= opt-where-am-i
TESTS+= opt-x-reduce-exported
TESTS+= order
+TESTS+= parse
TESTS+= parse-var
TESTS+= phony-end
TESTS+= posix
@@ -319,12 +320,12 @@ TESTS+= ternary
TESTS+= unexport
TESTS+= unexport-env
TESTS+= use-inference
-TESTS+= var-class
-TESTS+= var-class-cmdline
-TESTS+= var-class-env
-TESTS+= var-class-global
-TESTS+= var-class-local
-TESTS+= var-class-local-legacy
+TESTS+= var-scope
+TESTS+= var-scope-cmdline
+TESTS+= var-scope-env
+TESTS+= var-scope-global
+TESTS+= var-scope-local
+TESTS+= var-scope-local-legacy
TESTS+= var-eval-short
TESTS+= var-op
TESTS+= var-op-append
@@ -340,6 +341,7 @@ TESTS+= varfind
TESTS+= varmisc
TESTS+= varmod
TESTS+= varmod-assign
+TESTS+= varmod-assign-shell
TESTS+= varmod-defined
TESTS+= varmod-edge
TESTS+= varmod-exclam-shell
@@ -498,7 +500,6 @@ ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of
# settings FLAGS.test=-dv here, since that is closer to the test code.
FLAGS.cond-func-make= via-cmdline
-FLAGS.directive-ifmake= first second
FLAGS.doterror= # none, especially not -k
FLAGS.jobs-error-indirect= # none, especially not -k
FLAGS.jobs-error-nested= # none, especially not -k
@@ -531,6 +532,7 @@ SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \
SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1}
SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2}
SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3}
+SED_CMDS.opt-debug-hash= -e 's,\(numEntries\)=[1-9][0-9],\1=<entries>,'
SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(<pid>),'
SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid <pid>,'
SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process <pid>,'
@@ -541,6 +543,7 @@ SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: <shell>\) -q,\1,'
SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex}
SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output}
SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output}
+SED_CMDS.opt-where-am-i= -e '/usr.obj/d'
# For Compat_RunCommand, useShell == false.
SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,<not found: ...>,'
# For Compat_RunCommand, useShell == true.
@@ -736,6 +739,9 @@ _SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,'
_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
# replace anything after 'stopped in' with unit-tests
_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
+# Allow the test files to be placed anywhere.
+_SED_CMDS+= -e 's,\(\.PARSEDIR}\) = `'"/[^']*'"',\1 = <some-dir>,'
+_SED_CMDS+= -e 's,\(\.INCLUDEDFROMDIR}\) = `'"/[^']*'"',\1 = <some-dir>,'
_SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g'
# canonicalize ${.OBJDIR} and ${.CURDIR}
.if ${.OBJDIR} != ${.CURDIR}
diff --git a/contrib/bmake/unit-tests/comment.mk b/contrib/bmake/unit-tests/comment.mk
index d4fb041104a7..2471c8cf659d 100644
--- a/contrib/bmake/unit-tests/comment.mk
+++ b/contrib/bmake/unit-tests/comment.mk
@@ -1,4 +1,4 @@
-# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: comment.mk,v 1.4 2022/01/23 18:00:53 rillig Exp $
#
# Demonstrate how comments are written in makefiles.
@@ -15,7 +15,9 @@ on and on.
# Comments can be indented with spaces, but that is rather unusual.
# Comments can be indented with a tab.
- # These are not shell commands, they are just makefile comments.
+ # Since parse.c 1.127 from 2007-01-01, these are not shell commands,
+ # they are just makefile comments. Before that commit, these comments
+ # triggered the error message "Unassociated shell command".
.if 1 # There can be comments after conditions.
.endif # And after the closing directive.
diff --git a/contrib/bmake/unit-tests/cond-func-empty.mk b/contrib/bmake/unit-tests/cond-func-empty.mk
index 25c850d23d93..24cb7a680b2a 100644
--- a/contrib/bmake/unit-tests/cond-func-empty.mk
+++ b/contrib/bmake/unit-tests/cond-func-empty.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-empty.mk,v 1.16 2021/12/11 10:41:31 rillig Exp $
+# $NetBSD: cond-func-empty.mk,v 1.17 2021/12/28 22:13:56 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness.
@@ -189,5 +189,16 @@ VARNAME= ${VARNAME${:U1}}
.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2})
.endif
-all:
- @:;
+
+# If the word 'empty' is not followed by '(', it is not a function call but an
+# ordinary bare word. This bare word is interpreted as 'defined(empty)', and
+# since there is no variable named 'empty', the condition evaluates to false.
+.if empty
+. error
+.endif
+
+empty= # defined but empty
+.if empty
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/cond-func.exp b/contrib/bmake/unit-tests/cond-func.exp
index 8dc0f821a255..d0663ea68647 100644
--- a/contrib/bmake/unit-tests/cond-func.exp
+++ b/contrib/bmake/unit-tests/cond-func.exp
@@ -2,11 +2,11 @@ make: "cond-func.mk" line 36: Missing closing parenthesis for defined()
make: "cond-func.mk" line 51: Missing closing parenthesis for defined()
make: "cond-func.mk" line 54: Missing closing parenthesis for defined()
make: "cond-func.mk" line 94: The empty variable is never defined.
-make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...).
-make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...).
-make: "cond-func.mk" line 119: Symbols may start with a function name.
-make: "cond-func.mk" line 124: Symbols may start with a function name.
-make: "cond-func.mk" line 130: Missing closing parenthesis for defined()
+make: "cond-func.mk" line 103: A plain function name is parsed as defined(...).
+make: "cond-func.mk" line 110: A plain function name is parsed as defined(...).
+make: "cond-func.mk" line 120: Symbols may start with a function name.
+make: "cond-func.mk" line 125: Symbols may start with a function name.
+make: "cond-func.mk" line 131: Missing closing parenthesis for defined()
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-func.mk b/contrib/bmake/unit-tests/cond-func.mk
index 4ff43b72ef88..959367f5c6ab 100644
--- a/contrib/bmake/unit-tests/cond-func.mk
+++ b/contrib/bmake/unit-tests/cond-func.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cond-func.mk,v 1.11 2022/01/07 19:30:17 rillig Exp $
#
# Tests for those parts of the functions in .if conditions that are common
# among several functions.
@@ -94,19 +94,20 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces
. info The empty variable is never defined.
.endif
-# The plain word 'defined' is interpreted as '!empty(defined)'.
+# The plain word 'defined' is interpreted as 'defined(defined)', see
+# CondParser_ComparisonOrLeaf.
# That variable is not defined (yet).
.if defined
. error
.else
-. info A plain function name is parsed as !empty(...).
+. info A plain function name is parsed as defined(...).
.endif
-# If a variable named 'defined' is actually defined and not empty, the plain
-# symbol 'defined' evaluates to true.
-defined= non-empty
+# If a variable named 'defined' is actually defined, the bare word 'defined'
+# is interpreted as 'defined(defined)', and the condition evaluates to true.
+defined= # defined but empty
.if defined
-. info A plain function name is parsed as !empty(...).
+. info A plain function name is parsed as defined(...).
.else
. error
.endif
@@ -119,7 +120,7 @@ defined= non-empty
. info Symbols may start with a function name.
.endif
-defined-var= non-empty
+defined-var= # defined but empty
.if defined-var
. info Symbols may start with a function name.
.else
@@ -132,6 +133,3 @@ defined-var= non-empty
.else
. error
.endif
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.exp b/contrib/bmake/unit-tests/cond-op-parentheses.exp
index b44093304100..63f7b19570b5 100644
--- a/contrib/bmake/unit-tests/cond-op-parentheses.exp
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.exp
@@ -1,6 +1,7 @@
-make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112.
-make: "cond-op-parentheses.mk" line 19: Malformed conditional (()
-make: "cond-op-parentheses.mk" line 29: Malformed conditional ())
+make: "cond-op-parentheses.mk" line 19: String comparison operator must be either == or !=
+make: "cond-op-parentheses.mk" line 22: Malformed conditional ((3) > 2)
+make: "cond-op-parentheses.mk" line 40: Malformed conditional (()
+make: "cond-op-parentheses.mk" line 53: Malformed conditional ())
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.mk b/contrib/bmake/unit-tests/cond-op-parentheses.mk
index ca288cad5826..b790f8bec330 100644
--- a/contrib/bmake/unit-tests/cond-op-parentheses.mk
+++ b/contrib/bmake/unit-tests/cond-op-parentheses.mk
@@ -1,8 +1,26 @@
-# $NetBSD: cond-op-parentheses.mk,v 1.4 2021/01/19 17:49:13 rillig Exp $
+# $NetBSD: cond-op-parentheses.mk,v 1.5 2022/01/22 21:50:41 rillig Exp $
#
-# Tests for parentheses in .if conditions.
+# Tests for parentheses in .if conditions, which group expressions to override
+# the precedence of the operators '!', '&&' and '||'. Parentheses cannot be
+# used to form arithmetic expressions such as '(3+4)' though.
-# TODO: Implementation
+# Contrary to the C family of programming languages, the outermost condition
+# does not have to be enclosed in parentheses.
+.if defined(VAR)
+. error
+.elif !1
+. error
+.endif
+
+# Parentheses cannot enclose numbers as there is no need for it. Make does
+# not implement any arithmetic functions in its condition parser. If
+# absolutely necessary, use expr(1).
+# expect+1: String comparison operator must be either == or !=
+.if 3 > (2)
+.endif
+# expect+1: Malformed conditional ((3) > 2)
+.if (3) > 2
+.endif
# Test for deeply nested conditions.
.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \
@@ -10,7 +28,10 @@
1 \
)))))))))))))))))))))))))))))))))))))))))))))))))))))))) \
))))))))))))))))))))))))))))))))))))))))))))))))))))))))
-. info Parentheses can be nested at least to depth 112.
+# Parentheses can be nested at least to depth 112. There is nothing special
+# about this number though, much higher numbers work as well, at least on
+# NetBSD. The actual limit depends on the allowed call stack depth for C code
+# of the platform. Anyway, 112 should be enough for all practical purposes.
.else
. error
.endif
@@ -24,8 +45,11 @@
# An unbalanced closing parenthesis is a parse error.
#
-# As of 2021-01-19, CondParser_Term returned TOK_RPAREN even though this
-# function promised to only ever return TOK_TRUE, TOK_FALSE or TOK_ERROR.
+# Before cond.c 1.237 from 2021-01-19, CondParser_Term returned TOK_RPAREN
+# even though the documentation of that function promised to only ever return
+# TOK_TRUE, TOK_FALSE or TOK_ERROR. In cond.c 1.241, the return type of that
+# function was changed to a properly restricted enum type, to prevent this bug
+# from occurring again.
.if )
. error
.else
diff --git a/contrib/bmake/unit-tests/cond-short.mk b/contrib/bmake/unit-tests/cond-short.mk
index d41c38488fd6..f4e8f87043b5 100644
--- a/contrib/bmake/unit-tests/cond-short.mk
+++ b/contrib/bmake/unit-tests/cond-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-short.mk,v 1.18 2021/12/12 09:49:09 rillig Exp $
+# $NetBSD: cond-short.mk,v 1.19 2021/12/27 18:54:19 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@@ -14,14 +14,14 @@
# relevant variable expressions was that in the irrelevant variable
# expressions, undefined variables were allowed. This allowed for conditions
# like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an
-# error message 'Malformed conditional', but it still evaluated the
-# expression, even though the expression was irrelevant.
+# error message 'Malformed conditional', but the irrelevant expression was
+# still evaluated.
#
# Since the initial commit on 1993-03-21, the manual page has been saying that
# make 'will only evaluate a conditional as far as is necessary to determine',
# but that was wrong. The code in cond.c 1.1 from 1993-03-21 looks good since
# it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the
-# definition of Var_Parse does not call the third parameter 'doEval', as would
+# definition of Var_Parse did not call the third parameter 'doEval', as would
# be expected, but instead 'err', accompanied by the comment 'TRUE if
# undefined variables are an error'. This subtle difference between 'do not
# evaluate at all' and 'allow undefined variables' led to the unexpected
@@ -122,7 +122,9 @@ VAR= # empty again, for the following tests
.if 0 || empty(${echo "expected or empty" 1>&2 :L:sh})
.endif
-# Unreachable nested conditions are skipped completely as well.
+# Unreachable nested conditions are skipped completely as well. These skipped
+# lines may even contain syntax errors. This allows to skip syntactically
+# incompatible new features in older versions of make.
.if 0
. if ${echo "unexpected nested and" 1>&2 :L:sh}
diff --git a/contrib/bmake/unit-tests/cond-token-number.exp b/contrib/bmake/unit-tests/cond-token-number.exp
index b5bfaf95d575..f078cb007323 100644
--- a/contrib/bmake/unit-tests/cond-token-number.exp
+++ b/contrib/bmake/unit-tests/cond-token-number.exp
@@ -2,7 +2,7 @@ make: "cond-token-number.mk" line 15: Malformed conditional (-0)
make: "cond-token-number.mk" line 25: Malformed conditional (+0)
make: "cond-token-number.mk" line 35: Malformed conditional (!-1)
make: "cond-token-number.mk" line 45: Malformed conditional (!+1)
-make: "cond-token-number.mk" line 80: End of the tests.
+make: "cond-token-number.mk" line 89: End of the tests.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-token-number.mk b/contrib/bmake/unit-tests/cond-token-number.mk
index 93e2646a60eb..eef528f4b7c6 100644
--- a/contrib/bmake/unit-tests/cond-token-number.mk
+++ b/contrib/bmake/unit-tests/cond-token-number.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $
+# $NetBSD: cond-token-number.mk,v 1.7 2022/01/02 02:57:39 rillig Exp $
#
# Tests for number tokens in .if conditions.
#
@@ -69,13 +69,22 @@
. error
.endif
-# This is not a hexadecimal number, even though it has an x.
-# It is interpreted as a string instead, effectively meaning defined(3x4).
+# This is not a hexadecimal number, even though it has an x. It is
+# interpreted as a string instead. In a plain '.if', such a token evaluates
+# to true if it is non-empty. In other '.if' directives, such a token is
+# evaluated by either FuncDefined or FuncMake.
.if 3x4
.else
. error
.endif
+# Make can do radix conversion from hex.
+HEX= dead
+.if 0x${HEX} == 57005
+.else
+. error
+.endif
+
# Ensure that parsing continues until here.
.info End of the tests.
diff --git a/contrib/bmake/unit-tests/cond-token-plain.exp b/contrib/bmake/unit-tests/cond-token-plain.exp
index 8afde2d41788..b39e952bf3d0 100644
--- a/contrib/bmake/unit-tests/cond-token-plain.exp
+++ b/contrib/bmake/unit-tests/cond-token-plain.exp
@@ -27,7 +27,7 @@ lhs = "var&&name", rhs = "var&&name", op = !=
CondParser_Eval: ${:Uvar}||name != "var||name"
lhs = "var||name", rhs = "var||name", op = !=
CondParser_Eval: bare
-make: "cond-token-plain.mk" line 106: A bare word is treated like defined(...), and the variable 'bare' is not defined.
+make: "cond-token-plain.mk" line 105: A bare word is treated like defined(...), and the variable 'bare' is not defined.
CondParser_Eval: VAR
make: "cond-token-plain.mk" line 111: A bare word is treated like defined(...).
CondParser_Eval: V${:UA}R
@@ -56,6 +56,7 @@ CondParser_Eval: 0
make: "cond-token-plain.mk" line 201: Malformed conditional (${0:?:} || left == right)
CondParser_Eval: left == right || ${0:?:}
make: "cond-token-plain.mk" line 206: Malformed conditional (left == right || ${0:?:})
+make: "cond-token-plain.mk" line 225: Malformed conditional (VAR.${IF_COUNT::+=1} != "")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/cond-token-plain.mk b/contrib/bmake/unit-tests/cond-token-plain.mk
index 3e59f48bc3c7..1e9f30be9153 100644
--- a/contrib/bmake/unit-tests/cond-token-plain.mk
+++ b/contrib/bmake/unit-tests/cond-token-plain.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-token-plain.mk,v 1.14 2021/12/12 09:36:00 rillig Exp $
+# $NetBSD: cond-token-plain.mk,v 1.15 2021/12/30 02:14:55 rillig Exp $
#
# Tests for plain tokens (that is, string literals without quotes)
# in .if conditions. These are also called bare words.
@@ -209,5 +209,48 @@ ${:U\\\\}= backslash
# See cond-token-string.mk for similar tests where the condition is enclosed
# in "quotes".
-all:
- @:;
+.MAKEFLAGS: -d0
+
+
+# As of cond.c 1.320 from 2021-12-30, the code in CondParser_ComparisonOrLeaf
+# looks suspicious of evaluating the expression twice: first for parsing a
+# bare word and second for parsing the left-hand side of a comparison.
+#
+# In '.if' directives, the left-hand side of a comparison must not be a bare
+# word though, and this keeps CondParser_Leaf from evaluating the expression
+# for the second time. The right-hand side of a comparison may be a bare
+# word, but that side has no risk of being parsed more than once.
+#
+# expect+1: Malformed conditional (VAR.${IF_COUNT::+=1} != "")
+.if VAR.${IF_COUNT::+=1} != ""
+. error
+.else
+. error
+.endif
+.if ${IF_COUNT} != "1"
+. error
+.endif
+
+# A different situation is when CondParser.leftUnquotedOK is true. This
+# situation arises in expressions of the form ${cond:?yes:no}. As of
+# 2021-12-30, the condition in such an expression is evaluated before parsing
+# the condition, see varmod-ifelse.mk. To pass a variable expression to the
+# condition parser, it needs to be escaped. This rarely happens in practice,
+# in most cases the conditions are simple enough that it doesn't matter
+# whether the condition is first evaluated and then parsed, or vice versa.
+# A half-baked attempt at hiding this implementation detail is
+# CondParser.leftUnquotedOK, but that is a rather leaky abstraction.
+
+#.MAKEFLAGS: -dcv
+COND= VAR.$${MOD_COUNT::+=1}
+.if ${${COND} == "VAR.":?yes:no} != "yes"
+. error
+.endif
+
+# The value "1 1" demonstrates that the expression ${MOD_COUNT::+=1} was
+# evaluated twice. In practice, expressions that occur in conditions do not
+# have side effects, making this problem rather academic, but it is there.
+.if ${MOD_COUNT} != "1 1"
+. error
+.endif
+#.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/cond-token-string.exp b/contrib/bmake/unit-tests/cond-token-string.exp
index 45f9993457d3..1c0f086b464e 100644
--- a/contrib/bmake/unit-tests/cond-token-string.exp
+++ b/contrib/bmake/unit-tests/cond-token-string.exp
@@ -6,9 +6,9 @@ make: "cond-token-string.mk" line 37: Expected.
CondParser_Eval: "UNDEF"
make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty.
CondParser_Eval: " "
-make: "cond-token-string.mk" line 55: The string literal " " is not empty, even though it consists of whitespace only.
+make: "cond-token-string.mk" line 54: The string literal " " is not empty, even though it consists of whitespace only.
CondParser_Eval: "${UNDEF}"
-make: "cond-token-string.mk" line 64: An undefined variable in quotes expands to an empty string, which then evaluates to false.
+make: "cond-token-string.mk" line 63: An undefined variable in quotes expands to an empty string, which then evaluates to false.
CondParser_Eval: "${:Uvalue}"
make: "cond-token-string.mk" line 68: A nonempty variable expression evaluates to true.
CondParser_Eval: "${:U}"
diff --git a/contrib/bmake/unit-tests/dep-duplicate.exp b/contrib/bmake/unit-tests/dep-duplicate.exp
new file mode 100644
index 000000000000..039145f8fd97
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-duplicate.exp
@@ -0,0 +1,4 @@
+make: "dep-duplicate.inc" line 1: warning: duplicate script for target "all" ignored
+make: "dep-duplicate.main" line 3: warning: using previous script for "all" defined here
+main-output
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-duplicate.mk b/contrib/bmake/unit-tests/dep-duplicate.mk
new file mode 100644
index 000000000000..6f64ba1c1981
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-duplicate.mk
@@ -0,0 +1,27 @@
+# $NetBSD: dep-duplicate.mk,v 1.3 2022/01/20 19:24:53 rillig Exp $
+#
+# Test for a target whose commands are defined twice. This generates a
+# warning, not an error, so ensure that the correct commands are kept.
+#
+# Also ensure that the diagnostics mention the correct file in case of
+# included files. Since parse.c 1.231 from 2018-12-22 and before parse.c
+# 1.653 from 2022-01-20, the wrong filename had been printed if the file of
+# the first commands section was included by its relative path.
+
+all: .PHONY
+ @exec > dep-duplicate.main; \
+ echo '# empty line 1'; \
+ echo '# empty line 2'; \
+ echo 'all:; @echo main-output'; \
+ echo '.include "dep-duplicate.inc"'
+
+ @exec > dep-duplicate.inc; \
+ echo 'all:; @echo inc-output'
+
+ # The main file must be specified using a relative path, just like the
+ # default 'makefile' or 'Makefile', to produce the same result when
+ # run via ATF or 'make test'.
+ @${MAKE} -r -f dep-duplicate.main
+
+ @rm -f dep-duplicate.main
+ @rm -f dep-duplicate.inc
diff --git a/contrib/bmake/unit-tests/dep-op-missing.exp b/contrib/bmake/unit-tests/dep-op-missing.exp
new file mode 100644
index 000000000000..58b9be353eaa
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-op-missing.exp
@@ -0,0 +1,4 @@
+make: "dep-op-missing.tmp" line 1: Invalid line type
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 0
diff --git a/contrib/bmake/unit-tests/dep-op-missing.mk b/contrib/bmake/unit-tests/dep-op-missing.mk
new file mode 100644
index 000000000000..2079f3c19871
--- /dev/null
+++ b/contrib/bmake/unit-tests/dep-op-missing.mk
@@ -0,0 +1,13 @@
+# $NetBSD: dep-op-missing.mk,v 1.1 2021/12/14 00:02:57 rillig Exp $
+#
+# Test for a missing dependency operator, in a line with trailing whitespace.
+
+# Before parse.c 1.578 from 2021-12-14, there was some unreachable error
+# handling code in ParseDependencyOp. This test tried to reach it and failed.
+# To reach that point, there would have to be trailing whitespace in the line,
+# but that is removed in an early stage of parsing.
+
+all: .PHONY
+ @printf 'target ' > dep-op-missing.tmp
+ @${MAKE} -r -f dep-op-missing.tmp || exit 0
+ @rm dep-op-missing.tmp
diff --git a/contrib/bmake/unit-tests/dep-wildcards.exp b/contrib/bmake/unit-tests/dep-wildcards.exp
index fb8a44e2c80a..45eafb5d2693 100644
--- a/contrib/bmake/unit-tests/dep-wildcards.exp
+++ b/contrib/bmake/unit-tests/dep-wildcards.exp
@@ -2,8 +2,10 @@ dep-colon-bug-cross-file.mk
dep-colon.mk
dep-double-colon-indep.mk
dep-double-colon.mk
+dep-duplicate.mk
dep-exclam.mk
dep-none.mk
+dep-op-missing.mk
dep-percent.mk
dep-var.mk
dep-wildcards.mk
diff --git a/contrib/bmake/unit-tests/dep.exp b/contrib/bmake/unit-tests/dep.exp
index 39a9383953dd..6b7f0fabb12b 100644
--- a/contrib/bmake/unit-tests/dep.exp
+++ b/contrib/bmake/unit-tests/dep.exp
@@ -1 +1,5 @@
-exit status 0
+make: "dep.mk" line 11: Inconsistent operator for only-colon
+make: "dep.mk" line 13: Inconsistent operator for only-colon
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/dep.mk b/contrib/bmake/unit-tests/dep.mk
index b2463dfc6458..54566d43d2a1 100644
--- a/contrib/bmake/unit-tests/dep.mk
+++ b/contrib/bmake/unit-tests/dep.mk
@@ -1,8 +1,18 @@
-# $NetBSD: dep.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: dep.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
# Tests for dependency declarations, such as "target: sources".
-# TODO: Implementation
+.MAIN: all
+
+# As soon as a target is defined using one of the dependency operators, it is
+# restricted to this dependency operator and cannot use the others anymore.
+only-colon:
+# expect+1: Inconsistent operator for only-colon
+only-colon!
+# expect+1: Inconsistent operator for only-colon
+only-colon::
+# Ensure that the target still has the original operator. If it hadn't, there
+# would be another error message.
+only-colon:
all:
- @:;
diff --git a/contrib/bmake/unit-tests/depsrc-meta.exp b/contrib/bmake/unit-tests/depsrc-meta.exp
index 77e27582f7da..6f17dcf0ba8a 100644
--- a/contrib/bmake/unit-tests/depsrc-meta.exp
+++ b/contrib/bmake/unit-tests/depsrc-meta.exp
@@ -2,4 +2,6 @@ Skipping meta for actual-test: no commands
Skipping meta for .END: .SPECIAL
Targets from meta mode:
| TARGET depsrc-meta-target
+Targets from meta mode in jobs mode:
+| TARGET depsrc-meta-target
exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc-meta.mk b/contrib/bmake/unit-tests/depsrc-meta.mk
index d41aad9a9c96..2c7d63a5f2a2 100644
--- a/contrib/bmake/unit-tests/depsrc-meta.mk
+++ b/contrib/bmake/unit-tests/depsrc-meta.mk
@@ -1,31 +1,30 @@
-# $NetBSD: depsrc-meta.mk,v 1.4 2020/11/27 08:39:07 rillig Exp $
+# $NetBSD: depsrc-meta.mk,v 1.6 2022/01/26 22:47:03 rillig Exp $
#
# Tests for the special source .META in dependency declarations.
# TODO: Implementation
# TODO: Explanation
-.if make(actual-test)
+.MAIN: all
+.if make(actual-test)
.MAKEFLAGS: -dM
.MAKE.MODE= meta curDirOk=true
+.endif
actual-test: depsrc-meta-target
depsrc-meta-target: .META
@> ${.TARGET}-file
@rm -f ${.TARGET}-file
-.elif make(check-results)
-
check-results:
- @echo 'Targets from meta mode:'
+ @echo 'Targets from meta mode${.MAKE.JOBS:D in jobs mode}:'
@awk '/^TARGET/ { print "| " $$0 }' depsrc-meta-target.meta
@rm depsrc-meta-target.meta
-.else
-
all:
- @${MAKE} -f ${MAKEFILE} actual-test
- @${MAKE} -f ${MAKEFILE} check-results
+ @${MAKE} -r -f ${MAKEFILE} actual-test
+ @${MAKE} -r -f ${MAKEFILE} check-results
-.endif
+ @${MAKE} -r -f ${MAKEFILE} actual-test -j1
+ @${MAKE} -r -f ${MAKEFILE} check-results -j1
diff --git a/contrib/bmake/unit-tests/depsrc-use.mk b/contrib/bmake/unit-tests/depsrc-use.mk
index 17836cd39e23..3f73a5f04ad9 100644
--- a/contrib/bmake/unit-tests/depsrc-use.mk
+++ b/contrib/bmake/unit-tests/depsrc-use.mk
@@ -1,8 +1,13 @@
-# $NetBSD: depsrc-use.mk,v 1.4 2020/08/22 12:30:57 rillig Exp $
+# $NetBSD: depsrc-use.mk,v 1.5 2021/12/28 14:22:51 rillig Exp $
#
# Tests for the special source .USE in dependency declarations,
# which allows to append common commands to other targets.
+# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally
+# regarded as a candidate for the main target. On the other hand, a .USE
+# target was not.
+not-a-main-candidate: .USE
+
all: action directly
first: .USE
diff --git a/contrib/bmake/unit-tests/depsrc-usebefore.mk b/contrib/bmake/unit-tests/depsrc-usebefore.mk
index 001cfb0d71c1..58b3145e4f3f 100644
--- a/contrib/bmake/unit-tests/depsrc-usebefore.mk
+++ b/contrib/bmake/unit-tests/depsrc-usebefore.mk
@@ -1,4 +1,4 @@
-# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: depsrc-usebefore.mk,v 1.7 2021/12/28 14:22:51 rillig Exp $
#
# Tests for the special source .USEBEFORE in dependency declarations,
# which allows to prepend common commands to other targets.
@@ -7,6 +7,11 @@
# .USE
# depsrc-use.mk
+# Before make.h 1.280 from 2021-12-28, a .USEBEFORE target was accidentally
+# regarded as a candidate for the main target. On the other hand, a .USE
+# target was not.
+not-a-main-candidate: .USEBEFORE
+
all: action directly
first: .USEBEFORE
diff --git a/contrib/bmake/unit-tests/depsrc.exp b/contrib/bmake/unit-tests/depsrc.exp
index 06165e6f9ac4..147ea8b24371 100644
--- a/contrib/bmake/unit-tests/depsrc.exp
+++ b/contrib/bmake/unit-tests/depsrc.exp
@@ -1,4 +1,5 @@
: 'Undefined variables are expanded directly in the dependency'
: 'declaration. They are not preserved and maybe expanded later.'
: 'This is in contrast to local variables such as ${.TARGET}.'
+: Making .UNKNOWN from nothing.
exit status 0
diff --git a/contrib/bmake/unit-tests/depsrc.mk b/contrib/bmake/unit-tests/depsrc.mk
index ab9d04c1d3a4..4e5752c97184 100644
--- a/contrib/bmake/unit-tests/depsrc.mk
+++ b/contrib/bmake/unit-tests/depsrc.mk
@@ -1,7 +1,7 @@
-# $NetBSD: depsrc.mk,v 1.4 2020/12/22 19:38:44 rillig Exp $
+# $NetBSD: depsrc.mk,v 1.5 2021/12/13 23:38:54 rillig Exp $
#
# Tests for special sources (those starting with a dot, followed by
-# uppercase letters) in dependency declarations, such as .PHONY.
+# uppercase letters) in dependency declarations, such as '.PHONY'.
# TODO: Implementation
@@ -14,13 +14,19 @@ target: .PHONY source-${DEFINED_LATER}
DEFINED_LATER= later
#
source-: .PHONY
+ # This section applies.
: 'Undefined variables are expanded directly in the dependency'
: 'declaration. They are not preserved and maybe expanded later.'
: 'This is in contrast to local variables such as $${.TARGET}.'
source-later: .PHONY
+ # This section doesn't apply.
: 'Undefined variables are tried to be expanded in a dependency'
: 'declaration. If that fails because the variable is undefined,'
: 'the expression is preserved and tried to be expanded later.'
-all:
- @:;
+# Sources that look like keywords but are not known are interpreted as
+# ordinary sources.
+target: .UNKNOWN
+
+.UNKNOWN:
+ : Making ${.TARGET} from ${.ALLSRC:S,^$,nothing,W}.
diff --git a/contrib/bmake/unit-tests/deptgt-error.exp b/contrib/bmake/unit-tests/deptgt-error.exp
index 39a9383953dd..48e2f90954cf 100644
--- a/contrib/bmake/unit-tests/deptgt-error.exp
+++ b/contrib/bmake/unit-tests/deptgt-error.exp
@@ -1 +1,9 @@
-exit status 0
+false fails
+*** Error code 1 (continuing)
+
+Stop.
+make: stopped in unit-tests
+ERROR_INFO='This information is printed on 'errors'.'
+Making sub-error as prerequisite.
+Making .ERROR out of nothing.
+exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-error.mk b/contrib/bmake/unit-tests/deptgt-error.mk
index 5d515b95afc3..67f94e6999f7 100644
--- a/contrib/bmake/unit-tests/deptgt-error.mk
+++ b/contrib/bmake/unit-tests/deptgt-error.mk
@@ -1,9 +1,21 @@
-# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: deptgt-error.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .ERROR in dependency declarations, which
-# collects commands that are run when another target fails.
+# is made when another target fails.
-# TODO: Implementation
+all: .PHONY
+ false fails
-all:
- @:;
+.ERROR:
+ @echo 'Making ${.TARGET} out of nothing.'
+
+.ERROR: sub-error
+sub-error: .PHONY
+ @echo 'Making ${.TARGET} as prerequisite.'
+
+# Before making the '.ERROR' target, these variable values are printed.
+MAKE_PRINT_VAR_ON_ERROR= ERROR_INFO
+
+# Use single quotes to demonstrate that the output is only informational, it
+# does not use any established escaping mechanism.
+ERROR_INFO= This information is ${:Uprinted} on 'errors'.
diff --git a/contrib/bmake/unit-tests/deptgt-ignore.exp b/contrib/bmake/unit-tests/deptgt-ignore.exp
index 39a9383953dd..2aa1311c8ff7 100644
--- a/contrib/bmake/unit-tests/deptgt-ignore.exp
+++ b/contrib/bmake/unit-tests/deptgt-ignore.exp
@@ -1 +1,11 @@
-exit status 0
+error-failed before
+*** Error code 1 (continuing)
+error-ignored before
+*** Error code 1 (ignored)
+error-ignored after
+Making depends-on-ignored from error-ignored.
+`all' not remade because of errors.
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-ignore.mk b/contrib/bmake/unit-tests/deptgt-ignore.mk
index 49c14d2cfd43..a0191847e69f 100644
--- a/contrib/bmake/unit-tests/deptgt-ignore.mk
+++ b/contrib/bmake/unit-tests/deptgt-ignore.mk
@@ -1,9 +1,31 @@
-# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: deptgt-ignore.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .IGNORE in dependency declarations, which
# does not stop if a command from this target exits with a non-zero status.
+#
+# This test only applies to compatibility mode. In jobs mode such as with
+# '-j1', all commands for a single target are bundled into a single shell
+# program, which is a different implementation technique, the .IGNORE applies
+# there as well.
+
+.MAKEFLAGS: -d0 # force stdout to be unbuffered
+
+all: depends-on-failed depends-on-ignored
+.PHONY: all depends-on-failed depends-on-ignored error-failed error-ignored
+
+error-failed error-ignored:
+ @echo '${.TARGET} before'
+ @false
+ @echo '${.TARGET} after'
+
+depends-on-failed: error-failed
+ @echo 'Making ${.TARGET} from ${.ALLSRC}.'
+depends-on-ignored: error-ignored
+ @echo 'Making ${.TARGET} from ${.ALLSRC}.'
-# TODO: Implementation
+# Even though the command 'false' in the middle fails, the remaining commands
+# are still run. After that, the target is marked made, so targets depending
+# on the target with the ignored commands are made.
+.IGNORE: error-ignored
-all:
- @:;
+#.MAKEFLAGS: -dg2
diff --git a/contrib/bmake/unit-tests/deptgt-interrupt.exp b/contrib/bmake/unit-tests/deptgt-interrupt.exp
index 39a9383953dd..e59bdc7d7c41 100644
--- a/contrib/bmake/unit-tests/deptgt-interrupt.exp
+++ b/contrib/bmake/unit-tests/deptgt-interrupt.exp
@@ -1 +1,2 @@
-exit status 0
+Ctrl-C
+exit status 130
diff --git a/contrib/bmake/unit-tests/deptgt-interrupt.mk b/contrib/bmake/unit-tests/deptgt-interrupt.mk
index d94009a52e05..9a3b4d9e81c9 100644
--- a/contrib/bmake/unit-tests/deptgt-interrupt.mk
+++ b/contrib/bmake/unit-tests/deptgt-interrupt.mk
@@ -1,10 +1,11 @@
-# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: deptgt-interrupt.mk,v 1.4 2022/01/22 21:50:41 rillig Exp $
#
# Tests for the special target .INTERRUPT in dependency declarations, which
# collects commands to be run when make is interrupted while building another
# target.
-# TODO: Implementation
-
all:
- @:;
+ @kill -INT ${.MAKE.PID}
+
+.INTERRUPT:
+ @echo 'Ctrl-C'
diff --git a/contrib/bmake/unit-tests/deptgt-main.exp b/contrib/bmake/unit-tests/deptgt-main.exp
index 39a9383953dd..40c9a2c3cb8f 100644
--- a/contrib/bmake/unit-tests/deptgt-main.exp
+++ b/contrib/bmake/unit-tests/deptgt-main.exp
@@ -1 +1,2 @@
+This target real-main is the one that is made.
exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-main.mk b/contrib/bmake/unit-tests/deptgt-main.mk
index 84d05dc25ed6..184b6f3f73bb 100644
--- a/contrib/bmake/unit-tests/deptgt-main.mk
+++ b/contrib/bmake/unit-tests/deptgt-main.mk
@@ -1,10 +1,29 @@
-# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: deptgt-main.mk,v 1.4 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the special target .MAIN in dependency declarations, which defines
# the main target. This main target is built if no target has been specified
# on the command line or via MAKEFLAGS.
-# TODO: Implementation
+# The first target becomes the main target by default. It can be overridden
+# though.
+all: .PHONY
+ @echo 'This target is not made.'
-all:
- @:;
+# This target is not the first to be defined, but it lists '.MAIN' as one of
+# its sources. The word '.MAIN' only has a special meaning when it appears as
+# a _target_ in a dependency declaration, not as a _source_. It is thus
+# ignored.
+depsrc-main: .PHONY .MAIN
+ @echo 'This target is not made either.'
+
+# This target is the first to be marked with '.MAIN', so it replaces the
+# previous main target, which was 'all'.
+.MAIN: real-main
+real-main: .PHONY
+ @echo 'This target ${.TARGET} is the one that is made.'
+
+# This target is marked with '.MAIN' but there already is a main target. The
+# attribute '.MAIN' is thus ignored.
+.MAIN: too-late
+too-late: .PHONY
+ @echo 'This target comes too late, there is already a .MAIN target.'
diff --git a/contrib/bmake/unit-tests/deptgt-notparallel.exp b/contrib/bmake/unit-tests/deptgt-notparallel.exp
index 39a9383953dd..1e4d8ad7befb 100644
--- a/contrib/bmake/unit-tests/deptgt-notparallel.exp
+++ b/contrib/bmake/unit-tests/deptgt-notparallel.exp
@@ -1 +1,9 @@
+: Making 1 out of nothing.
+: Making 2 out of nothing.
+: Making 3 out of nothing.
+: Making 4 out of nothing.
+: Making 5 out of nothing.
+: Making 6 out of nothing.
+: Making 7 out of nothing.
+: Making 8 out of nothing.
exit status 0
diff --git a/contrib/bmake/unit-tests/deptgt-notparallel.mk b/contrib/bmake/unit-tests/deptgt-notparallel.mk
index db08aa9d3558..8d32ed8f2461 100644
--- a/contrib/bmake/unit-tests/deptgt-notparallel.mk
+++ b/contrib/bmake/unit-tests/deptgt-notparallel.mk
@@ -1,8 +1,16 @@
-# $NetBSD: deptgt-notparallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-notparallel.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
-# Tests for the special target .NOTPARALLEL in dependency declarations.
+# Tests for the special target .NOTPARALLEL in dependency declarations, which
+# prevents the job module from doing anything in parallel, by setting the
+# maximum jobs to 1. This only applies to the current make, it is not
+# exported to submakes.
-# TODO: Implementation
+.MAKEFLAGS: -j4
-all:
- @:;
+# Set opts.maxJobs back to 1. Without this line, the output would be in
+# random order, interleaved with separators like '--- 1 ---'.
+.NOTPARALLEL:
+
+all: 1 2 3 4 5 6 7 8
+1 2 3 4 5 6 7 8: .PHONY
+ : Making ${.TARGET} out of nothing.
diff --git a/contrib/bmake/unit-tests/deptgt-order.exp b/contrib/bmake/unit-tests/deptgt-order.exp
index 5f7dde0ac69d..ecbf03fcc572 100644
--- a/contrib/bmake/unit-tests/deptgt-order.exp
+++ b/contrib/bmake/unit-tests/deptgt-order.exp
@@ -1,3 +1,10 @@
+Parsing line 15: .ORDER: three one
+ParseDependency(.ORDER: three one)
+# .ORDER forces 'three' to be made before 'one'
+# three, unmade, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS, flags none
+# one, unmade, type OP_DEPENDS|OP_PHONY, flags none
+Parsing line 16: .MAKEFLAGS: -d0
+ParseDependency(.MAKEFLAGS: -d0)
: 'Making two out of one.'
: 'Making three out of two.'
: 'Making all out of three.'
diff --git a/contrib/bmake/unit-tests/deptgt-order.mk b/contrib/bmake/unit-tests/deptgt-order.mk
index f241331ae1e1..88f5958425dd 100644
--- a/contrib/bmake/unit-tests/deptgt-order.mk
+++ b/contrib/bmake/unit-tests/deptgt-order.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-order.mk,v 1.3 2021/06/17 15:25:33 rillig Exp $
+# $NetBSD: deptgt-order.mk,v 1.4 2021/12/13 23:38:54 rillig Exp $
#
# Tests for the special target .ORDER in dependency declarations.
@@ -11,7 +11,9 @@ three: two
# This .ORDER creates a circular dependency since 'three' depends on 'one'
# but 'one' is supposed to be built after 'three'.
+.MAKEFLAGS: -dp
.ORDER: three one
+.MAKEFLAGS: -d0
# XXX: The circular dependency should be detected here.
all: three
diff --git a/contrib/bmake/unit-tests/deptgt-path-suffix.exp b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
index 39a9383953dd..228a29851f48 100644
--- a/contrib/bmake/unit-tests/deptgt-path-suffix.exp
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.exp
@@ -1 +1,4 @@
-exit status 0
+make: "deptgt-path-suffix.mk" line 8: Suffix '.c' not defined (yet)
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt-path-suffix.mk b/contrib/bmake/unit-tests/deptgt-path-suffix.mk
index 3a7e697bc748..494a076a5520 100644
--- a/contrib/bmake/unit-tests/deptgt-path-suffix.mk
+++ b/contrib/bmake/unit-tests/deptgt-path-suffix.mk
@@ -1,8 +1,16 @@
-# $NetBSD: deptgt-path-suffix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-path-suffix.mk,v 1.3 2021/12/13 23:38:54 rillig Exp $
#
# Tests for the special target .PATH.suffix in dependency declarations.
# TODO: Implementation
+# expect+1: Suffix '.c' not defined (yet)
+.PATH.c: ..
+
+.SUFFIXES: .c
+
+# Now the suffix is defined, and the path is recorded.
+.PATH.c: ..
+
all:
@:;
diff --git a/contrib/bmake/unit-tests/deptgt.exp b/contrib/bmake/unit-tests/deptgt.exp
index bdac2aee3e6c..0a27f562293d 100644
--- a/contrib/bmake/unit-tests/deptgt.exp
+++ b/contrib/bmake/unit-tests/deptgt.exp
@@ -1,14 +1,17 @@
make: "deptgt.mk" line 10: warning: Extra target ignored
make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL"
-ParseReadLine (34): '${:U}: empty-source'
+Parsing line 34: ${:U}: empty-source
ParseDependency(: empty-source)
-ParseReadLine (35): ' : command for empty targets list'
-ParseReadLine (36): ': empty-source'
+Parsing line 35: : command for empty targets list
+Parsing line 36: : empty-source
ParseDependency(: empty-source)
-ParseReadLine (37): ' : command for empty targets list'
-ParseReadLine (38): '.MAKEFLAGS: -d0'
+Parsing line 37: : command for empty targets list
+Parsing line 38: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
make: "deptgt.mk" line 46: Unknown modifier "Z"
+make: "deptgt.mk" line 49: warning: Extra target ignored
+make: "deptgt.mk" line 52: warning: Extra target (ordinary) ignored
+make: "deptgt.mk" line 55: warning: Special and mundane targets don't mix. Mundane ones ignored
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/deptgt.mk b/contrib/bmake/unit-tests/deptgt.mk
index 15d7e59aeced..044644dcbd66 100644
--- a/contrib/bmake/unit-tests/deptgt.mk
+++ b/contrib/bmake/unit-tests/deptgt.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt.mk,v 1.11 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: deptgt.mk,v 1.12 2021/12/13 23:38:54 rillig Exp $
#
# Tests for special targets like .BEGIN or .SUFFIXES in dependency
# declarations.
@@ -45,5 +45,14 @@ ${:U}: empty-source
# that nobody uses it.
$$$$$$$${:U:Z}:
+# expect+1: warning: Extra target ignored
+.END ordinary:
+
+# expect+1: warning: Extra target (ordinary) ignored
+.PATH ordinary:
+
+# expect+1: Special and mundane targets don't mix. Mundane ones ignored
+ordinary .PATH:
+
all:
@:;
diff --git a/contrib/bmake/unit-tests/directive-dinclude.exp b/contrib/bmake/unit-tests/directive-dinclude.exp
index 39a9383953dd..5ea0dabb3c7e 100755
--- a/contrib/bmake/unit-tests/directive-dinclude.exp
+++ b/contrib/bmake/unit-tests/directive-dinclude.exp
@@ -1 +1,4 @@
-exit status 0
+make: "directive-dinclude-error.inc" line 1: Invalid line type
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-dinclude.mk b/contrib/bmake/unit-tests/directive-dinclude.mk
index 94fa5fb4429b..d968924a6a99 100755
--- a/contrib/bmake/unit-tests/directive-dinclude.mk
+++ b/contrib/bmake/unit-tests/directive-dinclude.mk
@@ -1,9 +1,24 @@
-# $NetBSD: directive-dinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $
+# $NetBSD: directive-dinclude.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .dinclude directive, which includes another file,
-# typically named .depend.
+# silently skipping it if it cannot be opened. This is primarily used for
+# including '.depend' files, that's where the 'd' comes from.
+#
+# The 'silently skipping' only applies to the case where the file cannot be
+# opened. Parse errors and other errors are handled the same way as in the
+# other .include directives.
+
+# No complaint that there is no such file.
+.dinclude "${.CURDIR}/directive-dinclude-nonexistent.inc"
+
+# No complaint either, even though the operating system error is ENOTDIR, not
+# ENOENT.
+.dinclude "${MAKEFILE}/subdir"
-# TODO: Implementation
+# Errors that are not related to opening the file are still reported.
+# expect: make: "directive-dinclude-error.inc" line 1: Invalid line type
+_!= echo 'syntax error' > directive-dinclude-error.inc
+.dinclude "${.CURDIR}/directive-dinclude-error.inc"
+_!= rm directive-dinclude-error.inc
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive-elifdef.mk b/contrib/bmake/unit-tests/directive-elifdef.mk
index f960c1513e8e..6a9925a67376 100644
--- a/contrib/bmake/unit-tests/directive-elifdef.mk
+++ b/contrib/bmake/unit-tests/directive-elifdef.mk
@@ -1,8 +1,21 @@
-# $NetBSD: directive-elifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-elifdef.mk,v 1.3 2022/01/22 16:23:56 rillig Exp $
#
-# Tests for the .elifdef directive.
+# Tests for the .elifdef directive, which is seldom used. Instead of writing
+# '.elifdef VAR', the usual form is the more versatile '.elif defined(VAR)'.
-# TODO: Implementation
+# At this point, VAR is not defined, so the condition evaluates to false.
+.if 0
+.elifdef VAR
+. error
+.endif
+
+VAR= # defined
+
+# At this point, VAR is defined, so the condition evaluates to true.
+.if 0
+.elifdef VAR
+.else
+. error
+.endif
all:
- @:;
diff --git a/contrib/bmake/unit-tests/directive-elifndef.mk b/contrib/bmake/unit-tests/directive-elifndef.mk
index 19bb66c11b01..87aaf2fdd9ac 100644
--- a/contrib/bmake/unit-tests/directive-elifndef.mk
+++ b/contrib/bmake/unit-tests/directive-elifndef.mk
@@ -1,8 +1,23 @@
-# $NetBSD: directive-elifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: directive-elifndef.mk,v 1.3 2022/01/22 21:50:41 rillig Exp $
#
-# Tests for the .elifndef directive.
+# Tests for the .elifndef directive, which is an obscure form of writing the
+# more usual '.elif !defined(VAR)'.
-# TODO: Implementation
+# At this point, VAR is not yet defined, and due to the 'n' in 'elifndef' the
+# condition evaluates to true.
+.if 0
+.elifndef VAR && VAR || VAR
+.else
+. error
+.endif
+
+VAR= # defined
+
+# At this point, VAR is defined, and due to the 'n' in 'elifndef' the
+# condition evaluates to false.
+.if 0
+.elifndef VAR && VAR || VAR
+. error
+.endif
all:
- @:;
diff --git a/contrib/bmake/unit-tests/directive-export-impl.exp b/contrib/bmake/unit-tests/directive-export-impl.exp
index 152fc0de4063..778d825996ff 100644
--- a/contrib/bmake/unit-tests/directive-export-impl.exp
+++ b/contrib/bmake/unit-tests/directive-export-impl.exp
@@ -1,8 +1,8 @@
-ParseReadLine (21): 'UT_VAR= <${REF}>'
+Parsing line 21: UT_VAR= <${REF}>
Global: UT_VAR = <${REF}>
-ParseReadLine (28): '.export UT_VAR'
+Parsing line 28: .export UT_VAR
Global: .MAKE.EXPORTED = UT_VAR
-ParseReadLine (32): ': ${UT_VAR:N*}'
+Parsing line 32: : ${UT_VAR:N*}
Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
@@ -14,6 +14,7 @@ CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>"
Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined)
Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
Modifier part: "echo "$UT_VAR""
+Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:O} is "UT_VAR"
@@ -23,7 +24,7 @@ Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined)
lhs = "<>", rhs = "<>", op = !=
-ParseReadLine (50): ': ${UT_VAR:N*}'
+Parsing line 50: : ${UT_VAR:N*}
Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
@@ -31,12 +32,13 @@ Pattern for ':N' is "*"
ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
-ParseReadLine (54): 'REF= defined'
+Parsing line 54: REF= defined
Global: REF = defined
CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<defined>"
Var_Parse: ${:!echo "\$UT_VAR"!} != "<defined>" (eval-defined)
Evaluating modifier ${:!...} on value "" (eval-defined, undefined)
Modifier part: "echo "$UT_VAR""
+Capturing the output of command "echo "$UT_VAR""
Var_Parse: ${.MAKE.EXPORTED:O:u} (eval)
Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR"
Result of ${.MAKE.EXPORTED:O} is "UT_VAR"
@@ -46,10 +48,10 @@ Var_Parse: ${UT_VAR} (eval)
Var_Parse: ${REF}> (eval)
Result of ${:!echo "\$UT_VAR"!} is "<defined>" (eval-defined, defined)
lhs = "<defined>", rhs = "<defined>", op = !=
-ParseReadLine (62): 'all:'
+Parsing line 62: all:
ParseDependency(all:)
Global: .ALLTARGETS = all
-ParseReadLine (63): '.MAKEFLAGS: -d0'
+Parsing line 63: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0
diff --git a/contrib/bmake/unit-tests/directive-for-escape.exp b/contrib/bmake/unit-tests/directive-for-escape.exp
index 492f82e16d1f..5fa4ae1ed877 100644
--- a/contrib/bmake/unit-tests/directive-for-escape.exp
+++ b/contrib/bmake/unit-tests/directive-for-escape.exp
@@ -27,29 +27,29 @@ make: "directive-for-escape.mk" line 43: value-with-modifier
For: end for 1
For: loop body:
. info ${:U\${UNDEF\:U\\$\\$}
-make: "directive-for-escape.mk" line 57: ${UNDEF:U\$
+make: "directive-for-escape.mk" line 72: ${UNDEF:U\backslash$
For: loop body:
. info ${:U{{\}\}}
-make: "directive-for-escape.mk" line 57: {{}}
+make: "directive-for-escape.mk" line 72: {{}}
For: loop body:
. info ${:Uend\}}
-make: "directive-for-escape.mk" line 57: end}
+make: "directive-for-escape.mk" line 72: end}
For: end for 1
For: loop body:
. info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end}
-make: "directive-for-escape.mk" line 69: begin<fallback>end
+make: "directive-for-escape.mk" line 84: begin<fallback>end
For: end for 1
For: loop body:
. info ${:U\$}
-make: "directive-for-escape.mk" line 77: $
+make: "directive-for-escape.mk" line 92: $
For: end for 1
For: loop body:
. info ${NUMBERS} ${:Ureplaced}
-make: "directive-for-escape.mk" line 85: one two three replaced
+make: "directive-for-escape.mk" line 100: one two three replaced
For: end for 1
For: loop body:
. info ${:Ureplaced}
-make: "directive-for-escape.mk" line 95: replaced
+make: "directive-for-escape.mk" line 110: replaced
For: end for 1
For: loop body:
. info . $$i: ${:Uinner}
@@ -62,31 +62,85 @@ For: loop body:
. info . $${i2}: ${i2}
. info . $${i,}: ${i,}
. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
-make: "directive-for-escape.mk" line 103: . $i: inner
-make: "directive-for-escape.mk" line 104: . ${i}: inner
-make: "directive-for-escape.mk" line 105: . ${i:M*}: inner
-make: "directive-for-escape.mk" line 106: . $(i): inner
-make: "directive-for-escape.mk" line 107: . $(i:M*): inner
-make: "directive-for-escape.mk" line 108: . ${i${:U}}: outer
-make: "directive-for-escape.mk" line 109: . ${i\}}: inner}
-make: "directive-for-escape.mk" line 110: . ${i2}: two
-make: "directive-for-escape.mk" line 111: . ${i,}: comma
-make: "directive-for-escape.mk" line 112: . adjacent: innerinnerinnerinner
+make: "directive-for-escape.mk" line 118: . $i: inner
+make: "directive-for-escape.mk" line 119: . ${i}: inner
+make: "directive-for-escape.mk" line 120: . ${i:M*}: inner
+make: "directive-for-escape.mk" line 121: . $(i): inner
+make: "directive-for-escape.mk" line 122: . $(i:M*): inner
+make: "directive-for-escape.mk" line 123: . ${i${:U}}: outer
+make: "directive-for-escape.mk" line 124: . ${i\}}: inner}
+make: "directive-for-escape.mk" line 125: . ${i2}: two
+make: "directive-for-escape.mk" line 126: . ${i,}: comma
+make: "directive-for-escape.mk" line 127: . adjacent: innerinnerinnerinner
For: end for 1
For: loop body:
. info eight $$$$$$$$ and no cents.
. info eight ${:Udollar}${:Udollar}${:Udollar}${:Udollar} and no cents.
-make: "directive-for-escape.mk" line 120: eight $$$$ and no cents.
-make: "directive-for-escape.mk" line 121: eight dollardollardollardollar and no cents.
-make: "directive-for-escape.mk" line 130: eight and no cents.
+make: "directive-for-escape.mk" line 135: eight $$$$ and no cents.
+make: "directive-for-escape.mk" line 136: eight dollardollardollardollar and no cents.
+make: "directive-for-escape.mk" line 145: eight and no cents.
For: end for 1
-make: "directive-for-escape.mk" line 137: newline in .for value
-make: "directive-for-escape.mk" line 137: newline in .for value
+make: "directive-for-escape.mk" line 152: newline in .for value
+make: "directive-for-escape.mk" line 152: newline in .for value
For: loop body:
. info short: ${:U" "}
. info long: ${:U" "}
-make: "directive-for-escape.mk" line 138: short: " "
-make: "directive-for-escape.mk" line 139: long: " "
+make: "directive-for-escape.mk" line 153: short: " "
+make: "directive-for-escape.mk" line 154: long: " "
+For: end for 1
+For: loop body:
+For: end for 1
+Parse_PushInput: .for loop in directive-for-escape.mk, line 167
+make: "directive-for-escape.mk" line 167: newline in .for value
+ in .for loop from directive-for-escape.mk:167 with i = "
+"
+For: loop body:
+: ${:U" "}
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
+Parsing line 168: : ${:U" "}
+ParseDependency(: " ")
+ParseEOF: returning to file directive-for-escape.mk, line 170
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
+Parsing line 170: .MAKEFLAGS: -d0
+ParseDependency(.MAKEFLAGS: -d0)
+For: end for 1
+For: loop body:
+# ${:U#}
+For: loop body:
+# ${:U\\\\#}
+For: end for 1
+For: loop body:
+# ${:U\$}
+For: loop body:
+# ${:U$i}
+For: loop body:
+# ${:U$(i)}
+For: loop body:
+# ${:U${i}}
+For: loop body:
+# ${:U$$}
+For: loop body:
+# ${:U$$$$}
+For: loop body:
+# ${:U${:U\$\$}}
+For: end for 1
+For: loop body:
+# ${:U${.TARGET}}
+For: loop body:
+# ${:U${.TARGET}}
+For: loop body:
+# ${:U$${.TARGET\}}
+For: loop body:
+# ${:U$${.TARGET\}}
+For: end for 1
+For: loop body:
+# ${:U(((}
+For: loop body:
+# ${:U{{{}
+For: loop body:
+# ${:U)))}
+For: loop body:
+# ${:U\}\}\}}
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for-escape.mk b/contrib/bmake/unit-tests/directive-for-escape.mk
index 725fa85d68c3..03a7a16b6a7b 100644
--- a/contrib/bmake/unit-tests/directive-for-escape.mk
+++ b/contrib/bmake/unit-tests/directive-for-escape.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for-escape.mk,v 1.12 2021/12/05 11:40:03 rillig Exp $
+# $NetBSD: directive-for-escape.mk,v 1.15 2022/01/27 20:15:14 rillig Exp $
#
# Test escaping of special characters in the iteration values of a .for loop.
# These values get expanded later using the :U variable modifier, and this
@@ -50,7 +50,22 @@ VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
# being that each '$' is written as '$$'.
#
# The .for loop splits ${VALUES} into 3 words, at the space characters, since
-# these are not escaped.
+# the '$$' is an ordinary character and the spaces are not escaped.
+# Word 1 is '${UNDEF:U\$\$'
+# Word 2 is '{{}}'
+# Word 3 is 'end}'
+# The first iteration expands the body of the .for loop to:
+# expect: . info ${:U\${UNDEF\:U\\$\\$}
+# The modifier ':U' unescapes the '\$' to a simple '$'.
+# The modifier ':U' unescapes the '\:' to a simple ':'.
+# The modifier ':U' unescapes the '\\' to a simple '\'.
+# The modifier ':U' resolves the expression '$\' to the word 'backslash', due
+# to the following variable definition.
+${:U\\}= backslash
+# FIXME: There was no expression '$\' in the original text of the previous
+# line, that's a surprise in the parser.
+# The modifier ':U' unescapes the '\$' to a simple '$'.
+# expect+4: ${UNDEF:U\backslash$
VALUES= $${UNDEF:U\$$\$$ {{}} end}
# XXX: Where in the code does the '\$\$' get converted into a single '\$'?
.for i in ${VALUES}
@@ -139,4 +154,47 @@ ${closing-brace}= <closing-brace> # alternative interpretation
. info long: ${i}
.endfor
+# No error since the newline character is not actually used.
+.for i in "${.newline}"
+.endfor
+
+# Between for.c 1.161 from 2022-01-08 and before for.c 1.163 from 2022-01-09,
+# a newline character in a .for loop led to a crash since at the point where
+# the error message including the stack trace is printed, the body of the .for
+# loop is assembled, and at that point, ForLoop.nextItem had already been
+# advanced.
+.MAKEFLAGS: -dp
+.for i in "${.newline}"
+: $i
+.endfor
+.MAKEFLAGS: -d0
+
+.MAKEFLAGS: -df
+.for i in \# \\\#
+# $i
+.endfor
+
+.for i in $$ $$i $$(i) $${i} $$$$ $$$$$$$$ $${:U\$$\$$}
+# $i
+.endfor
+
+# The expression '${.TARGET}' must be preserved as it is one of the 7 built-in
+# target-local variables. See for.c 1.45 from 2009-01-14.
+.for i in ${.TARGET} $${.TARGET} $$${.TARGET} $$$${.TARGET}
+# $i
+.endfor
+# expect: # ${:U${.TARGET}}
+# XXX: Why does '$' result in the same text as '$$'?
+# expect: # ${:U${.TARGET}}
+# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'?
+# expect: # ${:U$${.TARGET\}}
+# XXX: Why does '$' result in the same text as '$$'?
+# XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'?
+# expect: # ${:U$${.TARGET\}}
+
+.for i in ((( {{{ ))) }}}
+# $i
+.endfor
+.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/directive-for-generating-endif.exp b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
index 9e1301abf96f..5f57f89c250f 100755
--- a/contrib/bmake/unit-tests/directive-for-generating-endif.exp
+++ b/contrib/bmake/unit-tests/directive-for-generating-endif.exp
@@ -1,7 +1,7 @@
make: "directive-for-generating-endif.mk" line 21: if-less endif
make: "directive-for-generating-endif.mk" line 21: if-less endif
make: "directive-for-generating-endif.mk" line 21: if-less endif
-make: "directive-for-generating-endif.mk" line 0: 3 open conditionals
+make: "directive-for-generating-endif.mk" line 26: 3 open conditionals
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for.exp b/contrib/bmake/unit-tests/directive-for.exp
index 4e882aad7b68..dda487917e68 100755
--- a/contrib/bmake/unit-tests/directive-for.exp
+++ b/contrib/bmake/unit-tests/directive-for.exp
@@ -19,6 +19,24 @@ make: "directive-for.mk" line 148: outer "quoted" \"quoted\"
make: "directive-for.mk" line 154: Unknown modifier "Z"
make: "directive-for.mk" line 155: XXX: Not reached word1
make: "directive-for.mk" line 155: XXX: Not reached word3
+make: "directive-for.mk" line 160: no iteration variables in for
+make: "directive-for.mk" line 162: Missing argument for ".error"
+make: "directive-for.mk" line 163: for-less endfor
+make: "directive-for.mk" line 187: 1 open conditional
+make: "directive-for.mk" line 203: for-less endfor
+make: "directive-for.mk" line 204: if-less endif
+make: "directive-for.mk" line 212: if-less endif
+For: end for 1
+For: loop body:
+.\
+ for inner in i
+.\
+ endfor
+make: "directive-for.mk" line 229: Unexpected end of file in .for loop
+For: loop body:
+.\
+ endfor
+make: "directive-for.mk" line 227: for-less endfor
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-for.mk b/contrib/bmake/unit-tests/directive-for.mk
index 153762509b7a..572c4d6a5c92 100755
--- a/contrib/bmake/unit-tests/directive-for.mk
+++ b/contrib/bmake/unit-tests/directive-for.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for.mk,v 1.10 2020/12/27 09:58:35 rillig Exp $
+# $NetBSD: directive-for.mk,v 1.13 2022/01/15 12:35:18 rillig Exp $
#
# Tests for the .for directive.
#
@@ -11,7 +11,7 @@
# Using the .for loop, lists of values can be produced.
# In simple cases, the :@var@${var}@ variable modifier can be used to
-# reach the same effects.
+# achieve the same effects.
#
.undef NUMBERS
.for num in 1 2 3
@@ -135,7 +135,7 @@ EXPANSION${plus}= value
# Ensure that braces and parentheses are properly escaped by the .for loop.
# Each line must print the same word 3 times.
-# See GetEscapes.
+# See ForLoop_SubstBody.
.for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{
. info $v ${v} $(v)
.endfor
@@ -155,5 +155,76 @@ var= outer
. info XXX: Not reached ${var}
.endfor
-all:
- @:;
+
+# An empty list of variables to the left of the 'in' is a parse error.
+.for in value # expect+0: no iteration variables in for
+# XXX: The loop body is evaluated once, even with the parse error above.
+. error # expect+0: Missing argument for ".error"
+.endfor # expect+0: for-less endfor
+
+# An empty list of iteration values to the right of the 'in' is accepted.
+# Unlike in the shell, it is not a parse error.
+.for var in
+. error
+.endfor
+
+# If the iteration values become empty after expanding the expressions, the
+# body of the loop is not evaluated. It is not a parse error.
+.for var in ${:U}
+. error
+.endfor
+
+
+# The loop body can be empty.
+.for var in 1 2 3
+.endfor
+
+
+# A mismatched .if inside a .for loop is detected each time when the loop body
+# is processed.
+.for var in value
+. if 0
+.endfor # expect+0: 1 open conditional
+
+# If there are no iteration values, the loop body is not processed, and the
+# check for mismatched conditionals is not performed.
+.for var in ${:U}
+. if 0
+.endfor
+
+
+# When a .for without the corresponding .endfor occurs in an inactive branch
+# of an .if, the .for directive is just skipped, it does not even need a
+# corresponding .endfor. In other words, the behavior of the parser depends
+# on the actual values of the conditions in the .if clauses.
+.if 0
+. for var in value # does not need a corresponding .endfor
+.endif
+.endfor # expect+0: for-less endfor
+.endif # expect+0: if-less endif
+
+
+# When a .for without the corresponding .endfor occurs in an active branch of
+# an .if, the parser just counts the number of .for and .endfor directives,
+# without looking at any other directives.
+.if 1
+. for var in value
+. endif # expect+0: if-less endif
+. endfor # no 'for-less endfor'
+.endif # no 'if-less endif'
+
+
+# When make parses a .for loop, it assumes that there is no line break between
+# the '.' and the 'for' or 'endfor', as there is no practical reason to break
+# the line at this point. When make scans the outer .for loop, it does not
+# recognize the inner directives as such. When make scans the inner .for
+# loop, it recognizes the '.\n for' but does not recognize the '.\n endfor',
+# as LK_FOR_BODY preserves the backslash-newline sequences.
+.MAKEFLAGS: -df
+.for outer in o
+.\
+ for inner in i
+.\
+ endfor
+.endfor
+.MAKEFLAGS: -d0
diff --git a/contrib/bmake/unit-tests/directive-hyphen-include.exp b/contrib/bmake/unit-tests/directive-hyphen-include.exp
index 39a9383953dd..d0835fe464b1 100755
--- a/contrib/bmake/unit-tests/directive-hyphen-include.exp
+++ b/contrib/bmake/unit-tests/directive-hyphen-include.exp
@@ -1 +1,4 @@
-exit status 0
+make: "directive-hyphen-include-error.inc" line 1: Invalid line type
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-hyphen-include.mk b/contrib/bmake/unit-tests/directive-hyphen-include.mk
index 8c851be43215..fbfbeb200d4f 100755
--- a/contrib/bmake/unit-tests/directive-hyphen-include.mk
+++ b/contrib/bmake/unit-tests/directive-hyphen-include.mk
@@ -1,9 +1,23 @@
-# $NetBSD: directive-hyphen-include.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $
+# $NetBSD: directive-hyphen-include.mk,v 1.2 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .-include directive, which includes another file,
# silently skipping it if it cannot be opened.
+#
+# The 'silently skipping' only applies to the case where the file cannot be
+# opened. Parse errors and other errors are handled the same way as in the
+# other .include directives.
+
+# No complaint that there is no such file.
+.-include "${.CURDIR}/directive-hyphen-include-nonexistent.inc"
+
+# No complaint either, even though the operating system error is ENOTDIR, not
+# ENOENT.
+.-include "${MAKEFILE}/subdir"
-# TODO: Implementation
+# Errors that are not related to opening the file are still reported.
+# expect: make: "directive-hyphen-include-error.inc" line 1: Invalid line type
+_!= echo 'syntax error' > directive-hyphen-include-error.inc
+.-include "${.CURDIR}/directive-hyphen-include-error.inc"
+_!= rm directive-hyphen-include-error.inc
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive-if.exp b/contrib/bmake/unit-tests/directive-if.exp
index 89a394fc0f22..5682df501e9c 100644
--- a/contrib/bmake/unit-tests/directive-if.exp
+++ b/contrib/bmake/unit-tests/directive-if.exp
@@ -1,17 +1,18 @@
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: This is not conditional.
-make: "directive-if.mk" line 42: if-less else
+make: "directive-if.mk" line 41: Unknown directive "ifx"
make: "directive-if.mk" line 43: This is not conditional.
-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: "directive-if.mk" line 82: Don't do this, always put a space after a directive.
-make: "directive-if.mk" line 86: Don't do this, always put a space after a directive.
+make: "directive-if.mk" line 45: if-less else
+make: "directive-if.mk" line 47: This is not conditional.
+make: "directive-if.mk" line 49: if-less endif
+make: "directive-if.mk" line 53: Malformed conditional ()
+make: "directive-if.mk" line 63: Quotes in plain words are probably a mistake.
+make: "directive-if.mk" line 72: 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 after a directive.
+make: "directive-if.mk" line 82: Don't do this, always put a space around comparison operators.
+make: "directive-if.mk" line 88: Don't do this, always put a space after a directive.
+make: "directive-if.mk" line 92: Don't do this, always put a space after a directive.
+make: "directive-if.mk" line 100: Unknown directive "ifn"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-if.mk b/contrib/bmake/unit-tests/directive-if.mk
index b1ad2396b398..1acd5c958008 100644
--- a/contrib/bmake/unit-tests/directive-if.mk
+++ b/contrib/bmake/unit-tests/directive-if.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-if.mk,v 1.9 2020/12/19 22:33:11 rillig Exp $
+# $NetBSD: directive-if.mk,v 1.11 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .if directive.
#
@@ -37,13 +37,19 @@
# 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".
+# expect+1: Unknown directive "ifx"
.ifx 123
+# expect+1: This is not conditional.
.info This is not conditional.
+# expect+1: if-less else
.else
+# expect+1: This is not conditional.
.info This is not conditional.
+# expect+1: if-less endif
.endif
# Missing condition.
+# expect+1: Malformed conditional ()
.if
. error
.else
@@ -86,4 +92,9 @@
. info Don't do this, always put a space after a directive.
.endif
-all:
+
+# The directives '.ifdef' and '.ifmake' can be negated by inserting an 'n'.
+# This doesn't work for a plain '.if' though.
+#
+# expect+1: Unknown directive "ifn"
+.ifn 0
diff --git a/contrib/bmake/unit-tests/directive-ifdef.exp b/contrib/bmake/unit-tests/directive-ifdef.exp
index 1a1358988f39..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/directive-ifdef.exp
+++ b/contrib/bmake/unit-tests/directive-ifdef.exp
@@ -1,4 +1 @@
-make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible.
-make: "directive-ifdef.mk" line 23: String literals are tested for emptiness.
-make: "directive-ifdef.mk" line 27: String literals are tested for emptiness. Whitespace is non-empty.
exit status 0
diff --git a/contrib/bmake/unit-tests/directive-ifdef.mk b/contrib/bmake/unit-tests/directive-ifdef.mk
index 12f3648e8b2c..8a60fb4a2669 100644
--- a/contrib/bmake/unit-tests/directive-ifdef.mk
+++ b/contrib/bmake/unit-tests/directive-ifdef.mk
@@ -1,33 +1,51 @@
-# $NetBSD: directive-ifdef.mk,v 1.4 2021/01/21 23:03:41 rillig Exp $
+# $NetBSD: directive-ifdef.mk,v 1.5 2022/01/23 21:48:59 rillig Exp $
#
-# Tests for the .ifdef directive.
-
-# TODO: Implementation
+# Tests for the .ifdef directive, which evaluates bare words by calling
+# 'defined(word)'.
DEFINED= defined
+# There is no variable named 'UNDEF', therefore the condition evaluates to
+# false.
+.ifdef UNDEF
+. error
+.endif
+
+# There is a variable named 'DEFINED', so the condition evaluates to true.
+.ifdef DEFINED
+.else
+. error
+.endif
+
+# Since a bare word is an abbreviation for 'defined(word)', these can be
+# used to construct complex conditions.
+.ifdef UNDEF && DEFINED
+. error
+.endif
+.ifdef UNDEF || DEFINED
+.else
+. error
+.endif
+
# 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.
+# possible. The '.ifdef' only affects bare words, not function calls.
.ifdef defined(DEFINED)
-. info Function calls in .ifdef are possible.
.else
. error
.endif
-# String literals are handled the same in all variants of the .if directive.
-# They evaluate to true if they are not empty. Whitespace counts as non-empty
-# as well.
+# String literals are handled the same in all variants of the '.if' directive,
+# they evaluate to true if they are not empty, therefore this particular
+# example looks confusing and is thus not found in practice.
.ifdef ""
. error
.else
-. info String literals are tested for emptiness.
.endif
+# Whitespace counts as non-empty as well.
.ifdef " "
-. info String literals are tested for emptiness. Whitespace is non-empty.
.else
. error
.endif
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive-ifmake.exp b/contrib/bmake/unit-tests/directive-ifmake.exp
index fd4bcae151a0..bf4ded97911f 100644
--- a/contrib/bmake/unit-tests/directive-ifmake.exp
+++ b/contrib/bmake/unit-tests/directive-ifmake.exp
@@ -8,4 +8,8 @@ make: "directive-ifmake.mk" line 75: ok
: first
: second
: late-target
-exit status 0
+make: don't know how to make !edge (continuing)
+
+Stop.
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-ifmake.mk b/contrib/bmake/unit-tests/directive-ifmake.mk
index 4d49add72626..4824ce4d0570 100644
--- a/contrib/bmake/unit-tests/directive-ifmake.mk
+++ b/contrib/bmake/unit-tests/directive-ifmake.mk
@@ -1,12 +1,12 @@
-# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: directive-ifmake.mk,v 1.9 2022/01/22 16:23:56 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.
+# instead of using the more versatile '.if make(target)'.
-# The targets 'first' and 'second' are passed in on the command line.
+.MAKEFLAGS: first second
# This is the most basic form.
.ifmake first
@@ -15,9 +15,9 @@
. 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.
+# The '!' is interpreted as 'not'. A possible alternative interpretation of
+# this condition is whether the target named "!first" was requested. To
+# distinguish these cases, see the next test.
.ifmake !first
. warning unexpected
.else
@@ -78,5 +78,30 @@
.endif
-first second unmentioned late-target:
+# As an edge case, a target can actually be named "!first" on the command
+# line. There is no way to define a target of this name though since in a
+# dependency line, a plain '!' is interpreted as a dependency operator.
+
+.MAKEFLAGS: !edge
+.ifmake edge
+. error
+.endif
+
+# The '\!edge' in the following condition is parsed as a bare word. For such
+# a bare word, there is no escaping mechanism so the backslash passes through.
+# Since the condition function 'make' accepts a pattern instead of a plain
+# target name, the '\' is finally discarded in Str_Match.
+.ifmake \!edge
+.else
+. error
+.endif
+
+# In a dependency line, a plain '!' is interpreted as a dependency operator
+# (the other two are ':' and '::'). If the '!' is escaped by a '\', as
+# implemented in ParseDependencyTargetWord, the additional backslash is never
+# removed though. The target name thus becomes '\!edge' instead of the
+# intended '!edge'. Defining a target whose name contains a '!' will either
+# require additional tricks, or it may even be impossible.
+
+first second unmentioned late-target \!edge:
: $@
diff --git a/contrib/bmake/unit-tests/directive-include.exp b/contrib/bmake/unit-tests/directive-include.exp
index b339f393e7e8..42975d444c31 100755
--- a/contrib/bmake/unit-tests/directive-include.exp
+++ b/contrib/bmake/unit-tests/directive-include.exp
@@ -6,6 +6,8 @@ make: "directive-include.mk" line 25: Could not find nonexistent.mk
make: "directive-include.mk" line 47: Could not find "
make: "directive-include.mk" line 52: Unknown modifier "Z"
make: "directive-include.mk" line 52: Could not find nonexistent.mk
+make: "directive-include.mk" line 57: Cannot open /nonexistent
+make: "directive-include.mk" line 62: Invalid line type
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-include.mk b/contrib/bmake/unit-tests/directive-include.mk
index a6b300b3d273..edf27d02483e 100755
--- a/contrib/bmake/unit-tests/directive-include.mk
+++ b/contrib/bmake/unit-tests/directive-include.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-include.mk,v 1.7 2021/12/03 22:48:07 rillig Exp $
+# $NetBSD: directive-include.mk,v 1.11 2022/01/15 12:35:18 rillig Exp $
#
# Tests for the .include directive, which includes another file.
@@ -51,4 +51,35 @@ DQUOT= "
# FIXME: Add proper error handling, no file must be included here.
.include "nonexistent${:U123:Z}.mk"
+# The traditional include directive is seldom used.
+include /dev/null # comment
+# expect+1: Cannot open /nonexistent
+include /nonexistent # comment
+sinclude /nonexistent # comment
+include ${:U/dev/null} # comment
+include /dev/null /dev/null
+# expect+1: Invalid line type
+include
+
+# XXX: trailing whitespace in diagnostic, missing quotes around filename
+### TODO: expect+1: Could not find
+# The following include directive behaves differently, depending on whether
+# the current file has a slash or is a relative filename. In the first case,
+# make opens the directory of the current file and tries to read from it,
+# resulting in the error message """ line 1: Zero byte read from file".
+# In the second case, the error message is "Could not find ", without quotes
+# or any other indicator for the empty filename at the end of the line.
+#include ${:U}
+
+
+# Since parse.c 1.612 from 2022-01-01 and before parse.c 1.620 from
+# 2022-01-07, including an empty regular file called bmake_malloc(0), which
+# may return a null pointer. On OpenBSD, this led to a segmentation fault in
+# Buf_InitSize, which assumes that bmake_malloc never returns NULL, just like
+# all other places in the code.
+_!= > directive-include-empty
+.include "${.CURDIR}/directive-include-empty"
+_!= rm directive-include-empty
+
+
all:
diff --git a/contrib/bmake/unit-tests/directive-info.exp b/contrib/bmake/unit-tests/directive-info.exp
index 2652c191460c..70def02441d1 100644
--- a/contrib/bmake/unit-tests/directive-info.exp
+++ b/contrib/bmake/unit-tests/directive-info.exp
@@ -9,7 +9,7 @@ make: "directive-info.mk" line 22: Missing argument for ".info"
make: "directive-info.mk" line 23: Missing argument for ".info"
make: "directive-info.mk" line 26: Unknown directive "info-message"
make: "directive-info.mk" line 27: no-target: no-source
-make: "directive-info.mk" line 36: expect line 30 for multi-line message
+make: "directive-info.mk" line 35: expect line 35 for multi-line message
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-info.mk b/contrib/bmake/unit-tests/directive-info.mk
index 5feea0cde565..54f6a0f5aad0 100644
--- a/contrib/bmake/unit-tests/directive-info.mk
+++ b/contrib/bmake/unit-tests/directive-info.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-info.mk,v 1.8 2020/12/19 22:33:11 rillig Exp $
+# $NetBSD: directive-info.mk,v 1.9 2022/01/08 20:21:34 rillig Exp $
#
# Tests for the .info directive.
#
@@ -27,11 +27,12 @@
.info no-target: no-source # This is a .info directive, not a dependency.
# See directive.mk for more tests of this kind.
-# Since at least 2002-01-01, the line number that is used in error messages
-# and the .info directives is the number of completely read lines. For the
-# following multi-line directive, this means that the reported line number is
-# the one of the last line, not the first line.
-.info expect line 30 for\
+# Since at least 2002-01-01 and before parse.c 1.639 from 2022-01-08, the line
+# number that is used in error messages and the .info directives was the
+# number of completely read lines. For the following multi-line directive,
+# this meant that the reported line number was the one of the last line, not
+# of the first line.
+.info expect line 35 for\
multi$\
-line message
diff --git a/contrib/bmake/unit-tests/directive-sinclude.exp b/contrib/bmake/unit-tests/directive-sinclude.exp
index 39a9383953dd..ffdfefca0d4f 100755
--- a/contrib/bmake/unit-tests/directive-sinclude.exp
+++ b/contrib/bmake/unit-tests/directive-sinclude.exp
@@ -1 +1,4 @@
-exit status 0
+make: "directive-include-error.inc" line 1: Invalid line type
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/directive-sinclude.mk b/contrib/bmake/unit-tests/directive-sinclude.mk
index 1932e7b3ba13..a935ea2c068f 100755
--- a/contrib/bmake/unit-tests/directive-sinclude.mk
+++ b/contrib/bmake/unit-tests/directive-sinclude.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: directive-sinclude.mk,v 1.4 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the .sinclude directive, which includes another file,
# silently skipping it if it cannot be opened.
@@ -7,7 +7,17 @@
# opened. Parse errors and other errors are handled the same way as in the
# other .include directives.
-# TODO: Implementation
+# No complaint that there is no such file.
+.sinclude "${.CURDIR}/directive-include-nonexistent.inc"
-all:
- @:;
+# No complaint either, even though the operating system error is ENOTDIR, not
+# ENOENT.
+.sinclude "${MAKEFILE}/subdir"
+
+# Errors that are not related to opening the file are still reported.
+# expect: make: "directive-include-error.inc" line 1: Invalid line type
+_!= echo 'syntax error' > directive-include-error.inc
+.sinclude "${.CURDIR}/directive-include-error.inc"
+_!= rm directive-include-error.inc
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive-undef.exp b/contrib/bmake/unit-tests/directive-undef.exp
index 56c871429397..20df58a8dc73 100644
--- a/contrib/bmake/unit-tests/directive-undef.exp
+++ b/contrib/bmake/unit-tests/directive-undef.exp
@@ -1,6 +1,6 @@
make: "directive-undef.mk" line 29: The .undef directive requires an argument
make: "directive-undef.mk" line 86: Unknown modifier "Z"
-make: "directive-undef.mk" line 103: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore.
+make: "directive-undef.mk" line 102: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore.
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-unexport-env.mk b/contrib/bmake/unit-tests/directive-unexport-env.mk
index ef58ae732e6d..e9620684dfcb 100644
--- a/contrib/bmake/unit-tests/directive-unexport-env.mk
+++ b/contrib/bmake/unit-tests/directive-unexport-env.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-unexport-env.mk,v 1.7 2020/12/12 18:11:42 rillig Exp $
+# $NetBSD: directive-unexport-env.mk,v 1.8 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the .unexport-env directive.
#
@@ -21,5 +21,4 @@ UT_UNEXPORTED= value
.unexport-env UT_EXPORTED UT_UNEXPORTED
.MAKEFLAGS: -d0
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive-warning.exp b/contrib/bmake/unit-tests/directive-warning.exp
index b08b3207392c..932b88a151e2 100644
--- a/contrib/bmake/unit-tests/directive-warning.exp
+++ b/contrib/bmake/unit-tests/directive-warning.exp
@@ -1,11 +1,11 @@
-make: "directive-warning.mk" line 11: Unknown directive "warn"
-make: "directive-warning.mk" line 12: Unknown directive "warn"
-make: "directive-warning.mk" line 13: Unknown directive "warnin"
-make: "directive-warning.mk" line 14: Unknown directive "warnin"
-make: "directive-warning.mk" line 15: Missing argument for ".warning"
-make: "directive-warning.mk" line 16: warning: message
-make: "directive-warning.mk" line 17: Unknown directive "warnings"
-make: "directive-warning.mk" line 18: Unknown directive "warnings"
+make: "directive-warning.mk" line 9: Unknown directive "warn"
+make: "directive-warning.mk" line 10: Unknown directive "warn"
+make: "directive-warning.mk" line 11: Unknown directive "warnin"
+make: "directive-warning.mk" line 12: Unknown directive "warnin"
+make: "directive-warning.mk" line 13: Missing argument for ".warning"
+make: "directive-warning.mk" line 14: warning: message
+make: "directive-warning.mk" line 15: Unknown directive "warnings"
+make: "directive-warning.mk" line 16: Unknown directive "warnings"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive-warning.mk b/contrib/bmake/unit-tests/directive-warning.mk
index d586c9fed170..9d5cec1ff0b8 100644
--- a/contrib/bmake/unit-tests/directive-warning.mk
+++ b/contrib/bmake/unit-tests/directive-warning.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-warning.mk,v 1.6 2020/12/19 22:33:11 rillig Exp $
+# $NetBSD: directive-warning.mk,v 1.7 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the .warning directive.
#
@@ -6,16 +6,13 @@
# produced the wrong error message "Unknown directive". Since parse.c 1.503
# from 2020-12-19, the correct "Missing argument" is produced.
-# TODO: Implementation
-
.warn # misspelled
.warn message # misspelled
.warnin # misspelled
.warnin message # misspelled
.warning # "Missing argument"
-.warning message # ok
+.warning message # expect+0: message
.warnings # misspelled
.warnings messages # Accepted before 2020-12-13 01:07:54.
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/directive.exp b/contrib/bmake/unit-tests/directive.exp
index ee866b7ee2b3..2002fa73f58c 100644
--- a/contrib/bmake/unit-tests/directive.exp
+++ b/contrib/bmake/unit-tests/directive.exp
@@ -1,12 +1,14 @@
-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"
+make: "directive.mk" line 12: Unknown directive "indented"
+make: "directive.mk" line 14: Unknown directive "indented"
+make: "directive.mk" line 21: Unknown directive "info"
Global: .info =
Global: .info = value
-make: "directive.mk" line 26: := value
+make: "directive.mk" line 33: := value
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
+make: "directive.mk" line 42: Invalid line type
+make: "directive.mk" line 45: Invalid line type
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/directive.mk b/contrib/bmake/unit-tests/directive.mk
index d463ce4f009a..365a070f8f30 100644
--- a/contrib/bmake/unit-tests/directive.mk
+++ b/contrib/bmake/unit-tests/directive.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $
+# $NetBSD: directive.mk,v 1.6 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the preprocessing directives, such as .if or .info.
@@ -6,23 +6,30 @@
# Unknown directives are correctly named in the error messages,
# even if they are indented.
+# expect+1: Unknown directive "indented"
.indented none
+# expect+1: Unknown directive "indented"
. indented 2 spaces
+# expect+1: Unknown directive "indented"
. indented tab
# Directives must be written directly, not indirectly via variable
# expressions.
+# FIXME: The error message is misleading because it shows the expanded text of
+# the line, while the parser works on the unexpanded line.
+# expect+1: Unknown directive "info"
.${: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.
+# The following lines demonstrate how the parser tells an .info message apart
+# from a variable assignment to ".info", which syntactically is very similar.
.MAKEFLAGS: -dv
-.info:= value
+.info:= value # This is a variable assignment.
.info?= value # This is a variable assignment as well.
+# expect+1: := value
.info := value # The space after the '.info' makes this
# a directive.
.MAKEFLAGS: -d0
@@ -31,5 +38,8 @@
# Not even the space after the '.info' can change anything about this.
.${:Uinfo} : source
-all:
- @:;
+# expect+1: Invalid line type
+target-without-colon
+
+# expect+1: Invalid line type
+target-without-colon another-target
diff --git a/contrib/bmake/unit-tests/envfirst.mk b/contrib/bmake/unit-tests/envfirst.mk
deleted file mode 100644
index 60a331a5db64..000000000000
--- a/contrib/bmake/unit-tests/envfirst.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# $NetBSD: envfirst.mk,v 1.5 2021/02/04 21:42:47 rillig Exp $
-#
-# The -e option makes environment variables stronger than global variables.
-
-.MAKEFLAGS: -e
-
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-# Try to override the variable; this does not have any effect.
-FROM_ENV= value-from-mk
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-# Try to append to the variable; this also doesn't have any effect.
-FROM_ENV+= appended
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-# The default assignment also cannot change the variable.
-FROM_ENV?= default
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-# Neither can the assignment modifiers.
-.if ${FROM_ENV::=from-condition}
-.endif
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-# Even .undef doesn't work since it only affects the global scope,
-# which is independent from the environment variables.
-.undef FROM_ENV
-.if ${FROM_ENV} != value-from-env
-. error ${FROM_ENV}
-.endif
-
-all:
- @: nothing
diff --git a/contrib/bmake/unit-tests/hanoi-include.mk b/contrib/bmake/unit-tests/hanoi-include.mk
index 3ad0a751845a..3b9438bf2169 100644
--- a/contrib/bmake/unit-tests/hanoi-include.mk
+++ b/contrib/bmake/unit-tests/hanoi-include.mk
@@ -1,10 +1,10 @@
-# $NetBSD: hanoi-include.mk,v 1.1 2020/10/03 17:30:54 rillig Exp $
+# $NetBSD: hanoi-include.mk,v 1.2 2022/01/08 22:13:43 rillig Exp $
#
# Implements the Towers of Hanoi puzzle, thereby demonstrating a bunch of
-# useful programming techniques:
+# more or less useful programming techniques:
#
# * default assignment using the ?= assignment operator
-# * including the same file recursively
+# * including the same file recursively (rather unusual)
# * extracting the current value of a variable using the .for loop
# * using shell commands for calculations since make is a text processor
# * using the :: dependency operator for adding commands to a target
diff --git a/contrib/bmake/unit-tests/include-main.exp b/contrib/bmake/unit-tests/include-main.exp
index c8a670a1c14a..e677826373c1 100644
--- a/contrib/bmake/unit-tests/include-main.exp
+++ b/contrib/bmake/unit-tests/include-main.exp
@@ -2,13 +2,13 @@ make: "include-main.mk" line 14: main-before-ok
make: "include-main.mk" line 21: main-before-for-ok
make: "include-sub.mk" line 4: sub-before-ok
make: "include-sub.mk" line 14: sub-before-for-ok
-ParseReadLine (5): '. info subsub-ok'
+Parsing line 5: . info subsub-ok
make: "include-subsub.mk" line 5: subsub-ok
- in .for loop from include-sub.mk:31
- in .for loop from include-sub.mk:30
- in .for loop from include-sub.mk:29
- in .include from include-main.mk:27
-ParseReadLine (6): '.MAKEFLAGS: -d0'
+ in .for loop from include-sub.mk:31 with i = include
+ in .for loop from include-sub.mk:30 with i = nested
+ in .for loop from include-sub.mk:29 with i = deeply
+ in include-main.mk:27
+Parsing line 6: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
make: "include-sub.mk" line 38: sub-after-ok
make: "include-sub.mk" line 45: sub-after-for-ok
diff --git a/contrib/bmake/unit-tests/include-main.mk b/contrib/bmake/unit-tests/include-main.mk
index d3f122aef718..9a4c6630506b 100644
--- a/contrib/bmake/unit-tests/include-main.mk
+++ b/contrib/bmake/unit-tests/include-main.mk
@@ -1,11 +1,11 @@
-# $NetBSD: include-main.mk,v 1.6 2021/01/22 00:44:55 rillig Exp $
+# $NetBSD: include-main.mk,v 1.7 2022/01/08 23:41:43 rillig Exp $
#
# Until 2020-09-05, the .INCLUDEDFROMFILE magic variable did not behave
# as described in the manual page.
#
# The manual page says that it is the "filename of the file this Makefile
# was included from", while before 2020-09-05 it was the "filename in which
-# the latest .include happened". See parse.c, function ParseSetIncludeFile.
+# the latest .include happened". See parse.c, function SetParseFile.
#
# Since 2020-09-05, the .INCLUDEDFROMDIR and .INCLUDEDFROMFILE variables
# properly handle nested includes and even .for loops.
diff --git a/contrib/bmake/unit-tests/include-sub.mk b/contrib/bmake/unit-tests/include-sub.mk
index 0b8dc77398ab..57d2aafe9d1d 100644
--- a/contrib/bmake/unit-tests/include-sub.mk
+++ b/contrib/bmake/unit-tests/include-sub.mk
@@ -1,4 +1,4 @@
-# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $
+# $NetBSD: include-sub.mk,v 1.9 2022/01/08 23:41:43 rillig Exp $
.if ${.INCLUDEDFROMFILE} == "include-main.mk"
. info sub-before-ok
@@ -20,11 +20,11 @@
# To see the variable 'includes' in action:
#
# Breakpoints:
-# Parse_File at "Vector_Push(&includes)"
-# ParseMessage at entry
+# Parse_PushInput at "Vector_Push(&includes)"
+# HandleMessage at entry
# Watches:
-# ((const IFile *[10])(*includes.items))
-# *curFile
+# ((const IncludedFile *[10])(*includes.items))
+# *CurFile()
.for i in deeply
. for i in nested
diff --git a/contrib/bmake/unit-tests/meta-cmd-cmp.exp b/contrib/bmake/unit-tests/meta-cmd-cmp.exp
index bfc52123e3b2..dc63da3b346b 100644
--- a/contrib/bmake/unit-tests/meta-cmd-cmp.exp
+++ b/contrib/bmake/unit-tests/meta-cmd-cmp.exp
@@ -34,4 +34,20 @@ vs
Building .meta-cmd-cmp.cmp2
This line not compared FLAGS=
Skipping meta for .END: .SPECIAL
+filter0:
+Building .meta-cmd-cmp.filter
+Skipping meta for .END: .SPECIAL
+filter1:
+.meta-cmd-cmp.filter.meta: 2: a build command has changed
+@echo ccache cc -c foo.c > .meta-cmd-cmp.filter
+vs
+@echo cc -c foo.c > .meta-cmd-cmp.filter
+Building .meta-cmd-cmp.filter
+Skipping meta for .END: .SPECIAL
+filter2:
+`.meta-cmd-cmp.filter' is up to date.
+Skipping meta for .END: .SPECIAL
+filter3:
+`.meta-cmd-cmp.filter' is up to date.
+Skipping meta for .END: .SPECIAL
exit status 0
diff --git a/contrib/bmake/unit-tests/meta-cmd-cmp.mk b/contrib/bmake/unit-tests/meta-cmd-cmp.mk
index a1c0f7c10063..b2628fdbd2a9 100644
--- a/contrib/bmake/unit-tests/meta-cmd-cmp.mk
+++ b/contrib/bmake/unit-tests/meta-cmd-cmp.mk
@@ -1,4 +1,4 @@
-# $NetBSD: meta-cmd-cmp.mk,v 1.2 2020/12/05 22:51:34 sjg Exp $
+# $NetBSD: meta-cmd-cmp.mk,v 1.4 2022/01/27 06:02:59 sjg Exp $
#
# Tests META_MODE command line comparison
#
@@ -9,7 +9,7 @@
tf:= .${.PARSEFILE:R}
.if ${.TARGETS:Nall} == ""
-all: prep one two change1 change2 post
+all: prep one two change1 change2 filter0 filter1 filter2 filter3 post
CLEANFILES= ${tf}*
@@ -22,6 +22,7 @@ FLAGS?=
FLAGS2?=
tests= ${tf}.cmp ${tf}.nocmp ${tf}.cmp2
+filter_tests= ${tf}.filter
${tf}.cmp:
@echo FLAGS=${FLAGS:Uempty} > $@
@@ -35,6 +36,19 @@ ${tf}.cmp2:
@echo FLAGS2=${FLAGS2:Uempty} > $@
@echo This line not compared FLAGS=${FLAGS:Uempty} ${.OODATE:MNOMETA_CMP}
+COMPILER_WRAPPERS+= ccache distcc icecc
+WRAPPER?= ccache
+.ifdef WITH_CMP_FILTER
+.MAKE.META.CMP_FILTER+= ${COMPILER_WRAPPERS:S,^,N,}
+.endif
+.ifdef WITH_LOCAL_CMP_FILTER
+# local variable
+${tf}.filter: .MAKE.META.CMP_FILTER= ${COMPILER_WRAPPERS:S,^,N,}
+.endif
+
+${tf}.filter:
+ @echo ${WRAPPER} cc -c foo.c > $@
+
# these do the same
one two: .PHONY
@echo $@:
@@ -48,5 +62,23 @@ change2: .PHONY
@echo $@:
@${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS2=changed ${tests}
+filter0: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} ${filter_tests}
+
+filter1: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} WRAPPER= ${filter_tests}
+
+filter2: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} -DWITH_CMP_FILTER \
+ WRAPPER=distcc ${filter_tests}
+
+filter3: .PHONY
+ @echo $@:
+ @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} -DWITH_LOCAL_CMP_FILTER \
+ WRAPPER=icecc ${filter_tests}
+
# don't let gcov mess up the results
.MAKE.META.IGNORE_PATTERNS+= *.gcda
diff --git a/contrib/bmake/unit-tests/modts.exp b/contrib/bmake/unit-tests/modts.exp
deleted file mode 100644
index 18837016add4..000000000000
--- a/contrib/bmake/unit-tests/modts.exp
+++ /dev/null
@@ -1,14 +0,0 @@
-make: Bad modifier ":tx" for variable "LIST"
-LIST:tx="}"
-make: Bad modifier ":ts\X" for variable "LIST"
-LIST:ts/x:tu="\X:tu}"
-FU_mod-ts="a/b/cool"
-FU_mod-ts:ts:T="cool" == cool?
-B.${AAA:ts}="Baaa" == Baaa?
-:ts :S => aaxBbxaaxbbxaaxbb
-:ts :S space => axa a axc
-:ts :S space :M => axaxaxaxc
-:ts :S => axa a axc
-:ts :S :@ => axa a axc
-:ts :S :@ :M => axaxaxaxc
-exit status 0
diff --git a/contrib/bmake/unit-tests/modts.mk b/contrib/bmake/unit-tests/modts.mk
deleted file mode 100644
index 4776c5818ea5..000000000000
--- a/contrib/bmake/unit-tests/modts.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $
-
-LIST= one two three four five six
-
-FU_mod-ts= a / b / cool
-
-AAA= a a a
-B.aaa= Baaa
-
-all: mod-ts mod-ts-space
-
-# Use print or printf iff they are builtin.
-# XXX note that this causes problems, when make decides
-# there is no need to use a shell, so avoid where possible.
-.if ${(type print) 2> /dev/null || echo:L:sh:Mbuiltin} != ""
-PRINT= print -r --
-.elif ${(type printf) 2> /dev/null || echo:L:sh:Mbuiltin} != ""
-PRINT= printf '%s\n'
-.else
-PRINT= echo
-.endif
-
-mod-ts:
- @${PRINT} 'LIST:tx="${LIST:tx}"'
- @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"'
- @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"'
- @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
- @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
-
-mod-ts-space:
- # After the :ts modifier, the whole string is interpreted as a single
- # word since all spaces have been replaced with x.
- @${PRINT} ':ts :S => '${aa bb aa bb aa bb:L:tsx:S,b,B,:Q}
-
- # The :ts modifier also applies to word separators that are added
- # afterwards.
- @${PRINT} ':ts :S space => '${a ababa c:L:tsx:S,b, ,g:Q}
- @${PRINT} ':ts :S space :M => '${a ababa c:L:tsx:S,b, ,g:M*:Q}
-
- # Not all modifiers behave this way though. Some of them always use
- # a space as word separator instead of the :ts separator.
- # This seems like an oversight during implementation.
- @${PRINT} ':ts :S => '${a ababa c:L:tsx:S,b, ,g:Q}
- @${PRINT} ':ts :S :@ => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:Q}
-
- # A final :M* modifier applies the :ts separator again, though.
- @${PRINT} ':ts :S :@ :M => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*:Q}
diff --git a/contrib/bmake/unit-tests/modword.exp b/contrib/bmake/unit-tests/modword.exp
deleted file mode 100644
index 02e9974c02d6..000000000000
--- a/contrib/bmake/unit-tests/modword.exp
+++ /dev/null
@@ -1,126 +0,0 @@
-make: Bad modifier ":[]" for variable "LIST"
-LIST:[]="" is an error
-LIST:[0]="one two three four five six"
-LIST:[0x0]="one two three four five six"
-LIST:[000]="one two three four five six"
-LIST:[*]="one two three four five six"
-LIST:[@]="one two three four five six"
-LIST:[0]:C/ /,/="one,two three four five six"
-LIST:[0]:C/ /,/g="one,two,three,four,five,six"
-LIST:[0]:C/ /,/1g="one,two,three,four,five,six"
-LIST:[*]:C/ /,/="one,two three four five six"
-LIST:[*]:C/ /,/g="one,two,three,four,five,six"
-LIST:[*]:C/ /,/1g="one,two,three,four,five,six"
-LIST:[@]:C/ /,/="one two three four five six"
-LIST:[@]:C/ /,/g="one two three four five six"
-LIST:[@]:C/ /,/1g="one two three four five six"
-LIST:[@]:[0]:C/ /,/="one,two three four five six"
-LIST:[0]:[@]:C/ /,/="one two three four five six"
-LIST:[@]:[*]:C/ /,/="one,two three four five six"
-LIST:[*]:[@]:C/ /,/="one two three four five six"
-EMPTY=""
-EMPTY:[#]="1" == 1 ?
-ESCAPEDSPACE="\ "
-ESCAPEDSPACE:[#]="1" == 1 ?
-REALLYSPACE=" "
-REALLYSPACE:[#]="1" == 1 ?
-LIST:[#]="6"
-LIST:[0]:[#]="1" == 1 ?
-LIST:[*]:[#]="1" == 1 ?
-LIST:[@]:[#]="6"
-LIST:[1]:[#]="1"
-LIST:[1..3]:[#]="3"
-EMPTY:[1]=""
-ESCAPEDSPACE="\ "
-ESCAPEDSPACE:[1]="\ "
-REALLYSPACE=" "
-REALLYSPACE:[1]="" == "" ?
-REALLYSPACE:[*]:[1]=" " == " " ?
-LIST:[1]="one"
-make: Bad modifier ":[1.]" for variable "LIST"
-LIST:[1.]="" is an error
-make: Bad modifier ":[1]." for variable "LIST"
-LIST:[1].="}" is an error
-LIST:[2]="two"
-LIST:[6]="six"
-LIST:[7]=""
-LIST:[999]=""
-make: Bad modifier ":[-]" for variable "LIST"
-LIST:[-]="" is an error
-make: Bad modifier ":[--]" for variable "LIST"
-LIST:[--]="" is an error
-LIST:[-1]="six"
-LIST:[-2]="five"
-LIST:[-6]="one"
-LIST:[-7]=""
-LIST:[-999]=""
-LONGLIST:[17]="17"
-LONGLIST:[0x11]="17"
-LONGLIST:[021]="17"
-LIST:[0]:[1]="one two three four five six"
-LIST:[*]:[1]="one two three four five six"
-LIST:[@]:[1]="one"
-LIST:[0]:[2]=""
-LIST:[*]:[2]=""
-LIST:[@]:[2]="two"
-LIST:[*]:C/ /,/:[2]=""
-LIST:[*]:C/ /,/:[*]:[2]=""
-LIST:[*]:C/ /,/:[@]:[2]="three"
-LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18"
-make: Bad modifier ":[1.]" for variable "LIST"
-LIST:[1.]="" is an error
-make: Bad modifier ":[1..]" for variable "LIST"
-LIST:[1..]="" is an error
-make: Bad modifier ":[1.. ]" for variable "LIST"
-LIST:[1.. ]="" is an error
-LIST:[1..1]="one"
-make: Bad modifier ":[1..1.]" for variable "LIST"
-LIST:[1..1.]="" is an error
-LIST:[1..2]="one two"
-LIST:[2..1]="two one"
-LIST:[3..-2]="three four five"
-LIST:[-4..4]="three four"
-make: Bad modifier ":[0..1]" for variable "LIST"
-LIST:[0..1]="" is an error
-make: Bad modifier ":[-1..0]" for variable "LIST"
-LIST:[-1..0]="" is an error
-LIST:[-1..1]="six five four three two one"
-LIST:[0..0]="one two three four five six"
-LIST:[3..99]="three four five six"
-LIST:[-3..-99]="four three two one"
-LIST:[-99..-3]="one two three four"
-HASH="#" == "#" ?
-LIST:[${HASH}]="6"
-LIST:[${ZERO}]="one two three four five six"
-LIST:[${ZERO}x${ONE}]="one"
-LIST:[${ONE}]="one"
-LIST:[${MINUSONE}]="six"
-LIST:[${STAR}]="one two three four five six"
-LIST:[${AT}]="one two three four five six"
-make: Bad modifier ":[${EMPTY" for variable "LIST"
-LIST:[${EMPTY}]="" is an error
-LIST:[${LONGLIST:[21]:S/2//}]="one"
-LIST:[${LIST:[#]}]="six"
-LIST:[${LIST:[${HASH}]}]="six"
-LIST:[ -1.. +3]="six five four three"
-LIST:S/ /,/="one two three four five six"
-LIST:S/ /,/W="one,two three four five six"
-LIST:S/ /,/gW="one,two,three,four,five,six"
-EMPTY:S/^/,/=","
-EMPTY:S/^/,/W=","
-LIST:C/ /,/="one two three four five six"
-LIST:C/ /,/W="one,two three four five six"
-LIST:C/ /,/gW="one,two,three,four,five,six"
-EMPTY:C/^/,/=","
-EMPTY:C/^/,/W=","
-LIST:tW="one two three four five six"
-LIST:tw="one two three four five six"
-LIST:tW:C/ /,/="one,two three four five six"
-LIST:tW:C/ /,/g="one,two,three,four,five,six"
-LIST:tW:C/ /,/1g="one,two,three,four,five,six"
-LIST:tw:C/ /,/="one two three four five six"
-LIST:tw:C/ /,/g="one two three four five six"
-LIST:tw:C/ /,/1g="one two three four five six"
-LIST:tw:tW:C/ /,/="one,two three four five six"
-LIST:tW:tw:C/ /,/="one two three four five six"
-exit status 0
diff --git a/contrib/bmake/unit-tests/modword.mk b/contrib/bmake/unit-tests/modword.mk
deleted file mode 100644
index 95bb1fec78c3..000000000000
--- a/contrib/bmake/unit-tests/modword.mk
+++ /dev/null
@@ -1,161 +0,0 @@
-# $NetBSD: modword.mk,v 1.6 2021/03/14 16:00:07 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
-
-LIST= one two three
-LIST+= four five six
-LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
-
-EMPTY= # the space should be ignored
-ESCAPEDSPACE= \ # escaped space before the '#'
-REALLYSPACE:= ${EMPTY:C/^/ /W}
-HASH= \#
-AT= @
-STAR= *
-ZERO= 0
-ONE= 1
-MINUSONE= -1
-
-mod-squarebrackets: mod-squarebrackets-0-star-at \
- mod-squarebrackets-hash \
- mod-squarebrackets-n \
- mod-squarebrackets-start-end \
- mod-squarebrackets-nested \
- mod-squarebrackets-space
-
-mod-squarebrackets-0-star-at:
- @echo 'LIST:[]="${LIST:[]}" is an error'
- @echo 'LIST:[0]="${LIST:[0]}"'
- @echo 'LIST:[0x0]="${LIST:[0x0]}"'
- @echo 'LIST:[000]="${LIST:[000]}"'
- @echo 'LIST:[*]="${LIST:[*]}"'
- @echo 'LIST:[@]="${LIST:[@]}"'
- @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"'
- @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"'
- @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"'
- @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"'
- @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"'
- @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"'
- @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"'
- @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"'
- @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"'
- @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"'
- @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"'
- @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"'
- @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"'
-
-mod-squarebrackets-hash:
- @echo 'EMPTY="${EMPTY}"'
- @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?'
- @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
- @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?'
- @echo 'REALLYSPACE="${REALLYSPACE}"'
- @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?'
- @echo 'LIST:[#]="${LIST:[#]}"'
- @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?'
- @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?'
- @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"'
- @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
- @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
-
-mod-squarebrackets-n:
- @echo 'EMPTY:[1]="${EMPTY:[1]}"'
- @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
- @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
- @echo 'REALLYSPACE="${REALLYSPACE}"'
- @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
- @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
- @echo 'LIST:[1]="${LIST:[1]}"'
- @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
- @echo 'LIST:[1].="${LIST:[1].}" is an error'
- @echo 'LIST:[2]="${LIST:[2]}"'
- @echo 'LIST:[6]="${LIST:[6]}"'
- @echo 'LIST:[7]="${LIST:[7]}"'
- @echo 'LIST:[999]="${LIST:[999]}"'
- @echo 'LIST:[-]="${LIST:[-]}" is an error'
- @echo 'LIST:[--]="${LIST:[--]}" is an error'
- @echo 'LIST:[-1]="${LIST:[-1]}"'
- @echo 'LIST:[-2]="${LIST:[-2]}"'
- @echo 'LIST:[-6]="${LIST:[-6]}"'
- @echo 'LIST:[-7]="${LIST:[-7]}"'
- @echo 'LIST:[-999]="${LIST:[-999]}"'
- @echo 'LONGLIST:[17]="${LONGLIST:[17]}"'
- @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"'
- @echo 'LONGLIST:[021]="${LONGLIST:[021]}"'
- @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"'
- @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"'
- @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"'
- @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"'
- @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"'
- @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"'
- @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"'
- @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"'
- @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
- @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"'
-
-mod-squarebrackets-start-end:
- @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
- @echo 'LIST:[1..]="${LIST:[1..]}" is an error'
- @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error'
- @echo 'LIST:[1..1]="${LIST:[1..1]}"'
- @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
- @echo 'LIST:[1..2]="${LIST:[1..2]}"'
- @echo 'LIST:[2..1]="${LIST:[2..1]}"'
- @echo 'LIST:[3..-2]="${LIST:[3..-2]}"'
- @echo 'LIST:[-4..4]="${LIST:[-4..4]}"'
- @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
- @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
- @echo 'LIST:[-1..1]="${LIST:[-1..1]}"'
- @echo 'LIST:[0..0]="${LIST:[0..0]}"'
- @echo 'LIST:[3..99]="${LIST:[3..99]}"'
- @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"'
- @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"'
-
-mod-squarebrackets-nested:
- @echo 'HASH="${HASH}" == "#" ?'
- @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
- @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
- @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"'
- @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"'
- @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
- @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
- @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
- @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
- @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
- @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
- @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
-
-mod-squarebrackets-space:
- # As of 2020-11-01, it is possible to have spaces before the numbers
- # but not after them. This is an unintended side-effect of using
- # strtol for parsing the numbers.
- @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"'
-
-mod-C-W:
- @echo 'LIST:C/ /,/="${LIST:C/ /,/}"'
- @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"'
- @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"'
- @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"'
- @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"'
-
-mod-S-W:
- @echo 'LIST:S/ /,/="${LIST:S/ /,/}"'
- @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"'
- @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"'
- @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"'
- @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"'
-
-mod-tW-tw:
- @echo 'LIST:tW="${LIST:tW}"'
- @echo 'LIST:tw="${LIST:tw}"'
- @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"'
- @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"'
- @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"'
- @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"'
- @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"'
- @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"'
- @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"'
- @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"'
diff --git a/contrib/bmake/unit-tests/opt-debug-cond.exp b/contrib/bmake/unit-tests/opt-debug-cond.exp
index 39a9383953dd..c2bd1e168bcf 100644
--- a/contrib/bmake/unit-tests/opt-debug-cond.exp
+++ b/contrib/bmake/unit-tests/opt-debug-cond.exp
@@ -1 +1,6 @@
+CondParser_Eval: ${:U12345} > ${:U55555}
+lhs = 12345.000000, rhs = 55555.000000, op = >
+CondParser_Eval: "string" != "string"
+lhs = "string", rhs = "string", op = !=
+CondParser_Eval: "nonempty"
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-cond.mk b/contrib/bmake/unit-tests/opt-debug-cond.mk
index 2b9d1029c7d9..056996cf0ece 100644
--- a/contrib/bmake/unit-tests/opt-debug-cond.mk
+++ b/contrib/bmake/unit-tests/opt-debug-cond.mk
@@ -1,10 +1,24 @@
-# $NetBSD: opt-debug-cond.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-cond.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the -dc command line option, which adds debug logging for the
# evaluation of conditional expressions, such as in .if directives and
# ${cond:?then:else} expressions.
-# TODO: Implementation
+.MAKEFLAGS: -dc
-all:
- @:;
+# expect: CondParser_Eval: ${:U12345} > ${:U55555}
+# expect: lhs = 12345.000000, rhs = 55555.000000, op = >
+.if ${:U12345} > ${:U55555}
+
+# expect: CondParser_Eval: "string" != "string"
+# expect: lhs = "string", rhs = "string", op = !=
+.elif "string" != "string"
+
+# expect: CondParser_Eval: "nonempty"
+.elif "nonempty"
+
+.endif
+
+.MAKEFLAGS: -d0
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-debug-curdir.mk b/contrib/bmake/unit-tests/opt-debug-curdir.mk
index 3c37d2988675..ac5750e7e18c 100644
--- a/contrib/bmake/unit-tests/opt-debug-curdir.mk
+++ b/contrib/bmake/unit-tests/opt-debug-curdir.mk
@@ -1,8 +1,8 @@
-# $NetBSD: opt-debug-curdir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-curdir.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $
#
-# Tests for the -dC command line option, which does nothing, as of 2020-09-05.
+# Tests for the -dC command line option, which does nothing, as of 2020-09-05,
+# as the string "DEBUG(CWD" does not occur in the source code.
-# TODO: Implementation
+.MAKEFLAGS: -dC
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-debug-file.exp b/contrib/bmake/unit-tests/opt-debug-file.exp
index 39a9383953dd..8ff220b3f541 100644
--- a/contrib/bmake/unit-tests/opt-debug-file.exp
+++ b/contrib/bmake/unit-tests/opt-debug-file.exp
@@ -1 +1,12 @@
-exit status 0
+make: "opt-debug-file.mk" line 43: This goes to stderr only, once.
+make: "opt-debug-file.mk" line 45: This goes to stderr only, once.
+make: "opt-debug-file.mk" line 47: This goes to stderr, and in addition to the debug log.
+CondParser_Eval: ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1
+lhs = 1.000000, rhs = 1.000000, op = !=
+make: Missing delimiter for modifier ':S'
+make: Missing delimiter for modifier ':S'
+make: Missing delimiter for modifier ':S'
+CondParser_Eval: ${:!cat opt-debug-file.debuglog!:Mdelimiter:[#]} != 1
+lhs = 1.000000, rhs = 1.000000, op = !=
+Cannot open debug file "/nonexistent-6f21c672-a22d-4ef7/opt-debug-file.debuglog"
+exit status 2
diff --git a/contrib/bmake/unit-tests/opt-debug-file.mk b/contrib/bmake/unit-tests/opt-debug-file.mk
index 1ed477ef3c40..b878c2bcf734 100644
--- a/contrib/bmake/unit-tests/opt-debug-file.mk
+++ b/contrib/bmake/unit-tests/opt-debug-file.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-file.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
+# $NetBSD: opt-debug-file.mk,v 1.8 2022/01/11 19:47:34 rillig Exp $
#
# Tests for the -dF command line option, which redirects the debug log
# to a file instead of writing it to stderr.
@@ -11,14 +11,16 @@
VAR= value ${:Uexpanded}
# Hide the logging output for the remaining actions.
-# As of 2020-10-03, it is not possible to disable debug logging again.
+# Before main.c 1.362 from 2020-10-03, it was not possible to disable debug
+# logging again. Since then, an easier way is the undocumented option '-d0'.
.MAKEFLAGS: -dF/dev/null
# Make sure that the debug logging file contains some logging.
DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!}
# Grmbl. Because of the := operator in the above line, the variable
# value contains ${:Uexpanded}. This variable expression is expanded
-# upon further processing. Therefore, don't read from untrusted input.
+# when it is used in the condition below. Therefore, be careful when storing
+# untrusted input in variables.
#.MAKEFLAGS: -dc -dFstderr
.if !${DEBUG_OUTPUT:tW:M*VAR = value expanded*}
. error ${DEBUG_OUTPUT}
@@ -26,12 +28,44 @@ DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!}
# To get the unexpanded text that was actually written to the debug log
# file, the content of that log file must not be stored in a variable.
-# XXX: In the :M modifier, a dollar is escaped as '$$', not '\$'.
+#
+# XXX: In the :M modifier, a dollar is escaped using '$$', not '\$'. This
+# escaping scheme unnecessarily differs from all other modifiers.
.if !${:!cat opt-debug-file.debuglog!:tW:M*VAR = value $${:Uexpanded}*}
. error
.endif
-_!= rm opt-debug-file.debuglog
+.MAKEFLAGS: -d0
+
+
+# See Parse_Error.
+.MAKEFLAGS: -dFstdout
+. info This goes to stderr only, once.
+.MAKEFLAGS: -dFstderr
+. info This goes to stderr only, once.
+.MAKEFLAGS: -dFopt-debug-file.debuglog
+. info This goes to stderr, and in addition to the debug log.
+.MAKEFLAGS: -dFstderr -d0c
+.if ${:!cat opt-debug-file.debuglog!:Maddition:[#]} != 1
+. error
+.endif
+
+
+# See ApplyModifier_Subst, which calls Error.
+.MAKEFLAGS: -dFstdout
+: This goes to stderr only, once. ${:U:S
+.MAKEFLAGS: -dFstderr
+: This goes to stderr only, once. ${:U:S
+.MAKEFLAGS: -dFopt-debug-file.debuglog
+: This goes to stderr, and in addition to the debug log. ${:U:S
+.MAKEFLAGS: -dFstderr -d0c
+.if ${:!cat opt-debug-file.debuglog!:Mdelimiter:[#]} != 1
+. error
+.endif
+
-all:
- @:;
+# If the debug log file cannot be opened, make prints an error message and
+# exits immediately since the debug log file is usually selected from the
+# command line.
+_:= ${:!rm opt-debug-file.debuglog!}
+.MAKEFLAGS: -dF/nonexistent-6f21c672-a22d-4ef7/opt-debug-file.debuglog
diff --git a/contrib/bmake/unit-tests/opt-debug-hash.exp b/contrib/bmake/unit-tests/opt-debug-hash.exp
index 39a9383953dd..b239399ec44d 100644
--- a/contrib/bmake/unit-tests/opt-debug-hash.exp
+++ b/contrib/bmake/unit-tests/opt-debug-hash.exp
@@ -1 +1,6 @@
-exit status 0
+make: "opt-debug-hash.mk" line 11: Missing argument for ".error"
+make: Fatal errors encountered -- cannot continue
+HashTable targets: size=16 numEntries=0 maxchain=0
+HashTable Global variables: size=16 numEntries=<entries> maxchain=3
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/opt-debug-hash.mk b/contrib/bmake/unit-tests/opt-debug-hash.mk
index c8cb99acd261..8b757ff3f290 100644
--- a/contrib/bmake/unit-tests/opt-debug-hash.mk
+++ b/contrib/bmake/unit-tests/opt-debug-hash.mk
@@ -1,10 +1,11 @@
-# $NetBSD: opt-debug-hash.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-hash.mk,v 1.3 2022/01/22 18:59:24 rillig Exp $
#
# Tests for the -dh command line option, which adds debug logging for
# hash tables. Even more detailed logging is available by compiling
# make with -DDEBUG_HASH_LOOKUP.
-# TODO: Implementation
+.MAKEFLAGS: -dh
-all:
- @:;
+# Force a parse error, to demonstrate the newline character in the diagnostic
+# that had been missing before parse.c 1.655 from 2022-01-22.
+.error
diff --git a/contrib/bmake/unit-tests/opt-debug-parse.exp b/contrib/bmake/unit-tests/opt-debug-parse.exp
index 39a9383953dd..0e11024647a1 100644
--- a/contrib/bmake/unit-tests/opt-debug-parse.exp
+++ b/contrib/bmake/unit-tests/opt-debug-parse.exp
@@ -1 +1,26 @@
+Parse_PushInput: .for loop in opt-debug-parse.mk, line 16
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
+Parsing line 20: .info trace with multi-line .for loop head
+make: "opt-debug-parse.mk" line 20: trace with multi-line .for loop head
+ in .for loop from opt-debug-parse.mk:16 with var = value
+ParseEOF: returning to file opt-debug-parse.mk, line 22
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
+Parsing line 25: .include "/dev/null"
+Parse_PushInput: file /dev/null, line 1
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `null'
+SetFilenameVars: ${.INCLUDEDFROMDIR} = <some-dir> ${.INCLUDEDFROMFILE} = `opt-debug-parse.mk'
+ParseEOF: returning to file opt-debug-parse.mk, line 26
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
+Parse_PushInput: .for loop in opt-debug-parse.mk, line 30
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
+Parsing line 31: .info trace
+make: "opt-debug-parse.mk" line 31: trace
+ in .for loop from opt-debug-parse.mk:30 with a = 1, b = 2, c = 3
+Parsing line 31: .info trace
+make: "opt-debug-parse.mk" line 31: trace
+ in .for loop from opt-debug-parse.mk:30 with a = 4, b = 5, c = 6
+ParseEOF: returning to file opt-debug-parse.mk, line 33
+SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `opt-debug-parse.mk'
+Parsing line 35: .MAKEFLAGS: -d0
+ParseDependency(.MAKEFLAGS: -d0)
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-parse.mk b/contrib/bmake/unit-tests/opt-debug-parse.mk
index 3427b68beb96..c56b46c261b3 100644
--- a/contrib/bmake/unit-tests/opt-debug-parse.mk
+++ b/contrib/bmake/unit-tests/opt-debug-parse.mk
@@ -1,9 +1,37 @@
-# $NetBSD: opt-debug-parse.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-parse.mk,v 1.6 2022/01/08 23:52:26 rillig Exp $
#
# Tests for the -dp command line option, which adds debug logging about
# makefile parsing.
+.MAKEFLAGS: -dp
+
# TODO: Implementation
-all:
- @:;
+# Before parse.c 1.639 from 2022-01-08, PrintStackTrace and other diagnostics
+# printed a wrong line number, using the last line before the loop body, while
+# it should rather be the line number where the .for loop starts.
+#
+# Before parse.c 1.643 from 2022-01-08, PrintStackTrace tried to be too clever
+# by merging stack trace entries, printing confusing line numbers as a result.
+.for \
+ var \
+ in \
+ value
+.info trace with multi-line .for loop head
+.endfor
+
+# Before parse.c 1.461 from 2022-01-08, the debug log said it returned to
+# the line of the '.include' instead of the line following it.
+.include "/dev/null"
+
+
+# In .for loops with multiple variables, the variable details are included in
+# the stack trace, just as with a single variable.
+.for a b c in 1 2 3 ${:U4 5 6}
+.info trace
+.endfor
+
+
+.MAKEFLAGS: -d0
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-debug-var.exp b/contrib/bmake/unit-tests/opt-debug-var.exp
index 39a9383953dd..b8cbddeb30bb 100644
--- a/contrib/bmake/unit-tests/opt-debug-var.exp
+++ b/contrib/bmake/unit-tests/opt-debug-var.exp
@@ -1 +1,7 @@
+Global: ASSIGNED = value
+Global: SUBST =
+Global: SUBST = value
+Var_Parse: y(ASSIGNED) (eval)
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-var.mk b/contrib/bmake/unit-tests/opt-debug-var.mk
index 4d0ef9447324..5b0c5648ab55 100644
--- a/contrib/bmake/unit-tests/opt-debug-var.mk
+++ b/contrib/bmake/unit-tests/opt-debug-var.mk
@@ -1,9 +1,31 @@
-# $NetBSD: opt-debug-var.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-var.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the -dv command line option, which adds debug logging about
# variable assignment and evaluation.
-# TODO: Implementation
+.MAKEFLAGS: -dv
-all:
- @:;
+# expect: Global: ASSIGNED = value
+ASSIGNED= value
+
+# TODO: Explain why the empty assignment "Global: SUBST = " is needed.
+# expect: Global: SUBST = value
+SUBST:= value
+
+.if defined(ASSIGNED)
+.endif
+
+# The usual form of variable expressions is ${VAR}. The form $(VAR) is used
+# less often as it can be visually confused with the shell construct for
+# capturing the output of a subshell, which looks the same.
+#
+# In conditions, a call to the function 'empty' is syntactically similar to
+# the form $(VAR), only that the initial '$' is the 'y' of 'empty'.
+#
+# expect: Var_Parse: y(ASSIGNED) (eval)
+.if !empty(ASSIGNED)
+.endif
+
+.MAKEFLAGS: -d0
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-debug-x-trace.exp b/contrib/bmake/unit-tests/opt-debug-x-trace.exp
index 39a9383953dd..abd4a61e2d82 100644
--- a/contrib/bmake/unit-tests/opt-debug-x-trace.exp
+++ b/contrib/bmake/unit-tests/opt-debug-x-trace.exp
@@ -1 +1,3 @@
++ echo 'Counting 1 2 3 4 5 6 7'
+Counting 1 2 3 4 5 6 7
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-debug-x-trace.mk b/contrib/bmake/unit-tests/opt-debug-x-trace.mk
index 0936ba506966..54c04a9a4cf8 100644
--- a/contrib/bmake/unit-tests/opt-debug-x-trace.mk
+++ b/contrib/bmake/unit-tests/opt-debug-x-trace.mk
@@ -1,10 +1,12 @@
-# $NetBSD: opt-debug-x-trace.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $
+# $NetBSD: opt-debug-x-trace.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the -dx command line option, which runs shell commands with
# the -x option, thereby printing the actual commands as they are
# executed.
-# TODO: Implementation
+.MAKEFLAGS: -dx
-all:
- @:;
+# expect: + echo 'Counting 1 2 3 4 5 6 7'
+# expect: Counting 1 2 3 4 5 6 7
+all: .PHONY
+ @echo 'Counting ${:U:range=7}'
diff --git a/contrib/bmake/unit-tests/opt-define.mk b/contrib/bmake/unit-tests/opt-define.mk
index ce0516ba44bc..7c4bbc179316 100644
--- a/contrib/bmake/unit-tests/opt-define.mk
+++ b/contrib/bmake/unit-tests/opt-define.mk
@@ -1,8 +1,28 @@
-# $NetBSD: opt-define.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-define.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
-# Tests for the -D command line option.
+# Tests for the -D command line option, which defines global variables to the
+# value 1, like in the C preprocessor.
-# TODO: Implementation
+.MAKEFLAGS: -DVAR
-all:
- @:;
+# The variable has the exact value "1", not "1.0".
+.if ${VAR} != "1"
+. error
+.endif
+
+# The variable can be overwritten by assigning another value to it. This
+# would not be possible if the variable had been specified on the command line
+# as 'VAR=1' instead of '-DVAR'.
+VAR= overwritten
+.if ${VAR} != "overwritten"
+. error
+.endif
+
+# The variable can be undefined. If the variable had been defined in the
+# "Internal" scope instead, undefining it would have no effect.
+.undef VAR
+.if defined(VAR)
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-env.exp b/contrib/bmake/unit-tests/opt-env.exp
index 39a9383953dd..e584a77e01b9 100644
--- a/contrib/bmake/unit-tests/opt-env.exp
+++ b/contrib/bmake/unit-tests/opt-env.exp
@@ -1 +1,5 @@
-exit status 0
+make: "opt-env.mk" line 9: Malformed conditional (${FROM_ENV} != value-from-env)
+make: "opt-env.mk" line 16: value-from-mk
+
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/opt-env.mk b/contrib/bmake/unit-tests/opt-env.mk
index 32e95ef41f5a..0cfa1aa6470f 100644
--- a/contrib/bmake/unit-tests/opt-env.mk
+++ b/contrib/bmake/unit-tests/opt-env.mk
@@ -1,8 +1,45 @@
-# $NetBSD: opt-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-env.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the -e command line option.
-# TODO: Implementation
+# The variable FROM_ENV is defined in ./Makefile.
-all:
- @:;
+.MAKEFLAGS: -e
+
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+# Try to override the variable; this does not have any effect.
+FROM_ENV= value-from-mk
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+# Try to append to the variable; this also doesn't have any effect.
+FROM_ENV+= appended
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+# The default assignment also cannot change the variable.
+FROM_ENV?= default
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+# Neither can the assignment modifiers.
+.if ${FROM_ENV::=from-condition}
+.endif
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+# Even .undef doesn't work since it only affects the global scope,
+# which is independent from the environment variables.
+.undef FROM_ENV
+.if ${FROM_ENV} != value-from-env
+. error ${FROM_ENV}
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.exp b/contrib/bmake/unit-tests/opt-jobs-internal.exp
index 39a9383953dd..470bdbddd0f8 100644
--- a/contrib/bmake/unit-tests/opt-jobs-internal.exp
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.exp
@@ -1 +1,6 @@
-exit status 0
+make: internal error -- J option malformed (garbage)
+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 ...]
+exit status 2
diff --git a/contrib/bmake/unit-tests/opt-jobs-internal.mk b/contrib/bmake/unit-tests/opt-jobs-internal.mk
index 5426807ca98b..44755a797751 100644
--- a/contrib/bmake/unit-tests/opt-jobs-internal.mk
+++ b/contrib/bmake/unit-tests/opt-jobs-internal.mk
@@ -1,8 +1,9 @@
-# $NetBSD: opt-jobs-internal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-jobs-internal.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the (intentionally undocumented) -J command line option.
+#
+# Only test the error handling here, the happy path is covered in other tests
+# as a side effect.
-# TODO: Implementation
-
-all:
- @:;
+# expect: make: internal error -- J option malformed (garbage)
+.MAKEFLAGS: -Jgarbage
diff --git a/contrib/bmake/unit-tests/opt-raw.mk b/contrib/bmake/unit-tests/opt-raw.mk
index d3591bb99dab..91caffcd72ae 100644
--- a/contrib/bmake/unit-tests/opt-raw.mk
+++ b/contrib/bmake/unit-tests/opt-raw.mk
@@ -1,8 +1,14 @@
-# $NetBSD: opt-raw.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-raw.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
-# Tests for the -r command line option.
+# Tests for the -r command line option, which skips the system-defined default
+# rules from <sys.mk>.
-# TODO: Implementation
+# To provide a clean testing environment without unintended side effects,
+# these unit tests run make with the option '-r' by default. This means there
+# are no predefined suffixes and no predefined tools.
-all:
- @:;
+.if defined(CC)
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/opt-silent.exp b/contrib/bmake/unit-tests/opt-silent.exp
index 39a9383953dd..8863b8a2965d 100644
--- a/contrib/bmake/unit-tests/opt-silent.exp
+++ b/contrib/bmake/unit-tests/opt-silent.exp
@@ -1 +1,3 @@
+message
+silent message
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-silent.mk b/contrib/bmake/unit-tests/opt-silent.mk
index 7822d46ac48a..01e5b18e2b12 100644
--- a/contrib/bmake/unit-tests/opt-silent.mk
+++ b/contrib/bmake/unit-tests/opt-silent.mk
@@ -1,8 +1,10 @@
-# $NetBSD: opt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-silent.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the -s command line option.
-# TODO: Implementation
+.MAKEFLAGS: -s
+# No matter whether a command is prefixed by '@' or not, it is not echoed.
all:
- @:;
+ echo 'message'
+ @echo 'silent message'
diff --git a/contrib/bmake/unit-tests/opt-version.exp b/contrib/bmake/unit-tests/opt-version.exp
new file mode 100644
index 000000000000..1636aafc11e5
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-version.exp
@@ -0,0 +1,2 @@
+
+exit status 0
diff --git a/contrib/bmake/unit-tests/opt-version.mk b/contrib/bmake/unit-tests/opt-version.mk
new file mode 100644
index 000000000000..51a4e8a1a0aa
--- /dev/null
+++ b/contrib/bmake/unit-tests/opt-version.mk
@@ -0,0 +1,12 @@
+# $NetBSD: opt-version.mk,v 1.1 2021/12/23 11:05:59 rillig Exp $
+#
+# Tests for the command line option '--version', which outputs the version
+# number of make. NetBSD's make does not have a version number, but the bmake
+# distribution created from it has.
+
+# As of 2021-12-23, the output is a single empty line since the '--' does not
+# end the command line options. Command line parsing then continues as if
+# nothing had happened, and the '-version' is split into '-v ersion', which is
+# interpreted as "print the expanded variable named 'ersion'".
+
+.MAKEFLAGS: --version
diff --git a/contrib/bmake/unit-tests/opt-where-am-i.exp b/contrib/bmake/unit-tests/opt-where-am-i.exp
index 39a9383953dd..e64df44b83b7 100644
--- a/contrib/bmake/unit-tests/opt-where-am-i.exp
+++ b/contrib/bmake/unit-tests/opt-where-am-i.exp
@@ -1 +1,4 @@
+make: Entering directory `/'
+make: Leaving directory `/'
+make: Leaving directory `<curdir>'
exit status 0
diff --git a/contrib/bmake/unit-tests/opt-where-am-i.mk b/contrib/bmake/unit-tests/opt-where-am-i.mk
index 9158a598174c..f1eeca920a32 100644
--- a/contrib/bmake/unit-tests/opt-where-am-i.mk
+++ b/contrib/bmake/unit-tests/opt-where-am-i.mk
@@ -1,8 +1,14 @@
-# $NetBSD: opt-where-am-i.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-where-am-i.mk,v 1.4 2022/01/27 02:24:46 sjg Exp $
#
-# Tests for the -w command line option.
+# Tests for the -w command line option, which outputs the current directory
+# at the beginning and end of running make. This is useful when building
+# large source trees that involve several nested make calls.
-# TODO: Implementation
+# The first "Entering directory" is missing since the below .MAKEFLAGS comes
+# too late for it.
+.MAKEFLAGS: -w
all:
- @:;
+.if ${.CURDIR} != "/"
+ @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -r -f ${MAKEFILE:tA} -C /
+.endif
diff --git a/contrib/bmake/unit-tests/parse.exp b/contrib/bmake/unit-tests/parse.exp
new file mode 100644
index 000000000000..5807f9c17e2c
--- /dev/null
+++ b/contrib/bmake/unit-tests/parse.exp
@@ -0,0 +1,5 @@
+make: "parse.mk" line 7: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts
+make: "parse.mk" line 14: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/contrib/bmake/unit-tests/parse.mk b/contrib/bmake/unit-tests/parse.mk
new file mode 100644
index 000000000000..9dccc7f5b7ce
--- /dev/null
+++ b/contrib/bmake/unit-tests/parse.mk
@@ -0,0 +1,14 @@
+# $NetBSD: parse.mk,v 1.2 2022/01/22 17:10:51 rillig Exp $
+#
+# Test those parts of the parsing that do not belong in any of the other
+# categories.
+
+# expect+1: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts
+<<<<<< old
+
+# No diagnostic since the following line is parsed as a variable assignment,
+# even though the variable name is empty. See also varname-empty.mk.
+====== middle
+
+# expect+1: Makefile appears to contain unresolved CVS/RCS/??? merge conflicts
+>>>>>> new
diff --git a/contrib/bmake/unit-tests/posix.mk b/contrib/bmake/unit-tests/posix.mk
index fc4cbead3263..43219258306e 100644
--- a/contrib/bmake/unit-tests/posix.mk
+++ b/contrib/bmake/unit-tests/posix.mk
@@ -1,4 +1,4 @@
-# $NetBSD: posix.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $
+# $NetBSD: posix.mk,v 1.3 2022/01/23 18:15:29 rillig Exp $
all: x plus subs err
@@ -14,11 +14,10 @@ plus:
subs:
@echo make -n
- @${.MAKE} -f ${MAKEFILE} -n plus
+ @${.MAKE} -r -f ${MAKEFILE} -n plus
@echo make -n -j1
- @${.MAKE} -f ${MAKEFILE} -n -j1 plus
+ @${.MAKE} -r -f ${MAKEFILE} -n -j1 plus
err:
@(echo Now we expect an error...; exit 1)
@echo "Oops! you shouldn't see this!"
-
diff --git a/contrib/bmake/unit-tests/sh.mk b/contrib/bmake/unit-tests/sh.mk
index f79a4099e990..3bbedf4d678a 100644
--- a/contrib/bmake/unit-tests/sh.mk
+++ b/contrib/bmake/unit-tests/sh.mk
@@ -1,7 +1,10 @@
-# $NetBSD: sh.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: sh.mk,v 1.3 2022/01/23 16:09:38 rillig Exp $
#
# Tests for running shell commands from the targets, or from the != variable
# assignment operator or the :sh variable modifier.
+#
+# See also:
+# var-op-shell.mk for the assignment operator '!='
# TODO: Implementation
diff --git a/contrib/bmake/unit-tests/suff-incomplete.exp b/contrib/bmake/unit-tests/suff-incomplete.exp
index 2331436d378e..acb5f0542dbe 100644
--- a/contrib/bmake/unit-tests/suff-incomplete.exp
+++ b/contrib/bmake/unit-tests/suff-incomplete.exp
@@ -1,17 +1,17 @@
-ParseReadLine (9): '.SUFFIXES:'
+Parsing line 9: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-ParseReadLine (11): '.SUFFIXES: .a .b .c'
+Parsing line 11: .SUFFIXES: .a .b .c
ParseDependency(.SUFFIXES: .a .b .c)
Adding suffix ".a"
Adding suffix ".b"
Adding suffix ".c"
-ParseReadLine (17): '.a.b:'
+Parsing line 17: .a.b:
ParseDependency(.a.b:)
defining transformation from `.a' to `.b'
inserting ".a" (1) at end of list
inserting ".b" (2) at end of list
-ParseReadLine (21): '.a.c: ${.PREFIX}.dependency'
+Parsing line 21: .a.c: ${.PREFIX}.dependency
deleting incomplete transformation from `.a' to `.b'
ParseDependency(.a.c: ${.PREFIX}.dependency)
defining transformation from `.a' to `.c'
@@ -20,10 +20,10 @@ inserting ".c" (3) at end of list
# LinkSource: added child .a.c - ${.PREFIX}.dependency
# .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none
# ${.PREFIX}.dependency, unmade, type none, flags none
-ParseReadLine (23): '.DEFAULT:'
+Parsing line 23: .DEFAULT:
transformation .a.c complete
ParseDependency(.DEFAULT:)
-ParseReadLine (24): ' : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.'
+Parsing line 24: : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default.
transformation .DEFAULT complete
Wildcard expanding "all"...
SuffFindDeps "all"
diff --git a/contrib/bmake/unit-tests/suff-main-several.exp b/contrib/bmake/unit-tests/suff-main-several.exp
index b20f5ecf1143..7d499bcf5040 100644
--- a/contrib/bmake/unit-tests/suff-main-several.exp
+++ b/contrib/bmake/unit-tests/suff-main-several.exp
@@ -1,11 +1,11 @@
-ParseReadLine (8): '.1.2 .1.3 .1.4:'
+Parsing line 8: .1.2 .1.3 .1.4:
ParseDependency(.1.2 .1.3 .1.4:)
Setting main node to ".1.2"
-ParseReadLine (9): ' : Making ${.TARGET} from ${.IMPSRC}.'
-ParseReadLine (14): 'next-main:'
+Parsing line 9: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing line 14: next-main:
ParseDependency(next-main:)
-ParseReadLine (15): ' : Making ${.TARGET}'
-ParseReadLine (19): '.SUFFIXES: .1 .2 .3 .4'
+Parsing line 15: : Making ${.TARGET}
+Parsing line 19: .SUFFIXES: .1 .2 .3 .4
ParseDependency(.SUFFIXES: .1 .2 .3 .4)
Adding suffix ".1"
Adding suffix ".2"
@@ -26,42 +26,42 @@ defining transformation from `.1' to `.4'
inserting ".1" (1) at end of list
inserting ".4" (4) at end of list
Setting main node to "next-main"
-ParseReadLine (24): '.SUFFIXES:'
+Parsing line 24: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-ParseReadLine (32): '.SUFFIXES: .4 .3 .2 .1'
+Parsing line 32: .SUFFIXES: .4 .3 .2 .1
ParseDependency(.SUFFIXES: .4 .3 .2 .1)
Adding suffix ".4"
Adding suffix ".3"
Adding suffix ".2"
Adding suffix ".1"
-ParseReadLine (33): '.SUFFIXES:'
+Parsing line 33: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-ParseReadLine (34): '.SUFFIXES: .1 .2 .3 .4'
+Parsing line 34: .SUFFIXES: .1 .2 .3 .4
ParseDependency(.SUFFIXES: .1 .2 .3 .4)
Adding suffix ".1"
Adding suffix ".2"
Adding suffix ".3"
Adding suffix ".4"
-ParseReadLine (35): '.SUFFIXES:'
+Parsing line 35: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-ParseReadLine (36): '.SUFFIXES: .4 .3 .2 .1'
+Parsing line 36: .SUFFIXES: .4 .3 .2 .1
ParseDependency(.SUFFIXES: .4 .3 .2 .1)
Adding suffix ".4"
Adding suffix ".3"
Adding suffix ".2"
Adding suffix ".1"
-ParseReadLine (38): 'suff-main-several.1:'
+Parsing line 38: suff-main-several.1:
ParseDependency(suff-main-several.1:)
-ParseReadLine (39): ' : Making ${.TARGET} out of nothing.'
-ParseReadLine (40): 'next-main: suff-main-several.{2,3,4}'
+Parsing line 39: : Making ${.TARGET} out of nothing.
+Parsing line 40: next-main: suff-main-several.{2,3,4}
ParseDependency(next-main: suff-main-several.{2,3,4})
# LinkSource: added child next-main - suff-main-several.{2,3,4}
# next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none
# suff-main-several.{2,3,4}, unmade, type none, flags none
-ParseReadLine (42): '.MAKEFLAGS: -d0 -dg1'
+Parsing line 42: .MAKEFLAGS: -d0 -dg1
ParseDependency(.MAKEFLAGS: -d0 -dg1)
#*** Input graph:
# .1.2, unmade, type OP_TRANSFORM, flags none
diff --git a/contrib/bmake/unit-tests/suff-rebuild.exp b/contrib/bmake/unit-tests/suff-rebuild.exp
index 7ef53ae2e151..8c0979537524 100644
--- a/contrib/bmake/unit-tests/suff-rebuild.exp
+++ b/contrib/bmake/unit-tests/suff-rebuild.exp
@@ -1,36 +1,36 @@
-ParseReadLine (10): '.SUFFIXES:'
+Parsing line 10: .SUFFIXES:
ParseDependency(.SUFFIXES:)
Clearing all suffixes
-ParseReadLine (12): '.SUFFIXES: .a .b .c'
+Parsing line 12: .SUFFIXES: .a .b .c
ParseDependency(.SUFFIXES: .a .b .c)
Adding suffix ".a"
Adding suffix ".b"
Adding suffix ".c"
-ParseReadLine (14): 'suff-rebuild-example.a:'
+Parsing line 14: suff-rebuild-example.a:
ParseDependency(suff-rebuild-example.a:)
Adding "suff-rebuild-example.a" to all targets.
-ParseReadLine (15): ' : Making ${.TARGET} out of nothing.'
-ParseReadLine (17): '.a.b:'
+Parsing line 15: : Making ${.TARGET} out of nothing.
+Parsing line 17: .a.b:
ParseDependency(.a.b:)
defining transformation from `.a' to `.b'
inserting ".a" (1) at end of list
inserting ".b" (2) at end of list
-ParseReadLine (18): ' : Making ${.TARGET} from ${.IMPSRC}.'
-ParseReadLine (19): '.b.c:'
+Parsing line 18: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing line 19: .b.c:
transformation .a.b complete
ParseDependency(.b.c:)
defining transformation from `.b' to `.c'
inserting ".b" (2) at end of list
inserting ".c" (3) at end of list
-ParseReadLine (20): ' : Making ${.TARGET} from ${.IMPSRC}.'
-ParseReadLine (21): '.c:'
+Parsing line 20: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing line 21: .c:
transformation .b.c complete
ParseDependency(.c:)
defining transformation from `.c' to `'
inserting ".c" (3) at end of list
inserting "" (0) at end of list
-ParseReadLine (22): ' : Making ${.TARGET} from ${.IMPSRC}.'
-ParseReadLine (44): '.SUFFIXES: .c .b .a'
+Parsing line 22: : Making ${.TARGET} from ${.IMPSRC}.
+Parsing line 44: .SUFFIXES: .c .b .a
transformation .c complete
ParseDependency(.SUFFIXES: .c .b .a)
Adding ".END" to all targets.
diff --git a/contrib/bmake/unit-tests/var-class-cmdline.exp b/contrib/bmake/unit-tests/var-class-cmdline.exp
deleted file mode 100644
index 6df2155ca7eb..000000000000
--- a/contrib/bmake/unit-tests/var-class-cmdline.exp
+++ /dev/null
@@ -1,4 +0,0 @@
-make: "var-class-cmdline.mk" line 67: global
-make: "var-class-cmdline.mk" line 76: makeflags
-makeflags
-exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-global.mk b/contrib/bmake/unit-tests/var-class-global.mk
deleted file mode 100644
index 81345ffda463..000000000000
--- a/contrib/bmake/unit-tests/var-class-global.mk
+++ /dev/null
@@ -1,8 +0,0 @@
-# $NetBSD: var-class-global.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
-#
-# Tests for global variables, which are the most common variables.
-
-# TODO: Implementation
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/var-class-local.exp b/contrib/bmake/unit-tests/var-class-local.exp
deleted file mode 100644
index db85b47cae06..000000000000
--- a/contrib/bmake/unit-tests/var-class-local.exp
+++ /dev/null
@@ -1,5 +0,0 @@
-: 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/contrib/bmake/unit-tests/var-class-local.mk b/contrib/bmake/unit-tests/var-class-local.mk
deleted file mode 100644
index f9d56e539ff0..000000000000
--- a/contrib/bmake/unit-tests/var-class-local.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# $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 $@.
-
-# TODO: Implementation
-
-# Ensure that the name of the variable is exactly the given one.
-# The variable "@" is an alias for ".TARGET", so the implementation might
-# canonicalize these aliases at some point, and that might be surprising.
-# This aliasing happens for single-character variable names like $@ or $<
-# (see VarFind, CanonicalVarname), but not for braced or parenthesized
-# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse,
-# ParseVarname).
-.if ${@:L} != "@"
-. error
-.endif
-.if ${.TARGET:L} != ".TARGET"
-. error
-.endif
-.if ${@F:L} != "@F"
-. error
-.endif
-.if ${@D:L} != "@D"
-. error
-.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".
- : ${.TARGET} ${.TARGET::=overwritten}${.TARGET}
diff --git a/contrib/bmake/unit-tests/var-class.exp b/contrib/bmake/unit-tests/var-class.exp
deleted file mode 100644
index 39a9383953dd..000000000000
--- a/contrib/bmake/unit-tests/var-class.exp
+++ /dev/null
@@ -1 +0,0 @@
-exit status 0
diff --git a/contrib/bmake/unit-tests/var-class.mk b/contrib/bmake/unit-tests/var-class.mk
deleted file mode 100644
index b20fca565e16..000000000000
--- a/contrib/bmake/unit-tests/var-class.mk
+++ /dev/null
@@ -1,9 +0,0 @@
-# $NetBSD: var-class.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
-#
-# Tests for the different variable classes (local, command-line, global,
-# environment), and which of them takes precedence over the others.
-
-# TODO: Implementation
-
-all:
- @:;
diff --git a/contrib/bmake/unit-tests/var-eval-short.exp b/contrib/bmake/unit-tests/var-eval-short.exp
index c476c93125e3..f574a6444e1b 100644
--- a/contrib/bmake/unit-tests/var-eval-short.exp
+++ b/contrib/bmake/unit-tests/var-eval-short.exp
@@ -1,16 +1,16 @@
-make: "var-eval-short.mk" line 41: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar
-make: "var-eval-short.mk" line 41: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@})
-make: "var-eval-short.mk" line 81: Invalid time value at "${FAIL}}"
-make: "var-eval-short.mk" line 81: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}})
-make: "var-eval-short.mk" line 95: Invalid time value at "${FAIL}}"
-make: "var-eval-short.mk" line 95: Malformed conditional (0 && ${:Uword:localtime=${FAIL}})
+make: "var-eval-short.mk" line 44: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar
+make: "var-eval-short.mk" line 44: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@})
+make: "var-eval-short.mk" line 84: Invalid time value at "${FAIL}}"
+make: "var-eval-short.mk" line 84: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}})
+make: "var-eval-short.mk" line 98: Invalid time value at "${FAIL}}"
+make: "var-eval-short.mk" line 98: Malformed conditional (0 && ${:Uword:localtime=${FAIL}})
CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else}
Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only)
Parsing modifier ${0:?...}
Modifier part: "${FAIL}then"
Modifier part: "${FAIL}else"
Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined)
-ParseReadLine (160): 'DEFINED= defined'
+Parsing line 163: DEFINED= defined
Global: DEFINED = defined
CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only)
@@ -20,7 +20,7 @@ Parsing modifier ${DEFINED:?...}
Modifier part: "${FAIL}then"
Modifier part: "${FAIL}else"
Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular)
-ParseReadLine (163): '.MAKEFLAGS: -d0'
+Parsing line 166: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0
diff --git a/contrib/bmake/unit-tests/var-eval-short.mk b/contrib/bmake/unit-tests/var-eval-short.mk
index 1b9ccc714736..a099b6871d1e 100644
--- a/contrib/bmake/unit-tests/var-eval-short.mk
+++ b/contrib/bmake/unit-tests/var-eval-short.mk
@@ -1,12 +1,13 @@
-# $NetBSD: var-eval-short.mk,v 1.7 2021/09/07 20:41:58 rillig Exp $
+# $NetBSD: var-eval-short.mk,v 1.8 2021/12/27 18:54:19 rillig Exp $
#
# Tests for each variable modifier to ensure that they only do the minimum
-# necessary computations. If the result of the expression is not needed, they
-# should only parse the modifier but not actually evaluate it.
+# necessary computations. If the result of the expression is irrelevant,
+# the modifier should only be parsed. The modifier should not be evaluated,
+# but if it is evaluated for simplicity of the code (such as ':ts'), it must
+# not have any observable side effects.
#
# See also:
# var.c, the comment starting with 'The ApplyModifier functions'
-# ApplyModifier, for the order of the modifiers
# ParseModifierPart, for evaluating nested expressions
# cond-short.mk
@@ -17,6 +18,8 @@ FAIL= ${:!echo unexpected 1>&2!}
# is ignored as well. To do that, it is necessary to step through the code of
# each modifier.
+# TODO: Test the modifiers in the same order as they appear in ApplyModifier.
+
.if 0 && ${FAIL}
.endif
diff --git a/contrib/bmake/unit-tests/var-op-expand.mk b/contrib/bmake/unit-tests/var-op-expand.mk
index 237f7baf1c62..1d905aeb3757 100644
--- a/contrib/bmake/unit-tests/var-op-expand.mk
+++ b/contrib/bmake/unit-tests/var-op-expand.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-op-expand.mk,v 1.15 2021/11/30 23:52:19 rillig Exp $
+# $NetBSD: var-op-expand.mk,v 1.16 2021/12/28 10:47:00 rillig Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
@@ -9,7 +9,7 @@
# Force the test results to be independent of the default value of this
# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
# distribution and pkgsrc/devel/bmake.
-.MAKE.SAVE_DOLLARS:= yes
+.MAKE.SAVE_DOLLARS:= yes
# If the right-hand side does not contain a dollar sign, the ':=' assignment
# operator has the same effect as the '=' assignment operator.
diff --git a/contrib/bmake/unit-tests/var-op-shell.exp b/contrib/bmake/unit-tests/var-op-shell.exp
index 890bfa43c38e..0e9bd2cbc35a 100644
--- a/contrib/bmake/unit-tests/var-op-shell.exp
+++ b/contrib/bmake/unit-tests/var-op-shell.exp
@@ -1,7 +1,11 @@
-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 56: warning: "kill $$" exited on a signal
+make: "var-op-shell.mk" line 31: warning: "echo "failed"; false" returned non-zero status
+make: "var-op-shell.mk" line 37: warning: "false" returned non-zero status
+make: "var-op-shell.mk" line 59: warning: "kill $$" exited on a signal
/bin/no/such/command: not found
-make: "var-op-shell.mk" line 62: warning: "/bin/no/such/command" returned non-zero status
+make: "var-op-shell.mk" line 65: warning: "/bin/no/such/command" returned non-zero status
stderr
+Capturing the output of command "echo '$$$$'"
+Global: OUTPUT = $$$$
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/contrib/bmake/unit-tests/var-op-shell.mk b/contrib/bmake/unit-tests/var-op-shell.mk
index 0fdc54fc6041..bd2a48f17cc4 100644
--- a/contrib/bmake/unit-tests/var-op-shell.mk
+++ b/contrib/bmake/unit-tests/var-op-shell.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-op-shell.mk,v 1.4 2021/02/06 04:55:08 sjg Exp $
+# $NetBSD: var-op-shell.mk,v 1.6 2022/01/10 20:32:29 rillig Exp $
#
# Tests for the != variable assignment operator, which runs its right-hand
# side through the shell.
@@ -15,7 +15,7 @@ OUTPUT!= echo "success"'ful'
# 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.
+# The error message is still in Cmd_Exec 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
@@ -24,7 +24,10 @@ OUTPUT!= true
.endif
# The output of a shell command that failed is processed nevertheless.
-# TODO: Make this an error in lint mode.
+# Unlike the other places that run external commands (expression modifier
+# '::!=', expression modifier ':!...!'), a failed command generates only a
+# warning, not an "error". These "errors" are ignored in default mode, for
+# compatibility, but not in lint mode (-dL).
OUTPUT!= echo "failed"; false
.if ${OUTPUT} != "failed"
. error
@@ -78,4 +81,10 @@ OUTPUT!= echo '$$$$$$$$'
. error
.endif
+
+# As a debugging aid, log the exact command that is run via the shell.
+.MAKEFLAGS: -dv
+OUTPUT!= echo '$$$$$$$$'
+.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/var-op-sunsh.mk b/contrib/bmake/unit-tests/var-op-sunsh.mk
index 0d15b8c88b92..956c1192616c 100644
--- a/contrib/bmake/unit-tests/var-op-sunsh.mk
+++ b/contrib/bmake/unit-tests/var-op-sunsh.mk
@@ -1,8 +1,8 @@
-# $NetBSD: var-op-sunsh.mk,v 1.8 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: var-op-sunsh.mk,v 1.9 2022/01/16 09:38:04 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 !=
-# assignment operator, adopted from Sun make.
+# assignment operator, adopted from SUN make.
.MAKEFLAGS: -dL # Enable sane error messages
@@ -24,9 +24,12 @@ VAR :sh = echo colon-sh-spaced
# This was neither documented by NetBSD make nor by Solaris make and was
# an implementation error.
#
-# Since 2020-10-04, this is a normal variable assignment using the '='
-# assignment operator.
+# Since 2020-10-04, this is a normal variable assignment to the variable named
+# 'VAR:shell', using the '=' assignment operator.
VAR:shell= echo colon-shell
+# The variable name needs to be generated using a ${:U...} expression because
+# it is not possible to express the ':' as part of a literal variable name,
+# see ParseVarname.
.if ${${:UVAR\:shell}} != "echo colon-shell"
. error
.endif
@@ -95,30 +98,52 @@ VAR :sh :sh :sh :sh= echo multiple
# expanding nested expressions, the token ' :sh' can be used to add arbitrary
# text between the variable name and the assignment operator, it just has to
# be enclosed in braces or parentheses.
+#
+# Since the text to the left of the assignment operator '=' does not end with
+# ':sh', the effective assignment operator becomes '=', not '!='.
VAR :sh(Put a comment here)= comment in parentheses
.if ${VAR} != "comment in parentheses"
. error
.endif
# The unintended comment can include multiple levels of nested braces and
-# parentheses, they don't even need to be balanced since they are only
-# counted by Parse_IsVar and ignored by Parse_Var.
+# parentheses. Braces and parentheses are interchangeable, that is, a '(' can
+# be closed by either ')' or '}'. These braces and parentheses are only
+# counted by Parse_IsVar, in particular Parse_Var doesn't see them.
VAR :sh{Put}((((a}{comment}}}}{here}= comment in braces
.if ${VAR} != "comment in braces"
. error
.endif
-# Syntactically, the ':sh' modifier can be combined with the '+=' assignment
-# operator. In such a case the ':sh' modifier is silently ignored.
+# The assignment modifier ':sh' can be combined with the assignment operator
+# '+='. In such a case the ':sh' is silently ignored, and the effective
+# assignment operator is '+='.
#
-# XXX: This combination should not be allowed at all.
+# XXX: This combination should not be allowed at all, as it is confusing.
VAR= one
VAR :sh += echo two
.if ${VAR} != "one echo two"
. error ${VAR}
.endif
-# TODO: test VAR:sh!=command
+# The assignment modifier ':sh' can be combined with the assignment operator
+# '!='. In such a case the ':sh' is silently ignored, and the effective
+# assignment operator is '!=', just like with '+=' or the other compound
+# assignment operators.
+#
+# XXX: This combination should not be allowed at all, as it is confusing.
+VAR :sh != echo echo echo echo spaces-around
+.if ${VAR} != "echo echo echo spaces-around"
+. error ${VAR}
+.endif
+
+# If there is no space between the variable name and the assignment modifier
+# ':sh', the ':sh' becomes part of the variable name, as the parser only
+# expects a single assignment modifier to the left of the '=', which in this
+# case is the '!'.
+VAR:sh != echo echo echo echo space-after
+.if ${${:UVAR\:sh}} != "echo echo echo space-after"
+. error ${${:UVAR\:sh}}
+.endif
-all:
- @:;
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/var-recursive.exp b/contrib/bmake/unit-tests/var-recursive.exp
index 9739d8bcca13..44c381f94ff9 100644
--- a/contrib/bmake/unit-tests/var-recursive.exp
+++ b/contrib/bmake/unit-tests/var-recursive.exp
@@ -1,12 +1,19 @@
make: "var-recursive.mk" line 20: still there
Variable DIRECT is recursive.
+ in var-recursive.mk:21
make: stopped in unit-tests
Variable INDIRECT1 is recursive.
+ in var-recursive.mk:28
make: stopped in unit-tests
make: "var-recursive.mk" line 35: ok
Variable V is recursive.
+ in var-recursive.mk:43
+
+make: stopped in unit-tests
+: OK
+In a command near "var-recursive.mk" line 55: Variable VAR is recursive.
make: stopped in unit-tests
exit status 0
diff --git a/contrib/bmake/unit-tests/var-recursive.mk b/contrib/bmake/unit-tests/var-recursive.mk
index da1fb696d655..1825c8a63120 100644
--- a/contrib/bmake/unit-tests/var-recursive.mk
+++ b/contrib/bmake/unit-tests/var-recursive.mk
@@ -1,9 +1,9 @@
-# $NetBSD: var-recursive.mk,v 1.2 2020/10/31 13:45:00 rillig Exp $
+# $NetBSD: var-recursive.mk,v 1.4 2022/01/29 10:21:26 rillig Exp $
#
# Tests for variable expressions that refer to themselves and thus
# cannot be evaluated.
-TESTS= direct indirect conditional short
+TESTS= direct indirect conditional short target
# Since make exits immediately when it detects a recursive expression,
# the actual tests are run in sub-makes.
@@ -42,6 +42,18 @@ CONDITIONAL= ${1:?ok:${CONDITIONAL}}
V= $V
. info $V
+.elif ${TEST} == target
+
+# If a recursive variable is accessed in a command of a target, the makefiles
+# are not parsed anymore, so there is no location information from the
+# .includes and .for directives. In such a case, use the location of the last
+# command of the target to provide at least a hint to the location.
+VAR= ${VAR}
+target:
+ : OK
+ : ${VAR}
+ : OK
+
.else
. error Unknown test "${TEST}"
.endif
diff --git a/contrib/bmake/unit-tests/var-scope-cmdline.exp b/contrib/bmake/unit-tests/var-scope-cmdline.exp
new file mode 100644
index 000000000000..a1227a1dd1f2
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-scope-cmdline.exp
@@ -0,0 +1,4 @@
+make: "var-scope-cmdline.mk" line 67: global
+make: "var-scope-cmdline.mk" line 76: makeflags
+makeflags
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-class-cmdline.mk b/contrib/bmake/unit-tests/var-scope-cmdline.mk
index 679e051bb242..1f4a3e700253 100644
--- a/contrib/bmake/unit-tests/var-class-cmdline.mk
+++ b/contrib/bmake/unit-tests/var-scope-cmdline.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-class-cmdline.mk,v 1.5 2021/02/23 21:59:31 rillig Exp $
+# $NetBSD: var-scope-cmdline.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
#
# Tests for variables specified on the command line.
#
diff --git a/contrib/bmake/unit-tests/envfirst.exp b/contrib/bmake/unit-tests/var-scope-env.exp
index 39a9383953dd..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/envfirst.exp
+++ b/contrib/bmake/unit-tests/var-scope-env.exp
diff --git a/contrib/bmake/unit-tests/var-class-env.mk b/contrib/bmake/unit-tests/var-scope-env.mk
index 6e6b4891d3fd..d58ad7c6f2e2 100644
--- a/contrib/bmake/unit-tests/var-class-env.mk
+++ b/contrib/bmake/unit-tests/var-scope-env.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-class-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: var-scope-env.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
#
# Tests for variables specified in the process environment.
diff --git a/contrib/bmake/unit-tests/var-class-env.exp b/contrib/bmake/unit-tests/var-scope-global.exp
index 39a9383953dd..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/var-class-env.exp
+++ b/contrib/bmake/unit-tests/var-scope-global.exp
diff --git a/contrib/bmake/unit-tests/var-scope-global.mk b/contrib/bmake/unit-tests/var-scope-global.mk
new file mode 100644
index 000000000000..02ed8fe701c0
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-scope-global.mk
@@ -0,0 +1,18 @@
+# $NetBSD: var-scope-global.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
+#
+# Tests for global variables, which are the most common variables.
+
+# Global variables can be assigned and appended to.
+GLOBAL= value
+GLOBAL+= addition
+.if ${GLOBAL} != "value addition"
+. error
+.endif
+
+# Global variables can be removed from their scope.
+.undef GLOBAL
+.if defined(GLOBAL)
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/var-class-global.exp b/contrib/bmake/unit-tests/var-scope-local-legacy.exp
index 39a9383953dd..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/var-class-global.exp
+++ b/contrib/bmake/unit-tests/var-scope-local-legacy.exp
diff --git a/contrib/bmake/unit-tests/var-class-local-legacy.mk b/contrib/bmake/unit-tests/var-scope-local-legacy.mk
index bfd9733fd42b..e519d63e7c51 100644
--- a/contrib/bmake/unit-tests/var-class-local-legacy.mk
+++ b/contrib/bmake/unit-tests/var-scope-local-legacy.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-class-local-legacy.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: var-scope-local-legacy.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
#
# Tests for legacy target-local variables, such as ${<F} or ${@D}.
diff --git a/contrib/bmake/unit-tests/var-scope-local.exp b/contrib/bmake/unit-tests/var-scope-local.exp
new file mode 100644
index 000000000000..051ede288bc9
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-scope-local.exp
@@ -0,0 +1,21 @@
+Global: .ALLTARGETS = one
+Global: .ALLTARGETS = one two
+Var_Parse: ${.MAKE.TARGET_LOCAL_VARIABLES} (eval)
+Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
+Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
+Global: one two =
+Global: one two = three
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
+: Making var-scope-local.c out of nothing.
+: Making var-scope-local.o from var-scope-local.c.
+: Making basename "var-scope-local.o" in "." from "var-scope-local.c" in ".".
+: Making var-scope-local-assign.o with VAR="local".
+: Making var-scope-local-append.o with VAR="local to var-scope-local-append.o".
+: Making var-scope-local-append-global.o with VAR="global+local".
+: Making var-scope-local-default.o with VAR="global".
+: Making var-scope-local-subst.o with VAR="global+local".
+: Making var-scope-local-shell.o with VAR="output".
+: var-scope-local-use.o uses .USE VAR="global"
+: all overwritten
+exit status 0
diff --git a/contrib/bmake/unit-tests/var-scope-local.mk b/contrib/bmake/unit-tests/var-scope-local.mk
new file mode 100644
index 000000000000..91a5f525e855
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-scope-local.mk
@@ -0,0 +1,200 @@
+# $NetBSD: var-scope-local.mk,v 1.3 2022/01/29 00:52:53 rillig Exp $
+#
+# Tests for target-local variables, such as ${.TARGET} or $@. These variables
+# are relatively short-lived as they are created just before making the
+# target. In contrast, global variables are typically created when the
+# makefiles are read in.
+#
+# The 7 built-in target-local variables are listed in the manual page. They
+# are defined just before the target is actually made. Additional
+# target-local variables can be defined in dependency lines like
+# 'target: VAR=value', one at a time.
+
+.MAIN: all
+
+# The target-local variables can be used in expressions, just like other
+# variables. When these expressions are evaluated outside of a target, these
+# expressions are not yet expanded, instead their text is preserved, to allow
+# these expressions to expand right in time when the target-local variables
+# are actually set.
+#
+# Conditions like the ones below are evaluated in the scope of the command
+# line, which means that variables from the command line, from the global
+# scope and from the environment are resolved, in this order (but see the
+# command line option '-e'). In that phase, expressions involving
+# target-local variables need to be preserved, including the exact names of
+# the variables.
+#
+# Each of the built-in target-local variables has two equivalent names, for
+# example '@' is equivalent to '.TARGET'. The implementation might
+# canonicalize these aliases at some point, and that might be surprising.
+# This aliasing happens for single-character variable names like $@ or $<
+# (see VarFind, CanonicalVarname), but not for braced or parenthesized
+# expressions like ${@}, ${.TARGET} ${VAR:Mpattern} (see Var_Parse,
+# ParseVarname).
+#
+# In the following condition, make does not expand '$@' but instead changes it
+# to the long-format alias '$(.TARGET)'; note that the alias is not written
+# with braces, as would be common in BSD makefiles, but with parentheses.
+# This alternative form behaves equivalently though.
+.if $@ != "\$\(.TARGET)"
+. error
+.endif
+# In the long form of writing a target-local variable, the expression is
+# preserved exactly as written, no matter whether with '{' or '('.
+.if ${@} != "\$\{@}"
+. error
+.endif
+.if $(@) != "\$\(@)"
+. error
+.endif
+# If the variable expression contains modifiers, the behavior depends on the
+# actual modifiers. The modifier ':M' keeps the expression in the state
+# 'undefined'. Since the expression is still undefined after evaluating all
+# the modifiers, the value of the expression is discarded and the expression
+# text is used instead. This preserves the expressions based on target-local
+# variables as long as possible.
+.if ${@:M*} != "\$\{@:M*}"
+. error
+.endif
+# In the following examples, the expressions are based on target-local
+# variables but use the modifier ':L', which turns an undefined expression
+# into a defined one. At the end of evaluating the expression, the state of
+# the expression is not 'undefined' anymore, and the value of the expression
+# is the name of the variable, since that's what the modifier ':L' does.
+.if ${@:L} != "@"
+. error
+.endif
+.if ${.TARGET:L} != ".TARGET"
+. error
+.endif
+.if ${@F:L} != "@F"
+. error
+.endif
+.if ${@D:L} != "@D"
+. error
+.endif
+
+
+# Additional target-local variables may be defined in dependency lines.
+.MAKEFLAGS: -dv
+# In the following line, the ':=' may either be interpreted as an assignment
+# operator or as the dependency operator ':', followed by an empty variable
+# name and the assignment operator '='. It is the latter since in an
+# assignment, the left-hand side must be at most a single word. The empty
+# variable name is expanded twice, once for 'one' and once for 'two'.
+# expect: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
+# expect: Var_SetExpand: variable name "" expands to empty string, with value "three" - ignored
+one two:=three
+# If the two targets to the left are generated by a variable expression, the
+# line is parsed as a variable assignment since its left-hand side is a single
+# word.
+# expect: Global: one two = three
+${:Uone two}:=three
+.MAKEFLAGS: -d0
+
+
+.SUFFIXES: .c .o
+
+# One of the dynamic target-local variables is '.TARGET'. Since this is not
+# a suffix transformation rule, the variable '.IMPSRC' is not defined.
+# expect: : Making var-scope-local.c out of nothing.
+var-scope-local.c:
+ : Making ${.TARGET} ${.IMPSRC:Dfrom ${.IMPSRC}:Uout of nothing}.
+
+# This is a suffix transformation rule, so both '.TARGET' and '.IMPSRC' are
+# defined.
+# expect: : Making var-scope-local.o from var-scope-local.c.
+# expect: : Making basename "var-scope-local.o" in "." from "var-scope-local.c" in ".".
+.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}".
+
+# expect: : all overwritten
+all: var-scope-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".
+ : ${.TARGET} ${.TARGET::=overwritten}${.TARGET}
+
+
+# Begin tests for custom target-local variables, for all 5 variable assignment
+# operators.
+all: var-scope-local-assign.o
+all: var-scope-local-append.o
+all: var-scope-local-append-global.o
+all: var-scope-local-default.o
+all: var-scope-local-subst.o
+all: var-scope-local-shell.o
+
+var-scope-local-assign.o \
+var-scope-local-append.o \
+var-scope-local-append-global.o \
+var-scope-local-default.o \
+var-scope-local-subst.o \
+var-scope-local-shell.o:
+ : Making ${.TARGET} with VAR="${VAR}".
+
+# Target-local variables are enabled by default. Force them to be enabled
+# just in case a test above has disabled them.
+.MAKE.TARGET_LOCAL_VARIABLES= yes
+
+VAR= global
+
+# If the sources of a dependency line look like a variable assignment, make
+# treats them as such. There is only a single variable assignment per
+# dependency line, which makes whitespace around the assignment operator
+# irrelevant.
+#
+# expect-reset
+# expect: : Making var-scope-local-assign.o with VAR="local".
+var-scope-local-assign.o: VAR= local
+
+# Assignments using '+=' do *not* look up the global value, instead they only
+# look up the variable in the target's own scope.
+var-scope-local-append.o: VAR+= local
+# Once a variable is defined in the target-local scope, appending using '+='
+# behaves as expected. Note that the expression '${.TARGET}' is not resolved
+# when parsing the dependency line, its evaluation is deferred until the
+# target is actually made.
+# expect: : Making var-scope-local-append.o with VAR="local to var-scope-local-append.o".
+var-scope-local-append.o: VAR += to ${.TARGET}
+# To access the value of a global variable, use a variable expression. This
+# expression is expanded before parsing the whole dependency line. Since the
+# expansion happens to the right of both the dependency operator ':' and also
+# to the right of the assignment operator '=', the expanded text does not
+# affect the dependency or the variable assignment structurally. The
+# effective variable assignment, after expanding the whole line first, is thus
+# 'VAR= global+local'.
+# expect: : Making var-scope-local-append-global.o with VAR="global+local".
+var-scope-local-append-global.o: VAR= ${VAR}+local
+
+var-scope-local-default.o: VAR ?= first
+var-scope-local-default.o: VAR ?= second
+# XXX: '?=' does look at the global variable. That's a long-standing
+# inconsistency between the assignment operators '+=' and '?='. See
+# Var_AppendExpand and VarAssign_Eval.
+# expect: : Making var-scope-local-default.o with VAR="global".
+
+# Using the variable assignment operator ':=' provides another way of
+# accessing a global variable and extending it with local modifications. The
+# '$' has to be written as '$$' though to survive the expansion of the
+# dependency line as a whole.
+var-scope-local-subst.o: VAR := $${VAR}+local
+
+# The variable assignment operator '!=' assigns the output of the shell
+# command, as everywhere else.
+var-scope-local-shell.o: VAR != echo output
+
+
+# While VAR=use will be set for a .USE node, it will never be seen since only
+# the ultimate target's context is searched; the variable assignments from the
+# .USE target are not copied to the ultimate target's.
+a_use: .USE VAR=use
+ : ${.TARGET} uses .USE VAR="${VAR}"
+
+all: var-scope-local-use.o
+var-scope-local-use.o: a_use
diff --git a/contrib/bmake/unit-tests/var-class-local-legacy.exp b/contrib/bmake/unit-tests/var-scope.exp
index 39a9383953dd..39a9383953dd 100644
--- a/contrib/bmake/unit-tests/var-class-local-legacy.exp
+++ b/contrib/bmake/unit-tests/var-scope.exp
diff --git a/contrib/bmake/unit-tests/var-scope.mk b/contrib/bmake/unit-tests/var-scope.mk
new file mode 100644
index 000000000000..cbbcea526d54
--- /dev/null
+++ b/contrib/bmake/unit-tests/var-scope.mk
@@ -0,0 +1,9 @@
+# $NetBSD: var-scope.mk,v 1.1 2022/01/23 16:25:54 rillig Exp $
+#
+# Tests for the different variable scopes (local, command-line, global,
+# environment), and which of them takes precedence over the others.
+
+# TODO: Implementation
+
+all:
+ @:;
diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.exp b/contrib/bmake/unit-tests/varmod-assign-shell.exp
new file mode 100644
index 000000000000..3d4a90fb1be4
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-assign-shell.exp
@@ -0,0 +1,14 @@
+make: "varmod-assign-shell.mk" line 27: warning: "echo output; false" returned non-zero status
+Global: _ =
+Var_Parse: ${ASSIGNED::!=echo output; ${:Ufalse}} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${ASSIGNED::...} on value "previous" (eval-keep-dollar-and-undefined, regular)
+Modifier part: "echo output; false"
+Capturing the output of command "echo output; false"
+make: "echo output; false" returned non-zero status
+Result of ${ASSIGNED::!=echo output; ${:Ufalse}} is "" (eval-keep-dollar-and-undefined, regular)
+Global: _ =
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
+DIRECT=output
+ASSIGNED=previous
+exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.mk b/contrib/bmake/unit-tests/varmod-assign-shell.mk
new file mode 100644
index 000000000000..d03692942d5b
--- /dev/null
+++ b/contrib/bmake/unit-tests/varmod-assign-shell.mk
@@ -0,0 +1,36 @@
+# $NetBSD: varmod-assign-shell.mk,v 1.4 2022/01/10 20:32:29 rillig Exp $
+#
+# Tests for the variable modifier '::!=', which assigns the output of a shell
+# command to the variable, but only if the command exited successfully. This
+# is different from the other places that capture the output of an external
+# command (variable assignment operator '!=', expression modifier ':sh',
+# expression modifier ':!...!'), which also use the output when the shell
+# command fails or crashes.
+#
+# The variable modifier '::!=' and its close relatives have been around since
+# var.c 1.45 from 2000-06-01.
+#
+# Before 2020.08.25.21.16.53, the variable modifier '::!=' had a bug for
+# unsuccessful commands, it put the previous value of the variable into the
+# error message instead of the command that was executed. That's where the
+# counterintuitive error message 'make: "previous" returned non-zero status'
+# comes from.
+#
+# BUGS
+# Even though the variable modifier '::!=' produces an error message,
+# the exit status of make is still 0.
+#
+# Having an error message instead of a warning like for the variable
+# assignment operator '!=' is another unnecessary inconsistency.
+
+DIRECT= previous
+DIRECT!= echo output; false
+
+ASSIGNED= previous
+.MAKEFLAGS: -dv # to see the actual command
+_:= ${ASSIGNED::!=echo output; ${:Ufalse}}
+.MAKEFLAGS: -d0
+
+all:
+ @echo DIRECT=${DIRECT:Q}
+ @echo ASSIGNED=${ASSIGNED:Q}
diff --git a/contrib/bmake/unit-tests/varmod-ifelse.mk b/contrib/bmake/unit-tests/varmod-ifelse.mk
index 0e16032a6543..37e8f620d883 100644
--- a/contrib/bmake/unit-tests/varmod-ifelse.mk
+++ b/contrib/bmake/unit-tests/varmod-ifelse.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-ifelse.mk,v 1.17 2021/06/11 13:01:28 rillig Exp $
+# $NetBSD: varmod-ifelse.mk,v 1.18 2022/01/15 20:16: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.
@@ -11,9 +11,9 @@
# TODO: Implementation
# The variable name of the expression is expanded and then taken as the
-# condition. In this case it becomes:
+# condition. In the below example it becomes:
#
-# variable expression == "variable expression"
+# variable expression == "literal"
#
# This confuses the parser, which expects an operator instead of the bare
# word "expression". If the name were expanded lazily, everything would be
diff --git a/contrib/bmake/unit-tests/varmod-indirect.exp b/contrib/bmake/unit-tests/varmod-indirect.exp
index 9cff88eb760a..9ae41f002caa 100644
--- a/contrib/bmake/unit-tests/varmod-indirect.exp
+++ b/contrib/bmake/unit-tests/varmod-indirect.exp
@@ -1,6 +1,6 @@
make: "varmod-indirect.mk" line 19: Unknown modifier "${"
make: "varmod-indirect.mk" line 52: Unknown modifier "${"
-make: "varmod-indirect.mk" line 55: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression.
+make: "varmod-indirect.mk" line 53: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression.
make: "varmod-indirect.mk" line 140: before
make: "varmod-indirect.mk" line 140: after
make: "varmod-indirect.mk" line 146: before
@@ -10,11 +10,11 @@ make: "varmod-indirect.mk" line 152: after
make: "varmod-indirect.mk" line 156: Unknown modifier "Z"
make: "varmod-indirect.mk" line 157: before
make: "varmod-indirect.mk" line 157: after
-ParseReadLine (166): '_:= before ${UNDEF} after'
+Parsing line 166: _:= before ${UNDEF} after
Global: _ =
Var_Parse: ${UNDEF} after (eval-keep-dollar-and-undefined)
Global: _ = before ${UNDEF} after
-ParseReadLine (169): '_:= before ${UNDEF:${:US,a,a,}} after'
+Parsing line 169: _:= before ${UNDEF:${:US,a,a,}} after
Var_Parse: ${UNDEF:${:US,a,a,}} after (eval-keep-dollar-and-undefined)
Indirect modifier "S,a,a," from "${:US,a,a,}"
Evaluating modifier ${UNDEF:S...} on value "" (eval-keep-dollar-and-undefined, undefined)
@@ -23,18 +23,18 @@ Modifier part: "a"
ModifyWords: split "" into 1 word
Result of ${UNDEF:S,a,a,} is "" (eval-keep-dollar-and-undefined, undefined)
Global: _ = before ${UNDEF:S,a,a,} after
-ParseReadLine (179): '_:= before ${UNDEF:${:U}} after'
+Parsing line 179: _:= before ${UNDEF:${:U}} after
Var_Parse: ${UNDEF:${:U}} after (eval-keep-dollar-and-undefined)
Indirect modifier "" from "${:U}"
Global: _ = before ${UNDEF:} after
-ParseReadLine (184): '_:= before ${UNDEF:${:UZ}} after'
+Parsing line 184: _:= before ${UNDEF:${:UZ}} after
Var_Parse: ${UNDEF:${:UZ}} after (eval-keep-dollar-and-undefined)
Indirect modifier "Z" from "${:UZ}"
Evaluating modifier ${UNDEF:Z} on value "" (eval-keep-dollar-and-undefined, undefined)
make: "varmod-indirect.mk" line 184: Unknown modifier "Z"
Result of ${UNDEF:Z} is error (eval-keep-dollar-and-undefined, undefined)
Global: _ = before ${UNDEF:Z} after
-ParseReadLine (186): '.MAKEFLAGS: -d0'
+Parsing line 186: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d 0 -d pv -d
Global: .MAKEFLAGS = -r -k -d 0 -d pv -d 0
diff --git a/contrib/bmake/unit-tests/varmod-indirect.mk b/contrib/bmake/unit-tests/varmod-indirect.mk
index fa58997cc849..082efb035c74 100644
--- a/contrib/bmake/unit-tests/varmod-indirect.mk
+++ b/contrib/bmake/unit-tests/varmod-indirect.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-indirect.mk,v 1.9 2021/03/15 20:00:50 rillig Exp $
+# $NetBSD: varmod-indirect.mk,v 1.11 2022/01/15 12:35:18 rillig Exp $
#
# Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
# These can be used for very basic purposes like converting a string to either
@@ -15,7 +15,7 @@
# The following expression generates a parse error since its indirect
# modifier contains more than a sole variable expression.
#
-# expect+1: Unknown modifier '$'
+# expect+1: Unknown modifier "${"
.if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
. warning unexpected
.endif
@@ -47,7 +47,7 @@
# error. Because of this parse error, this feature cannot be used reasonably
# in practice.
#
-# expect+1: Unknown modifier '$'
+# expect+2: Unknown modifier "${"
#.MAKEFLAGS: -dvc
.if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}"
. warning FIXME: this expression should have resulted in a parse $\
diff --git a/contrib/bmake/unit-tests/varmod-loop.exp b/contrib/bmake/unit-tests/varmod-loop.exp
index d05b870d5b5e..11a74e571a17 100644
--- a/contrib/bmake/unit-tests/varmod-loop.exp
+++ b/contrib/bmake/unit-tests/varmod-loop.exp
@@ -1,10 +1,10 @@
-ParseReadLine (78): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$'
+Parsing line 78: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$
CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
lhs = "$$$$ $$$$ $$$$", rhs = "$$$$ $$$$ $$$$", op = !=
-ParseReadLine (83): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}'
+Parsing line 83: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}
CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
lhs = "$$ $$$$ $$$$", rhs = "$$ $$$$ $$$$", op = !=
-ParseReadLine (108): '.MAKEFLAGS: -d0'
+Parsing line 108: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
:varname-overwriting-target: :x1y x2y x3y: ::
mod-loop-dollar:1:
diff --git a/contrib/bmake/unit-tests/varmod-order-numeric.mk b/contrib/bmake/unit-tests/varmod-order-numeric.mk
index 40a1d408e9ae..70b2f2834370 100644
--- a/contrib/bmake/unit-tests/varmod-order-numeric.mk
+++ b/contrib/bmake/unit-tests/varmod-order-numeric.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-order-numeric.mk,v 1.5 2021/08/03 04:46:49 rillig Exp $
+# $NetBSD: varmod-order-numeric.mk,v 1.6 2022/02/04 23:43:10 rillig Exp $
#
# Tests for the variable modifiers ':On', which returns the words, sorted in
# ascending numeric order, and for ':Orn' and ':Onr', which additionally
@@ -8,9 +8,8 @@
# from 2021-07-30.
# This list contains only 32-bit numbers since the make code needs to conform
-# to C90, which does not provide integer types larger than 32 bit. It uses
-# 'long long' by default, but that type is overridable if necessary to support
-# older environments.
+# to C90, which does not necessarily provide integer types larger than 32 bit.
+# Make uses 'long long' for C99 or later, and 'long' for older C versions.
#
# To get 53-bit integers even in C90, it would be possible to switch to
# 'double' instead, but that would allow floating-point numbers as well, which
diff --git a/contrib/bmake/unit-tests/varmod-order.mk b/contrib/bmake/unit-tests/varmod-order.mk
index 7f3d485fe8e3..c6028fc10abd 100644
--- a/contrib/bmake/unit-tests/varmod-order.mk
+++ b/contrib/bmake/unit-tests/varmod-order.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-order.mk,v 1.7 2021/08/03 04:46:49 rillig Exp $
+# $NetBSD: varmod-order.mk,v 1.8 2022/01/15 12:35:18 rillig Exp $
#
# Tests for the :O variable modifier and its variants, which either sort the
# words of the value or shuffle them.
@@ -24,7 +24,7 @@ _:= ${NUMBERS:Onr
# Shuffling numerically doesn't make sense, so don't allow 'x' and 'n' to be
# combined.
#
-# expect-text: Bad modifier ":Oxn" for variable "NUMBERS"
+# expect: make: Bad modifier ":Oxn" for variable "NUMBERS"
# expect+1: Malformed conditional (${NUMBERS:Oxn})
.if ${NUMBERS:Oxn}
. error
@@ -35,7 +35,7 @@ _:= ${NUMBERS:Onr
# Extra characters after ':On' are detected and diagnosed.
# TODO: Add line number information to the "Bad modifier" diagnostic.
#
-# expect-text: Bad modifier ":On_typo" for variable "NUMBERS"
+# expect: make: Bad modifier ":On_typo" for variable "NUMBERS"
.if ${NUMBERS:On_typo}
. error
.else
@@ -44,7 +44,7 @@ _:= ${NUMBERS:Onr
# Extra characters after ':Onr' are detected and diagnosed.
#
-# expect-text: Bad modifier ":Onr_typo" for variable "NUMBERS"
+# expect: make: Bad modifier ":Onr_typo" for variable "NUMBERS"
.if ${NUMBERS:Onr_typo}
. error
.else
@@ -53,7 +53,7 @@ _:= ${NUMBERS:Onr
# Extra characters after ':Orn' are detected and diagnosed.
#
-# expect+1: Bad modifier ":Orn_typo" for variable "NUMBERS"
+# expect: make: Bad modifier ":Orn_typo" for variable "NUMBERS"
.if ${NUMBERS:Orn_typo}
. error
.else
@@ -64,7 +64,7 @@ _:= ${NUMBERS:Onr
# criteria are fixed, not computed, therefore allowing this redundancy does
# not make sense.
#
-# expect-text: Bad modifier ":Onn" for variable "NUMBERS"
+# expect: make: Bad modifier ":Onn" for variable "NUMBERS"
.if ${NUMBERS:Onn}
. error
.else
@@ -73,7 +73,7 @@ _:= ${NUMBERS:Onr
# Repeating the 'r' is not supported as well, for the same reasons as above.
#
-# expect-text: Bad modifier ":Onrr" for variable "NUMBERS"
+# expect: make: Bad modifier ":Onrr" for variable "NUMBERS"
.if ${NUMBERS:Onrr}
. error
.else
@@ -82,7 +82,7 @@ _:= ${NUMBERS:Onr
# Repeating the 'r' is not supported as well, for the same reasons as above.
#
-# expect-text: Bad modifier ":Orrn" for variable "NUMBERS"
+# expect: make: Bad modifier ":Orrn" for variable "NUMBERS"
.if ${NUMBERS:Orrn}
. error
.else
diff --git a/contrib/bmake/unit-tests/varmod-quote-dollar.exp b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
index 39a9383953dd..4346401c5a9d 100644
--- a/contrib/bmake/unit-tests/varmod-quote-dollar.exp
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.exp
@@ -1 +1,2 @@
+!"#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-quote-dollar.mk b/contrib/bmake/unit-tests/varmod-quote-dollar.mk
index fedbe8a10f4b..3316b04bed1e 100644
--- a/contrib/bmake/unit-tests/varmod-quote-dollar.mk
+++ b/contrib/bmake/unit-tests/varmod-quote-dollar.mk
@@ -1,10 +1,10 @@
-# $NetBSD: varmod-quote-dollar.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-quote-dollar.mk,v 1.3 2022/01/22 17:10:51 rillig Exp $
#
# Tests for the :q variable modifier, which quotes the string for the shell
# and doubles dollar signs, to prevent them from being interpreted by a
# child process of make.
-# TODO: Implementation
+ASCII_CHARS= ${.newline} !"\#$$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~
all:
- @:;
+ @${MAKE} -r -f /dev/null CHARS=${ASCII_CHARS:q} -V CHARS
diff --git a/contrib/bmake/unit-tests/varmod-select-words.exp b/contrib/bmake/unit-tests/varmod-select-words.exp
index 39a9383953dd..02e9974c02d6 100644
--- a/contrib/bmake/unit-tests/varmod-select-words.exp
+++ b/contrib/bmake/unit-tests/varmod-select-words.exp
@@ -1 +1,126 @@
+make: Bad modifier ":[]" for variable "LIST"
+LIST:[]="" is an error
+LIST:[0]="one two three four five six"
+LIST:[0x0]="one two three four five six"
+LIST:[000]="one two three four five six"
+LIST:[*]="one two three four five six"
+LIST:[@]="one two three four five six"
+LIST:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:C/ /,/g="one,two,three,four,five,six"
+LIST:[0]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:C/ /,/g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[@]:C/ /,/="one two three four five six"
+LIST:[@]:C/ /,/g="one two three four five six"
+LIST:[@]:C/ /,/1g="one two three four five six"
+LIST:[@]:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:[@]:C/ /,/="one two three four five six"
+LIST:[@]:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:[@]:C/ /,/="one two three four five six"
+EMPTY=""
+EMPTY:[#]="1" == 1 ?
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[#]="1" == 1 ?
+REALLYSPACE=" "
+REALLYSPACE:[#]="1" == 1 ?
+LIST:[#]="6"
+LIST:[0]:[#]="1" == 1 ?
+LIST:[*]:[#]="1" == 1 ?
+LIST:[@]:[#]="6"
+LIST:[1]:[#]="1"
+LIST:[1..3]:[#]="3"
+EMPTY:[1]=""
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[1]="\ "
+REALLYSPACE=" "
+REALLYSPACE:[1]="" == "" ?
+REALLYSPACE:[*]:[1]=" " == " " ?
+LIST:[1]="one"
+make: Bad modifier ":[1.]" for variable "LIST"
+LIST:[1.]="" is an error
+make: Bad modifier ":[1]." for variable "LIST"
+LIST:[1].="}" is an error
+LIST:[2]="two"
+LIST:[6]="six"
+LIST:[7]=""
+LIST:[999]=""
+make: Bad modifier ":[-]" for variable "LIST"
+LIST:[-]="" is an error
+make: Bad modifier ":[--]" for variable "LIST"
+LIST:[--]="" is an error
+LIST:[-1]="six"
+LIST:[-2]="five"
+LIST:[-6]="one"
+LIST:[-7]=""
+LIST:[-999]=""
+LONGLIST:[17]="17"
+LONGLIST:[0x11]="17"
+LONGLIST:[021]="17"
+LIST:[0]:[1]="one two three four five six"
+LIST:[*]:[1]="one two three four five six"
+LIST:[@]:[1]="one"
+LIST:[0]:[2]=""
+LIST:[*]:[2]=""
+LIST:[@]:[2]="two"
+LIST:[*]:C/ /,/:[2]=""
+LIST:[*]:C/ /,/:[*]:[2]=""
+LIST:[*]:C/ /,/:[@]:[2]="three"
+LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18"
+make: Bad modifier ":[1.]" for variable "LIST"
+LIST:[1.]="" is an error
+make: Bad modifier ":[1..]" for variable "LIST"
+LIST:[1..]="" is an error
+make: Bad modifier ":[1.. ]" for variable "LIST"
+LIST:[1.. ]="" is an error
+LIST:[1..1]="one"
+make: Bad modifier ":[1..1.]" for variable "LIST"
+LIST:[1..1.]="" is an error
+LIST:[1..2]="one two"
+LIST:[2..1]="two one"
+LIST:[3..-2]="three four five"
+LIST:[-4..4]="three four"
+make: Bad modifier ":[0..1]" for variable "LIST"
+LIST:[0..1]="" is an error
+make: Bad modifier ":[-1..0]" for variable "LIST"
+LIST:[-1..0]="" is an error
+LIST:[-1..1]="six five four three two one"
+LIST:[0..0]="one two three four five six"
+LIST:[3..99]="three four five six"
+LIST:[-3..-99]="four three two one"
+LIST:[-99..-3]="one two three four"
+HASH="#" == "#" ?
+LIST:[${HASH}]="6"
+LIST:[${ZERO}]="one two three four five six"
+LIST:[${ZERO}x${ONE}]="one"
+LIST:[${ONE}]="one"
+LIST:[${MINUSONE}]="six"
+LIST:[${STAR}]="one two three four five six"
+LIST:[${AT}]="one two three four five six"
+make: Bad modifier ":[${EMPTY" for variable "LIST"
+LIST:[${EMPTY}]="" is an error
+LIST:[${LONGLIST:[21]:S/2//}]="one"
+LIST:[${LIST:[#]}]="six"
+LIST:[${LIST:[${HASH}]}]="six"
+LIST:[ -1.. +3]="six five four three"
+LIST:S/ /,/="one two three four five six"
+LIST:S/ /,/W="one,two three four five six"
+LIST:S/ /,/gW="one,two,three,four,five,six"
+EMPTY:S/^/,/=","
+EMPTY:S/^/,/W=","
+LIST:C/ /,/="one two three four five six"
+LIST:C/ /,/W="one,two three four five six"
+LIST:C/ /,/gW="one,two,three,four,five,six"
+EMPTY:C/^/,/=","
+EMPTY:C/^/,/W=","
+LIST:tW="one two three four five six"
+LIST:tw="one two three four five six"
+LIST:tW:C/ /,/="one,two three four five six"
+LIST:tW:C/ /,/g="one,two,three,four,five,six"
+LIST:tW:C/ /,/1g="one,two,three,four,five,six"
+LIST:tw:C/ /,/="one two three four five six"
+LIST:tw:C/ /,/g="one two three four five six"
+LIST:tw:C/ /,/1g="one two three four five six"
+LIST:tw:tW:C/ /,/="one,two three four five six"
+LIST:tW:tw:C/ /,/="one two three four five six"
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-select-words.mk b/contrib/bmake/unit-tests/varmod-select-words.mk
index ab094bf056b0..910b67a24e39 100644
--- a/contrib/bmake/unit-tests/varmod-select-words.mk
+++ b/contrib/bmake/unit-tests/varmod-select-words.mk
@@ -1,12 +1,166 @@
-# $NetBSD: varmod-select-words.mk,v 1.3 2021/12/05 12:06:23 rillig Exp $
+# $NetBSD: varmod-select-words.mk,v 1.4 2022/01/23 16:09:38 rillig Exp $
#
# Tests for the :[...] variable modifier, which selects a single word
# or a range of words from a variable.
#
+# History:
+# The variable modifier ':[...]' was added on 2003-09-27.
+#
# See also:
# modword.mk (should be migrated here)
-# TODO: Implementation
+all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw
+
+LIST= one two three four five six
+LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+EMPTY= # the space should be ignored
+ESCAPEDSPACE= \ # escaped space before the '#', the actual value is '\ '
+REALLYSPACE:= ${:U }
+HASH= \#
+AT= @
+STAR= *
+ZERO= 0
+ONE= 1
+MINUSONE= -1
+
+mod-squarebrackets: mod-squarebrackets-0-star-at \
+ mod-squarebrackets-hash \
+ mod-squarebrackets-n \
+ mod-squarebrackets-start-end \
+ mod-squarebrackets-nested \
+ mod-squarebrackets-space
+
+mod-squarebrackets-0-star-at:
+ @echo 'LIST:[]="${LIST:[]}" is an error'
+ @echo 'LIST:[0]="${LIST:[0]}"'
+ @echo 'LIST:[0x0]="${LIST:[0x0]}"'
+ @echo 'LIST:[000]="${LIST:[000]}"'
+ @echo 'LIST:[*]="${LIST:[*]}"'
+ @echo 'LIST:[@]="${LIST:[@]}"'
+ @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"'
+ @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"'
+ @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"'
+ @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"'
+ @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"'
+ @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"'
+ @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"'
+ @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"'
+ @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"'
+ @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"'
+
+mod-squarebrackets-hash:
+ @echo 'EMPTY="${EMPTY}"'
+ @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?'
+ @echo 'LIST:[#]="${LIST:[#]}"'
+ @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?'
+ @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?'
+ @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"'
+ @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
+ @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
+
+mod-squarebrackets-n:
+ @echo 'EMPTY:[1]="${EMPTY:[1]}"'
+ @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+ @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
+ @echo 'REALLYSPACE="${REALLYSPACE}"'
+ @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
+ @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
+ @echo 'LIST:[1]="${LIST:[1]}"'
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1].="${LIST:[1].}" is an error'
+ @echo 'LIST:[2]="${LIST:[2]}"'
+ @echo 'LIST:[6]="${LIST:[6]}"'
+ @echo 'LIST:[7]="${LIST:[7]}"'
+ @echo 'LIST:[999]="${LIST:[999]}"'
+ @echo 'LIST:[-]="${LIST:[-]}" is an error'
+ @echo 'LIST:[--]="${LIST:[--]}" is an error'
+ @echo 'LIST:[-1]="${LIST:[-1]}"'
+ @echo 'LIST:[-2]="${LIST:[-2]}"'
+ @echo 'LIST:[-6]="${LIST:[-6]}"'
+ @echo 'LIST:[-7]="${LIST:[-7]}"'
+ @echo 'LIST:[-999]="${LIST:[-999]}"'
+ @echo 'LONGLIST:[17]="${LONGLIST:[17]}"'
+ @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"'
+ @echo 'LONGLIST:[021]="${LONGLIST:[021]}"'
+ @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"'
+ @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"'
+ @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"'
+ @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"'
+ @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"'
+ @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"'
+ @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
+ @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"'
+
+mod-squarebrackets-start-end:
+ @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+ @echo 'LIST:[1..]="${LIST:[1..]}" is an error'
+ @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error'
+ @echo 'LIST:[1..1]="${LIST:[1..1]}"'
+ @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
+ @echo 'LIST:[1..2]="${LIST:[1..2]}"'
+ @echo 'LIST:[2..1]="${LIST:[2..1]}"'
+ @echo 'LIST:[3..-2]="${LIST:[3..-2]}"'
+ @echo 'LIST:[-4..4]="${LIST:[-4..4]}"'
+ @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
+ @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
+ @echo 'LIST:[-1..1]="${LIST:[-1..1]}"'
+ @echo 'LIST:[0..0]="${LIST:[0..0]}"'
+ @echo 'LIST:[3..99]="${LIST:[3..99]}"'
+ @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"'
+ @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"'
+
+mod-squarebrackets-nested:
+ @echo 'HASH="${HASH}" == "#" ?'
+ @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
+ @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
+ @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"'
+ @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"'
+ @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
+ @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
+ @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
+ @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
+ @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
+ @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
+ @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
+
+mod-squarebrackets-space:
+ # As of 2020-11-01, it is possible to have spaces before the numbers
+ # but not after them. This is an unintended side-effect of using
+ # strtol for parsing the numbers.
+ @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"'
+
+mod-C-W:
+ @echo 'LIST:C/ /,/="${LIST:C/ /,/}"'
+ @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"'
+ @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"'
+ @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"'
+ @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"'
+
+mod-S-W:
+ @echo 'LIST:S/ /,/="${LIST:S/ /,/}"'
+ @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"'
+ @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"'
+ @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"'
+ @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"'
-all:
- @:;
+mod-tW-tw:
+ @echo 'LIST:tW="${LIST:tW}"'
+ @echo 'LIST:tw="${LIST:tw}"'
+ @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"'
+ @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"'
+ @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"'
+ @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"'
+ @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"'
+ @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"'
+ @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"'
+ @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"'
diff --git a/contrib/bmake/unit-tests/varmod-shell.exp b/contrib/bmake/unit-tests/varmod-shell.exp
index 9aef0c9e5acc..adcfe7f251a9 100644
--- a/contrib/bmake/unit-tests/varmod-shell.exp
+++ b/contrib/bmake/unit-tests/varmod-shell.exp
@@ -1,3 +1,13 @@
make: "echo word; false" returned non-zero status
make: "echo word; false" returned non-zero status
+Global: _ =
+Var_Parse: ${:!echo word; ${:Ufalse}!} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${:!...} on value "" (eval-keep-dollar-and-undefined, undefined)
+Modifier part: "echo word; false"
+Capturing the output of command "echo word; false"
+make: "echo word; false" returned non-zero status
+Result of ${:!echo word; ${:Ufalse}!} is "word" (eval-keep-dollar-and-undefined, defined)
+Global: _ = word
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-shell.mk b/contrib/bmake/unit-tests/varmod-shell.mk
index c736042f80a0..d449709cee0f 100644
--- a/contrib/bmake/unit-tests/varmod-shell.mk
+++ b/contrib/bmake/unit-tests/varmod-shell.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-shell.mk,v 1.6 2021/02/14 20:16:17 rillig Exp $
+# $NetBSD: varmod-shell.mk,v 1.7 2022/01/10 20:32:29 rillig Exp $
#
# Tests for the ':!cmd!' variable modifier, which runs the shell command
# given by the variable modifier and returns its output.
@@ -20,8 +20,7 @@
#
# 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.
+# command that was executed.
.if ${:!echo word; false!} != "word"
. error
.endif
@@ -29,4 +28,9 @@
. error
.endif
+
+.MAKEFLAGS: -dv # to see the actual command
+_:= ${:!echo word; ${:Ufalse}!}
+.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.exp b/contrib/bmake/unit-tests/varmod-sun-shell.exp
index 5087bc66d943..4954458b13e1 100644
--- a/contrib/bmake/unit-tests/varmod-sun-shell.exp
+++ b/contrib/bmake/unit-tests/varmod-sun-shell.exp
@@ -1,2 +1,13 @@
make: "echo word; false" returned non-zero status
+Global: _ =
+Var_Parse: ${echo word; ${:Ufalse}:L:sh} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${echo word; false:L} on value "" (eval-keep-dollar-and-undefined, undefined)
+Result of ${echo word; false:L} is "echo word; false" (eval-keep-dollar-and-undefined, defined)
+Evaluating modifier ${echo word; false:s...} on value "echo word; false" (eval-keep-dollar-and-undefined, defined)
+Capturing the output of command "echo word; false"
+make: "echo word; false" returned non-zero status
+Result of ${echo word; false:sh} is "word" (eval-keep-dollar-and-undefined, defined)
+Global: _ = word
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
exit status 0
diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.mk b/contrib/bmake/unit-tests/varmod-sun-shell.mk
index 712b36bc7030..97acc5bd8c0f 100644
--- a/contrib/bmake/unit-tests/varmod-sun-shell.mk
+++ b/contrib/bmake/unit-tests/varmod-sun-shell.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-sun-shell.mk,v 1.1 2021/02/14 20:16:17 rillig Exp $
+# $NetBSD: varmod-sun-shell.mk,v 1.2 2022/01/10 20:32:29 rillig Exp $
#
# Tests for the :sh variable modifier, which runs the shell command
# given by the variable value and returns its output.
@@ -18,4 +18,9 @@
. error
.endif
+
+.MAKEFLAGS: -dv # to see the actual command
+_:= ${echo word; ${:Ufalse}:L:sh}
+.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/varmod-to-separator.exp b/contrib/bmake/unit-tests/varmod-to-separator.exp
index 3f8f1b2a11eb..bfcfa3ebc103 100644
--- a/contrib/bmake/unit-tests/varmod-to-separator.exp
+++ b/contrib/bmake/unit-tests/varmod-to-separator.exp
@@ -1,19 +1,25 @@
-make: "varmod-to-separator.mk" line 107: Invalid character number at "400:tu}"
-make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
-make: "varmod-to-separator.mk" line 121: Invalid character number at "100:tu}"
-make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
+make: "varmod-to-separator.mk" line 153: Invalid character number at "400:tu}"
+make: "varmod-to-separator.mk" line 153: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
+make: "varmod-to-separator.mk" line 167: Invalid character number at "100:tu}"
+make: "varmod-to-separator.mk" line 167: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
make: Bad modifier ":ts\-300" for variable "WORDS"
-make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
+make: "varmod-to-separator.mk" line 174: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
make: Bad modifier ":ts\8" for variable "1 2 3"
-make: "varmod-to-separator.mk" line 136: Malformed conditional (${1 2 3:L:ts\8:tu})
+make: "varmod-to-separator.mk" line 182: Malformed conditional (${1 2 3:L:ts\8:tu})
make: Bad modifier ":ts\100L" for variable "1 2 3"
-make: "varmod-to-separator.mk" line 143: Malformed conditional (${1 2 3:L:ts\100L})
+make: "varmod-to-separator.mk" line 189: Malformed conditional (${1 2 3:L:ts\100L})
make: Bad modifier ":ts\x40g" for variable "1 2 3"
-make: "varmod-to-separator.mk" line 150: Malformed conditional (${1 2 3:L:ts\x40g})
+make: "varmod-to-separator.mk" line 196: Malformed conditional (${1 2 3:L:ts\x40g})
make: Bad modifier ":tx" for variable "WORDS"
-make: "varmod-to-separator.mk" line 158: Malformed conditional (${WORDS:tx} != "anything")
+make: "varmod-to-separator.mk" line 205: Malformed conditional (${WORDS:tx})
+make: Bad modifier ":ts\X" for variable "WORDS"
+make: "varmod-to-separator.mk" line 213: Malformed conditional (${WORDS:ts\X})
make: Bad modifier ":t\X" for variable "WORDS"
-make: "varmod-to-separator.mk" line 165: Malformed conditional (${WORDS:t\X} != "anything")
+make: "varmod-to-separator.mk" line 221: Malformed conditional (${WORDS:t\X} != "anything")
+make: Bad modifier ":ts\69" for variable ""
+make: "varmod-to-separator.mk" line 237: Malformed conditional (${:Ua b:ts\69})
+make: "varmod-to-separator.mk" line 246: Invalid character number at "1F60E}"
+make: "varmod-to-separator.mk" line 246: Malformed conditional (${:Ua b:ts\x1F60E})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varmod-to-separator.mk b/contrib/bmake/unit-tests/varmod-to-separator.mk
index 08c6126ecc68..e724a9a1ce82 100644
--- a/contrib/bmake/unit-tests/varmod-to-separator.mk
+++ b/contrib/bmake/unit-tests/varmod-to-separator.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-to-separator.mk,v 1.7 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: varmod-to-separator.mk,v 1.10 2022/01/23 21:48:59 rillig Exp $
#
# Tests for the :ts variable modifier, which joins the words of the variable
# using an arbitrary character as word separator.
@@ -80,6 +80,52 @@ WORDS= one two three four five six
. warning The :ts modifier followed by an :S modifier does not work.
.endif
+# After the modifier ':ts/', the expression value is a single word since all
+# spaces have been replaced with '/'. This single word does not start with
+# 'two', which makes the modifier ':S' a no-op.
+.if ${WORDS:ts/:S/^two/2/} != "one/two/three/four/five/six"
+. error
+.endif
+
+# After the :ts modifier, the whole string is interpreted as a single
+# word since all spaces have been replaced with x. Because of this single
+# word, only the first 'b' is replaced with 'B'.
+.if ${aa bb aa bb aa bb:L:tsx:S,b,B,} != "aaxBbxaaxbbxaaxbb"
+. error
+.endif
+
+# The :ts modifier also applies to word separators that are added
+# afterwards. First, the modifier ':tsx' joins the 3 words, then the modifier
+# ':S' replaces the 2 'b's with spaces. These spaces are part of the word,
+# so when the words are joined at the end of the modifier ':S', there is only
+# a single word, and the custom separator from the modifier ':tsx' has no
+# effect.
+.if ${a ababa c:L:tsx:S,b, ,g} != "axa a axc"
+. error
+.endif
+
+# Adding the modifier ':M*' at the end of the above chain splits the
+# expression value and then joins it again. At this point of splitting, the
+# newly added spaces are treated as word separators, resulting in 3 words.
+# When these 3 words are joined, the separator from the modifier ':tsx' is
+# used.
+.if ${a ababa c:L:tsx:S,b, ,g:M*} != "axaxaxaxc"
+. error
+.endif
+
+# Not all modifiers use the separator from the previous modifier ':ts' though.
+# The modifier ':@' always uses a space as word separator instead. This has
+# probably been an oversight during implementation. For consistency, the
+# result should rather be "axaxaxaxc", as in the previous example.
+.if ${a ababa c:L:tsx:S,b, ,g:@v@$v@} != "axa a axc"
+. error
+.endif
+
+# Adding a final :M* modifier applies the :ts separator again, though.
+.if ${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*} != "axaxaxaxc"
+. error
+.endif
+
# The separator can be \n, which is a newline.
.if ${WORDS:[1..3]:ts\n} != "one${.newline}two${.newline}three"
. warning The separator \n does not produce a newline.
@@ -155,9 +201,19 @@ WORDS= one two three four five six
# In the :t modifier, the :t must be followed by any of A, l, s, u.
-.if ${WORDS:tx} != "anything"
-. info This line is not reached because of the malformed condition.
-. info If this line were reached, it would be visible in the -dcpv log.
+# expect: make: Bad modifier ":tx" for variable "WORDS"
+.if ${WORDS:tx}
+. error
+.else
+. error
+.endif
+
+# The word separator must be can only be a single character.
+# expect: make: Bad modifier ":ts\X" for variable "WORDS"
+.if ${WORDS:ts\X}
+. error
+.else
+. error
.endif
# After the backslash, only n, t, an octal number, or x and a hexadecimal
@@ -166,10 +222,29 @@ 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
+# Since 2003.07.23.18.06.46 and before 2016.03.07.20.20.35, the modifier ':ts'
+# interpreted an "octal escape" as decimal if the first digit was not '0'.
+.if ${:Ua b:ts\61} != "a1b" # decimal would have been "a=b"
+. error
+.endif
-all:
+# Since the character escape is always interpreted as octal, let's see what
+# happens for non-octal digits. From 2003.07.23.18.06.46 to
+# 2016.02.27.16.20.06, the result was '1E2', since 2016.03.07.20.20.35 make no
+# longer accepts this escape and complains.
+# expect: make: Bad modifier ":ts\69" for variable ""
+.if ${:Ua b:ts\69}
+. error
+.else
+. error
+.endif
+
+# Try whether bmake is Unicode-ready.
+# expect+2: Invalid character number at "1F60E}"
+# expect+1: Malformed conditional (${:Ua b:ts\x1F60E})
+.if ${:Ua b:ts\x1F60E} # U+1F60E "smiling face with sunglasses"
+. error
+.else
+. error
+.endif
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs.exp b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
index 39a9383953dd..1308f9116240 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.exp
@@ -1 +1,8 @@
+undefined
+1
+--- echo ---
+5
+--- echo ---
+20
+00000000000000000000000000000001
exit status 0
diff --git a/contrib/bmake/unit-tests/varname-dot-make-jobs.mk b/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
index 1e99b3d28ea8..af5eebfe7498 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-jobs.mk
@@ -1,8 +1,24 @@
-# $NetBSD: varname-dot-make-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-jobs.mk,v 1.3 2022/01/26 22:47:03 rillig Exp $
#
-# Tests for the special .MAKE.JOBS variable.
+# Tests for the special .MAKE.JOBS variable, which is defined in jobs mode
+# only. There it contains the number of jobs that may run in parallel.
-# TODO: Implementation
+.MAIN: all
+
+echo: .PHONY
+ @echo ${.MAKE.JOBS:Uundefined}
all:
- @:;
+ @${MAKE} -r -f ${MAKEFILE} echo
+ @${MAKE} -r -f ${MAKEFILE} echo -j1
+ @${MAKE} -r -f ${MAKEFILE} echo -j5
+ @${MAKE} -r -f ${MAKEFILE} echo -j20
+ @${MAKE} -r -f ${MAKEFILE} echo -j00000000000000000000000000000001
+
+# expect: undefined
+# expect: 1
+# expect: 5
+# expect: 20
+# The value of .MAKE.JOBS is the exact text given in the command line, not the
+# canonical number. This doesn't have practical consequences though.
+# expect: 00000000000000000000000000000001
diff --git a/contrib/bmake/unit-tests/varname-dot-make-pid.mk b/contrib/bmake/unit-tests/varname-dot-make-pid.mk
index bea114d33547..d7ef5bfd5c44 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-pid.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-pid.mk
@@ -1,8 +1,16 @@
-# $NetBSD: varname-dot-make-pid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-pid.mk,v 1.3 2022/01/23 21:48:59 rillig Exp $
#
-# Tests for the special .MAKE.PID variable.
+# Tests for the special .MAKE.PID variable, which contains the process ID of
+# the make process itself.
-# TODO: Implementation
+# The process ID must be a positive integer.
+.if ${.MAKE.PID:C,[0-9],,g} != ""
+. error
+.elif !(${.MAKE.PID} > 0)
+. error
+.endif
-all:
- @:;
+# Ensure that the process exists.
+_!= kill -0 ${.MAKE.PID}
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varname-dot-make-ppid.mk b/contrib/bmake/unit-tests/varname-dot-make-ppid.mk
index c9471542ca35..91f13fd2feec 100644
--- a/contrib/bmake/unit-tests/varname-dot-make-ppid.mk
+++ b/contrib/bmake/unit-tests/varname-dot-make-ppid.mk
@@ -1,8 +1,23 @@
-# $NetBSD: varname-dot-make-ppid.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-ppid.mk,v 1.3 2022/01/23 21:48:59 rillig Exp $
#
-# Tests for the special .MAKE.PPID variable.
+# Tests for the special .MAKE.PPID variable, which contains the process ID of
+# make's parent process.
-# TODO: Implementation
+# The parent process ID must be a positive integer.
+.if ${.MAKE.PPID:C,[0-9],,g} != ""
+. error
+.elif !(${.MAKE.PPID} > 0)
+. error
+.endif
-all:
- @:;
+# Ensure that the process exists.
+_!= kill -0 ${.MAKE.PPID}
+
+# The parent process ID must be different from the process ID. If they were
+# the same, make would run as process 1, which is not a good idea because make
+# is not prepared to clean up after other processes.
+.if ${.MAKE.PPID} == ${.MAKE.PID}
+. error
+.endif
+
+all: .PHONY
diff --git a/contrib/bmake/unit-tests/varname-dot-shell.exp b/contrib/bmake/unit-tests/varname-dot-shell.exp
index bfbcfc960182..28516ab1ea84 100755
--- a/contrib/bmake/unit-tests/varname-dot-shell.exp
+++ b/contrib/bmake/unit-tests/varname-dot-shell.exp
@@ -1,31 +1,31 @@
-ParseReadLine (10): 'ORIG_SHELL:= ${.SHELL}'
+Parsing line 10: ORIG_SHELL:= ${.SHELL}
Global: ORIG_SHELL =
Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined)
Global:delete .SHELL (not found)
Command: .SHELL = (details omitted)
Global: ORIG_SHELL = (details omitted)
-ParseReadLine (12): '.SHELL= overwritten'
+Parsing line 12: .SHELL= overwritten
Global: .SHELL = overwritten
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
Var_Parse: ${ORIG_SHELL} (eval-defined)
lhs = "(details omitted)", rhs = "(details omitted)", op = !=
-ParseReadLine (19): '.MAKEFLAGS: .SHELL+=appended'
+Parsing line 19: .MAKEFLAGS: .SHELL+=appended
ParseDependency(.MAKEFLAGS: .SHELL+=appended)
Ignoring append to .SHELL since it is read-only
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
Var_Parse: ${ORIG_SHELL} (eval-defined)
lhs = "(details omitted)", rhs = "(details omitted)", op = !=
-ParseReadLine (27): '.undef .SHELL'
+Parsing line 27: .undef .SHELL
Global:delete .SHELL
-ParseReadLine (28): '.SHELL= newly overwritten'
+Parsing line 28: .SHELL= newly overwritten
Global: .SHELL = newly overwritten
CondParser_Eval: ${.SHELL} != ${ORIG_SHELL}
Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined)
Var_Parse: ${ORIG_SHELL} (eval-defined)
lhs = "(details omitted)", rhs = "(details omitted)", op = !=
-ParseReadLine (33): '.MAKEFLAGS: -d0'
+Parsing line 33: .MAKEFLAGS: -d0
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0
diff --git a/contrib/bmake/unit-tests/varname-dot-suffixes.mk b/contrib/bmake/unit-tests/varname-dot-suffixes.mk
index de8034a172cc..babbe20d1c7c 100644
--- a/contrib/bmake/unit-tests/varname-dot-suffixes.mk
+++ b/contrib/bmake/unit-tests/varname-dot-suffixes.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-dot-suffixes.mk,v 1.1 2021/12/12 22:16:48 rillig Exp $
+# $NetBSD: varname-dot-suffixes.mk,v 1.2 2022/01/15 12:35:18 rillig Exp $
#
# Tests for the special "variable" .SUFFIXES, which lists the suffixes that
# have been registered for use in suffix transformation rules. Suffixes are
@@ -67,7 +67,7 @@
.SUFFIXES+= append
# expect: Global: .SUFFIXES = assign ignored (read-only)
_:= ${.SUFFIXES::=assign}
-# expect: Command: .SUFFIXES = preserve ignored (read-only)
+# expect: Global: .SUFFIXES = preserve ignored (read-only)
_:= ${preserve:L:_=.SUFFIXES}
.MAKEFLAGS: -d0
@@ -96,6 +96,8 @@ _:= ${preserve:L:_=.SUFFIXES}
.MAKEFLAGS: -dv
# expect: Command: .SUFFIXES = 1 ignored (read-only)
# expect: Command: .SUFFIXES = 2 ignored (read-only)
+# XXX: Missing space after ':'
+# expect: Command:delete .SUFFIXES (not found)
.if ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz"
. error
.endif
diff --git a/contrib/bmake/unit-tests/varname-empty.exp b/contrib/bmake/unit-tests/varname-empty.exp
index 75a3f4151a8c..72e79abe1ea9 100644
--- a/contrib/bmake/unit-tests/varname-empty.exp
+++ b/contrib/bmake/unit-tests/varname-empty.exp
@@ -13,6 +13,7 @@ Var_SetExpand: variable name "" expands to empty string, with value "assigned" -
SetVar: variable name is empty - ignored
Var_SetExpand: variable name "" expands to empty string, with value "" - ignored
Var_SetExpand: variable name "" expands to empty string, with value "subst" - ignored
+Capturing the output of command "echo 'shell-output'"
Var_SetExpand: variable name "" expands to empty string, with value "shell-output" - ignored
Var_SetExpand: variable name "${:U}" expands to empty string, with value "assigned indirectly" - ignored
Var_AppendExpand: variable name "${:U}" expands to empty string, with value "appended indirectly" - ignored
diff --git a/contrib/bmake/unit-tests/varname-makeflags.mk b/contrib/bmake/unit-tests/varname-makeflags.mk
index 3b4fd91c3f57..f7840c2eb7a5 100644
--- a/contrib/bmake/unit-tests/varname-makeflags.mk
+++ b/contrib/bmake/unit-tests/varname-makeflags.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname-makeflags.mk,v 1.3 2020/12/01 20:37:30 rillig Exp $
+# $NetBSD: varname-makeflags.mk,v 1.5 2022/01/16 18:16:06 sjg Exp $
#
# Tests for the special MAKEFLAGS variable, which is basically just a normal
# environment variable. It is closely related to .MAKEFLAGS but captures the
@@ -23,4 +23,22 @@
. error
.endif
+
+# In POSIX mode, the environment variable MAKEFLAGS can contain letters only,
+# for compatibility. These letters are exploded to form regular options.
+OUTPUT!= env MAKEFLAGS=ikrs ${MAKE} -f /dev/null -v .MAKEFLAGS
+.if ${OUTPUT} != " -i -k -r -s -V .MAKEFLAGS"
+. error
+.endif
+
+# As soon as there is a single non-alphabetic character in the environment
+# variable MAKEFLAGS, it is no longer split. In this example, the word
+# "d0ikrs" is treated as a target, but the option '-v' prevents any targets
+# from being built.
+OUTPUT!= env MAKEFLAGS=d0ikrs ${MAKE} -r -f /dev/null -v .MAKEFLAGS
+.if ${OUTPUT} != " -r -V .MAKEFLAGS"
+. error ${OUTPUT}
+.endif
+
+
all:
diff --git a/contrib/bmake/unit-tests/varname.mk b/contrib/bmake/unit-tests/varname.mk
index f586c7602cb7..0fc908c36481 100644
--- a/contrib/bmake/unit-tests/varname.mk
+++ b/contrib/bmake/unit-tests/varname.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varname.mk,v 1.8 2020/11/02 22:59:48 rillig Exp $
+# $NetBSD: varname.mk,v 1.9 2022/01/27 10:42:02 rillig Exp $
#
# Tests for special variables, such as .MAKE or .PARSEDIR.
# And for variable names in general.
@@ -41,4 +41,46 @@ ${VARNAME}= try3
.MAKEFLAGS: -d0
+# All variable names of a scope are stored in the same hash table, using a
+# simple hash function. Ensure that HashEntry_KeyEquals handles collisions
+# correctly and that the correct variable is looked up. The strings "0x" and
+# "1Y" have the same hash code, as 31 * '0' + 'x' == 31 * '1' + 'Y'.
+V.0x= 0x
+V.1Y= 1Y
+.if ${V.0x} != "0x" || ${V.1Y} != "1Y"
+. error
+.endif
+
+# The string "ASDZguv", when used as a prefix of a variable name, keeps the
+# hash code unchanged, its own hash code is 0.
+ASDZguvV.0x= 0x
+ASDZguvV.1Y= 1Y
+.if ${ASDZguvV.0x} != "0x"
+. error
+.elif ${ASDZguvV.1Y} != "1Y"
+. error
+.endif
+
+# Ensure that variables with the same hash code whose name is a prefix of the
+# other can be accessed. In this case, the shorter variable name is defined
+# first to make it appear later in the bucket of the hash table.
+ASDZguv= once
+ASDZguvASDZguv= twice
+.if ${ASDZguv} != "once"
+. error
+.elif ${ASDZguvASDZguv} != "twice"
+. error
+.endif
+
+# Ensure that variables with the same hash code whose name is a prefix of the
+# other can be accessed. In this case, the longer variable name is defined
+# first to make it appear later in the bucket of the hash table.
+ASDZguvASDZguv.param= twice
+ASDZguv.param= once
+.if ${ASDZguv.param} != "once"
+. error
+.elif ${ASDZguvASDZguv.param} != "twice"
+. error
+.endif
+
all:
diff --git a/contrib/bmake/unit-tests/varparse-errors.exp b/contrib/bmake/unit-tests/varparse-errors.exp
index 27589e0b21af..e47127447cda 100644
--- a/contrib/bmake/unit-tests/varparse-errors.exp
+++ b/contrib/bmake/unit-tests/varparse-errors.exp
@@ -1,5 +1,11 @@
make: "varparse-errors.mk" line 38: Unknown modifier "Z"
make: "varparse-errors.mk" line 46: Unknown modifier "Z"
+make: Bad modifier ":OX" for variable ""
+make: "varparse-errors.mk" line 68: Undefined variable "${:U:OX"
+make: Bad modifier ":OX" for variable ""
+make: Bad modifier ":OX" for variable ""
+make: "varparse-errors.mk" line 68: Undefined variable "${:U:OX"
+make: Bad modifier ":OX" for variable ""
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/contrib/bmake/unit-tests/varparse-errors.mk b/contrib/bmake/unit-tests/varparse-errors.mk
index f0947bb9410a..51a403fa898f 100644
--- a/contrib/bmake/unit-tests/varparse-errors.mk
+++ b/contrib/bmake/unit-tests/varparse-errors.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varparse-errors.mk,v 1.4 2021/03/15 12:15:03 rillig Exp $
+# $NetBSD: varparse-errors.mk,v 1.5 2022/01/24 22:59:49 rillig Exp $
# Tests for parsing and evaluating all kinds of variable expressions.
#
@@ -48,4 +48,24 @@ VAR.${:U:Z}post= unknown modifier with text in the variable name
. error
.endif
+# Demonstrate an edge case in which the 'static' for 'errorReported' in
+# Var_Subst actually makes a difference, preventing "a plethora of messages".
+# Given that this is an edge case and the error message is wrong and thus
+# misleading anyway, that piece of code is probably not necessary. The wrong
+# condition was added in var.c 1.185 from 2014-05-19.
+#
+# To trigger this difference, the variable assignment must use the assignment
+# operator ':=' to make VarEvalMode_ShouldKeepUndef return true. There must
+# be 2 expressions that create a parse error, which in this case is ':OX'.
+# These expressions must be nested in some way. The below expressions are
+# minimal, that is, removing any part of it destroys the effect.
+#
+# Without the 'static', there would be one more message like this:
+# Undefined variable "${:U:OX"
+#
+#.MAKEFLAGS: -dv
+IND= ${:OX}
+_:= ${:U:OX:U${IND}} ${:U:OX:U${IND}}
+#.MAKEFLAGS: -d0
+
all:
diff --git a/contrib/bmake/unit-tests/varquote.mk b/contrib/bmake/unit-tests/varquote.mk
index fb8b1066ac15..3d5e8a7f32e9 100644
--- a/contrib/bmake/unit-tests/varquote.mk
+++ b/contrib/bmake/unit-tests/varquote.mk
@@ -1,10 +1,10 @@
-# $NetBSD: varquote.mk,v 1.4 2018/12/16 18:53:34 christos Exp $
+# $NetBSD: varquote.mk,v 1.5 2021/12/28 10:47:00 rillig Exp $
#
# Test VAR:q modifier
.if !defined(REPROFLAGS)
-REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src
-REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1'
+REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src
+REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1'
all:
@${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q}
@${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q}
diff --git a/contrib/bmake/util.c b/contrib/bmake/util.c
index dab18e2ece53..eeda3d8f8a0c 100644
--- a/contrib/bmake/util.c
+++ b/contrib/bmake/util.c
@@ -1,9 +1,9 @@
-/* $NetBSD: util.c,v 1.76 2021/02/03 08:00:36 rillig Exp $ */
+/* $NetBSD: util.c,v 1.78 2021/12/15 12:58:01 rillig Exp $ */
/*
* Missing stuff from OS's
*
- * $Id: util.c,v 1.49 2021/10/14 19:26:52 sjg Exp $
+ * $Id: util.c,v 1.50 2021/12/21 18:47:24 sjg Exp $
*/
#include <sys/param.h>
@@ -13,7 +13,7 @@
#include "make.h"
-MAKE_RCSID("$NetBSD: util.c,v 1.76 2021/02/03 08:00:36 rillig Exp $");
+MAKE_RCSID("$NetBSD: util.c,v 1.78 2021/12/15 12:58:01 rillig Exp $");
#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
extern int errno, sys_nerr;
@@ -22,12 +22,12 @@ extern char *sys_errlist[];
char *
strerror(int e)
{
- static char buf[100];
- if (e < 0 || e >= sys_nerr) {
- snprintf(buf, sizeof buf, "Unknown error %d", e);
- return buf;
- } else
- return sys_errlist[e];
+ static char buf[100];
+ if (e < 0 || e >= sys_nerr) {
+ snprintf(buf, sizeof buf, "Unknown error %d", e);
+ return buf;
+ } else
+ return sys_errlist[e];
}
#endif
@@ -57,9 +57,9 @@ findenv(const char *name, int *offset)
char *
getenv(const char *name)
{
- int offset;
+ int offset;
- return findenv(name, &offset);
+ return findenv(name, &offset);
}
int
@@ -73,7 +73,7 @@ unsetenv(const char *name)
return -1;
}
- while (findenv(name, &offset)) { /* if set multiple times */
+ while (findenv(name, &offset)) { /* if set multiple times */
for (p = &environ[offset];; p++)
if (!(*p = *(p + 1)))
break;
@@ -94,7 +94,7 @@ setenv(const char *name, const char *value, int rewrite)
return -1;
}
- if (*value == '=') /* no `=' in value */
+ if (*value == '=') /* no `=' in value */
value++;
l_value = strlen(value);
@@ -160,16 +160,15 @@ main(int argc, char *argv[])
static char *
strrcpy(char *ptr, char *str)
{
- int len = strlen(str);
-
- while (len != 0)
- *--ptr = str[--len];
+ int len = strlen(str);
- return ptr;
-} /* end strrcpy */
+ while (len != 0)
+ *--ptr = str[--len];
+ return ptr;
+}
-char *sys_siglist[] = {
+char *sys_siglist[] = {
"Signal 0",
"Hangup", /* SIGHUP */
"Interrupt", /* SIGINT */
@@ -218,7 +217,7 @@ char *sys_siglist[] = {
int
killpg(int pid, int sig)
{
- return kill(-pid, sig);
+ return kill(-pid, sig);
}
#if !defined(BSD) && !defined(d_fileno)
@@ -393,7 +392,7 @@ vsnprintf(char *s, size_t n, const char *fmt, va_list args)
fakebuf._cnt++;
putc('\0', &fakebuf);
if (fakebuf._cnt < 0)
- fakebuf._cnt = 0;
+ fakebuf._cnt = 0;
return n - fakebuf._cnt - 1;
#else
#ifndef _PATH_DEVNULL
diff --git a/contrib/bmake/var.c b/contrib/bmake/var.c
index 4542a2f9a2ed..53d325d0d95a 100644
--- a/contrib/bmake/var.c
+++ b/contrib/bmake/var.c
@@ -1,4 +1,4 @@
-/* $NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $ */
+/* $NetBSD: var.c,v 1.1009 2022/02/04 23:43:10 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -103,7 +103,6 @@
* Var_Parse Parse a variable expression such as ${VAR:Mpattern}.
*
* Var_Delete
- * Var_DeleteExpand
* Delete a variable.
*
* Var_ReexportVars
@@ -148,7 +147,7 @@
#include "metachar.h"
/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.1009 2022/02/04 23:43:10 rillig Exp $");
/*
* Variables are defined using one of the VAR=value assignments. Their
@@ -161,10 +160,11 @@ MAKE_RCSID("$NetBSD: var.c,v 1.973 2021/12/12 20:45:48 sjg Exp $");
* Scope variables are stored in a GNode.scope. The only way to undefine
* a scope variable is using the .undef directive. In particular, it must
* not be possible to undefine a variable during the evaluation of an
- * expression, or Var.name might point nowhere.
+ * expression, or Var.name might point nowhere. (There is another,
+ * unintended way to undefine a scope variable, see varmod-loop-delete.mk.)
*
- * Environment variables are temporary. They are returned by VarFind, and
- * after using them, they must be freed using VarFreeEnv.
+ * Environment variables are short-lived. They are returned by VarFind, and
+ * after using them, they must be freed using VarFreeShortLived.
*
* Undefined variables occur during evaluation of variable expressions such
* as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers.
@@ -181,14 +181,20 @@ typedef struct Var {
Buffer val;
/* The variable came from the command line. */
- bool fromCmd: 1;
+ bool fromCmd:1;
/*
- * The variable comes from the environment.
+ * The variable is short-lived.
* These variables are not registered in any GNode, therefore they
- * must be freed as soon as they are not used anymore.
+ * must be freed after use.
*/
- bool fromEnv: 1;
+ bool shortLived:1;
+
+ /*
+ * The variable comes from the environment.
+ * Appending to its value moves the variable to the global scope.
+ */
+ bool fromEnvironment:1;
/*
* The variable value cannot be changed anymore, and the variable
@@ -197,19 +203,19 @@ typedef struct Var {
*
* See VAR_SET_READONLY.
*/
- bool readOnly: 1;
+ bool readOnly:1;
/*
* The variable's value is currently being used by Var_Parse or
* Var_Subst. This marker is used to avoid endless recursion.
*/
- bool inUse: 1;
+ bool inUse:1;
/*
* The variable is exported to the environment, to be used by child
* processes.
*/
- bool exported: 1;
+ bool exported:1;
/*
* At the point where this variable was exported, it contained an
@@ -217,7 +223,7 @@ typedef struct Var {
* process is started, it needs to be exported again, in the hope
* that the referenced variable can then be resolved.
*/
- bool reexport: 1;
+ bool reexport:1;
} Var;
/*
@@ -250,10 +256,10 @@ typedef enum UnexportWhat {
/* Flags for pattern matching in the :S and :C modifiers */
typedef struct PatternFlags {
- bool subGlobal: 1; /* 'g': replace as often as possible */
- bool subOnce: 1; /* '1': replace only once */
- bool anchorStart: 1; /* '^': match only at start of word */
- bool anchorEnd: 1; /* '$': match only at end of word */
+ bool subGlobal:1; /* 'g': replace as often as possible */
+ bool subOnce:1; /* '1': replace only once */
+ bool anchorStart:1; /* '^': match only at start of word */
+ bool anchorEnd:1; /* '$': match only at end of word */
} PatternFlags;
/* SepBuf builds a string from words interleaved with separators. */
@@ -317,7 +323,8 @@ static bool save_dollars = false;
* be simpler or more complex than the current implementation.
*
* Each target has its own scope, containing the 7 target-local variables
- * .TARGET, .ALLSRC, etc. No other variables are in these scopes.
+ * .TARGET, .ALLSRC, etc. Variables set on dependency lines also go in
+ * this scope.
*/
GNode *SCOPE_CMDLINE;
@@ -337,7 +344,8 @@ static const char VarEvalMode_Name[][32] = {
static Var *
-VarNew(FStr name, const char *value, bool fromEnv, bool readOnly)
+VarNew(FStr name, const char *value,
+ bool shortLived, bool fromEnvironment, bool readOnly)
{
size_t value_len = strlen(value);
Var *var = bmake_malloc(sizeof *var);
@@ -345,7 +353,8 @@ VarNew(FStr name, const char *value, bool fromEnv, bool readOnly)
Buf_InitSize(&var->val, value_len + 1);
Buf_AddBytes(&var->val, value, value_len);
var->fromCmd = false;
- var->fromEnv = fromEnv;
+ var->shortLived = shortLived;
+ var->fromEnvironment = fromEnvironment;
var->readOnly = readOnly;
var->inUse = false;
var->exported = false;
@@ -399,8 +408,8 @@ GNode_FindVar(GNode *scope, Substring varname, unsigned int hash)
*
* Results:
* The found variable, or NULL if the variable does not exist.
- * If the variable is an environment variable, it must be freed using
- * VarFreeEnv after use.
+ * If the variable is short-lived (such as environment variables), it
+ * must be freed using VarFreeShortLived after use.
*/
static Var *
VarFindSubstring(Substring name, GNode *scope, bool elsewhere)
@@ -439,7 +448,7 @@ VarFindSubstring(Substring name, GNode *scope, bool elsewhere)
envName = Substring_Str(name);
envValue = getenv(envName.str);
if (envValue != NULL)
- return VarNew(envName, envValue, true, false);
+ return VarNew(envName, envValue, true, true, false);
FStr_Done(&envName);
if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) {
@@ -463,11 +472,11 @@ VarFind(const char *name, GNode *scope, bool elsewhere)
return VarFindSubstring(Substring_InitStr(name), scope, elsewhere);
}
-/* If the variable is an environment variable, free it, including its value. */
+/* If the variable is short-lived, free it, including its value. */
static void
-VarFreeEnv(Var *v)
+VarFreeShortLived(Var *v)
{
- if (!v->fromEnv)
+ if (!v->shortLived)
return;
FStr_Done(&v->name);
@@ -481,7 +490,7 @@ VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags)
{
HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL);
Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value,
- false, (flags & VAR_SET_READONLY) != 0);
+ false, false, (flags & VAR_SET_READONLY) != 0);
HashEntry_Set(he, v);
DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, value);
return v;
@@ -521,27 +530,6 @@ Var_Delete(GNode *scope, const char *varname)
}
/*
- * Remove a variable from a scope, freeing all related memory as well.
- * The variable name is expanded once.
- */
-void
-Var_DeleteExpand(GNode *scope, const char *name)
-{
- FStr varname = FStr_InitRefer(name);
-
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, SCOPE_GLOBAL, VARE_WANTRES,
- &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
- Var_Delete(scope, varname.str);
- FStr_Done(&varname);
-}
-
-/*
* Undefine one or more variables from the global scope.
* The argument is expanded exactly once and then split into words.
*/
@@ -948,7 +936,7 @@ ExistsInCmdline(const char *name, const char *val)
return true;
}
- VarFreeEnv(v);
+ VarFreeShortLived(v);
return false;
}
@@ -998,7 +986,7 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
scope->name, name, val);
return;
}
- Buf_Empty(&v->val);
+ Buf_Clear(&v->val);
Buf_AddStr(&v->val, val);
DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, val);
@@ -1023,8 +1011,10 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
if (!opts.varNoExportEnv)
setenv(name, val, 1);
/* XXX: What about .MAKE.EXPORTED? */
- /* XXX: Why not just mark the variable for needing export,
- * as in ExportVarPlain? */
+ /*
+ * XXX: Why not just mark the variable for needing export, as
+ * in ExportVarPlain?
+ */
Global_Append(MAKEOVERRIDES, name);
}
@@ -1033,35 +1023,7 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
save_dollars = ParseBoolean(val, save_dollars);
if (v != NULL)
- VarFreeEnv(v);
-}
-
-/* See Var_Set for documentation. */
-void
-Var_SetExpandWithFlags(GNode *scope, const char *name, const char *val,
- VarSetFlags flags)
-{
- const char *unexpanded_name = name;
- FStr varname = FStr_InitRefer(name);
-
- assert(val != NULL);
-
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
- if (varname.str[0] == '\0') {
- DEBUG2(VAR,
- "Var_SetExpand: variable name \"%s\" expands "
- "to empty string, with value \"%s\" - ignored\n",
- unexpanded_name, val);
- } else
- Var_SetWithFlags(scope, varname.str, val, flags);
-
- FStr_Done(&varname);
+ VarFreeShortLived(v);
}
void
@@ -1084,7 +1046,22 @@ Var_Set(GNode *scope, const char *name, const char *val)
void
Var_SetExpand(GNode *scope, const char *name, const char *val)
{
- Var_SetExpandWithFlags(scope, name, val, VAR_SET_NONE);
+ const char *unexpanded_name = name;
+ FStr varname = FStr_InitRefer(name);
+
+ assert(val != NULL);
+
+ Var_Expand(&varname, scope, VARE_WANTRES);
+
+ if (varname.str[0] == '\0') {
+ DEBUG2(VAR,
+ "Var_SetExpand: variable name \"%s\" expands "
+ "to empty string, with value \"%s\" - ignored\n",
+ unexpanded_name, val);
+ } else
+ Var_SetWithFlags(scope, varname.str, val, VAR_SET_NONE);
+
+ FStr_Done(&varname);
}
void
@@ -1094,12 +1071,6 @@ Global_Set(const char *name, const char *value)
}
void
-Global_SetExpand(const char *name, const char *value)
-{
- Var_SetExpand(SCOPE_GLOBAL, name, value);
-}
-
-void
Global_Delete(const char *name)
{
Var_Delete(SCOPE_GLOBAL, name);
@@ -1129,17 +1100,18 @@ Var_Append(GNode *scope, const char *name, const char *val)
DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data);
- if (v->fromEnv) {
+ if (v->fromEnvironment) {
/*
- * If the original variable came from the environment,
- * we have to install it in the global scope (we
- * could place it in the environment, but then we
- * should provide a way to export other variables...)
+ * The variable originally came from the environment.
+ * Install it in the global scope (we could place it
+ * in the environment, but then we should provide a
+ * way to export other variables...)
*/
- v->fromEnv = false;
+ v->fromEnvironment = false;
+ v->shortLived = false;
/*
* This is the only place where a variable is
- * created whose v->name is not the same as
+ * created in a scope, where v->name does not alias
* scope->vars->key.
*/
HashTable_Set(&scope->vars, name, v);
@@ -1174,22 +1146,14 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val)
assert(val != NULL);
- if (strchr(name, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(name, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- xname = FStr_InitOwn(expanded);
- if (expanded[0] == '\0') {
- DEBUG2(VAR,
- "Var_AppendExpand: variable name \"%s\" expands "
- "to empty string, with value \"%s\" - ignored\n",
- name, val);
- FStr_Done(&xname);
- return;
- }
- }
-
- Var_Append(scope, xname.str, val);
+ Var_Expand(&xname, scope, VARE_WANTRES);
+ if (xname.str != name && xname.str[0] == '\0')
+ DEBUG2(VAR,
+ "Var_AppendExpand: variable name \"%s\" expands "
+ "to empty string, with value \"%s\" - ignored\n",
+ name, val);
+ else
+ Var_Append(scope, xname.str, val);
FStr_Done(&xname);
}
@@ -1207,7 +1171,7 @@ Var_Exists(GNode *scope, const char *name)
if (v == NULL)
return false;
- VarFreeEnv(v);
+ VarFreeShortLived(v);
return true;
}
@@ -1225,13 +1189,7 @@ Var_ExistsExpand(GNode *scope, const char *name)
FStr varname = FStr_InitRefer(name);
bool exists;
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
+ Var_Expand(&varname, scope, VARE_WANTRES);
exists = Var_Exists(scope, varname.str);
FStr_Done(&varname);
return exists;
@@ -1258,13 +1216,13 @@ Var_Value(GNode *scope, const char *name)
if (v == NULL)
return FStr_InitRefer(NULL);
- if (!v->fromEnv)
+ if (!v->shortLived)
return FStr_InitRefer(v->val.data);
- /* Since environment variables are short-lived, free it now. */
- FStr_Done(&v->name);
- value = Buf_DoneData(&v->val);
- free(v);
+ value = v->val.data;
+ v->val.data = NULL;
+ VarFreeShortLived(v);
+
return FStr_InitOwn(value);
}
@@ -1475,7 +1433,6 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data)
{
const struct ModifyWord_SysVSubstArgs *args = data;
FStr rhs;
- char *rhsExp;
const char *percent;
if (Substring_IsEmpty(word))
@@ -1487,11 +1444,7 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data)
goto no_match;
rhs = FStr_InitRefer(args->rhs);
- if (strchr(rhs.str, '$') != NULL) {
- (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhsExp);
- /* TODO: handle errors */
- rhs = FStr_InitOwn(rhsExp);
- }
+ Var_Expand(&rhs, args->scope, VARE_WANTRES);
percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL;
@@ -1605,58 +1558,55 @@ VarREError(int reerr, const regex_t *pat, const char *str)
free(errbuf);
}
+/* In the modifier ':C', replace a backreference from \0 to \9. */
+static void
+RegexReplaceBackref(char ref, SepBuf *buf, const char *wp,
+ const regmatch_t *m, size_t nsub)
+{
+ unsigned int n = ref - '0';
+
+ if (n >= nsub)
+ Error("No subexpression \\%u", n);
+ else if (m[n].rm_so == -1) {
+ if (opts.strict)
+ Error("No match for subexpression \\%u", n);
+ } else {
+ SepBuf_AddBytesBetween(buf,
+ wp + (size_t)m[n].rm_so,
+ wp + (size_t)m[n].rm_eo);
+ }
+}
+
/*
- * Replacement of regular expressions is not specified by POSIX, therefore
- * re-implement it here.
+ * The regular expression matches the word; now add the replacement to the
+ * buffer, taking back-references from 'wp'.
*/
static void
-RegexReplace(const char *replace, SepBuf *buf, const char *wp,
+RegexReplace(Substring replace, SepBuf *buf, const char *wp,
const regmatch_t *m, size_t nsub)
{
const char *rp;
- unsigned int n;
-
- for (rp = replace; *rp != '\0'; rp++) {
- if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) {
- SepBuf_AddBytes(buf, rp + 1, 1);
- rp++;
- continue;
- }
- if (*rp == '&') {
+ for (rp = replace.start; rp != replace.end; rp++) {
+ if (*rp == '\\' && rp + 1 != replace.end &&
+ (rp[1] == '&' || rp[1] == '\\'))
+ SepBuf_AddBytes(buf, ++rp, 1);
+ else if (*rp == '\\' && rp + 1 != replace.end &&
+ ch_isdigit(rp[1]))
+ RegexReplaceBackref(*++rp, buf, wp, m, nsub);
+ else if (*rp == '&') {
SepBuf_AddBytesBetween(buf,
wp + (size_t)m[0].rm_so,
wp + (size_t)m[0].rm_eo);
- continue;
- }
-
- if (*rp != '\\' || !ch_isdigit(rp[1])) {
+ } else
SepBuf_AddBytes(buf, rp, 1);
- continue;
- }
-
- /* \0 to \9 backreference */
- n = rp[1] - '0';
- rp++;
-
- if (n >= nsub) {
- Error("No subexpression \\%u", n);
- } else if (m[n].rm_so == -1) {
- if (opts.strict) {
- Error("No match for subexpression \\%u", n);
- }
- } else {
- SepBuf_AddBytesBetween(buf,
- wp + (size_t)m[n].rm_so,
- wp + (size_t)m[n].rm_eo);
- }
}
}
struct ModifyWord_SubstRegexArgs {
regex_t re;
size_t nsub;
- const char *replace;
+ Substring replace;
PatternFlags pflags;
bool matched;
};
@@ -1686,7 +1636,7 @@ again:
if (xrv != REG_NOMATCH)
VarREError(xrv, &args->re, "Unexpected regex error");
no_match:
- SepBuf_AddStr(buf, wp);
+ SepBuf_AddBytesBetween(buf, wp, word.end);
return;
ok:
@@ -1836,7 +1786,9 @@ SubstringWords_JoinFree(SubstringWords words)
for (i = 0; i < words.len; i++) {
if (i != 0) {
- /* XXX: Use ch->sep instead of ' ', for consistency. */
+ /*
+ * XXX: Use ch->sep instead of ' ', for consistency.
+ */
Buf_AddByte(&buf, ' ');
}
Buf_AddBytesBetween(&buf,
@@ -1867,7 +1819,7 @@ VarQuote(const char *str, bool quoteDollar, LazyBuf *buf)
LazyBuf_AddStr(buf, newline);
continue;
}
- if (ch_isspace(*p) || is_shell_metachar((unsigned char)*p))
+ if (ch_isspace(*p) || ch_is_shell_meta(*p))
LazyBuf_Add(buf, '\\');
LazyBuf_Add(buf, *p);
if (quoteDollar && *p == '$')
@@ -1940,15 +1892,15 @@ VarHash(const char *str)
}
static char *
-VarStrftime(const char *fmt, bool zulu, time_t tim)
+VarStrftime(const char *fmt, time_t t, bool gmt)
{
char buf[BUFSIZ];
- if (tim == 0)
- time(&tim);
+ if (t == 0)
+ time(&t);
if (*fmt == '\0')
fmt = "%c";
- strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim));
+ strftime(buf, sizeof buf, fmt, gmt ? gmtime(&t) : localtime(&t));
buf[sizeof buf - 1] = '\0';
return bmake_strdup(buf);
@@ -2036,9 +1988,9 @@ static const char ExprDefined_Name[][10] = {
};
#if __STDC_VERSION__ >= 199901L
-#define const_member const
+#define const_member const
#else
-#define const_member /* no const possible */
+#define const_member /* no const possible */
#endif
/* An expression based on a variable, such as $@ or ${VAR:Mpattern:Q}. */
@@ -2234,11 +2186,15 @@ ParseModifierPartSubst(
VarEvalMode emode,
ModChain *ch,
LazyBuf *part,
- /* For the first part of the modifier ':S', set anchorEnd if the last
- * character of the pattern is a $. */
+ /*
+ * For the first part of the modifier ':S', set anchorEnd if the last
+ * character of the pattern is a $.
+ */
PatternFlags *out_pflags,
- /* For the second part of the :S modifier, allow ampersands to be
- * escaped and replace unescaped ampersands with subst->lhs. */
+ /*
+ * For the second part of the :S modifier, allow ampersands to be escaped
+ * and replace unescaped ampersands with subst->lhs.
+ */
struct ModifyWord_SubstArgs *subst
)
{
@@ -2508,9 +2464,11 @@ ApplyModifier_Defined(const char **pp, ModChain *ch)
LazyBuf_Init(&buf, p);
while (!IsDelimiter(*p, ch) && *p != '\0') {
- /* XXX: This code is similar to the one in Var_Parse.
- * See if the code can be merged.
- * See also ApplyModifier_Match and ParseModifierPart. */
+ /*
+ * XXX: This code is similar to the one in Var_Parse. See if
+ * the code can be merged. See also ApplyModifier_Match and
+ * ParseModifierPart.
+ */
/* Escaped delimiter or other special character */
/* See Buf_AddEscaped in for.c. */
@@ -2586,66 +2544,36 @@ TryParseTime(const char **pp, time_t *out_time)
return true;
}
-/* :gmtime */
+/* :gmtime and :localtime */
static ApplyModifierResult
-ApplyModifier_Gmtime(const char **pp, ModChain *ch)
+ApplyModifier_Time(const char **pp, ModChain *ch)
{
Expr *expr;
- time_t utc;
-
+ time_t t;
+ const char *args;
const char *mod = *pp;
- if (!ModMatchEq(mod, "gmtime", ch))
- return AMR_UNKNOWN;
+ bool gmt = mod[0] == 'g';
- if (mod[6] == '=') {
- const char *p = mod + 7;
- if (!TryParseTime(&p, &utc)) {
- Parse_Error(PARSE_FATAL,
- "Invalid time value at \"%s\"", mod + 7);
- return AMR_CLEANUP;
- }
- *pp = p;
- } else {
- utc = 0;
- *pp = mod + 6;
- }
-
- expr = ch->expr;
- if (Expr_ShouldEval(expr))
- Expr_SetValueOwn(expr,
- VarStrftime(Expr_Str(expr), true, utc));
-
- return AMR_OK;
-}
-
-/* :localtime */
-static ApplyModifierResult
-ApplyModifier_Localtime(const char **pp, ModChain *ch)
-{
- Expr *expr;
- time_t utc;
-
- const char *mod = *pp;
- if (!ModMatchEq(mod, "localtime", ch))
+ if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch))
return AMR_UNKNOWN;
+ args = mod + (gmt ? 6 : 9);
- if (mod[9] == '=') {
- const char *p = mod + 10;
- if (!TryParseTime(&p, &utc)) {
+ if (args[0] == '=') {
+ const char *p = args + 1;
+ if (!TryParseTime(&p, &t)) {
Parse_Error(PARSE_FATAL,
- "Invalid time value at \"%s\"", mod + 10);
+ "Invalid time value at \"%s\"", p);
return AMR_CLEANUP;
}
*pp = p;
} else {
- utc = 0;
- *pp = mod + 9;
+ t = 0;
+ *pp = args;
}
expr = ch->expr;
if (Expr_ShouldEval(expr))
- Expr_SetValueOwn(expr,
- VarStrftime(Expr_Str(expr), false, utc));
+ Expr_SetValueOwn(expr, VarStrftime(Expr_Str(expr), t, gmt));
return AMR_OK;
}
@@ -2700,7 +2628,6 @@ static ApplyModifierResult
ApplyModifier_ShellCommand(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
- const char *errfmt;
VarParseResult res;
LazyBuf cmdBuf;
FStr cmd;
@@ -2711,14 +2638,18 @@ ApplyModifier_ShellCommand(const char **pp, ModChain *ch)
return AMR_CLEANUP;
cmd = LazyBuf_DoneGet(&cmdBuf);
-
- errfmt = NULL;
- if (Expr_ShouldEval(expr))
- Expr_SetValueOwn(expr, Cmd_Exec(cmd.str, &errfmt));
- else
+ if (Expr_ShouldEval(expr)) {
+ char *output, *error;
+ output = Cmd_Exec(cmd.str, &error);
+ Expr_SetValueOwn(expr, output);
+ if (error != NULL) {
+ /* XXX: why still return AMR_OK? */
+ Error("%s", error);
+ free(error);
+ }
+ } else
Expr_SetValueRefer(expr, "");
- if (errfmt != NULL)
- Error(errfmt, cmd.str); /* XXX: why still return AMR_OK? */
+
FStr_Done(&cmd);
Expr_Define(expr);
@@ -2767,7 +2698,9 @@ ApplyModifier_Range(const char **pp, ModChain *ch)
for (i = 0; i < n; i++) {
if (i != 0) {
- /* XXX: Use ch->sep instead of ' ', for consistency. */
+ /*
+ * XXX: Use ch->sep instead of ' ', for consistency.
+ */
Buf_AddByte(&buf, ' ');
}
Buf_AddInt(&buf, 1 + (int)i);
@@ -2858,7 +2791,7 @@ ParseModifier_Match(const char **pp, const ModChain *ch,
static ApplyModifierResult
ApplyModifier_Match(const char **pp, ModChain *ch)
{
- const char mod = **pp;
+ char mod = **pp;
char *pattern;
ParseModifier_Match(pp, ch, &pattern);
@@ -2956,7 +2889,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
int error;
VarParseResult res;
LazyBuf reBuf, replaceBuf;
- FStr re, replace;
+ FStr re;
char delim = (*pp)[1];
if (delim == '\0') {
@@ -2977,8 +2910,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
FStr_Done(&re);
return AMR_CLEANUP;
}
- replace = LazyBuf_DoneGet(&replaceBuf);
- args.replace = replace.str;
+ args.replace = LazyBuf_Get(&replaceBuf);
args.pflags = PatternFlags_None();
args.matched = false;
@@ -2986,7 +2918,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
ParsePatternFlags(pp, &args.pflags, &oneBigWord);
if (!ModChain_ShouldEval(ch)) {
- FStr_Done(&replace);
+ LazyBuf_Done(&replaceBuf);
FStr_Done(&re);
return AMR_OK;
}
@@ -2994,7 +2926,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
error = regcomp(&args.re, re.str, REG_EXTENDED);
if (error != 0) {
VarREError(error, &args.re, "Regex compilation error");
- FStr_Done(&replace);
+ LazyBuf_Done(&replaceBuf);
FStr_Done(&re);
return AMR_CLEANUP;
}
@@ -3006,7 +2938,7 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord);
regfree(&args.re);
- FStr_Done(&replace);
+ LazyBuf_Done(&replaceBuf);
FStr_Done(&re);
return AMR_OK;
}
@@ -3304,15 +3236,12 @@ bad_modifier:
return AMR_BAD;
}
-#ifndef NUM_TYPE
-# ifdef HAVE_LONG_LONG_INT
-# define NUM_TYPE long long
-# elif defined(_INT64_T_DECLARED) || defined(int64_t)
-# define NUM_TYPE int64_t
-# else
-# define NUM_TYPE long
-# define strtoll strtol
-# endif
+#if __STDC__ >= 199901L || defined(HAVE_LONG_LONG_INT)
+# define NUM_TYPE long long
+# define PARSE_NUM_TYPE strtoll
+#else
+# define NUM_TYPE long
+# define PARSE_NUM_TYPE strtol
#endif
static NUM_TYPE
@@ -3321,7 +3250,7 @@ num_val(Substring s)
NUM_TYPE val;
char *ep;
- val = strtoll(s.start, &ep, 0);
+ val = PARSE_NUM_TYPE(s.start, &ep, 0);
if (ep != s.start) {
switch (*ep) {
case 'K':
@@ -3417,9 +3346,8 @@ ApplyModifier_Order(const char **pp, ModChain *ch)
else
goto bad;
*pp += 3;
- } else {
+ } else
goto bad;
- }
if (!ModChain_ShouldEval(ch))
return AMR_OK;
@@ -3446,54 +3374,55 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
VarParseResult res;
- LazyBuf buf;
- FStr then_expr, else_expr;
+ LazyBuf thenBuf;
+ LazyBuf elseBuf;
- bool value = false;
VarEvalMode then_emode = VARE_PARSE_ONLY;
VarEvalMode else_emode = VARE_PARSE_ONLY;
- CondEvalResult cond_rc = COND_PARSE; /* just not COND_INVALID */
+ CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */
if (Expr_ShouldEval(expr)) {
- cond_rc = Cond_EvalCondition(expr->name, &value);
- if (cond_rc != COND_INVALID && value)
+ cond_rc = Cond_EvalCondition(expr->name);
+ if (cond_rc == CR_TRUE)
then_emode = expr->emode;
- if (cond_rc != COND_INVALID && !value)
+ if (cond_rc == CR_FALSE)
else_emode = expr->emode;
}
- (*pp)++; /* skip past the '?' */
- res = ParseModifierPart(pp, ':', then_emode, ch, &buf);
+ (*pp)++; /* skip past the '?' */
+ res = ParseModifierPart(pp, ':', then_emode, ch, &thenBuf);
if (res != VPR_OK)
return AMR_CLEANUP;
- then_expr = LazyBuf_DoneGet(&buf);
- res = ParseModifierPart(pp, ch->endc, else_emode, ch, &buf);
+ res = ParseModifierPart(pp, ch->endc, else_emode, ch, &elseBuf);
if (res != VPR_OK) {
- FStr_Done(&then_expr);
+ LazyBuf_Done(&thenBuf);
return AMR_CLEANUP;
}
- else_expr = LazyBuf_DoneGet(&buf);
(*pp)--; /* Go back to the ch->endc. */
- if (cond_rc == COND_INVALID) {
- Error("Bad conditional expression '%s' in '%s?%s:%s'",
- expr->name, expr->name, then_expr.str, else_expr.str);
- FStr_Done(&then_expr);
- FStr_Done(&else_expr);
+ if (cond_rc == CR_ERROR) {
+ Substring thenExpr = LazyBuf_Get(&thenBuf);
+ Substring elseExpr = LazyBuf_Get(&elseBuf);
+ Error("Bad conditional expression '%s' in '%s?%.*s:%.*s'",
+ expr->name, expr->name,
+ (int)Substring_Length(thenExpr), thenExpr.start,
+ (int)Substring_Length(elseExpr), elseExpr.start);
+ LazyBuf_Done(&thenBuf);
+ LazyBuf_Done(&elseBuf);
return AMR_CLEANUP;
}
if (!Expr_ShouldEval(expr)) {
- FStr_Done(&then_expr);
- FStr_Done(&else_expr);
- } else if (value) {
- Expr_SetValue(expr, then_expr);
- FStr_Done(&else_expr);
+ LazyBuf_Done(&thenBuf);
+ LazyBuf_Done(&elseBuf);
+ } else if (cond_rc == CR_TRUE) {
+ Expr_SetValue(expr, LazyBuf_DoneGet(&thenBuf));
+ LazyBuf_Done(&elseBuf);
} else {
- FStr_Done(&then_expr);
- Expr_SetValue(expr, else_expr);
+ LazyBuf_Done(&thenBuf);
+ Expr_SetValue(expr, LazyBuf_DoneGet(&elseBuf));
}
Expr_Define(expr);
return AMR_OK;
@@ -3534,27 +3463,18 @@ ApplyModifier_Assign(const char **pp, ModChain *ch)
const char *op = mod + 1;
if (op[0] == '=')
- goto ok;
- if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=')
- goto ok;
+ goto found_op;
+ if ((op[0] == '+' || op[0] == '?' || op[0] == '!') && op[1] == '=')
+ goto found_op;
return AMR_UNKNOWN; /* "::<unrecognised>" */
-ok:
+found_op:
if (expr->name[0] == '\0') {
*pp = mod + 1;
return AMR_BAD;
}
- switch (op[0]) {
- case '+':
- case '?':
- case '!':
- *pp = mod + 3;
- break;
- default:
- *pp = mod + 2;
- break;
- }
+ *pp = mod + (op[0] == '+' || op[0] == '?' || op[0] == '!' ? 3 : 2);
res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf);
if (res != VPR_OK)
@@ -3572,31 +3492,25 @@ ok:
if (gv == NULL)
scope = SCOPE_GLOBAL;
else
- VarFreeEnv(gv);
+ VarFreeShortLived(gv);
}
- switch (op[0]) {
- case '+':
+ if (op[0] == '+')
Var_Append(scope, expr->name, val.str);
- break;
- case '!': {
- const char *errfmt;
- char *cmd_output = Cmd_Exec(val.str, &errfmt);
- if (errfmt != NULL)
- Error(errfmt, val.str);
- else
- Var_Set(scope, expr->name, cmd_output);
- free(cmd_output);
- break;
- }
- case '?':
- if (expr->defined == DEF_REGULAR)
- break;
- /* FALLTHROUGH */
- default:
+ else if (op[0] == '!') {
+ char *output, *error;
+ output = Cmd_Exec(val.str, &error);
+ if (error != NULL) {
+ Error("%s", error);
+ free(error);
+ } else
+ Var_Set(scope, expr->name, output);
+ free(output);
+ } else if (op[0] == '?' && expr->defined == DEF_REGULAR) {
+ /* Do nothing. */
+ } else
Var_Set(scope, expr->name, val.str);
- break;
- }
+
Expr_SetValueRefer(expr, "");
done:
@@ -3731,7 +3645,9 @@ ApplyModifier_SysV(const char **pp, ModChain *ch)
if (res != VPR_OK)
return AMR_CLEANUP;
- /* The SysV modifier lasts until the end of the variable expression. */
+ /*
+ * The SysV modifier lasts until the end of the variable expression.
+ */
res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf);
if (res != VPR_OK) {
LazyBuf_Done(&lhsBuf);
@@ -3775,10 +3691,12 @@ ApplyModifier_SunShell(const char **pp, ModChain *ch)
*pp = p + 2;
if (Expr_ShouldEval(expr)) {
- const char *errfmt;
- char *output = Cmd_Exec(Expr_Str(expr), &errfmt);
- if (errfmt != NULL)
- Error(errfmt, Expr_Str(expr));
+ char *output, *error;
+ output = Cmd_Exec(Expr_Str(expr), &error);
+ if (error != NULL) {
+ Error("%s", error);
+ free(error);
+ }
Expr_SetValueOwn(expr, output);
}
@@ -3867,15 +3785,14 @@ ApplyModifier(const char **pp, ModChain *ch)
case 'E':
return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix);
case 'g':
- return ApplyModifier_Gmtime(pp, ch);
+ case 'l':
+ return ApplyModifier_Time(pp, ch);
case 'H':
return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head);
case 'h':
return ApplyModifier_Hash(pp, ch);
case 'L':
return ApplyModifier_Literal(pp, ch);
- case 'l':
- return ApplyModifier_Localtime(pp, ch);
case 'M':
case 'N':
return ApplyModifier_Match(pp, ch);
@@ -4106,7 +4023,7 @@ ApplyModifiers(
}
*pp = p;
- assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */
+ assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */
return;
bad_modifier:
@@ -4417,17 +4334,18 @@ ParseVarnameLong(
v = VarFindSubstring(name, scope, true);
- /* At this point, p points just after the variable name,
- * either at ':' or at endc. */
+ /*
+ * At this point, p points just after the variable name, either at
+ * ':' or at endc.
+ */
- if (v == NULL) {
- if (Substring_Equals(name, ".SUFFIXES"))
- v = VarNew(Substring_Str(name),
- Suff_NamesStr(), false, true);
- else
- v = FindLocalLegacyVar(name, scope,
- out_true_extraModifiers);
- }
+ if (v == NULL && Substring_Equals(name, ".SUFFIXES")) {
+ char *suffixes = Suff_NamesStr();
+ v = VarNew(FStr_InitRefer(".SUFFIXES"), suffixes,
+ true, false, true);
+ free(suffixes);
+ } else if (v == NULL)
+ v = FindLocalLegacyVar(name, scope, out_true_extraModifiers);
if (v == NULL) {
/*
@@ -4442,6 +4360,7 @@ ParseVarnameLong(
*out_false_pp = p;
*out_false_res = EvalUndefined(dynamic, start, p,
name, emode, out_false_val);
+ LazyBuf_Done(&varname);
return false;
}
@@ -4459,7 +4378,8 @@ ParseVarnameLong(
* is still undefined, Var_Parse will return an empty string
* instead of the actually computed value.
*/
- v = VarNew(LazyBuf_DoneGet(&varname), "", false, false);
+ v = VarNew(LazyBuf_DoneGet(&varname), "",
+ true, false, false);
*out_true_exprDefined = DEF_UNDEF;
} else
LazyBuf_Done(&varname);
@@ -4472,20 +4392,6 @@ ParseVarnameLong(
return true;
}
-/* Free the environment variable now since we own it. */
-static void
-FreeEnvVar(Var *v, Expr *expr)
-{
- char *varValue = Buf_DoneData(&v->val);
- if (expr->value.str == varValue)
- expr->value.freeIt = varValue;
- else
- free(varValue);
-
- FStr_Done(&v->name);
- free(v);
-}
-
#if __STDC_VERSION__ >= 199901L
#define Expr_Literal(name, value, emode, scope, defined) \
{ name, value, emode, scope, defined }
@@ -4626,8 +4532,14 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
}
expr.name = v->name.str;
- if (v->inUse)
+ if (v->inUse) {
+ if (scope->fname != NULL) {
+ fprintf(stderr, "In a command near ");
+ PrintLocation(stderr, false,
+ scope->fname, scope->lineno);
+ }
Fatal("Variable %s is recursive.", v->name.str);
+ }
/*
* XXX: This assignment creates an alias to the current value of the
@@ -4644,8 +4556,8 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
* Before applying any modifiers, expand any nested expressions from
* the variable value.
*/
- if (strchr(Expr_Str(&expr), '$') != NULL &&
- VarEvalMode_ShouldEval(emode)) {
+ if (VarEvalMode_ShouldEval(emode) &&
+ strchr(Expr_Str(&expr), '$') != NULL) {
char *expanded;
VarEvalMode nested_emode = emode;
if (opts.strict)
@@ -4664,7 +4576,7 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
}
if (haveModifier) {
- p++; /* Skip initial colon. */
+ p++; /* Skip initial colon. */
ApplyModifiers(&expr, &p, startc, endc);
}
@@ -4673,31 +4585,30 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
*pp = p;
- if (v->fromEnv) {
- FreeEnvVar(v, &expr);
-
- } else if (expr.defined != DEF_REGULAR) {
- if (expr.defined == DEF_UNDEF) {
- if (dynamic) {
- Expr_SetValueOwn(&expr,
- bmake_strsedup(start, p));
- } else {
- /*
- * The expression is still undefined,
- * therefore discard the actual value and
- * return an error marker instead.
- */
- Expr_SetValueRefer(&expr,
- emode == VARE_UNDEFERR
- ? var_Error : varUndefined);
- }
+ if (expr.defined == DEF_UNDEF) {
+ if (dynamic)
+ Expr_SetValueOwn(&expr, bmake_strsedup(start, p));
+ else {
+ /*
+ * The expression is still undefined, therefore
+ * discard the actual value and return an error marker
+ * instead.
+ */
+ Expr_SetValueRefer(&expr,
+ emode == VARE_UNDEFERR
+ ? var_Error : varUndefined);
+ }
+ }
+
+ if (v->shortLived) {
+ if (expr.value.str == v->val.data) {
+ /* move ownership */
+ expr.value.freeIt = v->val.data;
+ v->val.data = NULL;
}
- /* XXX: This is not standard memory management. */
- if (expr.value.str != v->val.data)
- Buf_Done(&v->val);
- FStr_Done(&v->name);
- free(v);
+ VarFreeShortLived(v);
}
+
*out_val = expr.value;
return VPR_OK; /* XXX: Is not correct in all cases */
}
@@ -4726,7 +4637,7 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope,
if (val.str == var_Error || val.str == varUndefined) {
if (!VarEvalMode_ShouldKeepUndef(emode)) {
p = nested_p;
- } else if (emode == VARE_UNDEFERR || val.str == var_Error) {
+ } else if (val.str == var_Error) {
/*
* XXX: This condition is wrong. If val == var_Error,
@@ -4748,10 +4659,12 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope,
p = nested_p;
*inout_errorReported = true;
} else {
- /* Copy the initial '$' of the undefined expression,
+ /*
+ * 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. */
+ * expand nested expressions if already possible. See
+ * unit-tests/varparse-undef-partial.mk.
+ */
Buf_AddByte(buf, *p);
p++;
}
@@ -4798,9 +4711,11 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res)
const char *p = str;
Buffer 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? */
+ /*
+ * Set true if an error has already been reported, to prevent a
+ * plethora of messages when recursing
+ */
+ /* See varparse-errors.mk for why the 'static' is necessary here. */
static bool errorReported;
Buf_Init(&res);
@@ -4819,6 +4734,19 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res)
return VPR_OK;
}
+void
+Var_Expand(FStr *str, GNode *scope, VarEvalMode emode)
+{
+ char *expanded;
+
+ if (strchr(str->str, '$') == NULL)
+ return;
+ (void)Var_Subst(str->str, scope, emode, &expanded);
+ /* TODO: handle errors */
+ FStr_Done(str);
+ *str = FStr_InitOwn(expanded);
+}
+
/* Initialize the variables module. */
void
Var_Init(void)